Skip to content

Register the text profile of ops::rmsnorm_rope and route the two full-attention call sites to it - #222

Closed
MichaelDementii wants to merge 1 commit into
Neroued:masterfrom
MichaelDementii:perf/rmsnorm-rope-text
Closed

MichaelDementii wants to merge 1 commit into
Neroued:masterfrom
MichaelDementii:perf/rmsnorm-rope-text

Conversation

@MichaelDementii

@MichaelDementii MichaelDementii commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Problem and scope

Related Issue: #272

Both full-attention call sites normalize the query, normalize the key, and rotate both — three Op
calls for one semantic step. ops::rmsnorm_rope already fuses exactly that, and is registered for
two profiles: the DFlash2 pair at [128,32,W,B] and the context-K single at [128,8,T]. Neither
covers the text heads, which are 256 wide with a 64-channel rotation and want the result out of
place — so the text stack keeps issuing the three.

On qwen3.6-35b-a3b that is ten of the forty text layers, plus the MTP tail once per draft step.

Scope is a third profile of that Op and the dispatch that chooses it. No existing overload changes,
and no caller of them is touched.

Implementation

The Op. A third overload takes q_in [256,Q,T], k_in [256,K,T] and writes q_out, k_out,
for (Q,K) of (16,2) or (24,4). Only the first 64 channels rotate; the remaining 192 carry the
normalized value through. The norm weight enters as a delta around one — the Offset epilogue the text
stack normalizes with — unlike the two in-place forms, which multiply by the stored weight directly.
The rotation consumes the BF16 represented normalized value, so the result is bit-identical to
rmsnorm(q_in) → rmsnorm(k_in) → rope(q_out, k_out). Its contract says so, and the test checks it.

The dispatch. text_qk_norm_rope sits beside text_rope in execution/attention.cpp, where the
MRoPE axis check already lives, so both call sites go through one place. It takes the fused Op where
the profile matches, and the three calls everywhere else — the MRoPE path, any other geometry, and
above a width bound.

The width bound is the part I would not have guessed

The fused form wins by 41.94 to 52.07 % at decode widths and loses above about six hundred
tokens:

T (16,2) (24,4)
256 −30.01 % −21.83 %
320 −26.40 % −0.90 %
512 −10.99 % −1.91 %
576 −10.35 % +2.91 %
1024 +5.23 % +21.87 %
2048 +17.20 % +32.05 %

The two geometries cross at different widths. (24,4) is positive from 576 onward; (16,2) stays
ahead through 896 and turns at 960. So the bound has to suit the earlier of the two.

It is set at 256, where the fused route is −30.01 % on (16,2) and −21.83 % on (24,4).
Those figures pool the two sweeps, which read that width independently at −30.03 / −21.97 and
−29.99 / −21.70.

I did not set it at the crossover, because the margin disappears well before the sign does. On
(24,4) the approach is −11.43 at 288, −0.90 at 320, −3.12, −6.30, −8.58, −5.98 out to 448 — and
−0.90 % is inside the null's own worst kept pass, so at 320 this stand can no longer tell the two
routes apart. 256 is the last width where the gain is larger than the instrument on both geometries
by more than an order of magnitude.

I have no established mechanism for the crossover. The obvious one — that one warp owns one head, so
the kernel stops gaining once the width alone fills the machine — does not predict that non-monotone
approach, and nothing here measures occupancy. What is measured is where the sign changes, on both
geometries, at every width between.

The Op itself is valid at any width; this is a dispatch choice, and both branches are the same
arithmetic bit for bit.

Decode issues T = W × B, at most 128, so the bound never excludes it. A prefill chunk is a
multiple of 128; the default is 1024, so in practice prefill keeps the three calls.

The predicate covers every model this engine ships a card for. There are five cards over three base
models, and their published text_config gives:

base model heads kv heads head_dim rotary layers full-attention
Qwen3.6-27B 24 4 256 64 64 16
Qwen3.8-27B 24 4 256 64 64 16
Qwen3.6-35B-A3B 16 2 256 64 40 10

head_dim 256 and rotary 64 on all three, and the two head geometries are exactly the two the
predicate lists and the two the sweep measures. (24,4) is the pair that crosses earliest, so it is
the one that sets the bound.

