Skip to content

[WS1][CUDA][Qwen3-Next] C1: zero-centred RMSNorm references and CUDA kernel (RFC #428 stack 2/4) - #467

Merged
Flink-ddd merged 18 commits into
RL-Align:test-qwennextfrom
fusheng-ji:feat/cuda-qwen3-next-c1-norm
Oct 26, 2026
Merged

Flink-ddd merged 18 commits into
RL-Align:test-qwennextfrom
fusheng-ji:feat/cuda-qwen3-next-c1-norm

Conversation

@fusheng-ji

@fusheng-ji fusheng-ji commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

RFC #428 stack 2/4 (base test-qwennext, merge bottom-up): #466 (merged 2026-10-06) → #467 → #468 → #469.

Bitwise status. Advances C1 of #428 (Embedding, RMSNorm, residual, final norm exactness) on the CUDA track, at the levels this branch's tests assert:

  • L1 (prefix-slice and concurrency) for the zero-centred CUDA op Qwen3NextRMSNormCudaOp; batch size, chunking, padding and permutation are covered by [WS1][CUDA][Qwen3-Next] Gated RMSNorm CUDA kernel for the GDN block (RFC #428 stack 3/4) #468's C3/C4 gates;
  • L1 for the PyTorch references Qwen3NextRMSNormOp (slice, concurrency, padding) and Qwen3NextRMSNormGatedOp (prefix-slice, concurrency);
  • L0 for the zero-centred PyTorch reference only (CPU, fp32). No test repeats the CUDA op.

L2 is not claimed. Neither PyTorch reference is bitwise equal to any vLLM path probed, the CUDA op was not compared against vLLM, and the CUDA kernel and the PyTorch reference are not bitwise equal to each other.

Evidence location: measurement outputs are not part of this PR's diff. They are on the fork's pr_assets branch (links fixed to one commit). At the head: B200, H100, negative control. Older: pr467/ (pre-migration B200 reports and charts). Paths such as reports/experiments/... below name where a runner writes its output.

What

  • Two norms, two conventions. Qwen3-Next's decoder input/post-attention norms, final norm and attention q_norm/k_norm use Qwen3NextRMSNorm (vLLM GemmaRMSNorm): zero-centred weight, (1 + w) with the +1 in fp32. The GDN block uses Qwen3NextRMSNormGated (vLLM RMSNormGated): plain w, gated by silu. Folding the +1 into a bf16 weight first is incorrect.
  • PyTorch references (qwen3_next_rms_norm.py), fixed-order fp32 via shape_invariant_rstd, with forward and forward_fp32:
    • Qwen3NextRMSNormOp subclasses NativeRMSNormOp with weight_offset = 1.0, applied only under if cls.weight_offset: because 0.0 + w rewrites -0.0 to +0.0;
    • Qwen3NextRMSNormGatedOp (vLLM: fp32 weight multiply) and Qwen3NextRMSNormGatedHFOp (transformers: cast first, then multiply) differ only in _scale_by_weight.
  • CUDA kernel. The forward and dx kernels gain weight_offset (default 0.0), added in fp32 only when nonzero, so -0.0 weights on the plain path stay bit-exact. The dw kernel is unchanged. Existing callers are unaffected. weight_offset=1.0 is torch.equal to an explicit fp32 1 + w and differs from a bf16-folded 1 + w; both are asserted.
  • Finding: transformers and vLLM disagree on the gated norm. vLLM multiplies the weight in fp32; transformers casts to the input dtype first. In bf16 the two differ by more than 1e-3 (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. Qwen3NextRMSNormGatedHFOp is kept as a tested witness, with no L1 claim.
  • One behaviour change to the plain op. RMSNormCudaOp.parameter_vjp_contributions_fp32 takes rstd from _C.rmsnorm_forward, the statistic the kernel's backward uses, instead of recomputing it with torch.rsqrt. Its only consumer is the C4 gate's gradient adapter.
  • Question for the maintainers. The RFC's C1 plan names "vLLM strict ops" as the provider; this PR adds an independent PyTorch reference and the repo's own CUDA kernel instead. Which source of truth should the Qwen3-Next CUDA strict profile use: (a) a shared vLLM forward under 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 (now tools/validation/models/qwen3_next_norm_reuse_check.py), committed in this PR. qwen3_next_rms_norm.json was written from a clean tree on an otherwise idle B200 (pre-migration; recorded as a66493c, now 4f92dfc0). 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 as 88f59f7, now da6aa8db).

qwen3_next_rms_norm vs existing implementations

Implementation Batch-invariant Forward correctly rounded Worst grad err Forward / forward + backward C3/C4 gates
rl-kernel Qwen3NextRMSNormCudaOp yes 99.9993% 2.4e-03 426 µs / 31.5 ms pass
rl-kernel PyTorch reference no (49 rows differ; 94 sub-batches differ) 99.9993% 2.4e-03 2146 µs / 5024 µs —
transformers 5.17.0 Qwen3NextRMSNorm no (90 rows differ; 142 sub-batches differ) 99.9992% 2.4e-03 1449 µs / 4468 µs C3 pass, C4 fail
Liger 0.8.4 RMSNorm, offset 1, gemma yes 99.9993% 2.4e-03 133 µs / 974 µs pass
FLA 0.5.2 rms_norm, weight passed as 1 + w yes 73.0161% 5.5e-03 147 µs / 1051 µs pass
TE 2.20.2 RMSNorm(zero_centered_gamma) no (175 rows differ; 245 sub-batches differ) 99.9992% 2.4e-03 156 µs / 779 µs —
Megatron BatchInvariantRMSNormFn(zero_centered_gamma=True) excluded: not a valid comparison — — — —
FlashInfer 0.6.18.post1 gemma_rmsnorm yes 99.9992% — 91 µs / — —
vLLM 0.30.0 GemmaRMSNorm.forward_cuda no (16 rows differ; 26 sub-batches differ) 99.9992% — 1454 µs / — —

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's BatchInvariantRMSNormFn at 52fbcbc accepts zero_centered_gamma=True but ignores it in its forward, multiplying by weight instead of weight + 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

zero-centred RMSNorm vs existing implementations

B200, hidden 2048, BF16 rl-kernel CUDA PyTorch reference transformers vLLM GemmaRMSNorm* FlashInfer gemma_rmsnorm*
rows differing alone vs in a batch, forward / dx (of 768) 0 / 0 0 / 12 2 / 11 2 / n/a 0 / n/a
forward, 65536 rows 426 µs 2148 µs 1449 µs 1450 µs 91 µs
backward, 65536 rows 30.9 ms 3.0 ms 3.1 ms n/a n/a

* forward only. Accuracy is the same for all five: forward max error 1.56e-2 against the FP64 golden (BF16 output rounding), dx / dweight within 2.8e-3 / 2.1e-3 of their maximum. From qwen3-next-rms-norm-b200/report.json (scripts/qwen3_next_norm_evidence.py, clean tree at 3d0bae7, 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 to csrc/cuda/norm/ and one comment line in rmsnorm.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 ignores zero_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._C checked to load from it, clean tree. Files: b200-pr467-b200.

check result at d42c8357
unit suite (see Tests) 203 passed, 0 failed, 73 skipped
norm evidence report ran, rc 0, git_dirty: false
existing-implementation comparison rl-kernel CUDA batch-invariant; PyTorch reference, transformers, vLLM not BI; FlashInfer BI
negative control (d73120c1, same tree as the head) test_cuda_parameter_contributions_reproduce_backward: 4 of 8 cases fail with the old rstd recomputation, 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.

check result at d42c8357
unit suite (see Tests) 203 passed, 0 failed, 73 skipped
norm evidence report ran, rc 0
existing-implementation comparison, idle GPU same BI verdicts as B200 (rl-kernel CUDA and FlashInfer BI; PyTorch reference, transformers, vLLM, TE not BI)

The negative control was run on B200 only.

Status

  • Head d42c8357 on test-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.
  • CI at the head: lint, unit-tests, docs and DCO pass; ws1-gtest is skipped.
  • Review: the 2026-10-07 change request (add the CUDA cases to the WS1 GPU gate; detect an older _C build up front) was answered in bec7e7a and awaits re-review.
  • Open: the decision question under Prior art, and the source-of-truth question under What.

History

  • 2026-10-26: evidence links retargeted to the current pr_assets commit after a redaction pass over the H100 evidence; no change to results or code.
  • Migration to test-qwennext: merged test-qwennext at a5a92ecf as requested in RFC #428; code moved to rl_engine/backends, rl_engine/reference, rl_engine/runtime, rl_engine/validation, tests to tests/models/qwen3_next, tools to tools/validation/{models,operators}. After the migration: 160 passed, 73 skipped (B200).
  • 2026-10-08: added the existing-implementation comparison (qwen3_next_norm_reuse_check.py, evidence under docs/usage/evidence/qwen3-next-norm-reuse-b200), the comparison charts (53767ab, 3d0bae7, d3eb894), and bec7e7a (reject stale RMSNorm bindings, enable GPU coverage).
  • 2026-10-04: re-stacked onto [WS1][CUDA][Qwen3-Next] Restore CUDA dispatch for rms_norm (RFC #428 stack 1/4) #466 at 6a4f078 (adds the device guard a maintainer asked for), all commits signed off; the stack was retargeted to test-qwennext.
  • Corrections (d03c852): tests resolve tolerances from the contract's reduction row (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 and d03c852 are the corrected versions.
  • Measured on [WS1][CUDA][Qwen3-Next] Gated RMSNorm CUDA kernel for the GDN block (RFC #428 stack 3/4) #468's branch: the C3/C4 gates for the zero-centred CUDA op pass bitwise (max_abs=0) under batch-size, chunk, padding and permutation transforms (hidden 2048, head-dim 128).
  • Probes, not committed: the decoder norm differs from every vLLM path probed on 36–39 of 40 seeds (max 1.56e-2); the gated reference differs from vLLM's eager paths on 6/40 and 18/40 seeds and from its compiled path on 17–18/40. Compiling is a third numerical path for both norms, and in a standalone compile VLLM_BATCH_INVARIANT=1 has 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)
check main (32b765e) this branch (d03c852)
full suite 17 failed, 2942 passed, 923 skipped 15 failed, 3015 passed, 923 skipped
tests/test_qwen3_next_norm.py — (new) 65 passed
tests/check_qwen3_next_norm_providers.py — (new) 9 passed
tests/test_rms_norm.py — 57 passed, 72 skipped
tests/test_ws1_gtest_gpu.py — 9 passed
tests/test_vjp_fp32.py — 50 passed
rl_engine/tests/test_dispatch.py — 13 passed
check_operator rms_norm bf16, H=4096 — pass_rate=1.0000

No new failures by test ID; the 15 failures also fail on main. The commands then (old paths):

MAX_JOBS=32 TORCH_CUDA_ARCH_LIST=10.0 RL_KERNEL_REQUIRE_EXT=1 pip install --no-build-isolation --no-deps -e .
python -m pytest tests/test_qwen3_next_norm.py tests/test_rms_norm.py tests/test_ws1_gtest_gpu.py tests/test_vjp_fp32.py -q -p no:randomly
python -m pytest tests/check_qwen3_next_norm_providers.py -q -p no:randomly   # needs vLLM 0.30.0
python scripts/check_operator.py --op rms_norm --candidate cuda --device cuda --dtype bf16 --batch 2 --seq 16 --normalized-dim 4096 --seed 123 --check-grad

Tests

At this PR's head d42c8357, one GPU per card, extension built from a clean checkout of that commit:

pytest tests/models/qwen3_next tests/ops/norm/test_rms_norm.py tests/ops/autograd/test_vjp_fp32.py tests/runtime/test_dispatch.py tests/validation/operators/test_ws1_gtest_gpu.py -q -p no:randomly -rfs
python tools/validation/models/qwen3_next_norm_evidence.py --out report.json
Card passed / failed / skipped
B200 (sm_100) 203 / 0 / 73
H100 (sm90) 203 / 0 / 73

Skips (environment only, identical on both cards): 72 × rmsnorm_ascend kernel not compiled (needs KERNEL_ALIGN_FORCE_ASCEND=1 on an Ascend host), 1 × needs at least two CUDA devices. tests/models/qwen3_next/check_qwen3_next_norm_providers.py imports real vLLM, so the default suite does not collect it; run it explicitly (needs vLLM 0.30.0).

The ws1-gtest CI 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.0 is torch.equal to an explicit fp32 1 + w and not equal to a bf16-folded one; -0.0 weights are preserved on the plain path (fp32/fp16/bf16); batch sizes {1, 2, 8, 16, 32, 48, 64, 512} of 512 rows give torch.equal outputs; dx sees 1 + w and dw does not; and the CUDA op stays within the contract's reduction bf16 row (5e-2/2e-2) of the PyTorch reference.

Scope / Known limitations

  • CUDA only. The decoder norm is not bitwise equal to any vLLM path probed; the references are checked against FP64 and the vLLM conventions, not against vLLM bits. Only layer 0's input norm takes the no-residual path in vLLM; the other 96 of 97 decoder-norm calls carry a residual and go through fused_add_rms_norm, for which this PR has no reference.
  • L2 is not claimed. This PR alone does not meet RFC §13 item 2 ("CUDA-C1: every CUDA provider passes L0/L1"): the CUDA op has no L0 test. Residual (also in C1) is not addressed.
  • Known cost: the CUDA backward is about 10× slower than transformers at 65536 rows (30.9 ms vs 3.1 ms). dweight is 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.
  • The gated norm has no CUDA kernel here (that is [WS1][CUDA][Qwen3-Next] Gated RMSNorm CUDA kernel for the GDN block (RFC #428 stack 3/4) #468); the gated pair is documented there. The activation is fixed at silu, which matches the released config (vLLM also accepts sigmoid).
  • Not covered: vLLM's fused fused_qk_rmsnorm_rope_gate path for q_norm/k_norm (separate provider); the compiled-graph path of vLLM; the VLLM_GDN_DECODE_KERNEL="cuda" default does not take effect for Qwen3-Next (Triton decode kernel is used).
  • Left as is: tests/models/qwen3_next/test_qwen3_next_norm.py duplicates _rand and requires_cuda_rmsnorm from test_rms_norm.py.
Files
file what
rl_engine/reference/norm/qwen3_next_rms_norm.py the three PyTorch references
rl_engine/kernels/ops/pytorch/norm/rms_norm.py NativeRMSNormOp.weight_offset, guarded; shared check_norm_weight
csrc/cuda/norm/rmsnorm.cu, csrc/bindings/ops.cpp, rl_engine/_C.pyi weight_offset in the forward and dx kernels and bindings
rl_engine/backends/cuda/norm/rmsnorm.py Qwen3NextRMSNormCudaOp; parameter-VJP rstd from the kernel
tests/models/qwen3_next/ norm tests (CPU and CUDA cases) and the vLLM provider checks (not collected by default)
docs/operators/qwen3-next-rms-norm.md operator page

Summary by CodeRabbit

  • New Features
    • Added Qwen3-Next RMSNorm support, including zero-centred weights and gated variants, with PyTorch and CUDA implementations.
    • RMSNorm now supports an optional weight offset; the default behavior remains unchanged.
  • Documentation
    • Added a guide to Qwen3-Next RMSNorm, including supported implementations, usage details, and performance and accuracy results.
  • Tools
    • Added utilities to compare implementations and visualize performance, accuracy, and batch-invariance results.

@coderabbitai

coderabbitai Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Walkthrough
📝 Walkthrough

Walkthrough

The pull request adds zero-centred and gated Qwen3-Next RMSNorm operators in PyTorch and CUDA. It extends the RMSNorm API with a weight offset, checks extension compatibility, adds tests and provider comparisons, and provides benchmark evidence and operator documentation.

Changes

Qwen3-Next RMSNorm

Layer / File(s) Summary
PyTorch operators and reference tests
rl_engine/kernels/ops/pytorch/norm/rms_norm.py, rl_engine/kernels/ops/pytorch/norm/qwen3_next_rms_norm.py, tests/test_qwen3_next_norm.py
The shared PyTorch RMSNorm adds weight-shape validation and a configurable offset. New operators implement zero-centred and gated conventions, with tests for formulas, dtypes, shapes, repeatability, and batch invariance.
CUDA offset plumbing and compatibility
csrc/cuda/rmsnorm.cu, csrc/ops.cpp, rl_engine/_C.pyi, rl_engine/kernels/ops/cuda/norm/rmsnorm.py, tests/test_qwen3_next_norm.py, tests/test_rms_norm.py, .github/workflows/ci.yml, .github/workflows/ws1-gtest-gpu.yml, ci/run_ws1_gtest.sh
The extension passes the offset to CUDA forward and input-gradient kernels and exposes API version 2. The Python CUDA operator validates the API version and adds a Qwen3-Next operator with offset 1.0. Tests cover offset behavior and extension compatibility; CI includes the new test module and watches the CUDA changes.
Provider comparisons and benchmark tooling
tests/check_qwen3_next_norm_providers.py, scripts/qwen3_next_norm_evidence.py, scripts/qwen3_next_norm_reuse_check.py, scripts/plot_qwen3_next_norm_evidence.py, docs/usage/evidence/qwen3-next-*/...
Provider checks compare vLLM paths and the custom operator. CUDA scripts measure accuracy, row invariance, and latency across available implementations, then write reports and optional plots. The added JSON records contain B200 measurements and environment details.
Operator documentation and navigation
docs/operators/qwen3-next-rms-norm.md, docs/operators/README.md, docs/.nav.yml
The operator page describes the zero-centred convention, entry points, support limits, test commands, and recorded measurements. The operator index and navigation link to the page.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~30 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant RMSNormCudaOp
  participant rmsnorm_cuda
  participant PyBindRMSNorm
  participant RMSNormCUDAKernel
  Caller->>RMSNormCudaOp: Invoke RMSNorm operation
  RMSNormCudaOp->>rmsnorm_cuda: Pass configured weight_offset
  rmsnorm_cuda->>PyBindRMSNorm: Call forward or input-gradient API
  PyBindRMSNorm->>RMSNormCUDAKernel: Run offset-aware CUDA kernel
  RMSNormCUDAKernel-->>PyBindRMSNorm: Return RMSNorm result
Loading
Merge Risk: Low · up to af137

The CUDA wrapper accepts non-contiguous inputs despite the guide’s wording, and the Megatron comparison should not be used as a zero-centred result. These are bounded documentation and evidence concerns rather than a demonstrated operator failure.

Security Architecture Review

Security architecture risk: 🔵 Low · up to af137

This is a bounded compatibility change with safeguards against mismatched versions. No new security weakness was established in the reviewed paths, but deployment and broader usage remain only partially assessed.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The inspected numerical change operates on tensors in the invoking process and its CUDA device. It adds a scalar semantic parameter, not an authorization capability. Wider tenant or service exposure is not established because deployment topology and complete caller coverage are unavailable.

Trust Boundaries and Controls

  • observed — The reuse checker imports caller-selected local source and writes caller-selected output under its existing process authority. Normal contract checks launch child processes with argument arrays, and adapter replacement occurs inside those children. This is not a sandbox for untrusted implementations, but the inspected entrypoint does not grant additional privileges.

Resilience and Maintainability Implications

  • inferred — Repeated or concurrent backward calls do not show a new shared gradient-state transition: saved tensors and offsets belong to each autograd context. Shared backward telemetry is lock-protected and contains execution metadata rather than tensors. This supports isolation by inspection, not a demonstrated concurrent-runtime guarantee.

Hardening Proposals

  • proposed — For direct native consumers, consider making same-device and complete backward shape requirements explicit at the native boundary. This would strengthen containment of malformed tensor inputs; it addresses inherited validation gaps rather than a verified PR-introduced finding.
Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 24.84% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 157 functions across 15 files. (4 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed The title clearly and concisely describes the main changes: Qwen3-Next zero-centred RMSNorm references and the CUDA kernel implementation.
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Full details: Docstring Coverage

Explanation

Docstring coverage is 24.84% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 157 functions across 15 files. (4 skipped: 4 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@fusheng-ji
fusheng-ji force-pushed the feat/cuda-qwen3-next-c1-norm branch from 3c2e73f to 3c9d0af Compare October 4, 2026 01:49
@fusheng-ji fusheng-ji changed the title [WS1][CUDA] [Qwen3-Next] RMSNorm references and zero-centred CUDA kernel [WS1][CUDA][Qwen3-Next] C1: zero-centred RMSNorm references and CUDA kernel (RFC #428 stack 2/4) Oct 4, 2026
@Flink-ddd Flink-ddd added new-model qwen WS1 platform: cuda Specific optimizations or bugs in NVIDIA graphics cards (such as FlashInfer, TMA optimizations) labels Oct 4, 2026
@Flink-ddd
Flink-ddd changed the base branch from main to test-qwennext October 4, 2026 03:40
@Flink-ddd
Flink-ddd deleted the branch RL-Align:test-qwennext October 4, 2026 04:41
@Flink-ddd Flink-ddd closed this Oct 4, 2026
@Flink-ddd Flink-ddd reopened this Oct 4, 2026
`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
fusheng-ji force-pushed the feat/cuda-qwen3-next-c1-norm branch from 3c9d0af to 812d560 Compare October 4, 2026 11:22
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>
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>
@fusheng-ji

Copy link
Copy Markdown
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
fusheng-ji force-pushed the feat/cuda-qwen3-next-c1-norm branch from b5a530c to d73120c Compare October 19, 2026 08:46
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
fusheng-ji force-pushed the feat/cuda-qwen3-next-c1-norm branch from d73120c to d42c835 Compare October 23, 2026 21:44
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

Copy link
Copy Markdown
Collaborator

LGTM. cc @Flink-ddd please take a look.

@Flink-ddd Flink-ddd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. The previous review comments have been addressed, and the H100 and B200 results look good. Ready to merge into test-qwennext.

@Flink-ddd
Flink-ddd merged commit c382dd4 into RL-Align:test-qwennext Oct 26, 2026
6 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new-model platform: cuda Specific optimizations or bugs in NVIDIA graphics cards (such as FlashInfer, TMA optimizations) qwen WS1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants