Fix Qwen3 FP16 garbage on CUDA backend: cross-workgroup race in RMS-norm reduction#132
Open
mikepapadim wants to merge 1 commit into
Open
Fix Qwen3 FP16 garbage on CUDA backend: cross-workgroup race in RMS-norm reduction#132mikepapadim wants to merge 1 commit into
mikepapadim wants to merge 1 commit into
Conversation
…-workgroup data race (workgroup 0 combined other groups' partials with no synchronization); NVIDIA path now uses a race-free single-workgroup reduction
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.
Fix: Qwen3 FP16 emits garbage on the CUDA backend — cross-workgroup data race in the RMS-norm reduction
Symptom. Qwen3-1.7B FP16 single-token decode on the CUDA backend produces garbage (endless
\n+ stray tokens, argmax stuck) from the first sampled token; Qwen3-4B works. Deterministic, both CUDA-graph modes.Root cause.
reductionOneBlockWithLayerwrites each workgroup's partial sum tooutput[groupId+1], then threadgid==0(workgroup 0) sumsoutput[1..N]in the same kernel with no inter-workgroup synchronization or memory fence — a data race. On the NON_NVIDIA scheduler this race doesn't exist (a separatereductionFinalNormalizationtask combines the partials); the NVIDIA path skips that task and relies on the racy in-kernel combine. Whether the race is lost is schedule/compilation dependent: Llama-1B and Qwen3-4B happen to win it, Qwen3-1.7B deterministically loses it — every layer's RMS-norm scale is computed from stale partials, corrupting the hidden state everywhere at once.Diagnosis (bisection, all on RTX 4090, TornadoVM 5.0.1-jdk21-dev CUDA backend):
<think>), then the decode graph degraded → single-token layer graph implicated.@Parallelreference implementations → still garbage.argmax='<think>', host-recomputed logits ≡ GPU logits).Fix. New
reductionOneBlockWithLayerSingleGroup: one workgroup strides over the input, reduces in local memory, lane 0 writes the final scale — race-free by construction, one kernel, no extra launch. The Qwen3 FP16 layer graphs use it on the NVIDIA scheduler path (gridglobal == local == localSize); the NON_NVIDIA path keeps the existing partials + finalize pair.Verified (all coherent, greedy):
\n\n\n…9…avier)<think> Okay, the user is asking for the capital of France… Paris(matches the validated batched-decode output)Perf unchanged: Qwen3-1.7B ~51 tok/s, Llama-1B ~90 tok/s before/after.
Note. The same racy combine is still used by the Llama layer graphs and the logits layer (
TransformerComputeKernels.reductionOneBlockWithLayer) — they currently win the race on this hardware, but they are latent instances of the same bug class; happy to extend the fix there if you want it in this PR.🤖 Generated with Claude Code