[AMDGPU] Add DPP row_shl fast path for shuffle_down reduction offsets#770
[AMDGPU] Add DPP row_shl fast path for shuffle_down reduction offsets#770ZJLi2013 wants to merge 1 commit into
Conversation
amdgpu_shuffle_down_i32 previously always routed through the generic
ds_bpermute + permlane64 cross-half helper (~50-60 cycles). For the
power-of-two reduction-step offsets {1,2,4,8} the source lane stays
within the same 16-lane DPP row, so a single v_mov_b32_dpp row_shl:N
computes the shuffle directly (~4-12 cycles).
Lanes whose source falls outside the row keep their own value (r is
seeded with value); reduction trees treat those boundary lanes as
don't-care once the tree has contracted below 16 active lanes, so
reduce_{add,min,max}_tiled and block.reduce stay correct.
offset 16 crosses a row and offset >= 32 crosses the wave half, so both
fall through to the generic helper unchanged. shuffle_up keeps the
helper: its Hillis-Steele scan consumers keep all lanes active and read
across row boundaries at offset 1, which row_shr cannot express.
Emitted as inline asm (not __builtin_amdgcn_update_dpp) because
runtime.cpp is compiled to bitcode by the host clang and only retargeted
to amdgcn at JIT time; the instruction text is carried verbatim into the
IR, same technique as the existing +v fence in the cross-half helper.
Measured on R9700 (gfx1201 / RDNA4), single-wave dependent-chain
latency microbenchmark: offsets {1,2,4,8} ~42.7ns -> ~20ns (~2.1x);
offset 16/32 unchanged (still helper). reduce_add_tiled(log2_size=6)
== 2080 (sum 1..64), reduction consumers PASS.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9998427dc4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // time via constant propagation after inlining, so the dead branches are eliminated. offset 16 crosses | ||
| // a row and >=32 crosses the wave half, so they fall through to the generic helper. | ||
| i32 r = value; | ||
| if (offset == 1) { __asm__ volatile("v_mov_b32_dpp %0, %1 row_shl:1 row_mask:0xf bank_mask:0xf" : "+v"(r) : "v"(value)); return r; } |
There was a problem hiding this comment.
Preserve shuffle_down across DPP row boundaries
When subgroup.shuffle_down(value, 1) reaches this AMDGPU fast path, lanes at 16-lane DPP row boundaries (for example lane 15) keep their own value instead of reading lane 16, even though the public API/codegen route all subgroupShuffleDown calls here and document lane i + offset semantics for every in-range lane. This may be acceptable for the current reduction tree's dead lanes, but it corrupts any direct shuffle_down use or future algorithm that needs boundary lanes; keep the generic helper for boundary-sensitive calls or make this optimization reduction-specific.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
@ZJLi2013 This codex comment seems serious and structural to me.
I don't think we can corrupt the meaning of 'shuffle down'. It is a well known operation, and should behave unchanged, across all platforms.
One option that occurs to me is to create a new operation 'shift_left', and implement this across all GPU platforms (Metal, Vulkan, CUDA, AMD).
Thoughts?
Summary
amdgpu_shuffle_down_i32currently always routes through the generic cross-halfds_bpermute+permlane64helper, even for the small power-of-two offsets used by reduction trees. The existingFIXMEcalls this out:This PR implements that fast path for offsets {1, 2, 4, 8}: a single
v_mov_b32_dppper shuffle instead of the multi-instruction cross-half gather. Measured ~2.1x lower latency on RDNA4 and ~1.36x on CDNA3, with reduction consumers unchanged.(One correction to the FIXME:
shuffle_downreads lanei + offset, i.e. data moves left, so the correct instruction isrow_shl, notrow_shr.)Change
For offsets {1, 2, 4, 8} emit DPP directly; everything else falls through to the existing helper:
Design notes:
row_shl:Nshifts within each 16-lane row. Offsets {1,2,4,8} stay inside a row; offset 16 crosses a row and offset >= 32 crosses the wave half, so both keep the generic helper.offsetis a compile-time constant after inlining, so the dead branches are eliminated.ris seeded withvalue). This matches how the reduction consumers useshuffle_down:reduce_*_tiled/block.reduceare decreasing-offset trees (32 -> 1), so by the time the offset is small the live lanes have contracted into a single 16-lane row and the boundary lanes are don't-care.shuffle_upintentionally unchanged. Its consumers are increasing-offset Hillis-Steele scans where every lane stays active and reads across a row boundary at offset 1 (lane 16 reads lane 15);row_shrreturns the boundary lane's own value there, which would break the scan.shuffle_upkeeps the generic helper.__builtin_amdgcn_update_dpp.runtime.cppis compiled to bitcode by the host clang and only retargeted to amdgcn at JIT time, so the intrinsic is not available at compile time. The instruction text is carried verbatim into the IR — the same technique already used by the"+v"fence inamdgpu_cross_half_shuffle_i32.Testing
Rebuilt the runtime bitcode from source and ran standalone quadrants kernels (no Genesis), single wave64,
log2_size=6.Correctness (
reduce_add_tiled(6)lane 0 == sum(1..64) == 2080; interiorshuffle_downchecked per lane):Latency — single wave64, long data-dependent chain,
nspershuffle_down + fixed ALU(before = generic helper, after = DPP; offsets 16/32 stay on the helper in both and serve as an in-binary control):R9700 (gfx1201 / RDNA4):
MI300 (gfx942 / CDNA3):
The CDNA3 speedup is smaller because there the cross-half helper is already a single wave-global
ds_bpermute(permlane64is identity on CDNA), so the baseline is cheaper; DPP still wins. The unchanged offset-16/32 numbers confirm only {1,2,4,8} take the new path and that the measurement is stable.