The call site is on the full-attention path only — TextContext::run_layers picks the mixer per
layer on config_.layer_types[layer] == MixerKind::FullAttention, and the call lives inside
attn_mix. So it fires 16 times per forward pass on the 27B models and 10 on
the 35B-A3B, not once per layer. At T=1 that is 16 × 1.611 µs = 25.8 µs saved per pass on (24,4)
and 10 × 1.553 = 15.5 µs on (16,2). I am not turning that into a share of a decode round, because
this package does not measure a round.

Verification

RTX 5090 at a 575 W cap, driver 616.64, CUDA 13.1.115, CMAKE_CUDA_ARCHITECTURES=120a, Release,
base f76e19c0. Both arms' CMake caches agree on every value once the tree paths are normalised;
they differ in one line, and only in its cache type annotation — CMAKE_CUDA_COMPILER:UNINITIALIZED
against :STRING, same /usr/local/cuda/bin/nvcc on both. That is an artifact of one tree being
configured with the variable on the command line and the other inheriting it, not a build
difference.

The operator sweeps were taken on dc58675f; master has moved six commits since and this branch is
rebased onto today's head. None of the six touches any of the eleven files here. The gates were
re-run on the submission base; the sweeps were not, so they are quoted as what they are —
measured on dc58675f.

What makes them still comparable is that the instrument did not move either. bench/ops/ rmsnorm_rope_bench.cu is unchanged between the two bases, and bench/ops/ninfer_bench_common.h
gained 67 lines and lost zero — a new measure_cold_launch_prepared for in-place Ops, which
this bench does not call. It calls bench::measure_graph, untouched. So the code that produced the
sweep numbers is byte-identical on the base this ships against.

What the dispatch takes, and what it declines

The benchmark gains the text profile and a --route split|fused switch, so both routes live in one
binary and the comparison is one process per pass: --route split issues the three calls the model
issues today, --route fused issues the Op, and --route split again is the identical-baseline null.
Six passes, the arm order rotated through six permutations, graph execution, 20 warmups and 200
samples over 32 inner repetitions.

A pass is dropped for a cell when the two identical runs disagree by more than 1 %. Over the wide
sweep, 29 of 240; over the dense sweep of the crossover region, 19 of 252. The rule bounds what
survives, so the honest statement of the instrument's width is the worst kept pass: 0.97 % and
0.96 %, with medians of 0.15 % and 0.18 % and 95th percentiles of 0.87 % and 0.69 %.

band cells median best worst faster
taken by the dispatch, T ≤ 256 38 −43.27 % −52.07 % −21.83 % 38 of 38
declined, keeps the three calls 38 −3.58 % −26.85 % +32.05 % 25 of 38
T (16,2) split → fused, µs (24,4) split → fused, µs
1 3.72 → 2.17 3.81 → 2.20
8 4.25 → 2.19 4.50 → 2.20
16 4.36 → 2.18 4.61 → 2.23
64 4.67 → 2.52 5.02 → 2.78
128 4.88 → 2.90 5.34 → 3.38
256 5.40 → 3.78 6.16 → 4.81

The fused form is flat at about 2.2 µs from T=1 to T=16 — 2.171 to 2.204 on (16,2), 2.191 to
2.232 on (24,4) — while the logical traffic it moves over that range grows about sixteen-fold
(8.5 → 135.3 GB/s and 13.1 → 205.6 GB/s). Its time is independent of the work. (24,4) leaves the
plateau first, at 2.361 µs by T=24. The split form climbs over the same range, 3.72 → 4.36 µs on
(16,2) and 3.81 → 4.61 on (24,4), with a step between T=6 and T=7 on both — +0.38 µs and
+0.52 µs — that a constant three-launch overhead does not explain.

No width the dispatch takes is slower on either geometry.

Output

NINFER_OP_REPORT_STATS=1 at %.17g over the whole test suite, run on both bases:

base shared cases differing only in candidate only in master
dc58675f 11900 0 56 0
5b4303c0 11912 0 56 0
f76e19c0 (this one) 11986 0 56 0

Master gains cases between the bases as it gains tests of its own, and both arms see them. What does not move is the part being claimed: no shared case differs in any field,
and the candidate adds exactly the 56 text-profile checks the test gains. ctest 120/120 on
both arms on both bases.

The test judges the new form against the independent FP64 oracle and, separately, against the three
calls it replaces, bit for bit.

Strength control. Bit-exactness is the claim, so the test has to be able to see a perturbation far
below any oracle criterion. Scaling the rotation by a factor of 1.000001 — one part per
million, about 3900× below one BF16 ulp — fails 33 of the 60 exact-equality comparisons the
suite makes against the split route, across 20 of its 30 text cases, 20 on the query and 13 on the
key (equals split route: exact mismatch at index …; the run's own summary line reads
failures=33). Restoring it passes. It does not fail everywhere, and it should not: that far below
the output quantum, the nudge only moves a value that was already sitting on a rounding boundary,
which is also why the check has to be exact equality — a tolerance would have reported nothing.

clang-format reports no replacements on any of the eleven files, and none gains a line over the
100-column limit that master did not already have.

No end-to-end number

This stand cannot resolve a change of this size in a decode run. An identical-baseline arm — the
same build measured twice — reads +0.55 % median over six passes, spanning −0.44 % to +0.89 %.
That arm was collected on this machine for a different submission, same build configuration and
artifact; I am quoting it as a property of the stand, not as a measurement of this change.

An earlier collection of the same arm at two repetitions read +1.81 % median with a +5.41 % worst
pass; ten repetitions and three warmups is what tightened it, and both are in the record. Either
way the arm is wider than a change that saves tens of microseconds per pass, so the claim here is
at the operator level.

Limits

  • No end-to-end number, for the reason above.
  • The bound at 256 leaves the band from 288 to 896 on the table, where the fused form is still ahead
    on (16,2) — by 26.85 % at T=288, falling to 1.45 % at 896 — but is already marginal on (24,4).
    A per-geometry bound would recover it; both geometries ship in this engine's model cards, so one
    constant that is safe for both is what this ships.
  • causal_softmax_attention_cached's own sites are not involved; only the two full-attention ones.
  • The MRoPE path keeps the three calls, and is unmeasured here — the route condition excludes it by
    the position axis, which is the existing require_rope_axes test.
  • Widths above 2048 were not swept. The Op accepts them; the dispatch does not reach them.

@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 👍 / 👎.

@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)
MichaelDementii pushed a commit to MichaelDementii/ninfer that referenced this pull request Sep 16, 2026
12.17 One figure, three populations: say which one you are quoting.
      A wide sweep, a dense sweep and their pool read T=256 at -21.97, -21.70
      and -21.83. Correcting the narrative to one while the tables stayed on
      another left the body disagreeing with itself two screens apart.

12.18 Evidence borrowed from another submission has to travel with a label.
      The end-to-end null quoted here was collected for a different change on
      the same stand. The figure is right; nothing in raw/ carried it, so the
      audit read it as a number nobody measured. Copy the raw in, say where it
      came from, and say it is evidence about the instrument rather than the
      change - including the earlier collection that was rejected for being
      five times wider.

12.19 Structure is looked up, not recalled. "Both registered geometries" was
      written from memory; the tree has five model cards over three base
      models. One command to fetch the published configs turned the hedge into
      the strongest claim in the submission, and produced the fact the package
      was missing outright: the call site is on the full-attention path, so it
      fires 16 times per pass of 64 layers, not once per layer.
MichaelDementii pushed a commit to MichaelDementii/ninfer that referenced this pull request Sep 16, 2026
A Q8 threshold sweep contained, by accident, a band above T=57 where the
threshold selects nothing and both columns run identical code. They did not
read the same: <= 0.25 % apart on one shape and about 1 % on another,
systematically favouring whichever column ran first. That is the methods own
bias, priced inside the measurement without a third arm. Found by llm-5090-3b.

Named the pattern because two of our packages already use it - Neroued#264 sweeps
whole-tile widths that take the same route on both arms (+0.05 %, and chunk
1536 at -0.05 % beside a +2.37 % candidate), Neroued#268 carries 404 cells whose node
count is unchanged (+0.00 %). Neroued#222 cannot have one, since its arms differ at
every width, so its burden falls entirely on the null arm - which is why its
worst kept pass is quoted as carefully as it is.
Both full-attention call sites normalized the query, normalized the key and
rotated both - three Op calls for one semantic step.
ops::rmsnorm_rope already fuses exactly that and was already registered for
two profiles, the DFlash2 pair at [128,32,W,B] and the context-K single at
[128,8,T]; neither covers the text heads, which are 256 wide with a
64-channel rotation and want the result out of place. So the text stack kept
issuing the three. On qwen3.6-35b-a3b that is ten of the forty text layers,
plus the MTP tail once per draft step.

