fix(#1157): NemotronH decoded with STALE input ids, and the recurrent carry was never the cause - #1221
Merged
Merged
Conversation
…-prefill discriminator The A3 gate diverges on real weights while the CPU gate is 12/12 green, so neither arm can say whether the decode step reads the state the previous step wrote. Two instruments, both off by default. `VT_NEMOTRON_H_DIAG` prints, per step, the decode/prefill split, the recurrent slot indices, the has-initial mask, and per Mamba2 layer the L2 of the state gathered in and the state written out. On the CPU fixture it reads a healthy carry, which is what makes it usable as a negative control on the device. `nemotron-h-gen --fresh-prefill` generates the same stream one token per completion from a growing prompt, so every token comes out of a prefill and nothing is carried. Same engine, same weights, same public entry point, so a stream that is right this way and wrong the normal way names the carry. This is scaffolding for the #1157 measurement, not the repair. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
…slot can bisect The CPU arm now decodes this checkpoint token-exact against the oracle golden, so the divergence is on the device side and the question is which device arm. The MoE block is 23 of the model's 52 layers and its own gate exercises T=4 and T=2 only, never the T=1 a decode step is, so it is the arm with the least coverage at the shape that fails. `VT_NEMOTRON_H_DEVICE_MOE=0` routes those layers back through the host reference the CPU run proves correct, and `--both-modes` runs the decode and fresh-prefill streams over ONE engine load, which is the only affordable shape when a load is minutes long. Both default to today's behaviour, so nothing moves unless a diagnostic asks it to. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
… the decode one `NemotronHMoeBlockDevice` runs on 23 of this model's 52 layers, and its gate measured T=4 and T=2. Both are prefill shapes. Every token after the first comes out of a step carrying exactly one token, so the width the model spends its entire decode in was the width nothing measured. It is not a cosmetic gap. `MarlinMoeAlignBlockSizeSelect` and `MarlinMoeAlignSizes` branch on the token count relative to the expert count, and T=1 against 128 experts is on the other side of that branch from T=4. A width loop rather than a third copy, so the three cannot drift, with the covered count asserted afterwards: a loop that ran over nothing would otherwise report a clean pass, which is the shape this tree keeps finding. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
…he failing model is a third `fa2_decode_qwen3` is DEFAULT ON for any bf16 causal pure-decode at head_dim 128. Its own comment scopes it to "only Qwen3-dense hits this", and that stopped being true when NemotronH-3.5-Lightning landed: it is head_dim 128 as well, and its decode goes through the same launcher. Every case in this file measured 16/8 and 32/8 — ngroups 2 and 4, the two Qwen3-dense gate configs. NemotronH is 32 query heads over 2 KV heads, ngroups 16, four times the widest group count the swapped presentation was ever measured at. The launcher packs ngroups as seqlen_q, so the group count is not a detail of the geometry, it is the grid. The ratio is added to all five d128 cases rather than to one, so the prefill arm, the plain-varlen decode, the group-swap decode, the swap-vs-plain near-tie and the num_splits cap all see it. Nothing else moves. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
…very decode step saw the same token `ModelForwardInput::device_token_ids` is non-null exactly when the async runner's device combine has already spliced each DECODE row's sampled token into its own device buffer and left the host `token_ids` stale on purpose — not materializing it on the host is the synchronize ENG-ASYNC-SCHED W4 exists to remove. `NemotronHPagedForward` uploaded the host vector anyway, so on the default CUDA path every decode step embedded the same placeholder id. The measurement, all on the released 30B NVFP4 checkpoint and the committed oracle golden, through `include/vllm.h` and nothing else: CPU, decode 96/96, STRICT PASS, full rows 3 GB10, decode 4/24 GB10, fresh-prefill 24/24 (one token per completion: no decode step) and the per-layer trace names the mechanism rather than inferring it. At the prefill step CPU and GB10 agree to six digits on every one of the 52 layers. At the first decode step the gathered conv/SSM state is IDENTICAL on the two — the recurrent carry is exact — while layer 0's embedding row differs, and it reads 0.228135 on GB10 at BOTH decode steps although they consume different tokens. A constant embedding is a constant input id. This refutes the cause on record. #1157 reasoned that `gm.num_decodes` might classify a decode as a prefill and hand the mixer zeros; the trace reports `nd=1 np=0 init=[1]` on every decode step, and mutating that mask to 0 turns the existing A2-P CPU gate RED (1 case, 6 assertions), so the gate was never blind to it. Kimi-Linear was cut from this same divergence (kimi_linear_device.cpp:2270) and every other registered forward already honours the field. Nothing could see that this one did not: the runner sets the pointer only under VLLM_CPP_CUDA with a live device mirror, so no CPU gate can reach the branch. The host-side id range check stays on the host branch only. Validating device ids would need the D2H synchronize this path exists to delete. Closes #1157 FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
… the seam that allowed it The row's public record still said the A3 gate was pending on a toolchain, and `#1157` still carried a cause that the measurement refutes. Both are corrected against numbers rather than re-quoted. `benchmark-record.md` carries the three-arm table the diagnosis rests on — host 96/96, GB10 decode 4/24, GB10 fresh-prefill 24/24, one binary and one checkpoint — plus the per-layer trace that puts the divergence at layer 0's embedding row while the recurrent state gathered identical on both sides. `docs/STATUS.md` and `docs/BENCHMARKS.md` move the row from "gate pending" to "host gate passes, device fixed, sm_121a re-run pending a lease", inside the STATUS size ratchet. [#1217](#1217) is filed and listed under the spec's `## Owed`: the runner hands `device_token_ids` to whatever model the step routes to, its own comment claims a model that ignores it is never given one, and nothing enforces that. Two models have now been cut from the identical divergence. Not fixed here because both closes change a shared seam or checker semantics. The two diagnostic knobs are documented in `docs/ENVIRONMENT.md` rather than allowlisted as kernel-internal: `VT_NEMOTRON_H_DIAG` is how this bug was separated from the carry, and the next reader of this model should find it. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
FOLLOWING_AGENTS_PROTOCOL origin/main moved 26 files under this branch while the #1157 diagnosis was running on a leased GB10. Merged rather than rebased so the branch keeps the measurement order: the instruments, then the host and device arms they produced, then the fix those arms name. No conflict in either direction; nothing this branch owns was touched. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
… not call itself temporary The block header said TEMPORARY while `docs/ENVIRONMENT.md` documents the knob and the next divergence on this model will be diagnosed with it. It now says what it is for instead: the runner hands a decode step a device-resident input id AND a recurrent page, and when the tokens come out wrong only the per-layer numbers say which of the two the step actually read. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
GitHub reported CONFLICTING; the merge resolves clean locally because GitHub does not run the `merge=union` driver `.gitattributes:7` sets. The union auto-merge of the index was DISCARDED anyway: main's file taken wholesale, only rows whose KEY main lacks re-appended, main's file asserted a strict PREFIX of the result, zero duplicate keys. The fix itself is unchanged by the merge and re-verified present after it. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot
pushed a commit
that referenced
this pull request
Aug 18, 2026
…DING because I read a body written before its own job ran The previous commit here wrote the A3 end-to-end token gate as `PENDING` and said no GB10 96/96 existed. That was wrong, and the way it was wrong is worth keeping: I searched `origin/main` and five `row/MODEL-NEMOTRON-H-*` branches, found nothing, and then took #1221's PULL REQUEST BODY as the current state. Its "Still owed: the sm_121a green-after re-run" was TRUE WHEN WRITTEN and stale by the time I read it. A body is a snapshot of its author's knowledge at write time, not a live field, and no amount of branch searching corrects for that, because the artifact was never in git at all. It is on the NAS. `/usr/local/nas_share/rc/nh1157/gate_fixed.out`: TOKEN MATCH: 96/96 over 3 prompt(s) (full rows=3, short rows=0, mode=decode) STRICT PASS against the pinned oracle `vllm=0.23.1rc1.dev1511+g555967922` on `nemotron-3.5-lightning-30b-nvfp4` at revision `29f2d1746d8f41e316523194b19018707749b1b1`. Verified by reading the files, not by trusting the report of them. It is the DEVICE leg. The binary is `libvllm 0.0.3+cuda` and `cfg.log` records `fp4-mma`, `cutlass-nvfp4` and `cutlass-fp8` `ENABLED for [121a]`. The run logs `Asynchronous scheduling is enabled (max_concurrent_batches=2)`, which is exactly where `device_token_ids` is non-null; on the host queue it is always null and the #1157 defect cannot arise. The decisive one is neither: the same binary on the same checkpoint with ONLY `nemotron_h_device.cpp` reverted to the fix's parent scores `4/24 (full rows=0, short rows=3)` and bails at 8 tokens. A host-leg run would have been unmoved by that revert. The delta is the proof; the pass alone is not. So the row now says the gate PASSES and says whose it is. It belongs to #1221, branch `row/MODEL-NEMOTRON-H-ABI-A2P-1157-fix` at `6e9e8955`, OPEN and `CONFLICTING`. `main`'s last touch of `nemotron_h_device.cpp` is `a6df72777` (A2-P), so `main` does not carry the fix and is not gated. A gate that passed and a tree that is gated are different facts, and this record keeps them apart. No performance number is claimed anywhere. `gate_fixed.out` carries 264.4s to load and 327-343s per 32-token prompt; that is a correctness run whose `lm_head` and 46 FP8 mamba projections still execute host-side. Both public rows say so in the words "NOT a benchmark", so nothing can later lift them into a grid. One config caveat also travels: `--gpu-memory-utilization 0.92` did not size the KV pool, which fell back to 256 blocks (#83). The row stays `PARTIAL`. `ACTIVE` still fails the `CLAIM-*` rule, and a gate that passes off `main` is not a reason to move a lifecycle state. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
This was referenced Aug 18, 2026
localai-bot
pushed a commit
that referenced
this pull request
Aug 18, 2026
The explicit merge of `row/MODEL-NEMOTRON-H-ABI-A2P-1157-fix` this branch carried was a stand-in for a fix that had not landed. It has: `0ea5d249f` is an ancestor of `origin/main`, so the measured base is now main itself and the diff collapses to this branch's own work. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code] # Conflicts: # examples/nemotron_h_gen/main.cpp
localai-bot
pushed a commit
that referenced
this pull request
Aug 18, 2026
…overs one configuration rather than a model (#1250) #1221 landed as `0ea5d249f`, verified by content on `main` rather than by the API, so the spec's stand-in wording about measuring off the fix branch is dead and the measured base is a SHA at or after that merge. The second edit is the one that matters. A token gate is evidence about the configuration it ran, and this one ran three prompts, 32 greedy tokens each, batch 1 sequential, `max_model_len 512`, a 256-block pool. The timing legs run that configuration and nothing else, and a leg that deviates in KV sizing, batching or sampling is reported as ungated rather than allowed to borrow coverage the gate never gave it. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot
pushed a commit
that referenced
this pull request
Aug 18, 2026
`main` gained #1221 while this branch was open, so `docs/BENCHMARKS.md` and `docs/STATUS.md` conflicted on the NemotronH rows. Resolved by taking main's rows as the base and correcting the one claim that main is now STALE on. Main says "GB10 read 4/24 ... sm_121a re-run pending a lease" and "sm_121a re-run pending". That was true when #1221 was written. The re-run has since happened: the A3 gate PASSES on GB10 at 96/96, `STRICT PASS`, mode=decode, against `vllm=0.23.1rc1.dev1511+g555967922` on `nemotron-3.5-lightning-30b-nvfp4` revision `29f2d1746d8f41e316523194b19018707749b1b1`, with a red-before of 4/24 on the SAME binary and checkpoint with only `nemotron_h_device.cpp` reverted. Artefacts: `/usr/local/nas_share/rc/nh1157/{gate_fixed,gate_red,cfg}.out`. `cfg.log` shows `fp4-mma`, `cutlass-nvfp4`, `cutlass-fp8`, `marlin-nvfp4` and `fa2` all `ENABLED for [121a]`, so the build was not degraded, and both legs log `Asynchronous scheduling is enabled`, which is the path where `device_token_ids` is live and the defect lived. The delta, not the pass, is the proof: on the host leg reverting the fix would have changed nothing. STILL NOT A BENCHMARK. The 264.4 s load and 43 405 MB peak are recorded as a correctness run and labelled as such, because NVFP4 `lm_head` (A2-Q2b) and the 46 FP8 W8A8 mamba projections (A2-Q1, #940) still execute host-side. No throughput, latency or memory ratio is claimed on any axis. The index was reconciled by discard-and-reapply: main's file taken wholesale, only rows whose key main lacks re-appended, main's file a strict PREFIX. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot
pushed a commit
that referenced
this pull request
Aug 18, 2026
…and the GPU is idle for 93.7% of the decode (#1250, #1253) Numerator only, and no ratio is claimed. On an idle `dgx:gpu0` through `rc run`, at a tree containing #1221, with all five CUDA feature lines `ENABLED for [121a]`: engine load 280.9 s, 10.3194 s per output token, 0.09691 tok/s at batch 1, peak host 44,616 MB of 122,502. Every timing leg is a gated leg -- 96/96 `mode=decode` on both, `STRICT PASS`, 192 of 192 tokens, zero short rows -- so no number here comes from a configuration whose tokens were not compared in the same process. The same-binary A/B over one load is 1.0016 warm and the five warm prompts spread 0.245%, so the figure is stable enough that the next lever's delta will be readable. The load-bearing part is not the rate, it is why. `nvidia-smi` reported GPU utilization 0% in 2,019 of 2,155 samples across the measured window, so the GPU was busy in 6.31% of it, and `gpu_clock_state`'s own comparison gate would REFUSE that window for failing its majority-busy floor. The refusal is the result: this decode is not GPU work. It is the 23 of 52 layers that download the normed hidden, run the mixer on the CPU queue and upload it once per layer per token, plus the NVFP4 `lm_head` projecting on the host. So A2-Q1 (#940) and then A2-Q2b are the next traceable hypotheses, and each now has a prediction it has to answer rather than a story: it must raise that busy fraction. The denominator has its identity pinned and its blocker measured rather than assumed. With `python3-dev` present the pinned oracle initialized, loaded in 230.4 s and completed `torch.compile`, which is further than it has ever got inside a lease, then took 104,992 MB of host in the next step and was killed by this row's watchdog. The box did not reboot. `gpu_memory_utilization` 0.9 of ~119 GiB of unified memory is ~107 GB, which is that peak, so the fraction is the first thing to vary. And vLLM's hybrid allocator forces a 4192-token attention block against our 32, so the two sides can be matched on KV token capacity and never on block count; a future ratio has to say which. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot
pushed a commit
that referenced
this pull request
Aug 20, 2026
…DING because I read a body written before its own job ran The previous commit here wrote the A3 end-to-end token gate as `PENDING` and said no GB10 96/96 existed. That was wrong, and the way it was wrong is worth keeping: I searched `origin/main` and five `row/MODEL-NEMOTRON-H-*` branches, found nothing, and then took #1221's PULL REQUEST BODY as the current state. Its "Still owed: the sm_121a green-after re-run" was TRUE WHEN WRITTEN and stale by the time I read it. A body is a snapshot of its author's knowledge at write time, not a live field, and no amount of branch searching corrects for that, because the artifact was never in git at all. It is on the NAS. `/usr/local/nas_share/rc/nh1157/gate_fixed.out`: TOKEN MATCH: 96/96 over 3 prompt(s) (full rows=3, short rows=0, mode=decode) STRICT PASS against the pinned oracle `vllm=0.23.1rc1.dev1511+g555967922` on `nemotron-3.5-lightning-30b-nvfp4` at revision `29f2d1746d8f41e316523194b19018707749b1b1`. Verified by reading the files, not by trusting the report of them. It is the DEVICE leg. The binary is `libvllm 0.0.3+cuda` and `cfg.log` records `fp4-mma`, `cutlass-nvfp4` and `cutlass-fp8` `ENABLED for [121a]`. The run logs `Asynchronous scheduling is enabled (max_concurrent_batches=2)`, which is exactly where `device_token_ids` is non-null; on the host queue it is always null and the #1157 defect cannot arise. The decisive one is neither: the same binary on the same checkpoint with ONLY `nemotron_h_device.cpp` reverted to the fix's parent scores `4/24 (full rows=0, short rows=3)` and bails at 8 tokens. A host-leg run would have been unmoved by that revert. The delta is the proof; the pass alone is not. So the row now says the gate PASSES and says whose it is. It belongs to #1221, branch `row/MODEL-NEMOTRON-H-ABI-A2P-1157-fix` at `6e9e8955`, OPEN and `CONFLICTING`. `main`'s last touch of `nemotron_h_device.cpp` is `a6df72777` (A2-P), so `main` does not carry the fix and is not gated. A gate that passed and a tree that is gated are different facts, and this record keeps them apart. No performance number is claimed anywhere. `gate_fixed.out` carries 264.4s to load and 327-343s per 32-token prompt; that is a correctness run whose `lm_head` and 46 FP8 mamba projections still execute host-side. Both public rows say so in the words "NOT a benchmark", so nothing can later lift them into a grid. One config caveat also travels: `--gpu-memory-utilization 0.92` did not size the KV pool, which fell back to 256 blocks (#83). The row stays `PARTIAL`. `ACTIVE` still fails the `CLAIM-*` rule, and a gate that passes off `main` is not a reason to move a lifecycle state. `docs/FEATURES.md` moves in the same commit, because it has to. This commit edits `.agents/model-matrix.md`, which `scripts/check-doc-checkpoint.py` reads as a `feature_surface` change by path (`FEATURE_SURFACE_FILES`), and that class owes `docs/FEATURES.md` in the SAME commit -- the checker walks a range one commit at a time, so no later commit can pay the debt. The row for `NemotronHForCausalLM` said nothing at all about the end-to-end token gate while the matrix said it was PENDING, and it would have kept saying nothing now that the gate passes. It now carries the same two facts the matrix does, in the correctness column and in that column's own words: 96/96 `STRICT PASS` on GB10, on #1221's open branch and not on `main`. The added text is 90 characters, which leaves the cell at 192 of its 220-character cap and the row at 499 of 600, so no other row pays for it. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot
pushed a commit
that referenced
this pull request
Aug 20, 2026
`main` gained #1221 while this branch was open, so `docs/BENCHMARKS.md` and `docs/STATUS.md` conflicted on the NemotronH rows. Resolved by taking main's rows as the base and correcting the one claim that main is now STALE on. Main says "GB10 read 4/24 ... sm_121a re-run pending a lease" and "sm_121a re-run pending". That was true when #1221 was written. The re-run has since happened: the A3 gate PASSES on GB10 at 96/96, `STRICT PASS`, mode=decode, against `vllm=0.23.1rc1.dev1511+g555967922` on `nemotron-3.5-lightning-30b-nvfp4` revision `29f2d1746d8f41e316523194b19018707749b1b1`, with a red-before of 4/24 on the SAME binary and checkpoint with only `nemotron_h_device.cpp` reverted. Artefacts: `/usr/local/nas_share/rc/nh1157/{gate_fixed,gate_red,cfg}.out`. `cfg.log` shows `fp4-mma`, `cutlass-nvfp4`, `cutlass-fp8`, `marlin-nvfp4` and `fa2` all `ENABLED for [121a]`, so the build was not degraded, and both legs log `Asynchronous scheduling is enabled`, which is the path where `device_token_ids` is live and the defect lived. The delta, not the pass, is the proof: on the host leg reverting the fix would have changed nothing. STILL NOT A BENCHMARK. The 264.4 s load and 43 405 MB peak are recorded as a correctness run and labelled as such, because NVFP4 `lm_head` (A2-Q2b) and the 46 FP8 W8A8 mamba projections (A2-Q1, #940) still execute host-side. No throughput, latency or memory ratio is claimed on any axis. The index was reconciled by discard-and-reapply: main's file taken wholesale, only rows whose key main lacks re-appended, main's file a strict PREFIX. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
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.
The A3 token gate diverged on real weights and the cause on record was the
recurrent carry. It was not.
NemotronHPagedForwardembedded the HOSTinput.token_idswhileModelForwardInput::device_token_idswas non-null, andthat field's contract is that the host vector is STALE for decode rows
(
model_registry.h:314-324) — not materialising it on the host is thesynchronize ENG-ASYNC-SCHED W4 exists to remove. So on the default CUDA path
every decode step embedded the same placeholder id.
FOLLOWING_AGENTS_PROTOCOL
What the measurement says
One binary, one checkpoint (released
nemotron-3.5-lightning-30b-nvfp4,revision
29f2d174), one committed oracle golden, driven throughinclude/vllm.halone:STRICT PASS, full rows 3The GB10
gotstreams reproduce the earlier recorded run byte for byte, so thisis the same defect on a fresh build and not drift. The build was not degraded:
CUDA 13.x from the
ubuntu2404/sbsalane,CFG_RC=0,cutlass-nvfp4/cutlass-fp8/marlin-nvfp4/fa2allENABLED for [121a],BUILD_RC=0,compile_errors=0, binary sha256b4677cdb7cf5….The per-layer trace puts it at the first operation of the decode step rather
than inferring it. At the prefill step host and GB10 agree to six digits on all
52 layers. At the first decode step the gathered conv/SSM state is IDENTICAL on
the two —
|conv|=310.374,|ssm|=3985.8on both, so the recurrent carry isexact — while layer 0's embedding row differs, and it reads
0.228135on GB10at BOTH decode steps although they consumed different tokens. A constant
embedding is a constant input id.
What this refutes
#1157 reasoned that
gm.num_decodesmight classify a decode as a prefill so the gather would handthe mixer zeros. On real weights the trace reports
nd=1 np=0 init=[1]on everydecode step, and mutating that mask to
0turns the A2-P CPU gate RED (1 case,6 assertions) — that gate was never blind to that defect. It was blind to this
one structurally: the runner sets
device_token_idsonly underVLLM_CPP_CUDAwith a live device mirror, so no CPU gate can reach the branchat all. That is why the red-before here is a device measurement rather than a
unit test.
What else is in the change
Instruments, all off by default.
VT_NEMOTRON_H_DIAGprints thedecode/prefill split, the state indices, the has-initial mask, and per Mamba2
layer the L2 of the state gathered in and written out — it is what separated the
carry from the input id, and it reads a healthy carry on the CPU fixture, which
is what makes it usable as a negative control.
nemotron-h-gen --fresh-prefill/
--both-modesgenerate the same stream without ever taking a decode step,over one engine load.
VT_NEMOTRON_H_DEVICE_MOE=0was the bisect lever for thedevice MoE. Both env vars are documented in
docs/ENVIRONMENT.md.Two coverage gaps found while bisecting, both at the decode shape. The
device MoE gate measured T=4 and T=2, two prefill widths, never the T=1 the
model spends its whole decode in. The FA-2 d128 decode op gate measured GQA
ratios 2 and 4 (
16/8,32/8), and NemotronH is32/2, ngroups 16 — the pathis default-ON for any bf16 causal pure-decode at head_dim 128, so it is not
Qwen3-dense-only as its comment claims. Neither new case has run on a GPU
yet; a lease is queued to measure them, and they skip loudly everywhere else.
#1217 is filed and owed,
not fixed here. The runner hands
device_token_idsto whatever model the steproutes to; the field's comment claims a model that ignores it is never given
one, and nothing enforces that. Kimi-Linear was cut from this same divergence
(
kimi_linear_device.cpp:2270-2280) and NemotronH is the second. Both closes —an explicit
ModelFactory::honors_device_token_idswith a runner fallback, or achecker over the registered
.forwardentry points — change a shared seam orchecker semantics, which AGENTS.md sends through its own spec.
Still owed
The sm_121a green-after re-run of the full 96-token gate under this fix. The
fleet lease for it is queued;
docs/BENCHMARKS.mdrecords the device leg aspending rather than as a pass, and the host leg as the pass it is.
Note for the merger: #1158 is
open against the same
benchmark-record.md,STATUS.md,BENCHMARKS.mdandboth specs, and records the failure this change explains. Whichever lands second
should take the target-branch version whole and re-apply its own scoped edit.
Closes #1157
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]