fix: zero-init fp32 UE8M0 activation scales in the masked MoE down-GEMM - #32385
Open
yueming-yuan wants to merge 50 commits into
Open
fix: zero-init fp32 UE8M0 activation scales in the masked MoE down-GEMM#32385yueming-yuan wants to merge 50 commits into
yueming-yuan wants to merge 50 commits into
Conversation
Rewired prefill routed-experts collection to the v0.5.15 batch_result_processor._maybe_collect_routed_experts API.
Kept only the net-new delta on v0.5.15: fail-fast assertion in pause_generation + unit-test disaggregation_mode setup. The decode.py whitespace and inline test_pause_resume_in_place are dropped (v0.5.15 covers them via PauseResumeInPlaceMixin).
Re-applied onto v0.5.15's rewritten kimik2_detector: capture the model-emitted id at both ToolCallItem sites (streaming gated on the name-carrying delta), add KimiK2RawIdDetector + registry + serving_chat branch.
Co-authored-by: Allen Zhu <allenzhu@berkeley.edu> Re-based onto v0.5.15: sampler uses true_on_policy.enabled with v0.5.15's get_flags() sampling backend; the _handle_data_parallelism context-parallel-lm-head relaxation is ported into the v0.5.15 arg_groups/overrides.py resolution pipeline.
yueming-yuan
requested review from
1am9trash,
Alisehen,
AniZpZ,
ByronHsu,
DarkSharpness,
FlamingoPg,
HydraQYH,
JustinTong0323,
Kangyan-Zhou,
OrangeRedeng,
ShangmingCai,
YAMY1234,
b8zhong,
bingxche,
celve,
fzyzcjy,
hebiao064,
hubertlu-tw,
ishandhanani,
jybsuper,
kkHuang-amd,
lifuhuang,
rainj-me,
sogalin,
xiezhq-hermann,
yctseng0211,
yhyang201,
yizhang2077,
yuan-luo and
yushengsu-thu
as code owners
July 29, 2026 20:58
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.
Problem
Qwen3-30B-A3B RL with
--rollout-fp8on 4xB300 dies during decode CUDA graph capture:The 719 is only the sticky symptom. The real error is the device-side assert above:
0x807fffffmasks an fp32's sign bit and mantissa, so deep_gemm requires every scale factor to be a positive power of two (UE8M0) before it packs them into its own layout.Cause
In
_varlen_deep_gemm_silu_mul_quant, the fp32 fallback branch allocates the activation-scale buffer uninitialized:The Triton kernel is correct — it does round to a power of two (
tl.exp2(tl.ceil(tl.log2(...)))) — but it only writes rows belowmasked_m. deep_gemm's scale-factor layout transform validates the entire padded tensor, including the inactive rows, so whatever was left in that memory trips the assert.Which branch runs is decided by:
The JIT fast path allocates packed int32 scales, which never go through the fp32 validation, so it is immune. Only
G % 4 != 0reaches the exposed fp32 branch:G = D / 128That is why this has gone unnoticed: it is a latent uninitialized-memory bug that only models with
G % 4 != 0can reach.Evidence
1. Minimal reproduction — same masked grouped GEMM, only the inactive rows differ:
So the transform does validate padding.
2. Instrumented run — printing every scale operand of both MoE GEMMs at the failure point:
w13_scale)down_input_scale)w2_scale)The only fp32 scale in the whole step is
down_input_scale, its trailing dim isG = 6(confirming the fallback branch), and ~58% of its entries are not powers of two.Fix
torch.empty->torch.zeros.0.0is0x00000000, so it satisfies the assert, and the value in inactive rows cannot affect the result becausemasked_mexcludes them from the GEMM. Cost is one 393 KB memset per call (64x256x6x4 B) against a multi-GB GEMM; under CUDA graphs it is captured as a graph node and replays correctly.Rejected alternatives: emitting packed int32 from this branch would require changing the Triton kernel's output format, and the fast path requires
G % 4 == 0precisely because it packs four scales per int32 —G = 6would first have to be padded to 8. Bounding the transform bymasked_minside deep_gemm is arguably the more correct fix (validating inactive rows is pointless work) but that is upstream of this repo.Verification
Qwen3-30B-A3B RL on 4xB300,
--rollout-fp8, bf16 training, fp32 optimizer, TP4/PP1/CP1/EP4, colocated, dapo-math-17k, 8192 response length. Before the fix the engine never survives decode CUDA graph capture:smxx_layout.cuh:131asserts719 unspecified launch failureresponse_len/mean6312Three full 3-step runs, differing only in how the paused training actor is backed up, all with zero asserts and no NaN:
train_rollout_logprob_abs_diff(steps 0/1/2)Stable across steps and identical across the three configurations, i.e. the rollout/train logprob agreement is unaffected.
(An earlier revision of this section reported only step 0. That run was truncated by an unrelated problem — the image's
torch_memory_saverpredated its disk-backup backend, so disk-mode offload silently restored garbage. With the matchingtorch_memory_saverinstalled, all three configurations run to completion.)A companion PR, #32386, targets
main, where the default plain-silu path has since moved to packed int32 and is no longer affected, but thegemm1_alphabranch still carries the same pattern.