A third overload takes q_in [256,Q,T] and k_in [256,K,T] and writes q_out and
k_out, for (Q,K) of (16,2) or (24,4). Only the first 64 channels rotate; the
remaining 192 carry the normalized value through. The norm weight enters as a
delta around one - the Offset epilogue the text stack normalizes with - unlike
the two in-place forms, which multiply by the stored weight directly. The
rotation consumes the BF16 represented normalized value, so the result is
bit-identical to rmsnorm(q_in) -> rmsnorm(k_in) -> rope(q_out, k_out).

text_qk_norm_rope sits beside text_rope in execution/attention.cpp, where the
MRoPE axis check already lives, so both call sites go through one place. It
takes the fused Op where the profile matches and the three calls everywhere
else: the MRoPE path, any other geometry, and above a width bound.

The width bound is the part that had to be measured. The fused form wins by
41.94 to 52.07 % at decode widths and loses above about six hundred tokens:

  T      (16,2)   (24,4)
  256   -30.01%  -21.83%
  320   -26.40%   -0.90%
  512   -10.99%   -1.91%
  576   -10.35%   +2.91%
  1024   +5.23%  +21.87%
  2048  +17.20%  +32.05%

The two geometries cross at different widths - (24,4) has already fallen to
-0.90 % at T=320 and is positive from 576, while (16,2) stays ahead through
896 and turns at 960 - so the dispatch stops at 256, where the fused route is
30.01 % ahead on (16,2) and 21.83 % on (24,4). One width further out (24,4) is
at -0.90 %, inside this stand's own null, so the margin is gone before the
sign is. I have no established mechanism for the crossover; the obvious one
does not predict the non-monotone approach to it, and nothing here measures
occupancy.

The predicate covers every model with a card in the tree: three base models,
all head_dim 256 and rotary 64, and exactly two head geometries. Qwen3.6-27B
and Qwen3.8-27B are (24,4) with 16 full-attention layers of 64; Qwen3.6-35B-A3B
is (16,2) with 10 of 40. The call sits on the full-attention path, so it fires
16 or 10 times per pass, not once per layer. The Op itself is valid at any
width - the test drives it directly out to 16384 - and this is a dispatch
choice. Decode issues T = W x B, at most 128, so the bound never excludes it;
a prefill chunk is a multiple of 128 and the default is 1024, so prefill keeps
the three calls.

The benchmark gains the text profile and a --route split|fused switch, so both
routes live in one binary and the comparison is one process per pass: split
issues the three calls, fused issues the Op, and split again is the
identical-baseline null. Six passes with the arm order rotated through six
permutations, graph execution, 20 warmups and 200 samples over 32 inner
repetitions. A pass is dropped when the two identical runs disagree by more
than 1 %: 29 of 240 on the wide sweep and 19 of 252 on the dense sweep of the
crossover region. The rule bounds what survives, so the width of the
instrument is the worst kept pass: 0.97 % and 0.96 %, with medians of 0.15 %
and 0.18 %.

  band                                  cells   median     best    worst  faster
  taken by the dispatch, T <= 256          38  -43.27%  -52.07%  -21.83%  38/38
  declined, keeps the three calls          38   -3.58%  -26.85%  +32.05%  25/38

  T     (16,2) split -> fused     (24,4) split -> fused
  1      3.72 -> 2.17 us           3.81 -> 2.20 us
  8      4.25 -> 2.19 us           4.50 -> 2.20 us
  16     4.36 -> 2.18 us           4.61 -> 2.23 us
  64     4.67 -> 2.52 us           5.02 -> 2.78 us
  128    4.88 -> 2.90 us           5.34 -> 3.38 us
  256    5.40 -> 3.78 us           6.16 -> 4.81 us

The fused form is flat at about 2.2 us from T=1 to T=16 on both geometries
while the traffic it moves grows about sixteen-fold, so its time is
independent of the work; (24,4) leaves the plateau first, at 2.36 us by T=24.
The split form climbs over the same range, 3.72 to 4.36 us on (16,2) and 3.81
to 4.61 on (24,4). No width the dispatch takes is slower on either geometry.

