Skip to content

Extend rmsnorm_rope to the text profile: one graph node instead of three, +0.60% decode on Qwen3.6-35B-A3B - #222

Open
MichaelDementii wants to merge 2 commits into
Neroued:masterfrom
MichaelDementii:perf/rmsnorm-rope-text
Open

Extend rmsnorm_rope to the text profile: one graph node instead of three, +0.60% decode on Qwen3.6-35B-A3B#222
MichaelDementii wants to merge 2 commits into
Neroued:masterfrom
MichaelDementii:perf/rmsnorm-rope-text

Conversation

@MichaelDementii

Copy link
Copy Markdown
Contributor

TextContext::attn_mix and mtp_forward_tail each spend three graph nodes on one operation:
normalize Q, normalize K, rotate both. Across 13 full-attention layers and the MTP path that is
~26 nodes per decode round.

ops::rmsnorm_rope already fuses exactly this, but only for the D=128 / 32-8 profile and only in
place, so the text stack cannot use it. The split kernels still carry the remains of an abandoned
attempt: qk_norm_rope_text16x2_kernel computes lane_sin/lane_cos, reads
kTextRopeInvFrequency, issues four __shfl_sync, and uses none of it.

Change

A third overload of ops::rmsnorm_rope for the text profile — D=256 heads, (16,2) or (24,4),
rotary width 64, out of place — in the same Op directory, with the same validation layer and
contract style. The two call sites take it when the geometry matches and the position axis is 1-D,
and keep the three separate calls otherwise, so mrope and any future geometry are untouched and
nothing can throw.

Bit-exactness is by construction, not by tuning: the sum of squares accumulates in the layout
rmsnorm_warp_bf16x2_kernel uses (pair = lane + k*32), the epilogue is the same
rmsnorm_epilogue<Offset>, the normalized value is rounded to BF16 before the rotation, and the
coefficients come from the same fixed_sincos<Text1D>. RoPE pairs channel p with p+32, which
that layout keeps in different lanes, so the partner arrives through __shfl_xor_sync(..., 16).

7 files, +249 / −6 in product code.

Evidence

ninfer_rmsnorm_rope_test gains 26 text cases — both head geometries, T from 1 to 4096, both graph
modes — each judged twice on the same inputs:

  • the independent FP64 oracle, extended to the text formula;
  • bit equality against rmsnormrmsnormrope, which is the property a caller relies on
    when it swaps one for the other, and which no tolerance can stand in for.

The text profile needed its own tolerances (relative L2 2.5e-3, pair 1.4e-2, against the D128 form's
1.85e-3 / 6.9e-3): normalizing over twice as many channels puts the FP32 reduction further from an
FP64 oracle. Those limits belong to the route rather than to the fusion — the fused result is
bit-identical to the split one, so the same limits have to admit the shipped route either way.

Strength control: perturbing the kernel's inv by one part in 10⁶ makes the test fail.

ctest 114/114 on both sides; clang-format clean on every touched file.

Effect

ninfer_bench -pg 8192,512 --prefill-chunk 4096 --max-ctx 9216 --spec mtp --draft-tokens 3 --lm-head-draft -r 2 --warmup 1, metric decode_output_tok_s_mean. Eight passes, arm order
mirrored between passes, third arm = master rebuilt under a second label as a zero control.

median vs master range passes positive
this change +0.596 % decode +0.445 … +0.675 8 / 8
zero control +0.021 % −0.047 … +0.094

The worst pass of the change is above the best pass of the control.

Prefill is not claimed: −0.13 % median against a zero control of −0.12 % on the same runs.

The level of the claim is the schedule. Per-call kernel time roughly halves, but most of that is
launch cost rather than work, so an operator benchmark overstates the product effect by about a
factor of two; the number worth quoting is two nodes removed per call and +0.6 % of decode.

RTX 5090 sm_120a, CUDA 13.1, Release, qwen3_6_35b_a3b.ninfer.

🤖 Generated with Claude Code

…of three

TextContext::attn_mix and mtp_forward_tail each spent three graph nodes on one
operation - normalize Q, normalize K, rotate both - which is about twenty-six
nodes per decode round across thirteen full-attention layers and the MTP path.
ops::rmsnorm_rope already fuses exactly this, but only for the D128 32/8 profile
and only in place, so the text stack could not use it.

Adds a third overload for the text profile: D256 heads, (16,2) or (24,4), rotary
width 64, out of place, in the same Op directory with the same validation layer.
The two call sites take it when the geometry matches and the position axis is
one-dimensional, and keep the three separate calls otherwise, so mrope and any
future geometry are unaffected.

The result is bit-identical to the route it replaces, by construction: the sum
of squares accumulates in the layout rmsnorm_warp_bf16x2_kernel uses, the
epilogue is the same rmsnorm_epilogue<Offset>, the normalized value is rounded
to BF16 before the rotation, and the coefficients come from the same
fixed_sincos<Text1D>.

Decode throughput +0.596% median over eight mirrored passes, all eight positive,
fully separated from a zero control that spans -0.047 to +0.094%. Prefill is
unchanged within the same control. ninfer_rmsnorm_rope_test gains twenty-six
text cases, each judged both against the independent FP64 oracle and against
rmsnorm -> rmsnorm -> rope bit for bit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-09T19:14:38.841940Z ac6a309 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5f7a4c4a77

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +67 to +68
if (positions.ne[1] == 1) {
ops::rmsnorm_rope(positions, q_norm, k_norm, q, k, qn, kn, stream);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Fall back when the token count exceeds the fused limit

For one-dimensional text prefill this condition selects the fused overload regardless of T, but that overload rejects T > 8192 in rmsnorm_rope.cpp:116-117. The public --prefill-chunk contract permits any positive multiple of 128 (docs/cli.md:203), target validation imposes no 8192 ceiling, and both variants support contexts up to 262144, so a valid configuration such as --max-context 16384 --prefill-chunk 16384 now throws during ordinary prefill (and offline scoring) where the former split route worked. Include the fused operator's token bound in this dispatch or remove that artificial bound.

AGENTS.md reference: AGENTS.md:L35-L40

Useful? React with 👍 / 👎.

…form

The text overload rejected T above 8192 while the call site dispatched to it
for any T, so a prefill chunk wider than 8192 - which docs/cli.md permits, any
positive multiple of 128 - threw where the split route had worked. The bound was
not the kernel's: one warp owns one head, and the only real ceiling is the launch
grid, which the validation now states instead.

Two cases added past the old bound, T=8320 at (16,2) and T=16384 at (24,4),
checked against rmsnorm -> rmsnorm -> rope bit for bit. The FP64 oracle stays on
the widths it can afford to evaluate.

Reported by Codex on the pull request.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@MichaelDementii

Copy link
Copy Markdown
Contributor Author

Codex is right and this was a real regression: the 8192 ceiling was mine, not the kernel's, and the
call site dispatched to the fused Op regardless of T, so --prefill-chunk 16384 would have thrown
where the split route worked.

Fixed by removing the invented bound rather than teaching the dispatch about it: one warp owns one
head, so the only real ceiling is the launch grid, and the validation now says exactly that. Two
cases added past the old ceiling — T = 8320 at (16,2) and T = 16384 at (24,4) — checked against
the split route bit for bit; the FP64 oracle stays on the widths it can afford.

@MichaelDementii

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Provided git ref ac6a309cd2e199f099719e9197af2626e0ba7e05 does not exist
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@MichaelDementii

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: ac6a309cd2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Gevil added a commit to Gevil/ninfer that referenced this pull request Sep 11, 2026
… pick list corrected - Neroued#222 parent + Neroued#195 series were missing from the audit)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants