Repository navigation
Conversation
In the RMSNorm and LayerNorm quant kernels, block_reduce_max has lane 0 of each wave store its partial max into s_red[wave] (s_sum[wave] in LayerNorm). Slot 0 still holds the result of the preceding block reduction, which every wave loads after that reduction's last barrier, and nothing orders those loads before wave 0's store. A wave that loads slot 0 late reads wave 0's partial max instead of the sum, so it normalizes its part of the row with a wrong value, and the row max and the quant scale of the whole row are wrong too. Add a barrier before the first store, as block_reduce in softmax_kernel already does for its max and sum reductions. The RMSNorm whole-tile path reads its sum of squares from s_red2 and is not affected; the RMSNorm generic path and both LayerNorm paths read slot 0. Signed-off-by: Jin Pan <jin.pan@amd.com>
This was referenced Oct 7, 2026
cschenjunlin
requested changes
Oct 16, 2026
| wave = tid // WARP_SIZE | ||
| w = wave_reduce_max(val) | ||
| # Other waves may still be reading the previous block reduction's result from s_sum[0]. | ||
| gpu.barrier() |
Contributor
There was a problem hiding this comment.
This PR adds a gpu.barrier() to block_reduce_max in all four quant builders. It fixes the race caused by reusing the same LDS slot for the sum and max reductions.
However, this PR is based on the main branch as of September 29. Commit 6487994 in #848 has already fixed the same issue, and the current main already contains the following code in rmsnorm_kernel.py (lines 1133–1140):
# s_red still holds the sum reduction's result, which every wave
# broadcast-reads on exit. Only this barrier orders that read
# against the store below; without it a late wave reads this max.
gpu.barrier()
w = wave_reduce_max(val)
if lane == 0:
fx.memref_store(w, s_red, wave)
gpu.barrier()
I used git merge-tree to simulate the merge. This PR merges cleanly with main, and the redundant barrier could be merged into main without noticing. Therefore, I recommend removing one of duplicative gpu.barrier().
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The RMSNorm and LayerNorm quant kernels run two block reductions per row through one LDS buffer: a sum, then the row max for the quant scale.
block_reduce_maxlets wave 0 store its partial max into slot 0 before every wave has loaded the sum from that slot. This PR adds a barrier before that store.Motivation
The end of the sum reduction and the start of the max reduction are:
No barrier orders a wave's
sum = s_red[0]before wave 0's store into slot 0. A wave that loads slot 0 late uses wave 0's partial max as the sum. That wave normalizes its part of the row with a wrongrrms(LayerNorm: a wrong mean), so its outputs, the row max, and the quant scale of the whole row are wrong.block_reduceinsoftmax_kernel.pyreuses one buffer for its max and sum reductions in the same way. It already starts with a barrier for this reason (#1022).Affected paths, when the workgroup has more than one wave:
s_red2and is not affected.s_sum[0], whichblock_reduce_maxreuses.The race shows only when wave 0 finishes the whole second pass over the row and its wave reduction before another wave runs the load that follows the barrier.
Changes
block_reduce_maxin_build_rmsnorm_quant_module,_build_fused_add_rmsnorm_quant_module,_build_layernorm_quant_module, and_build_fused_add_layernorm_quant_modulerunsgpu.barrier()before lane 0 of each wave stores its partial max.RED_SLOTS == 1) reduce in registers and do not change.Testing
tests/kernels/test_rmsnorm.pyandtests/kernels/test_layernorm.pyrun every changed builder. A test cannot force the wave order that triggers the race.Dependencies
Breaking Changes
None.
Related PRs