Output is unchanged: NINFER_OP_REPORT_STATS=1 at %.17g over the whole test
suite gives 11912 cases shared between the arms, all byte-identical, and the
56 cases the candidate adds are the text-profile checks the test gains. The
same comparison on the previous base dc58675 read 11900 shared, 0 differing,
56 added - master gained 12 cases from its own LinearSwiGLu retune, and the
part being claimed did not move. The test
judges the new form against the independent FP64 oracle and, separately,
against the three calls it replaces, bit for bit.

Strength control: bit-exactness is the claim, so the test has to see a
perturbation far below any oracle criterion. Scaling the rotation by one part
per million - about 3900x below one BF16 ulp - fails 33 of the 60 exact-equality
comparisons the suite makes against the split route, over 20 of its 30 text
cases; restoring it passes. It does not fail everywhere, and should not: that
far below the output quantum the nudge only moves a value already sitting on a
rounding boundary.

ctest 120/120 on both arms, on both bases.

No end-to-end number: an identical-baseline decode arm on this machine reads
+0.55 % median over six passes spanning -0.44 % to +0.89 %, which is wider than
a change that saves tens of microseconds per pass. That arm was collected for
another submission on the same stand and is quoted as a property of the stand,
not as a measurement of this change. An earlier collection of the same arm at
two repetitions read +1.81 % median with a +5.41 % worst pass; ten repetitions
and three warmups is what tightened it.
@MichaelDementii MichaelDementii changed the title Extend rmsnorm_rope to the text profile: one graph node instead of three, +0.60% decode on Qwen3.6-35B-A3B Register the text profile of ops::rmsnorm_rope and route the two full-attention call sites to it Sep 17, 2026
@MichaelDementii

Copy link
Copy Markdown
Contributor Author

Rewritten on today's master (f76e19c0). New anchor issue: #272.

The part I have to raise myself, because it reverses what I argued here in September.

In this thread I removed a width bound
and wrote that "one warp owns one head, so the only real ceiling is the launch grid". This version
puts a width bound back, at 256 tokens.

The reason is different from the one I removed. That bound was a correctness ceiling I had invented,
and Codex was right that it was wrong. This one is a dispatch choice on measured speed: above the
bound the fused Op is slower than the three calls it replaces, by up to +32.05 % at T=2048 on
(24,4). The Op itself stays valid at any width — the test drives it directly out to 16384 — and
both branches are the same arithmetic bit for bit.

I also no longer have a mechanism for where the crossover sits. "One warp owns one head" does not
predict (24,4)'s non-monotone approach to it (−11.43, −0.90, −3.12, −6.30, −8.58, −5.98 at T=288
through 448), and nothing here measures occupancy. What is measured is where the sign changes, on
both geometries, at every width between.

Three claims from the previous body are withdrawn, not restated:

  • "three graph nodes" and "~26 nodes per decode round" — this package never counted graph nodes.
    It counts Op calls, which is what it can show.
  • "across 13 full-attention layers" — wrong. The call site is on the full-attention path, so it
    fires 16 times per pass of 64 layers on the 27B models and 10 of 40 on qwen3.6-35b-a3b.
  • "+0.60% decode" in the old title — withdrawn. An identical-baseline decode arm on this stand
    reads +0.55 % median over six passes spanning −0.44 to +0.89 %, so the stand cannot resolve a
    change of this size end to end. The claim is at the operator level and the title now says so.

What is new in evidence: a width bound chosen from a dense sweep of the crossover region rather
than from a guess; the predicate checked against every model card in the tree (three base models,
all head_dim 256 and rotary 64, exactly the two head geometries the predicate lists); and the
byte-identical witness re-run on three bases as master moved — 11900, 11912 and 11986 shared cases,
zero differing each time, exactly 56 added.

ctest 120/120 on both arms on the submission base, clang-format clean on all eleven files.

@MichaelDementii

Copy link
Copy Markdown
Contributor Author

Closed in favour of #273, which is the same change rewritten on today's master (f76e19c0) with a
new anchor issue, #272.

Not a rebase. Three claims made here are withdrawn in the new one rather than restated — the graph
node count, "13 full-attention layers" (it is 16 of 64 on the 27B models and 10 of 40 on
qwen3.6-35b-a3b), and the +0.60 % decode figure, which the stand cannot resolve. And one
decision is reversed: this PR argued that a width bound should not exist and removed one; #273 puts
one back at 256 tokens, for a different reason and with the sweep that justifies it.

Opening a fresh PR rather than updating this one, since this has been sitting since 2026-09-09 and
the rewrite is large enough that a diff against it would not be readable.

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