Repository navigation
[WS1][CUDA][Qwen3-Next] Gated RMSNorm CUDA kernel for the GDN block (RFC #428 stack 3/4) - #468
Conversation
Flink-ddd
left a comment
There was a problem hiding this comment.
please resolve the code conflicts first, Thank you.
cd8ee97 to
b65426a
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
Adds the CUDA kernel behind `Qwen3NextRMSNormGatedOp`, and registers both it and
the zero-centred decoder norm from the previous commit.
y = x * rstd * (weight_offset + weight) * act(gate)
Every multiply is fp32 with one cast at the store, matching vLLM's RMSNormGated
with norm_before_gate=True and group_size=None -- the only configuration the GDN
block constructs. Other configurations are rejected rather than approximated.
`expf` is used rather than the `__expf` intrinsic: the fast intrinsic trades
accuracy for speed and would move the result away from the fp32 reference.
Forward reuses the existing block_reduce_sum / choose_threads(H), so for a fixed
H the reduction tree is independent of the row count -- that is the L1 guarantee
-- and `rstd` comes out bitwise identical to the ungated kernel for the same x,
which is asserted.
Backward is assembled from deterministic pieces:
dx new kernel, the ungated dx with (w + offset) -> (w + offset) * act(z)
dweight reuses rmsnorm_dweight_rows_fp32 + the ascending-row fp32 left fold
dgate row-local and reduction-free, fp32 in the wrapper
Both backwards route through one `_fold_dweight_rows` helper so the file keeps a
single left-fold entrypoint, which tests/test_vjp_fp32.py pins, and one
`_require_cuda_symbols` helper so the module has a single availability contract.
The gated op is deliberately NOT a subclass of RMSNormCudaOp: it takes an extra
required tensor, so it cannot stand in for one. Same reasoning as on the PyTorch
side, and stated in its docstring so the question is not reopened.
Registration: `rms_norm_gated` and `qwen3_next_rms_norm` in OP_SPECS (both with a
cuda-sm90 candidate, as every other reduction spec carries), operator_inputs
builders, OpBackend members and priority maps on all five platforms,
test_dispatch assertions, and the WS1 registered-ops set in
tests/test_ws1_gtest_gpu.py. Operator pages added per docs/operators/README.md,
which states the page is part of the operator contract.
Claim level: L0 repeatable and L1 batch-invariant. NOT L2 -- see
tests/check_qwen3_next_norm_providers.py, which pins the dispatch facts and
bounds the gap against vLLM rather than asserting equality.
Verified on 2x B200 (sm_100, driver 580.126.20, torch 2.13.0+cu130, vllm 0.30.0):
pytest tests/test_qwen3_next_norm.py -q
pytest tests/check_qwen3_next_norm_providers.py -q
python scripts/check_operator.py --op rms_norm_gated --candidate cuda \
--device cuda --dtype bf16 --check-grad -> pass_rate=1.0000
python scripts/check_operator.py --op qwen3_next_rms_norm --candidate cuda \
--device cuda --dtype bf16 --check-grad -> pass_rate=1.0000
pytest tests/ rl_engine/tests/ -q -p no:randomly \
--ignore=tests/test_rocm_aiter_api_contract.py
Both new files pass, both operators report pass_rate=1.0000 on the CUDA
candidate, and the full suite gains no failure. The ignored file fails to import
on the merge-base as well.
Absolute suite counts are reported in the PR description against a named base
commit, not here: they shift whenever a sibling test is added, so a count frozen
in a commit message goes stale the moment the branch is rebased.
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…atistics Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…ion fallback The zero-centred norm was registered CUDA-first alongside the gated one, but only rms_norm_gated had dispatch tests. Mirror them: pin the priority on all five platforms, and assert that the inherited RMSNormCudaOp.__init__ symbol check makes the registry fall through to the PyTorch reference. Each assertion was checked to fail on CPU with the behaviour removed: PyTorch first in the CUDA list, the ROCm entry dropped, and the __init__ check removed. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…ctivation, offset
The bitwise rstd identity was asserted for one configuration only (bf16, H=128,
silu, offset 0). Parametrize it over fp32/fp16/bf16 x H in {128, 2048, 5120} x
{silu, sigmoid} x offset in {0.0, 1.0}, 36 cases, with the plain kernel taking
the same offset.
All 36 are expected to hold by construction: both kernels run the same
sum-of-squares loop, block_reduce_sum and choose_threads(H) launch shape, and
neither the gate nor the offset enters the statistic. The H values cover both
block sizes (128 and 512 threads) and two per-thread serial lengths. Not yet run
on a GPU; the cases skip without the compiled extension.
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…16, 17) R2-1 (private bf16 tolerances cited as the reduction row) was resolved in the preceding merge, which switched the gated tests to c1's _forward_tol. - R2-2 docs/operators/qwen3-next-rms-norm-gated.md: Accuracy now points to tolerance_contract.json (forward_accuracy, reduction x dtype); the 35% / 6.25e-2 HF-vs-vLLM figure is labelled a one-off with its setup (one seed, B200, bf16, head_v_dim=128, 512 rows, eager); the 40-seed figures are attributed to tests/check_qwen3_next_norm_providers.py. Withdrawn, as on the c1 page: the cast-order isolation, the per-element fp32 ULP wording, and "fails closed (RFC section 6 item 7)" for configurations the op has no parameter for. Claim levels now read L1 (prefix slices; batch size, chunking, padding, permutation in the C3/C4 gates), L0 not separately tested; the rstd sweep is described. - R2-7 csrc/ops.cpp: drop the dim and size TORCH_CHECKs that rmsnorm_check_weight already performs, in rmsnorm_forward, rmsnorm_backward_dx and rmsnorm_gated_check. Error messages for those cases now come from rmsnorm_check_weight; no test matches the removed messages. - R2-8 workload_report() in rl_engine/testing/ws1_workload.py replaces the payload block duplicated in both gate scripts. full_model_evidence is read from the manifest: False for the Qwen3-Next norm manifest, None (not declared) for the Dense manifest, which previously reported a hard-coded False. - R2-9 the gate scripts import validate_norm_dimensions at module level, and --manifest has a help string. - R2-12 the four dispatch tests become two, parametrized over both norms. The fallback test now resolves for device="cuda", so the CUDA-first list is walked even on a CPU-only host, and asserts the CUDA backend was tried and failed. Before, on CPU, get_op went through the CPU list and the assertion held trivially. - R2-13 SPDX headers on rl_engine/testing/qwen3_next_workload.py and tests/test_qwen3_next_workload.py. - R2-14 ci/run_ws1_gtest.sh runs the four Qwen3-Next gates as explicit commands instead of a loop over interpolated script names. - R2-16 Qwen3NextRMSNormGatedCudaOp takes activation as a constructor argument, validated before the extension check; the test no longer mutates the instance. "swish" stays as vLLM's alias for "silu", now asserted bitwise on CUDA. - R2-17 the Python dgate path adds weight_offset only when it is nonzero, like the kernels, so a -0.0 weight keeps its sign in dgate. New tests: activation rejected at construction (CPU), swish == silu (CUDA), dgate signed zero (CUDA, 3 dtypes). Checked on CPU that the fallback and constructor tests fail with the behaviour removed. The CUDA tests and the csrc change are not yet run on a GPU. Deferred: R2-6 (shared sum-of-squares device function), R2-10 (private validator imports), R2-11. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
… guard The six rmsnorm launchers compiled on both CUDA and ROCm (plain forward and dx, gated forward and dx, partial and reduce dweight) constructed a non-optional c10::cuda::CUDAGuard. No other file the ROCm build compiles uses that class outside a USE_ROCM guard, while activation.cu and deterministic_attention.cu use at::cuda::OptionalCUDAGuard. Match the guard used by activation.cu so the ROCm build relies only on patterns it already compiles. The guard follows the same tensor as before (x, or partial_dw for the reduce launcher). CUDA behaviour is unchanged: these tensors are always on a CUDA device, so the optional guard always sets the device. The left-fold launcher sits inside the USE_ROCM guard and keeps its c10::cuda::CUDAGuard. The ROCm build is still untested. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…th the stacked C1 branch Carried over from the former merge of the C1 branch into this one: the decoder-norm page again states the registration and the check_operator command, the gated tests use the contract's reduction tolerances through _forward_tol, and the reference docstrings name the gated page. Same final tree as before the re-stack. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Not changed; this is not a behaviour change. Before, the launch already read |
Not changed: a float32 |
Not changed. Autograd casts the incoming gradient to the dtype of the forward output before |
Not changed. The extension check is |
Not changed: the reference now has the CUDA contract, which rejects |
…antics validate_norm_manifest never required the Dense top-level keys (version, workload_id, seed, chain_semantics, provenance_boundary), and never validated chain_semantics, although the C3/C4 runners read seed and chain_semantics.loss_reduction and workload_report reads workload_id. It now requires the same _REQUIRED_TOP_LEVEL keys as the Dense validator and runs _validate_chain_semantics; the committed manifest already satisfies both, so it and its identity hash are unchanged. The two-op check compares sorted lists, so a duplicated op entry is rejected instead of collapsing in a set. The reference gated norm's dtype/device error now names both tensors' dtype and device. Tests: missing top-level keys, a float16 execution dtype, a duplicated op and an explicit null scope are rejected; the gated CUDA backward is offset-aware (dx and dgate see 1 + w, dweight does not); the gated parameter-VJP rows fold to autograd's dweight for N-D x too. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Brings in RL-Align#468's a203719: the norm manifest's top-level keys, chain semantics and duplicate-op checks, the gate dtype message and the new norm tests. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Brings in RL-Align#468's a203719: the norm manifest's top-level keys, chain semantics and duplicate-op checks, the gate dtype message and the new norm tests. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Brings in RL-Align#468's a203719: the norm manifest's top-level keys, chain semantics and duplicate-op checks, the gate dtype message and the new norm tests. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Brings in RL-Align#468's a203719: the norm manifest's top-level keys, chain semantics and duplicate-op checks, the gate dtype message and the new norm tests. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Brings in RL-Align#468's a203719: the norm manifest's top-level keys, chain semantics and duplicate-op checks, the gate dtype message and the new norm tests. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Brings in RL-Align#468's a203719: the norm manifest's top-level keys, chain semantics and duplicate-op checks, the gate dtype message and the new norm tests. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Brings in the second-round OpenCodeReview fixes of RL-Align#468 to RL-Align#549: the norm manifest checks, the gate dtype message, the new norm tests and the de-duplicated CI entry. One conflict, in the CPU job's Qwen3-Next list: it keeps this branch's test_qwen3_next_ep.py and drops the repeated test_file_digest.py, as RL-Align#549 does. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Brings in the second-round OpenCodeReview fixes of RL-Align#468 to RL-Align#557. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Not changed: the offset is applied wherever it enters a derivative. |
Not changed: |
Not changed. |
Same point as 543-I4381854144, 543-I4381854154; not changed. |
Not changed. The C3/C4 runners build the norm parameters with |
Not changed: every |
Fixed in a203719 on #468: the error now names both tensors' dtype and device; |
Same point as 468-S5, 469-S1; not changed. Both sides reject a gate whose dtype differs from |
Agreed, no change: the gated kernel computes |
Same point as 468-S3; not changed. Both callers pass module constants: the norm validator passes the 13-key |
Same point as 468-S4, 543-I4381854144; not changed. Every manifest that |
Not changed: the wrapper re-raises with |
Flink-ddd
left a comment
There was a problem hiding this comment.
LGTM. The review findings have been addressed or explained, and the corrected reuse comparison and Hopper validation look good. Ready to merge into test-qwennext.
What
y = x * rstd * (weight_offset + weight) * act(gate)RMSNormGatedwithnorm_before_gate=Trueandgroup_size=None, which is how the GDN block constructs it. The op has no parameter for either, so other values are not implemented rather than rejected.silu(swishis an alias) orsigmoid, fixed at construction and validated before the extension check. The registry constructssilu; vLLM takes it fromconfig.output_gate_type(defaultsilu), so the registered op matches the released config, not every config. The kernel usesexpf, not__expf; aKERNEL_ALIGN_USE_FAST_MATH=1build (off by default) turns it back into the intrinsic.rstdcopies the plain kernel's sum-of-squares loop with the sameblock_reduce_sumandchoose_threads(H)launch shape, so for a fixed H the reduction tree does not depend on the row count (what L1 rests on). The 36-case identity test spans fp32/fp16/bf16 × H ∈ {128, 2048, 5120} × {silu, sigmoid} × offset ∈ {0, 1}.dxis a new kernel (the plaindxwith(w + offset)replaced by(w + offset) * act(z));dweightreusesrmsnorm_dweight_rows_fp32and the ascending-row fp32 left fold through one shared_fold_dweight_rows;dgateis row-local, reduction-free fp32.parameter_vjp_contributions_fp32takesrstdfrom_C.rmsnorm_gated_forward, the statistic the backward uses (pinned bytest_cuda_gated_parameter_vjp_contributions_match_the_fold)._require_cuda_gated(), which checks both gated symbols andrmsnorm_api_version == 2and raisesRuntimeError(not anassert), so the registry falls through to the PyTorch reference on a build without them.rms_norm_gatedandqwen3_next_rms_normare added toOP_SPECS(each with acuda-sm90candidate),operator_inputs.py, the registry (CUDA first, PyTorch reference elsewhere) and the WS1 registered-ops set. This is the first branch wherecheck_operator --opaccepts their names.csrc/bindings/ops.cppnowTORCH_CHECKs the plain kernels' inputs; fp64 is now rejected byrmsnorm_forwardandrmsnorm_backward_dx(onmainthey dispatched it). TheUSE_ROCMguard around thec10/cudaincludes inrmsnorm.cuis removed and the launchers useat::cuda::OptionalCUDAGuardasactivation.cudoes; the ROCm build has not been run. The shared C3/C4 gate scripts gain--manifestand adapters can be model-scoped; the Qwen3-Next norm manifest is a norm-only workload and its validator raisesWorkloadErrorfor any malformed manifest.Prior art & reuse decision
Measured with
tools/validation/models/qwen3_next_norm_reuse_check.py, committed in this PR;rms_norm_gated.jsonwas written from a clean tree on an otherwise idle B200 (recorded asabb56c2, now6ac74cd8). Batch invariance is bitwise over every row alone vs inside full batches of three sizes, the full 262144-row batch vs sub-batches of 4097, 65536 and 131072 rows that cover every row and of 1 and 7 rows sampled at a stride of 512 rows, and a dense batch-size sweep. The reuse reports re-run at the head (B200 and H100, see Results) cover every row for every sub-batch size and also find the rl-kernel op batch-invariant. Accuracy is against FP64; the C3/C4 column runs this repository's gates with the candidate swapped for each library, judging the library's owndweight(rerun at339bc3ff,rms_norm_gated_gates.json).Decision: write our own; no existing implementation passes C4. FLA's
layernorm_gated.rmsnorm_fnis the closest: batch-invariant on every check, vLLM's arithmetic (99.9987% correctly rounded, like this kernel), a backward, and 882 µs vs 115 ms forward + backward. It passes C3 but fails C4 ondweightunder chunked prefill and singleton-aggregate, as do transformers and FLA'sfused_norm_gate. Those configurations adddweightacross calls, and each library returns it in BF16. This kernel passes because it declares FP32 per-rowdweightcontributions that are folded in a fixed order. That fold is also why its backward is slow. vLLM'sforward_cudais not batch-invariant and has no backward. An earlier version of this section said FLA "passes the same gates"; that run judged rl-kernel'sdweightfor every swapped library (review r4379475184).Comparison charts
RMSNormGated*dx,dgate(of 768)* forward only. Written by
qwen3_next_norm_evidence.pyon an otherwise idle B200 (recorded as822b085, nowff271d5d); the same report re-measures the zero-centred op. Known cost: the CUDA backward is about 75× slower than transformers at 262144 rows, for the same reason as #467 (a serial ascendingdweightfold that meets the singleton-aggregate check bitwise); FLA'srmsnorm_fnpasses the same gate without one.Results
B200 and H100 are separate data sets; per RFC #428 §2.2 no claim is made across cards. Both cards were run at the head
5aa9aa1con an otherwise idle GPU from a clean tree.1e2eaec0..5aa9aa1cchanged only the Python guard_require_cuda_gated, anrstdreshape in the gated backward, aWorkloadErrorwrapper for malformed norm manifests, comments, and a comment in the WS1 gate script; no kernel source.B200 (sm_100)
One NVIDIA B200 (driver 580.126.20), torch 2.13.0+cu130, vLLM 0.30.0, transformers 5.17.0, FlashInfer 0.6.18.post1;
rl_engine._Cchecked to load from the tree under test; Transformer Engine, Megatron-core, Liger and FLA not installed, so those comparison rows are absent. Files:b200-pr468-head-5aa9aa1c-idle-b200.5aa9aa1crmsnorm_ascendnot compiled, 1 needs two CUDA devices)check_operatorrms_norm/rms_norm_gated/qwen3_next_rms_normpass_rate=1.0000eachpassed=Trueand bitwise (max_abs=0)git_dirty: false;qwen3_next_rms_norm: rl-kernel CUDA batch-invariant, reference / transformers / vLLM not, FlashInfer yes;rms_norm_gated: rl-kernel CUDA, reference and transformers batch-invariant, vLLM not. Against the1e2eaec0reports only timings, commit ids and paths differH100 (sm90)
One NVIDIA H100-94GB (95830 MiB, driver 580.173.02), torch 2.13.0+cu130, vLLM 0.30.0, transformers 5.13.1, FlashInfer 0.6.18.post1; extension loaded from the tree under test; Liger and FLA not installed; Transformer Engine 2.18.0 ran for
qwen3_next_rms_norm(not batch-invariant); megatron-core 0.16.0rc0 was excluded by the harness's zero-weight probe, and neither library applies to the gated op. Files:h100-pr468-head-5aa9aa1c-h100; the norm report and both reuse checks were run a second time at the head intoh100-pr468-h100on 2026-10-26 UTC, with the same probe and reuse verdicts.5aa9aa1ccheck_operatorrms_norm/rms_norm_gated/qwen3_next_rms_normpass_rate=1.0000eachmax_abs=0)git_dirty: false.qwen3_next_rms_norm: rl-kernel CUDA 0 differing forward rows and 0 differing dx rows; PyTorch reference 8 differing dx rows; transformers 1 forward and 9 dx; vLLM forward 1; FlashInfer forward 0.rms_norm_gated: rl-kernel CUDA, reference and transformers 0 differing rows; vLLM forward 0 on the probe and 3 differing rows at batch 4097 in the reuse sweepEarlier measurements (historical, superseded by the tables above)
5aa9aa1c(b200-pr468-head-5aa9aa1c-b200): unit suite 307 / 0 / 73,check_operatorand the four C3/C4 gates as above; the GPU was shared with other jobs, so the idle-GPU run replaces it. Its C3/C4 invariance lines are identical to the idle run.1e2eaec0(b200-pr468-b200, five commits before the head): 300 / 0 / 73; norm report and comparisons ran;check_operator3 of 3pass_rate=1.0000.1e2eaec0(inh100-pr468-h100): unit suite andcheck_operator300 / 0 / 73, three opspass_rate=1.0000.abb56c2,a66493c,88f59f7,822b085; now6ac74cd8,4f92dfc0,da6aa8db,ff271d5d(same code).Pre-migration (
main32b765evs this branch0cceb84, same B200 type, driver 580.126.20, triton 3.7.1, Python 3.12.14, extension built for sm_100; failure sets compared by test ID):main0cceb84passed=True; 72 invariance and singleton-aggregate comparisons, all bitwise (max_abs=0) across batch size, chunk, padding and permutationcuda:1inputs write tocuda:1, bitwise equal tocuda:0; the current device is unchanged (one-off script, not in this PR)Status
a2037197ontest-qwennext, which now carries the merged [WS1][CUDA][Qwen3-Next] C1: zero-centred RMSNorm references and CUDA kernel (RFC #428 stack 2/4) #467 (merged here in6c63f0b7) and, through it, [WS1][CUDA][Qwen3-Next] Restore CUDA dispatch for rms_norm (RFC #428 stack 1/4) #466.ws1-gtestis skipped (no self-hosted runner).4a3d19f2API-version guard,f9fa9b85rstdreshape,d634d0bdWorkloadErrorfor malformed manifests,08eee506and5aa9aa1ccomments). No human review decision yet. @inaniloquentee's C4 finding (swapped libraries were judged on rl-kernel'sdweight) is fixed in339bc3ff; the rerun and the changed reuse decision are in789a2854.History
caedea59rejects inputs past the kernels' 32-bit row offsets and drops the unusedgate_activation_grad;a2037197makes the norm manifest require the Dense top-level keys and valid chain semantics and reject a duplicated op, names both tensors in the gate dtype error, and adds tests for the offset-aware gated backward and N-D parameter-VJP rows. Kernel arithmetic is unchanged, so the measured results stand.test-qwennextafter [WS1][CUDA][Qwen3-Next] C1: zero-centred RMSNorm references and CUDA kernel (RFC #428 stack 2/4) #467 landed (6c63f0b7, no tree change). Review r4379475184: the reuse check's swapped wrappers stop inheritingparameter_vjp_contributions_fp32(339bc3ff); C3/C4 rerun on a B200 at339bc3ff: every existing implementation now fails C4 ondweight, so the reuse decision changes from FLAlayernorm_gated.rmsnorm_fnto this kernel (789a2854).5aa9aa1con an idle GPU (unit suite,check_operator, C3/C4, norm report and both comparison reports); Results restructured into one table per card with the older measurements collapsed; evidence links retargeted to the currentpr_assetscommit.1e2eaec0) are in Results; 2026-10-26 H100 norm report and reuse checks updated to5aa9aa1cinh100-pr468-h100.test-qwennextata5a92ecf(notice); code moved torl_engine/backends,reference,runtime,validation; tests totests/models/qwen3_next; tools totools/validation/{models,operators}. After the migration: 295 passed, 72 skipped in the relevant suites (B200).qwen3_next_norm_reuse_check.py) and the comparison charts; merged [WS1][CUDA][Qwen3-Next] C1: zero-centred RMSNorm references and CUDA kernel (RFC #428 stack 2/4) #467's latest state (1edc9d4, withbec7e7a), where the plain op uses [WS1][CUDA][Qwen3-Next] C1: zero-centred RMSNorm references and CUDA kernel (RFC #428 stack 2/4) #467's_require_cuda_rmsnormand the gated op kept the generic symbol check, since superseded by_require_cuda_gated. At1edc9d4(two B200s): norm suites 263 passed (72 skipped, all Ascend), providers check 9 passed, the four C3/C4 gates pass.test-qwennext; re-stacked linearly onto [WS1][CUDA][Qwen3-Next] C1: zero-centred RMSNorm references and CUDA kernel (RFC #428 stack 2/4) #467 (d03c852) with every commit signed off; patch-id and final tree identical to the pre-rebase tip. [WS1][CUDA][Qwen3-Next] Restore CUDA dispatch for rms_norm (RFC #428 stack 1/4) #466 added the device guard in the plain launchers; the duplicate guards here were reduced to theT == 0early returns, kernel launch checks and the guards on the two gated launchers.0cceb84switched the launchers' guard toat::cuda::OptionalCUDAGuard, the form the ROCm build already compiles.+1e-4fto each square in the gated sum of squares fails therstdtest in all 36 configurations; makingdgateadd the offset unconditionally fails exactlytest_cuda_gated_dgate_keeps_signed_zero_weight(3 dtypes); mappingswishtosigmoidfails exactlytest_cuda_gated_swish_is_an_alias_for_silu.RMSNormGated.forward_cudais batch-invariant, and that FLA'sfused_norm_gateis batch-invariant at the workload size; the Prior art section above replaces them.4ddbaa0/aaf0986(full suite 15 failed, 3098 passed, 923 skipped, no new failures; first run of the C3/C4 gates, all 72 comparisons bitwise) are superseded by the0cceb84row.Tests
At head
5aa9aa1c(B200; the layout below is the post-migration one):check_operatorrms_norm/rms_norm_gated/qwen3_next_rms_normpass_rate=1.0000each5aa9aa1c: 68 invariance pairs (12 + 12 + 16 + 28), allpassed=Trueand bitwise (max_abs=0)The same commands at this head on one idle H100-94GB: unit suite 307 passed, 0 failed, 73 skipped;
check_operatorpass_rate=1.0000for each of the three ops; C3/C4 68 invariance pairs, all bitwise (max_abs=0). Skips are environment only: 72 ×rmsnorm_ascendkernel not compiled, 1 × needs two CUDA devices. The repository's GPU workflows (ws1-gtest,provider) are skipped without a self-hosted runner, so the B200 / H100 runs above are the GPU test evidence.Scope / Known limitations
hipifytranslatesrmsnorm.cuwithout error, and the headers and launch-check macro are already used unguarded by sources the ROCm build compiles.forward_native, FLAforward_cuda(enforce_eager), andforward_nativecompiled by Inductor (default mode). The RFC's profile table points to a shared vLLM forward; the Qwen3 dense strict profile instead patches vLLM'sRMSNormto PyTorch eager arithmetic. The answer also decides which gated convention is the strict default (vLLM's fp32 weight multiply vs transformers' cast-first).VLLM_BATCH_INVARIANT=1does not reach this norm in either mode (one-off probe, not committed).qwen3_next_norm_manifest.jsonpinsQwen/Qwen3-Next-80B-A3B-Instructat9c7f2fbe…with 41 shard SHA-256s. The validator checks internal consistency only (hex format, total size,content_hash); the values are reproducible from the public Hugging Face LFS metadata at that revision. The gates never load weights (synthetic inputs at the checkpoint's dimensions,full_model_evidence: false).rstdguarantee rests on the 36-case test rather than shared code (a shared device function is a follow-up);qwen3_next_workload.pyimports six private validators fromws1_workloadand back;validate_norm_dimensionshard-codes2048/128already present inNORM_OPS; the Triton op's parameter-VJP hook still recomputesrstdwithmean(-1)as onmain.Summary by CodeRabbit
New Features
Bug Fixes
Tests