Repository navigation
[WS1][CUDA][Qwen3-Next] C1: zero-centred RMSNorm references and CUDA kernel (RFC #428 stack 2/4) - #467
Merged
Flink-ddd merged 18 commits intoOct 26, 2026
Conversation
fusheng-ji
requested review from
Flink-ddd,
KJLdefeated,
bitborne,
inaniloquentee and
maxiaosong1124
as code owners
October 4, 2026 01:41
fusheng-ji
force-pushed
the
feat/cuda-qwen3-next-c1-norm
branch
from
October 4, 2026 01:49
3c2e73f to
3c9d0af
Compare
`test_registry_dispatches_rms_norm` asserts that the registry resolves
`rms_norm` to `RMSNormCudaOp` whenever CUDA and the compiled kernels are
both present, but `OpBackend` had no CUDA member for this operator and the
CUDA priority list contained only `PYTORCH_NATIVE_RMS_NORM`, so the assert
could never hold. The test therefore fails on any CUDA machine that builds
the native extension, and only passes when `_HAS_CUDA_RMSNORM` is false --
which is why an unbuilt CI has not caught it.
`RMSNormCudaOp` is already a first-class backend elsewhere: it is the
`"cuda"` candidate in `gtest/operator_specs.py` and is used directly by
`attention_preprocess.py`. Only the registry was missing it.
Add `OpBackend.CUDA_RMS_NORM` and put it ahead of the PyTorch reference in
the CUDA priority list. Because `_load_backend` only catches import errors
and this module imports cleanly without `_C`, a CUDA-first list would
otherwise hand out an op that raises at call time on an unbuilt install; so
`RMSNormCudaOp.__init__` now validates the extension and its three symbols,
matching `_require_cuda_activation` in the activation ops. The registry
already treats a backend whose construction raises as unavailable, so the
list degrades to `NativeRMSNormOp` as before.
Only the `cuda` priority map changes; rocm/musa/cpu/npu are untouched.
Verified on 2x B200 (sm_100, driver 580.126.20, torch 2.13.0+cu130) with the
extension rebuilt from source:
pytest tests/test_rms_norm.py -q
pytest tests/ rl_engine/tests/ -q -p no:randomly \
--ignore=tests/test_rocm_aiter_api_contract.py
tests/test_rms_norm.py passes, including test_registry_dispatches_rms_norm, which
fails on the merge-base. 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>
…back Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…ires Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
fusheng-ji
force-pushed
the
feat/cuda-qwen3-next-c1-norm
branch
from
October 4, 2026 11:22
3c9d0af to
812d560
Compare
The forward, backward-dx and both backward-dw launchers took the current CUDA stream without switching to the input's device, so a tensor on cuda:1 while cuda:0 is current launched on the wrong GPU. Add a device guard on the input's device in each launcher. Use at::cuda::OptionalCUDAGuard with the headers included unconditionally, as activation.cu does: those launchers compile in the ROCm build too, where the file's existing c10::cuda::CUDAGuard stays inside the !USE_ROCM block. A two-device test checks that the op runs on the input's device and matches the single-device result. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
Advances RFC RL-Align#428 C1 on the CUDA track. Claim level: L0 repeatable and L1 batch-invariant. L2 is NOT claimed -- see "On exactness against vLLM" below. Qwen3-Next's decoder and final norms store a zero-centred weight and compute `x * rstd * (1 + w)`, with the `1 +` applied in fp32 after the upcast. Folding it into a bf16 weight beforehand rounds the offset away, so it has to reach the kernel as a parameter rather than being pre-applied by the caller. NativeRMSNormOp gains `weight_offset` (default 0.0) Qwen3NextRMSNormOp subclasses it, overriding only weight_offset = 1.0 Qwen3NextRMSNormGatedOp GDN gated norm: plain w, weight multiply in fp32 Qwen3NextRMSNormGatedHFOp the transformers convention, kept as a witness The offset is applied under `if cls.weight_offset:` rather than unconditionally, because `0.0 + w` rewrites -0.0 to +0.0. torch.equal does not notice that, but a bitwise comparison does, and the plain path must stay bit-for-bit what it was. A test pins it at the bit level. The gated pair exists because transformers and vLLM disagree on where the gated norm's weight multiply happens, and the gap is not a ULP: on bf16 / head_v_dim=128 they differ in 35% of elements with max|diff| = 6.25e-2. Isolating the cast order alone reproduces the gap (5.3e-2), so the cast order dominates rather than the reduction order. The two conventions share their validation and normalization and differ only in a `_scale_by_weight` hook. CUDA: `weight_offset` added to the forward and dx kernels, defaulting to 0.0 so every existing caller and binding is unaffected. The dw kernel is untouched: d/dw (offset + w) == d/dw w. weight_offset=1.0 vs an explicit fp32 (1 + w) weight bitwise equal weight_offset=1.0 vs a bf16-folded (1 + w) weight differs, as required default offset vs the previous kernel bitwise equal The first line is the correctness argument: the in-kernel offset is the same arithmetic as the fp32 reference, not an approximation. The second is a regression guard -- if it ever passes, the offset has stopped being fp32. On exactness against vLLM ------------------------- Measured over 40 seeds (bf16, head_v_dim=128, 512 rows): ours vs forward_native 6/40 seeds differ, worst 1.56e-2 ours vs forward_cuda 18/40 seeds differ, worst 3.91e-3 forward_native vs forward_cuda 21/40 seeds differ, worst 1.56e-2 vLLM's own two paths are not bitwise equal to each other, so "bitwise equal to vLLM" is undefined until a single provider is named. In fp32 the two paths differ on ~36% of elements, every one by an fp32 ULP -- the tree shapes differ, the semantics do not. What this reproduces is the convention (fp32 weight multiply, single trailing cast); the residual is the reduction tree. The reduction stays the repo's fixed 32-wide chunked sum, which is what buys L1. It was introduced for NPU but is needed on CUDA too: over 20 seeds at H=2048 in bf16 -- Qwen3-Next's own hidden_size and dtype -- a plain mean(-1) broke slice invariance on 1 of 20 while the chunked reduction broke on 0 of 20. Matching stock vLLM bitwise would mean adopting a reduction that is not itself batch-invariant, i.e. trading L1 for L2. tests/check_qwen3_next_norm_providers.py pins the dispatch facts and bounds the gap, asserting magnitudes rather than equality so a vLLM bump that changes the provider fails loudly. It is named `check_` rather than `test_`, following tests/distributed/check_*.py: it imports real vLLM, and tests/test_framework_operator_integrations.py asserts vllm is absent from sys.modules, an invariant any collected test importing vLLM would break for the whole session. Also: `rl_engine/_C.pyi` updated for the new `weight_offset` argument (CI runs mypy against it), `tests/test_qwen3_next_norm.py` added to the CI test list in .github/workflows/ci.yml, and an operator page added per docs/operators/README.md ("the documentation page is part of the operator contract"). Verified on 2x B200 (sm_100, driver 580.126.20, torch 2.13.0+cu130, triton 3.7.1, vllm 0.30.0, transformers 5.17.0): pytest tests/test_qwen3_next_norm.py -q pytest tests/check_qwen3_next_norm_providers.py -q pytest tests/ rl_engine/tests/ -q -p no:randomly \ --ignore=tests/test_rocm_aiter_api_contract.py Both new files pass 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>
… VJPs Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
…t norms Tests: the low-precision and CUDA-vs-golden comparisons hard-coded atol=2e-2/rtol=1.6e-2, which is the contract's elementwise bf16 row. They now resolve forward_accuracy for op_class="reduction" from tolerance_contract.json. That loosens bf16 (5e-2/2e-2) and tightens fp16 (1e-3/1e-3, previously 2e-2); the fp16 cases still pass. The vLLM provider-gap bounds in tests/check_qwen3_next_norm_providers.py are labelled as gap bounds, not contract thresholds. Docs and docstrings: correct statements this branch had committed. - The operator page no longer says the op is registered or prints a check_operator command; the gtest spec and registry entry arrive with the gated-norm PR. - Withdrawn: "7 of 1048576 differ" (single seed, no script), the 35% / 5.3e-2 cast-order isolation (an fp32 round-trip is a no-op), "needed on CUDA as well", and "matching vLLM means trading L1 for L2" (one unreproduced observation). Replaced with the scoped claim levels and the probe results. - The provider check no longer claims to establish which path vLLM dispatches, or a per-element fp32 ULP bound. - The module docstring is cut to the contract and links to the operator page. Comments: why the kernel's offset add is guarded (signed zero, with the pinning test), and why parameter_vjp_contributions_fp32 passes the offset. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
fusheng-ji
force-pushed
the
feat/cuda-qwen3-next-c1-norm
branch
from
October 4, 2026 13:20
812d560 to
d03c852
Compare
Resolve the adjacent RMSNorm constructor and weight_offset insertion while preserving both the CUDA symbol guard and the zero-centred norm implementation. Validation: 130 passed, 128 skipped across Qwen3-Next norm, RMSNorm, FP32 VJP and dispatch tests. The resolved tree is identical to the previous PR tip. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
This was referenced Oct 11, 2026
Contributor
Author
|
@Flink-ddd @inaniloquentee This PR is ready for your review at your earliest convenience. |
fusheng-ji
added a commit
to fusheng-ji/RL-Kernel
that referenced
this pull request
Oct 15, 2026
Per the layout review on RL-Align#549, this branch's own files move the way RL-Align#549 already moved them, by applying that migration to this branch's versions: - validation modules and JSON resources go to rl_engine/validation/models/qwen3_next/, runners and stage scripts to tools/validation/models/qwen3_next/; imports, resource paths, CI scripts and tests follow; - the chain plot moves to the Qwen3-Next benchmark PR; - the committed chain evidence becomes a ZIP release attachment with a short summary in reports/experiments/qwen3_next/ (bytes unchanged), so the pre-commit exclusion for it is dropped. The RMSNorm files that RL-Align#467/RL-Align#468 introduce keep their paths here. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
The raw results (JSON, figures, audit files) now live on the fork's pr_assets branch; the documents link to them at a fixed commit. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
fusheng-ji
force-pushed
the
feat/cuda-qwen3-next-c1-norm
branch
from
October 19, 2026 08:46
b5a530c to
d73120c
Compare
fusheng-ji
added a commit
to fusheng-ji/RL-Kernel
that referenced
this pull request
Oct 19, 2026
… autotune cache edge-case probe Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
fusheng-ji
force-pushed
the
feat/cuda-qwen3-next-c1-norm
branch
from
October 23, 2026 21:44
d73120c to
d42c835
Compare
fusheng-ji
added a commit
to fusheng-ji/RL-Kernel
that referenced
this pull request
Oct 23, 2026
Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
fusheng-ji
added a commit
to fusheng-ji/RL-Kernel
that referenced
this pull request
Oct 23, 2026
Per the layout review on RL-Align#549, this branch's own files move the way RL-Align#549 already moved them, by applying that migration to this branch's versions: - validation modules and JSON resources go to rl_engine/validation/models/qwen3_next/, runners and stage scripts to tools/validation/models/qwen3_next/; imports, resource paths, CI scripts and tests follow; - the chain plot moves to the Qwen3-Next benchmark PR; - the committed chain evidence becomes a ZIP release attachment with a short summary in reports/experiments/qwen3_next/ (bytes unchanged), so the pre-commit exclusion for it is dropped. The RMSNorm files that RL-Align#467/RL-Align#468 introduce keep their paths here. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
fusheng-ji
added a commit
to fusheng-ji/RL-Kernel
that referenced
this pull request
Oct 25, 2026
Separate labelled dataset from B200: pinned heads, pytest/reuse/gates, and norm reports on Hopper.
fusheng-ji
added a commit
to fusheng-ji/RL-Kernel
that referenced
this pull request
Oct 25, 2026
, RL-Align#468 and RL-Align#469 at their current heads Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
fusheng-ji
added a commit
to fusheng-ji/RL-Kernel
that referenced
this pull request
Oct 26, 2026
RL-Align#468 and refresh their checksums Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
inaniloquentee
approved these changes
Oct 26, 2026
Collaborator
|
LGTM. cc @Flink-ddd please take a look. |
Flink-ddd
approved these changes
Oct 26, 2026
Flink-ddd
left a comment
Collaborator
There was a problem hiding this comment.
LGTM. The previous review comments have been addressed, and the H100 and B200 results look good. Ready to merge into test-qwennext.
fusheng-ji
added a commit
to fusheng-ji/RL-Kernel
that referenced
this pull request
Oct 26, 2026
test-qwennext now carries RL-Align#467 with rewritten SHAs. Every conflict is in a file this branch already extends (the norm page, the CUDA norm backend, the reuse check and its test, the evidence runner and plot script); this branch's version is a superset of the merged RL-Align#467 content, so each keeps this branch's side and the resulting tree equals the previous tip. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
fusheng-ji
added a commit
to fusheng-ji/RL-Kernel
that referenced
this pull request
Oct 26, 2026
Brings in RL-Align#468's merge of test-qwennext (RL-Align#467 with rewritten SHAs) and its review fixes since e8daba5. Conflicts: the workload scope check, its test and the plot docstring take RL-Align#468's later version of the same changes; the CPU-test line in ci.yml keeps this branch's extra test_gdn_state_contract.py. The diff against RL-Align#468 is again only the C6 files. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
fusheng-ji
added a commit
to fusheng-ji/RL-Kernel
that referenced
this pull request
Oct 26, 2026
Brings in RL-Align#469's merge of RL-Align#468 (which carries test-qwennext with RL-Align#467) and RL-Align#469's provider-workflow changes since this branch last merged it. One conflict, in the provider job's pytest step: it keeps RL-Align#469's fail-closed skip check and adds this branch's check_qwen3_next_forward.py to the module list. That module asserts CUDA instead of skipping, so the skip check does not change its outcome. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
fusheng-ji
added a commit
to fusheng-ji/RL-Kernel
that referenced
this pull request
Oct 26, 2026
Brings in RL-Align#503's merge of RL-Align#469 (which carries RL-Align#468 and test-qwennext with RL-Align#467). One conflict, in the provider job's pytest step: it keeps RL-Align#469's fail-closed skip check and runs this branch's full module list through it. The added mixer modules assert CUDA rather than skip, except check_qwen3_next_gdn_bridge.py, which skips only without CUDA, the case the check exists to fail. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
fusheng-ji
added a commit
to fusheng-ji/RL-Kernel
that referenced
this pull request
Oct 26, 2026
Brings in RL-Align#507's merge of RL-Align#503 (which carries RL-Align#469, RL-Align#468 and test-qwennext with RL-Align#467). One conflict, in the provider job's pytest step: it keeps RL-Align#469's fail-closed skip check and runs this branch's full module list through it. The paged-attention and batch-invariance modules skip only without CUDA or vLLM, the cases the check exists to fail. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
fusheng-ji
added a commit
to fusheng-ji/RL-Kernel
that referenced
this pull request
Oct 26, 2026
Brings in RL-Align#543's merge of RL-Align#507 (which carries RL-Align#503, RL-Align#469, RL-Align#468 and test-qwennext with RL-Align#467). One conflict, in the provider job's pytest step: it keeps RL-Align#469's fail-closed skip check and runs this branch's full module list through it; the added check_qwen3_next_bi_requests.py has no skip path. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
fusheng-ji
added a commit
to fusheng-ji/RL-Kernel
that referenced
this pull request
Oct 26, 2026
Brings in RL-Align#544's merge of RL-Align#543 (which carries RL-Align#507, RL-Align#503, RL-Align#469, RL-Align#468 and test-qwennext with RL-Align#467). This branch moved the norm workload, manifest and evidence scripts under qwen3_next/, so the conflicts take RL-Align#468's later content at this branch's paths: the workload scope check and its tests, the gtest manifest comment and the plot docstring. The two norm pages keep this branch's runner path. RL-Align#468's reuse check, new here, stays at its test-qwennext path and reads the moved manifest. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
fusheng-ji
added a commit
to fusheng-ji/RL-Kernel
that referenced
this pull request
Oct 26, 2026
Brings in RL-Align#557's merge of RL-Align#549 (which carries RL-Align#544 down to RL-Align#468 and test-qwennext with RL-Align#467) and RL-Align#557's fixes since this branch last merged it. No conflicts. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
fusheng-ji
added a commit
to fusheng-ji/RL-Kernel
that referenced
this pull request
Oct 26, 2026
Brings in RL-Align#549's merge of RL-Align#544 (which carries RL-Align#543 down to RL-Align#468 and test-qwennext with RL-Align#467). One conflict, in the provider workflow: the pinned-provider step takes RL-Align#469's fail-closed skip check over the same module list, and this branch's four-GPU vLLM model-contract step follows it unchanged. Signed-off-by: Wenbo Ji <36562829+fusheng-ji@users.noreply.github.com>
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.
What
q_norm/k_normuseQwen3NextRMSNorm(vLLMGemmaRMSNorm): zero-centred weight,(1 + w)with the+1in fp32. The GDN block usesQwen3NextRMSNormGated(vLLMRMSNormGated): plainw, gated bysilu. Folding the+1into a bf16 weight first is incorrect.qwen3_next_rms_norm.py), fixed-order fp32 viashape_invariant_rstd, withforwardandforward_fp32:Qwen3NextRMSNormOpsubclassesNativeRMSNormOpwithweight_offset = 1.0, applied only underif cls.weight_offset:because0.0 + wrewrites-0.0to+0.0;Qwen3NextRMSNormGatedOp(vLLM: fp32 weight multiply) andQwen3NextRMSNormGatedHFOp(transformers: cast first, then multiply) differ only in_scale_by_weight.weight_offset(default0.0), added in fp32 only when nonzero, so-0.0weights on the plain path stay bit-exact. The dw kernel is unchanged. Existing callers are unaffected.weight_offset=1.0istorch.equalto an explicit fp321 + wand differs from a bf16-folded1 + w; both are asserted.test_gated_conventions_diverge_in_low_precision), and agree bitwise in fp32. RFC §6 item 5 requires casts in the same order on both sides, so a trainer built on transformers' gated semantics cannot be bitwise-exact against vLLM on GDN layers.Qwen3NextRMSNormGatedHFOpis kept as a tested witness, with no L1 claim.RMSNormCudaOp.parameter_vjp_contributions_fp32takesrstdfrom_C.rmsnorm_forward, the statistic the kernel's backward uses, instead of recomputing it withtorch.rsqrt. Its only consumer is the C4 gate's gradient adapter.VLLM_BATCH_INVARIANT=1, (b) vLLM patched to the repo's arithmetic as the Qwen3 dense strict profile does, or (c) the repo's CUDA kernels on both sides? The answer also decides which gated convention is the strict default.Prior art & reuse decision
Measured with
scripts/qwen3_next_norm_reuse_check.py(nowtools/validation/models/qwen3_next_norm_reuse_check.py), committed in this PR.qwen3_next_rms_norm.jsonwas written from a clean tree on an otherwise idle B200 (pre-migration; recorded asa66493c, now4f92dfc0). Batch invariance is bitwise: every row alone vs inside full batches of three sizes, the full 65536-row batch vs sub-batches of 2048, 4097 and 32768 rows that cover every row and of 1 and 7 rows sampled at a stride of 128 rows (this branch's runner; the 2026-10-26 runner of #468 covers every row for every sub-batch size), and a dense batch-size sweep. Accuracy is against FP64; latency is at the workload size. The C3/C4 column runs this repository's own gate scripts with the CUDA candidate replaced by a subclass of this op that calls the other library (the gate column comes from #468's branch, recorded as88f59f7, nowda6aa8db).Decision: open, and the reuse rule points to Liger. Liger's
LigerRMSNormFunction(offset=1.0, casting_mode="gemma") is batch-invariant on every check, as accurate as this kernel, and passes this repository's C3/C4 gates, singleton-aggregate included. It is 3× faster forward and 32× faster forward + backward (974 µs vs 31.5 ms). transformers' implementation and Transformer Engine's zero-centred RMSNorm are not batch-invariant (transformers also fails C4). Megatron'sBatchInvariantRMSNormFnat52fbcbcacceptszero_centered_gamma=Truebut ignores it in its forward, multiplying byweightinstead ofweight + 1, so it computes a different function and has no row in the table; the earlier measurements of it were removed from the evidence (see its correction note). This kernel's only remaining advantage is that it adds no dependency. Question for @inaniloquentee: switch to Liger's implementation, or keep this kernel?Comparison charts
GemmaRMSNorm*gemma_rmsnorm*dx(of 768)* forward only. Accuracy is the same for all five: forward max error 1.56e-2 against the FP64 golden (BF16 output rounding),
dx/dweightwithin 2.8e-3 / 2.1e-3 of their maximum. Fromqwen3-next-rms-norm-b200/report.json(scripts/qwen3_next_norm_evidence.py, clean tree at3d0bae7, otherwise idle B200).Results
B200 and H100 are separate data sets; per RFC #428 §2.2 no claim is made across cards. Both were rerun at the head
d42c8357, which differs from the pre-migration commits behind the tables above only in the move tocsrc/cuda/norm/and one comment line inrmsnorm.cu; the Python layer was relocated to the new layout. On B200 Liger, FLA, Transformer Engine and Megatron-core were not installed for the reruns; on H100 Transformer Engine 2.18.0 ran (not batch-invariant), megatron-core 0.16.0rc0 was excluded by the harness's zero-weight probe (it ignoreszero_centered_gamma), and Liger and FLA were not installed. The Liger, FLA and Transformer Engine 2.20.2 rows therefore exist only in the older B200 table above.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; extension built in place from the pinned worktree,
rl_engine._Cchecked to load from it, clean tree. Files:b200-pr467-b200.d42c8357git_dirty: falsed73120c1, same tree as the head)test_cuda_parameter_contributions_reproduce_backward: 4 of 8 cases fail with the oldrstdrecomputation, 8 of 8 pass with the current code; the end-to-end C4 gate passes with either, so only the unit test detects the change (logs)H100 (sm90)
One NVIDIA H100 (driver 580.173.02), torch 2.13.0+cu130, vLLM 0.30.0, transformers 5.13.1, FlashInfer 0.6.18.post1, clean tree. Files:
h100-pr467-h100.d42c8357The negative control was run on B200 only.
Status
d42c8357ontest-qwennext; [WS1][CUDA][Qwen3-Next] Restore CUDA dispatch for rms_norm (RFC #428 stack 1/4) #466 merged on 2026-10-06 (6a4f078), so nothing waits below this PR.ws1-gtestis skipped._Cbuild up front) was answered inbec7e7aand awaits re-review.History
pr_assetscommit after a redaction pass over the H100 evidence; no change to results or code.test-qwennext: mergedtest-qwennextata5a92ecfas requested in RFC #428; code moved torl_engine/backends,rl_engine/reference,rl_engine/runtime,rl_engine/validation, tests totests/models/qwen3_next, tools totools/validation/{models,operators}. After the migration: 160 passed, 73 skipped (B200).qwen3_next_norm_reuse_check.py, evidence underdocs/usage/evidence/qwen3-next-norm-reuse-b200), the comparison charts (53767ab,3d0bae7,d3eb894), andbec7e7a(reject stale RMSNorm bindings, enable GPU coverage).6a4f078(adds the device guard a maintainer asked for), all commits signed off; the stack was retargeted totest-qwennext.d03c852): tests resolve tolerances from the contract'sreductionrow (loosens bf16 to 5e-2/2e-2, tightens fp16 to 1e-3/1e-3; fp16 cases still pass); the operator page no longer claims registration. Withdrawn: "7 of 1048576 differ" (one seed, no script), the 35% / 5.3e-2 cast-order isolation (an fp32 round-trip is a no-op), "needed on CUDA as well", "matching vLLM means trading L1 for L2" (one unreproduced observation), and the claim that no training-capable batch-invariant implementation exists (Liger is one).6741d16's commit message still carries some of these; this description andd03c852are the corrected versions.max_abs=0) under batch-size, chunk, padding and permutation transforms (hidden 2048, head-dim 128).VLLM_BATCH_INVARIANT=1has no effect. Prefix-slice batch invariance had no failures for the dim-0 unbacked compile, default eager, or eager under BI; non-prefix slices were not probed.Pre-migration validation (
d03c852, B200, historical)main(32b765e)d03c852)tests/test_qwen3_next_norm.pytests/check_qwen3_next_norm_providers.pytests/test_rms_norm.pytests/test_ws1_gtest_gpu.pytests/test_vjp_fp32.pyrl_engine/tests/test_dispatch.pycheck_operator rms_normbf16, H=4096pass_rate=1.0000No new failures by test ID; the 15 failures also fail on
main. The commands then (old paths):Tests
At this PR's head
d42c8357, one GPU per card, extension built from a clean checkout of that commit:Skips (environment only, identical on both cards): 72 ×
rmsnorm_ascendkernel not compiled (needsKERNEL_ALIGN_FORCE_ASCEND=1on an Ascend host), 1 × needs at least two CUDA devices.tests/models/qwen3_next/check_qwen3_next_norm_providers.pyimports real vLLM, so the default suite does not collect it; run it explicitly (needs vLLM 0.30.0).The
ws1-gtestCI job is skipped without a self-hosted runner, so the CPU lint/unit/docs checks plus the GPU runs above are the test evidence. The CUDA tests assert:weight_offset=1.0istorch.equalto an explicit fp321 + wand not equal to a bf16-folded one;-0.0weights are preserved on the plain path (fp32/fp16/bf16); batch sizes {1, 2, 8, 16, 32, 48, 64, 512} of 512 rows givetorch.equaloutputs;dxsees1 + wanddwdoes not; and the CUDA op stays within the contract'sreductionbf16 row (5e-2/2e-2) of the PyTorch reference.Scope / Known limitations
fused_add_rms_norm, for which this PR has no reference.dweightis a serial ascending fold over rows so it meets the gradient-invariance contract's singleton-aggregate check bitwise; Liger passes the same gate without a serial fold, so the fold is not what the gate requires.silu, which matches the released config (vLLM also acceptssigmoid).fused_qk_rmsnorm_rope_gatepath forq_norm/k_norm(separate provider); the compiled-graph path of vLLM; theVLLM_GDN_DECODE_KERNEL="cuda"default does not take effect for Qwen3-Next (Triton decode kernel is used).tests/models/qwen3_next/test_qwen3_next_norm.pyduplicates_randandrequires_cuda_rmsnormfromtest_rms_norm.py.Files
rl_engine/reference/norm/qwen3_next_rms_norm.pyrl_engine/kernels/ops/pytorch/norm/rms_norm.pyNativeRMSNormOp.weight_offset, guarded; sharedcheck_norm_weightcsrc/cuda/norm/rmsnorm.cu,csrc/bindings/ops.cpp,rl_engine/_C.pyiweight_offsetin the forward and dx kernels and bindingsrl_engine/backends/cuda/norm/rmsnorm.pyQwen3NextRMSNormCudaOp; parameter-VJPrstdfrom the kerneltests/models/qwen3_next/docs/operators/qwen3-next-rms-norm.mdSummary by CodeRabbit