fix(ENG-CUDAGRAPH-BREAK): three registrations never read the async mirror's identifiers, on the graph path and the eager path alike (#1305) - #1391
Merged
Conversation
…ector and never read the device mirror (#1305) `qwen3_moe_registry.cpp`, `deepseek_v2_registry.cpp` and `glm4_moe_lite_registry.cpp` route a step into a model that ignored `ModelForwardInput::device_token_ids` entirely. On the asynchronous serving path the runner's combine splices each decode row's sampled token into the DEVICE identifiers on the main queue and leaves the host `token_ids` deliberately stale (`src/vllm/v1/worker/gpu/runner.cpp`, the mirror arm, which is default ON), so those three models embedded the previous step's identifiers for every decode row — on the decode-graph arm AND on both eager arms. The three registries now publish `detail::DeviceTokenIdsScope`, the same mechanism `qwen3.cpp`, `qwen3_5.cpp`, `mistral_registry.cpp`, `internlm2_registry.cpp` and `llama_registry.cpp` already use, so every embed in the two model translation units consumes it. The decode-graph drivers take the version of that fix `#1305` asks for rather than a fifth private copy: each padded size slot now owns a `vllm::StepTokenIds`, whose destination is a device buffer with a stable address and whose refresh runs through `vt::PersistentStepInput` — the host arm for the padded vector, then the DEVICE arm over the real prefix, both enqueued on the main queue so the second is ordered after the combine instead of racing it. That gives the capability its first production caller of `RefreshFromDevice`, which W4 landed with none, and it gives a step that re-read the mirror an observable that separates it from one that uploaded a stale vector: no token gate and no segment count can tell those two apart. WHAT THIS DOES NOT DO. It does not remove `qwen3.cpp`'s decline. W4 (#1307) measured that decline's recorded CAUSE false, so its failure mode is unexplained and a refactor does not retire it. The embed still sits OUTSIDE the captured region in every driver, because `vt::Embedding` allocates a device flag and synchronizes the stream, so the identifiers are read once per step from a stable device address rather than from inside the replay; `StepTokenIds` is the destination that future change needs, not that change. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
… ModelRegistry::Forward (#1305) The defect #1305 names is SILENTLY WRONG TOKENS at concurrency, not a fault, so the gate asserts the identifiers themselves rather than a class that constructs. It enters at `ModelRegistry::Forward` over a synthetic safetensors checkpoint, which is the production entry point a user arrives through; a case that drove the driver directly would measure the type and not the registration. Three runs per architecture, because two of them cannot separate the cases: the reference has the right host identifiers and no mirror; the CONTROL has stale host identifiers and no mirror and must DIFFER; the gate has the same stale host vector with the right identifiers reaching the model ONLY through `ModelForwardInput::device_token_ids` and must be bit-identical to the reference. Without the control, a model that ignored its identifiers entirely would satisfy the gate. `vt::StepInputStats::device_refreshes` carries the other half. It moves only inside `vt::PersistentStepInput::RefreshFromDevice`, so a driver that hand-rolled the same copy would produce identical logits and leave it at zero — and a step that re-read the mirror and one that uploaded a stale host vector leave the same bytes-shaped destination, which no token gate can separate. RED before the fix, for the intended reason: 2 cases, 59 assertions, 12 failed, exit 1, with 200 of 200 logit values differing per step on both architectures and every counter at 0. GREEN after: 59/59, exit 0. Bounded honestly. A CPU "replay" recomputes nothing (`decode_graph_seam_harness.h`), so only the cold and capture steps carry information and only those two are compared. The depth-2 four-concurrent battery on a real device is a different gate; it needs a GPU and a checkpoint, and the spec records it as owed. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…ain gate nobody had read (#1305, #1390) The spec's `## Owed` carried #1305 as "three registrations admit an asynchronous step with NO decline", owned by a stage that would get a `dgx` window. Reading the tree found a larger defect than the issue described and a fix that needs no decline at all, so the entry records the resolution, what the fix actually was, and the one thing still owed rather than being struck. The `RefreshFromDevice` entry is retired: it landed with no production caller and now has one, in both migrated drivers, reached from `ModelRegistry::Forward`. A new entry, and it is a red `main` gate rather than this row's work: `test_qwen3_5_decode_graph_seam` exits 139 at `5f68e60df`, which is `origin/main` exactly, while its assertion line reads 135 of 135 passed. The number a reader greps says the suite is green. It is order-dependent — the case passes alone — and `gdb` puts the fault inside the CPU paged-attention kernel on a threadpool worker. Filed as #1390 with `ENG-CUDAGRAPH-BREAK` as the owner, not fixed in flow: a segmentation fault in another stage's newly landed code, mechanism unlocated, in a file under concurrent edit for #1380. No lifecycle state moved, so `docs/STATUS.md` and `docs/BENCHMARKS.md` owe nothing. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…buffer from the backend (#1305) Two changes that cost nothing on CPU and are what make this file the device gate the moment it runs on one. The mirror's identifiers now live in a real `vt::Backend::Alloc` block rather than at the host vector's address. On CPU the two are the same thing. On a device they are not, and a host address there is the wrong kind of pointer for a field the runner's combine writes. Every step is compared instead of the first two. On CPU a "replay" recomputes nothing, so steps 2 and 3 hold what step 1 produced and the comparison is true for that reason; on a device a replay recomputes, and those two steps become the assertion the reported defect is actually about — that a REPLAY does not generate from stale identifiers. Re-measured, and the records carry the new numbers: RED at 2 cases / 65 assertions / 10 failed / exit 1 with 800 of 800 values differing on both architectures, GREEN at 65/65 exit 0. Deleting the registry's scope line reds 4 assertions and puts all 800 values back; swapping the seam's DEVICE arm for its HOST arm leaves the logits bit identical at 0 of 800 and reds only the counters. The spec's `## Owed` now names why the device battery did not run as a fleet state rather than as an intention: `dgx:gpu0`, the only box carrying the checkpoint, read busy, and the two ready devices carry none. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…ch nothing gated (#1305) The gate this file shipped with covered two decode-graph drivers and left the half of #1305 the pull request called its most important finding completely untested. A fresh review deleted the `TakeDeviceTokenIds()` + `d.b.Copy` block from BOTH `EmbedInto(const std::vector<int32_t>&)` overloads -- restoring the pre-fix eager behaviour in `qwen3_moe.cpp` and `deepseek_v2.cpp` -- and the binary stayed green at 2/2 cases and 65/65 assertions. It also deleted the two-line `DeviceTokenIdsScope` from `glm4_moe_lite_registry.cpp`, the third of the three registries the body claims to change, and that stayed green too. So this adds four cases and routes all six through one A/B/C helper. The EAGER lane is entered by NOT constructing `StaticGraphCpu`: a plain CPU platform answers `support_static_graph_mode()` false, so the registry's own predicate falls through to `ForwardDevice` and `ForwardBody` embeds from the host vector. That is the lane every non-CUDA and every non-pure-decode step takes, and it is the lane no graph refusal could ever have mitigated, which is why the fix for it had to be the consumption rather than a decline. The THIRD registration gets its own fixture rather than a claim. GLM-4-MoE-Lite shares `DeepseekV2DecodeGraph`, `DeepseekV2Model` and the weights struct with DeepSeek-V2 down to the loader, so the only thing it owns is its own scope -- and deleting that one scope leaves both DeepSeek cases green. `DsConfigJson` therefore takes the architecture, and the same geometry serves both. `through_seam` is the lane-identity assertion, not decoration. The graph driver refreshes a `vllm::StepTokenIds` and moves `vt::PersistentStepInput`'s process-wide counters once per step; the eager arms copy the override straight over their own per-step `DBuf` and must never touch that seam. Asserting the counters BOTH ways means a change that quietly moved a case onto the other lane could not keep it green. The file's opening comment also said the device contract is "directly testable here" because a host pointer is device-addressable on CPU. That is the wrong way round: both refresh arms reduce to the same memcpy from the same address there, which is exactly why the DEVICE half is NOT testable on this backend. The counters stand in for which arm ran, and they gate the instrument rather than the behaviour. Corrected, with the residual named. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…and two comments this change had made false (#1305) Three repairs a fresh review asked for, none of which changes behaviour. FOUR PRIVATE COPIES BECOME ONE. Taking the scoped override and splicing it over an embed's device buffer is four lines plus five, and `qwen3.cpp` and `qwen3_5.cpp` each spelled both out. #1305 then added a third and a fourth, in `qwen3_moe.cpp` and `deepseek_v2.cpp` -- in a row whose stated purpose is deleting hand-rolled copies. `detail::TakeDeviceTokenIds` and `detail::ApplyDeviceTokenIds` now sit in `qwen3_5_internal.h` beside the `DeviceTokenIdsScope` that publishes what they read, defined in `qwen3_5.cpp` beside `DeviceTokenIdsOverride()`. All four models call them. The refusal messages keep their per-caller wording through `what`, so a shape disagreement still names the model it came from. TWO PRODUCTION COMMENTS WERE LEFT FACTUALLY FALSE by the change that landed, and a record edit rides in the pull request whose change made it stale. `persistent_step_input.h` still told the reader that `RefreshFromDevice` lands with NO production caller and that grepping for it returns its definition and nothing else. `step_token_ids.h:102` is that caller, reached by three shipped registrations. And `qwen3.cpp`'s decline still said the fix it names "DOES NOT EXIST IN ANY DRIVER" and that every batched driver embeds from the host vector; two of the nine now hold their identifiers in a `vllm::StepTokenIds`. Both are corrected to what is now true, and both keep naming what is still owed, because the half that matters did not change: the refresh runs OUTSIDE the capture in every driver that has it, so reading the identifiers at REPLAY time -- the decline's own wording for the fix -- exists nowhere. The decline in `qwen3.cpp` is untouched. W4 measured its recorded cause false, so its mechanism is unexplained, and a refactor that plausibly addresses an explanation nobody has confirmed does not retire it. `StepTokenIds` also now says which of its accessors nothing reads, rather than leaving a reader to assume the counters it exposes are the ones under test. Five of its six have no caller; what the gate reads is the process-wide `vt::GetStepInputStats()`. Evidence, all on the CPU gate lane: green 6/6 cases, 191/191 assertions, exit 0 M4b (delete both `ApplyDeviceTokenIds` call sites, the post-hoist form of the review's M4) compile_rc=0, exit=1, 3/6 cases, 3 EAGER cases red on `differing == 0` MHELPER (shared body copies 0 bytes) compile_rc=0, exit=1, 3/6 cases, the same three red An earlier MHELPER that deleted the copy outright FAILED TO BUILD on -Wunused-parameter and its verdict was discarded, not read as a pass. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
… before the final gate (#1305) `origin/main` moved to `96ed8346f` while this branch was under fresh-implementer repair: `edfa62b3c` (SPEC-DFLASH2) and `96ed8346f` (ENV-LEASE-CLOCK-PINNING). Neither touches the device-token-id path, but merge-tree clean is not merge-tree builds, so the branch takes the merge before its gate run rather than after. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…half does not (#1305) The records said #1305 CLOSED. It should not, and the reason is sharper than "the battery did not run". The issue SPLITS. The EAGER half -- the half no graph refusal could ever have mitigated, and the half the landing change called its most important finding -- is fixed on all three registrations and is now gated on both lanes. That half deserves to close. The GRAPH half does not. The mechanism `Qwen3MoeDecodeGraph` and `DeepseekV2DecodeGraph` now have is functionally what `qwen3.cpp` ALREADY HAD at `338cbbfd1^`: a registry scope, consumed by `EmbedInto`, copying the mirror's identifiers over the embed source OUTSIDE the capture. W4 recorded at `qwen3.cpp:1083-1095` that the depth-2 graph-ON battery STILL FAILED with exactly that in place. A stable device address buys nothing while the embed stays outside the capture, which the change itself concedes. So landing it is not evidence that the degeneration is gone, and #1305's own settlement condition is that battery, against Qwen3-Coder and DeepSeek-V2-Lite, which did not run. #1305 stays OPEN with the `ENG-CUDAGRAPH-BREAK` row as owner, the pull request references it without a closing keyword, and `qwen3.cpp`'s decline stands. Also recorded, because a fresh review measured them and nothing in the tree said so: * The gate that landed covered half of what the change claims. Deleting the consumer from BOTH eager `EmbedInto` overloads left it green at 2/2 and 65/65; deleting the third registry's scope did too. Repaired to 6 cases / 191 assertions / exit 0, with the three detecting mutations tabulated. * The DEVICE half of the refresh contract is untested on any device. On CPU `Backend::Alloc` returns host-addressable memory, so both refresh arms are the same memcpy from the same address; swapping the device arm for the host arm leaves the logits bit-identical and reds only the counters. Those counters gate the instrument, not the behaviour, and the entry now says so instead of implying coverage. * `test_qwen3_5_decode_graph_seam` (#1390) re-measured on this branch: exit 139 at the same crash case and site with and without this branch's changes, and its printed counts are not reproducible across three runs of ONE unchanged binary. Only the exit code carries a verdict there. And one duplicate removed: the row's issue cell listed #1305 twice. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…RAPH-DEVIDS (#1305) `origin/main` moved again while this branch's records were being written: `5f4eb356e` (ENG-EXPERT-STREAM-DEVICE W0b-W0d). It touches the expert-streaming slot arm and the platform surface, not the device-token-id path, but a clean merge is not a build, so the branch takes it before the gate re-runs. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…RAPH-DEVIDS (#1305) The third move of `origin/main` during this repair: `7265cea08` (SPEC-PROMPT-TOKEN-DIVERGENCE), which is spec text and touches no file under `src/` or `include/`. Taken so the branch's gate ran against the head it will land on rather than one behind it. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…nge does not give (#1305, #1390) A fresh re-review returned PASS_WITH_FINDINGS on the code and one blocking record defect. This repairs the records only; no source line moves. The blocking one: `## Owed` said `last_source()` and `StepInputSource` "gain their reader with it". They do not, and this same change says so in three other places — both headers state it, and the pull-request body explicitly retracts an earlier draft that claimed it. The `## Owed` entry had kept the retracted wording. Re-derived rather than assumed: `grep -rn 'last_source()' src/` returns NOTHING, the only readers of a value are six `CHECK`s in `tests/vt/test_persistent_step_input.cpp`, and `step_token_ids.h:122` forwards to `cell_.last_source()` but is itself never called. A record asserting a reader exists is precisely the drift the two header repairs in this branch were made to end, which is why it blocks rather than rides along. Four smaller ones. `persistent_step_input.h` said "every caller of either is a test", false by exactly one — that forwarding wrapper — so it now says every caller that reads a VALUE is a test and names the wrapper. The #1390 measurement was recorded three ways: the spec presented `8 cases, 7 passed, 1 failed, 135 of 135` as the measurement while the matrix and the pull-request body already carried the correction that those printed counts are NOT reproducible on one unchanged binary. The spec now agrees with them and states the general rule plainly: on a crashing suite no assertion count means anything, because the process dies before the harness totals it, and only the exit code carries a verdict. `.agents/issue-index.md` is append-only and still shows the original numbers, so the spec and the issue are named as the authority over that row. And a stray `.;` where the appended #1305 block was concatenated onto the #1380 sentence in `engine-matrix.md` field 7. Two findings are RECORDED as bounded residuals in `## Outcome` rather than repaired, each with what bounds it. The shape-refusal hoist moves `__FILE__`/`__LINE__` to `qwen3_5.cpp:562` for all four refusals, so three of the four callers now report the wrong file; no test asserts those strings (grepped for the message and for each of the four `what` values), the `what` prefix still carries caller identity, and the audience is somebody reading one refusal from a log. The two rewritten call sites in files this branch was not repairing (`qwen3.cpp:213`, `qwen3_5.cpp:7829`) are covered only by checkpoint-gated skips reporting `assertions: 0`; the shared body they call IS gated, so the ungated surface is two argument lists, the gap pre-dates this branch, and net the hoist improves coverage because a defect in the shared body now reds `test_moe_async_device_ids`. #1305 stays OPEN, owned by `ENG-CUDAGRAPH-BREAK`, pending the depth-2 four-concurrent battery on a `dgx` window with checkpoints. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…RAPH-DEVIDS (#1305) Keeps the branch current before the records repair is pushed. The incoming commit is #1418's SGLANG-ORACLE-LEASE-WHEEL spec, which touches five files this branch does not: `.agents/environment.md`, `.agents/oracles/sglang.md`, `.agents/sglang-matrix.md` and the two `sglang-wheel-in-lease` spec files. The two file sets are disjoint, so nothing is re-applied and no keyed record is three-way merged. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
… left ragged (#1305) Prose only, in the two `## Owed` passages the previous commit edited. The `last_source()` correction had left `through` alone on its own line, and the #1390 correction had run one line to 127 columns and another to 140 by joining new text onto the sentence that followed it. The ORDER-DEPENDENT observation is now its own paragraph, which is what it always was. No claim, anchor or number changes. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
localai-bot
added a commit
that referenced
this pull request
Aug 19, 2026
… STEP needs, not what one tensor is (#1380) (#1393) A speculative decode-graph capture did a `cudaMalloc` inside the captured region on `thor:gpu0` (sm_110) and threw, so a replay was unreachable on a default-ON path (`VT_SPEC_DECODE_GRAPH`) and the queue was poisoned afterwards. ## The call site, named rather than inferred `cudaMalloc: operation not permitted when stream is capturing` names the API and no call site, so a backtrace was taken AT the failing allocation with the CUDA backend's own `Alloc` instrumented and the binary built `-g`. It resolves to `DBuf` construction inside `GdnBlockPaged` -- `dconv`, the GDN causal-conv output in `src/vllm/model_executor/models/qwen3_5.cpp` -- reached through `RunDenseLayerPaged` and `DenseForwardLayers` from the `GraphCaptureScope` in `Qwen3_5DenseDecodeGraph::Step`. The request was **960 bytes**, which at the gate's synthetic shape is BOTH `[T=6, conv_dim=80]` bf16 and `[S=6, vocab=40]` f32. That coincidence is the defect. `DevicePool` hands out blocks by SIZE CLASS and knows nothing about tensors. The driver pre-grew by allocating and freeing ONE `[S, vocab]` f32 block, reasoning that the capture retains its logits while the other ring slot still holds its own. The retention half is right; the "one block of that shape" half is not. A `VT_POOL_TRACE_CLASS` trace on the device measured the forward holding TWO blocks of that class live at once -- the GDN projection output and the causal-conv output it feeds -- inside a region where a miss aborts the capture. **And the pre-grow supplied nothing at all**, which is sharper than "one block". An alloc-and-free only reaches the driver when that class's free list is EMPTY. At the second ring slot's capture the class had ONE block free, because the first slot had retained the other, so the pre-grow took that block, returned it, and grew the pool by zero against a demand of two. ## The fix `DevicePool` now measures, per size class, the PEAK number of blocks a step holds live above the baseline it started from -- the transient demand of the FORWARD, not of whatever was resident when it ran -- and `PreGrowForCapture` makes the free list able to serve that profile before `BeginCapture`, where a `cudaMalloc` is legal. Both Qwen3.5 decode drivers record the profile at the end of their COLD step, per SLOT, because two shapes interleave through one pool and last-step pool state would answer for whichever step ran most recently. It is idempotent and a no-op once the free list is deep enough, which is the steady state on a warm server. ## Measured on `thor:gpu0` (sm_110, driver 595.78, nvcc 13.0.88, `-DVLLM_CPP_CUDA=ON -DVLLM_CPP_CUDA_ARCHITECTURES=110 -DVLLM_CPP_TRITON=OFF`) RED at `9c27fb9f5`, the case alone in a fresh process -- 752 assertions, 2 failed, exit 1: ``` spec step 2: captured=1 threw='' spec step 3: captured=1 threw='vt cuda: cudaMalloc: operation not permitted when stream is capturing' spec step 4: captured=1 threw='vt cuda: embedding: operation failed due to a previous error during capture' ``` GREEN at `ffeea2172`, the whole G1 file -- **6 cases, 3306 assertions, 0 failed**, the five pre-existing drivers unchanged at `0 differing, 4 replays` each and the new case reading `5 steps x 240 logits, 0 differing, 3 replays`. RE-RUN at the merged head `15906f0a4`, same box, four gates: | Gate | Result | |---|---| | `test_decode_graph_seam_g1_cuda` | 6 cases, **3306 assertions**, 0 failed; the spec case `0 differing, 3 replays` | | `test_device_pool` | 10 cases, 37 assertions, 0 failed | | `test_qwen3_5_decode_graph_seam` | 8 cases, 138 assertions, 0 failed -- identical to `origin/main` on the same build | | `test_breakable_graph` | 30 cases, 265 assertions, 0 failed | `b316afc05` and `47143457f` sit on top of that head and are comment corrections in `src/vt/cpu/cpu_paged_attn.cpp` and `src/vllm/model_executor/models/qwen3_5.cpp` with no code in either. ## The merge, and what a reviewer should check first GitHub reported this CONFLICTING and it is not. `git merge-tree --write-tree origin/main HEAD` returns rc 0, no CONFLICT line, clean tree `4eec7dd20`; the forge ignores this repository's `merge=union` driver and reads an append to `.agents/issue-index.md` as a conflict. Merged locally at `96ed8346f`, never rebased. **The union merge is verified rather than assumed**, because two relocations can merge cleanly into a duplicate: against `origin/main`'s index the merged file is 441 rows plus exactly ONE added line and zero removed, `diff` reports a single hunk, and each of #1380 and #1394 has exactly one row — re-checked on the built tree inside the lease. **One thing this PR does NOT gate, stated plainly.** The pre-grow profile comes from the driver's COLD step, so the fix rests on the captured forward demanding no more blocks of any size class than the eager forward at that shape did. That is a reading of the code and **no test asserts it**. A capture-only allocation with no eager counterpart would reopen #1380 and nothing here would say so until a capture threw on a device. The spec's `## Owed` names the two things that would settle it and which is worth building. **An earlier revision of this body gave the WRONG reason for believing it, and the belief survives on a different one.** It said the two arms "run the same `DenseForwardLayers`". They call the same function and take DIFFERENT branches: the cold arm passes 13 arguments and lets `persistent_sdi` default to `nullptr` (`qwen3_5.cpp:11153`), the capture arm passes `dbuf ? s.dev.get() : nullptr` as a 14th (`:11084`), and the MoE driver carries the same asymmetry (`:10595` against `:10529`). What actually holds the containment is what those branches DO, at `:8858-8890`: the `nullptr` arm calls `BuildStepDevInputs` into the main pool and then `MaybeBuildAttnCosSin`, while the non-null arm allocates NOTHING there and only calls `FillAttnCosSin` into buffers `s.dev` already owns — built pre-capture under `ActivePoolScope persistent_scope(&PersistentDecodeInputPool(d.b))` (`:11043`, `:10493`), a different pool from the one `PreGrowForCapture` grows. So at the one point the arms diverge, the captured region's main-pool demand is a strict SUBSET, and the asymmetry runs in the safe direction. Both `warm = false` paths (`:10902`, `:10952`; `:10355`, `:10406`) reset the graph and force a fresh cold step, so no slot pre-grows from a stale profile. Sameness was never the argument, and asserting it invited a reviewer to check the wrong thing. Two mutations, each compiled clean, each restoring to an empty diff: | Mutation | `test_decode_graph_seam_g1_cuda` | `test_device_pool` | |---|---|---| | 1, driver half: restore the single-block pre-grow | RED, 752 assertions / 2 failed, the original symptom | green | | 2, pool half: `PreGrowForCapture` grows nothing | RED, 752 assertions / 2 failed | RED | ## It reproduces on sm_121a, so the architecture question is measured #1380 said the first thing a fix had to establish was whether this reproduces on `dgx:gpu0`, because SPEC-DSPARK W8 recorded a WORKING speculative capture on GB10. **It reproduces.** NVIDIA GB10, capability 12.1, driver 580.173.02, nvcc 13.0.88, `-DVLLM_CPP_CUDA_ARCHITECTURES=121a -DVLLM_CPP_TRITON=OFF`, binary resolving `libcudart.so.13` and `libcublasLt.so.13`: | sha | `dgx:gpu0` (GB10, sm_121a) | `thor:gpu0` (sm_110) | |---|---|---| | red `eae8cc3f0` | `cudaMalloc: operation not permitted when stream is capturing`, then the queue poisoned; 507 assertions / 8 failed / exit 1 | same message, same per-step shape, same counts | | fixed `15906f0a4` | 6 cases, **3306 assertions**, 0 failed, exit 0; `0 differing, 3 replays` | identical | Two architectures, both arms, identical readings. That supersedes the argument this rested on — that a host-side `DevicePool` size-class deficit cannot be architecture-specific — by measuring it. The argument is still worth keeping, because `tests/vllm/models/test_device_pool.cpp` reproduces the same deficit with fake host backends, **no GPU and no CUDA at all**. **W8 is explained rather than contradicted.** Whether two tensors collide in a size class is arithmetic over the MODEL's dimensions, not the device's: at the real 35B, `[S, vocab]` f32 with `vocab = 151936` shares a class with nothing the GDN block allocates, so W8's capture had no competitor for its class. A larger pool never "hid" the defect so much as never met it at that shape — which is why a pool-size-dependent hiding was never going to be the answer. ## The gate the case had to become, and why it could not have been written the obvious way The case W6 landed pinned steps 0 through 2 and printed the rest, so it recorded the blocker but could not fail while the blocker was live. Turning it into the gate took two corrections that are worth more than the diff: - **`max_num_reqs == 0` does not select an eager arm on a speculative shape.** `S = spec_step ? B : PadToCaptureSize(B)` never consults it, so that arm captures too, and the "eager" reference was a second capturing driver competing for the same pool -- measured, the refusal moved from the second ring slot to the first. The eager arm is `Qwen3_5DenseModel::ForwardDevice`, the body the driver's own disabled path runs. - **An interleaved reference HIDES the defect.** An eager forward between the graph arm's steps deepens the shared free list on the graph arm's behalf. The interleaved shape of this case passed 1240 assertions at the un-fixed head while the driver stepped alone threw. The eager loop now runs to completion first; each arm owns its own KV and GDN state, so it is the same computation. The spec carried the opposite advice on both points and now carries the measurement. ## A second defect this uncovered, fixed in the same flow The `DevicePool` change moves which pooled block holds a tensor and therefore what follows it, and that turned a SILENT out-of-bounds read into a SIGSEGV in `tests/vllm/models/test_qwen3_5_decode_graph_seam.cpp`. `gdb` puts it at `src/vt/cpu/cpu_paged_attn.cpp:224`, one frame under `FullAttnBlockPaged`, reached from the eager fallback of `Qwen3_5DecodeGraph::Step`. `PagedAttentionKernel` reads `btab[r * bt_row + (j / block_size) * bt_col]` for every `j < seq_lens[r]` and never checked that the block table has that many columns. The seam case's `SpecAttnMeta` hardcoded one column, and its shape C sits at `pos = 20` with `q = 4`, so `seq_lens = 24` against `block_size = 16`. **This closes a live crash on `main`, not a latent one, and an earlier revision of this body said otherwise.** Two readings of that file at `5f68e60df` exactly: one build gives 8 cases / 138 assertions / exit 0, and the operator's gives a deterministic **SIGSEGV, exit 139, three runs of three**. Both are real. The read is out of bounds at `main` unconditionally; whether it FAULTS is the allocator's business rather than the caller's. "Passes at main" was a statement about one build presented as a statement about the tree, and it is withdrawn. What settles the presence of the defect independently of any heap layout is measured: `origin/main` built with ONLY the kernel refusal and WITHOUT the test-data fix -- **the refusal fired**. And the assertion counts never disagreed; a SIGSEGV truncates doctest's count, so 135 is the crashed-partway run of the same 8 cases and 138 is the complete one. This branch adds no assertion to that file, and the file is byte-identical between `5f68e60df` and `96ed8346f`. Both halves land here: the kernel refuses a short table with one compare per request outside the token loop, and the helper sizes its table for the sequence length it declares. **IT CLOSES #1394 AND IT DOES NOT CLOSE THE READ, and the difference is worth being exact about.** #1394 is scoped to `vt::cpu::PagedAttentionKernel` and to the seam case that fed it a short table; both halves land here, so `Closes` is honest. The same unbounded read is live on **five other backends** — [#1406](#1406) owns it, under row `ENG-CUDAGRAPH-BREAK`, and the spec's `## Owed` carries the enumeration: | Backend | Sites | Where | |---|---|---| | CUDA | 12 | `cuda_paged_attn.cu:230,385,521,676,837,1067,1310,1387,1583,1663,1830,1912` | | ROCm | 7 | `rocm_paged_attn.hip:226,424,557,765,1064,1277,1530` | | Metal | 5 | `metal_msl.h:1054,1142,1293,1341,1591` | | Tenstorrent | 2 | `tenstorrent_ops.cpp:3070,3100`, the HOST fallback | | Vulkan | 1 | `shaders/vt_paged_attn.comp:110` | **27 sites, counted with `grep -c` per file so the counts SUM** — #1406 said nine in its first revision and nineteen in its second, each time by stopping at the first surface that answered, so this one is re-derived rather than inherited. Tenstorrent's device-staging path (`:2258-2267`) is deliberately NOT in the count: it walks `c < max_blocks` and is already bounded. The seam cannot hold the bound for all six while `PagedAttentionArgs::max_seq_len` is documented at `include/vt/ops.h:806-812` as a value for which "an upper bound is safe", and Vulkan and Metal do not even pass the column count into their shaders (`vulkan_ops.cpp:994-995`, `metal_ops.mm:940-941`), so fixing those two means widening a shader ABI. **THE REFUSAL LANDS UNGATED, AND THIS IS A STAGED SLICE UNDER "NOTHING LANDS DEAD".** What is unreached is the TEST, not the code: the refusal sits on the production paged-attention path, but nothing feeds it a short table any more — because this pull request's own second half sizes `SpecAttnMeta`'s table correctly. Measured: delete the guard at `cpu_paged_attn.cpp:145-155` and `test_qwen3_5_decode_graph_seam` stays 8/8 and `test_ops_paged_attn` stays 14/14, both exit 0; `grep -rn "block table is shorter" tests/` returns nothing. The detecting `CHECK_THROWS_AS` case is written and reviewed in [#1407](#1407) ([#1390](#1390)), which merges IMMEDIATELY after this one in the sequence #1393 → #1407 → #1391. Duplicating it here would leave two gates for one refusal and no owner for either. Owning row: `ENG-CUDAGRAPH-BREAK`; named in the spec's `## Owed`, in the commit body, and here. Until #1407 lands, the predicate is supported by the G1 measurement above — `origin/main` with ONLY the kernel refusal and WITHOUT the test-data fix, where the refusal FIRED — and by no test in this tree. ## Review repairs A fresh review returned PASS_WITH_FINDINGS. It verified the pool arithmetic and reproduced both detecting mutations; every finding was about closure scope and missing gates, and none changed the arithmetic. Repaired in `197958e42`: | # | Finding | Repair | |---|---|---| | F1 | `Closes #1394` claimed while the read stayed live elsewhere | `Closes` kept as honest on #1394's actual scope; #1406 named here and in `## Owed`, and #1406's own enumeration corrected from 19 to **27** — it was missing Tenstorrent, Vulkan and Metal entirely | | F2 | the refusal lands with no detecting test | declared as a staged slice in all three required places, with #1407 as the gate | | F3 | the `## Owed` argument's stated basis was factually wrong | replaced with the branch-level argument that actually holds, verified in both drivers | | F4 | `kStepKind` mislabelled the spec case's steps — "capture" printed for the second COLD step, in the very case built to say which step failed | `CompareStep` now takes the table; the spec case passes `kSpecRingStepKind` | | F5 | `PreGrowForCapture` ignores the soft cap | comment naming the hazard: `Put` frees to the driver over the cap and is called from INSIDE the captured forward, so a `cudaFree` would abort the capture exactly as the `cudaMalloc` did — inert only because every platform sets the cap to 0 | | F6 | `.agents/environment.md` closed a well-hedged entry with a conclusion two events cannot support | softened to match the entry's own register | | F7 | `MarkStepBoundary()` is called on `Pool(b)` only | recorded in `## Owed`; two unread counters, not a defect | | F8 | the append-only index row for #1394 keeps withdrawn wording | left as-is (append-only); the spec and #1406 both now say the issue body is the authority | ### Re-gated after the repairs, on the merged tree Built fresh at `a9837a9d6` (`Release`, `-DVLLM_CPP_CUDA=OFF -DVLLM_CPP_TRITON=OFF -DVLLM_CPP_SERVER=OFF`, `-j 4`), because merge-tree clean is not merge-tree builds and this merge touched `qwen3_5.cpp` from both sides: | Gate | Result | |---|---| | `test_qwen3_5_decode_graph_seam` | exit 0, **8 cases / 8 passed**, 138 assertions | | `test_ops_paged_attn` | exit 0, **14 cases / 14 passed**, 1643 assertions | | `test_device_pool` | exit 0, **10 cases / 10 passed**, 37 assertions | | `test_breakable_graph` | exit 0, **30 cases / 30 passed**, 265 assertions | | `test_decode_graph_seam_g1_cuda` | compiles clean; **NOT A PASS** — 6 cases, `assertions: 0`, seven `SKIP: no CUDA backend registered` lines. F4's label change is compile-verified only | Two mutations, each with its compile status and each restored to an empty `git status`: | Mutation | Compile | Result | |---|---|---| | **B**, delete the guard at `cpu_paged_attn.cpp:145-155` | `rc=0` | `test_qwen3_5_decode_graph_seam` **exit 0, 8/8, 138**; `test_ops_paged_attn` **exit 0, 14/14, 1643**. NOTHING GOES RED — which is the F2 finding, measured here rather than accepted on report | | **C**, revert only `SpecAttnMeta`'s table sizing, KEEP the guard | `rc=0` | **exit 1, 8 cases / 7 passed / 1 failed**, refusal fires: `paged_attention: the block table is shorter than the sequence it must address ... at src/vt/cpu/cpu_paged_attn.cpp:152` | **Mutation C is the load-bearing one, and mutation B is why.** B shows no test in this tree detects the guard's removal; C shows the predicate is right anyway, by a route that does not depend on heap layout — feed the kernel a short table with the guard in place and it refuses, which no allocator accident can produce or suppress. That is the evidence to cite for this refusal, NOT the SIGSEGV: whether the unbounded read faults is build- and allocator-dependent, and this branch has now produced both readings on different builds. **A note on the 135, because it was misread once.** Mutation C reports `assertions: 135` against the baseline's 138, and this run did NOT crash — it failed cleanly, exit 1. The three missing assertions are the ones after the throw point in the one aborted case. So 135 is what an early-terminating case reads, whichever way it terminates, and the earlier attempt to treat 138→135 as a clean "truncation" signature was reading a number that does not carry that meaning. FOLLOWING_AGENTS_PROTOCOL Closes #1380 Closes #1394 Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --------- Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
…ine-matrix row by hand (#1305) #1393 and #1407 landed, and both this branch and #1393 had edited the `ENG-CUDAGRAPH-BREAK` row of `.agents/engine-matrix.md`. That row is a KEYED RECORD, so AGENTS.md forbids accepting the automatic three-way merge: the resolution takes `origin/main`'s version of the file WHOLE and re-applies this branch's scoped edits on top of it, field by field. What each side had changed, established before resolving rather than after. Of the row's nine fields, #1393 moved exactly one — field 6, `Our tests/evidence` — rewriting the "#1380 is BLOCKED" passage into the measured "#1380 FIXED" result on `thor:gpu0` and `dgx:gpu0` and appending #1394. This branch moved three: field 5 and field 9 by pure append, which #1393 never touched, and field 6 by appending its own #1305 gate block. So field 6 is the only genuine merge, and it takes #1393's text whole with this branch's block appended after it. Fields 1-4, 7 and 8 were asserted byte-identical on all three sides before anything was written. Verified rather than assumed: `git diff origin/main -- .agents/engine-matrix.md` touches line 64 and nothing else, the other 290 lines are byte-for-byte equal to `origin/main`, the row carries 10 pipes and 9 fields exactly as the table header at line 55 declares, and the owner cell names #1305 once and #1390 once. #1393's markers survive the resolution and none of this branch's do the reverse: the `.;` this branch introduced and repaired stays repaired, and the file has no `.;` left. `.agents/specs/eng-cudagraph-break.md` auto-merged, which is correct for a per-row spec rather than a keyed multi-row table, and both sides' sections are present with no conflict markers anywhere in the tree. 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
Nine commits landed on main while this branch carried the fresh review's repairs: #1393, #1407 and #1391 among them. The branch touches src/vllm/multimodal, tests/vllm/multimodal and the row's own spec, none of which any of them edits, so the merge is textually clean; it is taken rather than rebased because the branch is already pushed and a rebase would discard the base its review was taken against. 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.
Three registrations routed an asynchronous step into a model that never read
ModelForwardInput::device_token_ids, so the decode graph AND both eager armsembedded identifiers the runner deliberately leaves stale.
#1305 reads as a decode-graph hazard:
qwen3_moe_registry.cpp,deepseek_v2_registry.cppandglm4_moe_lite_registry.cppadmit a pure-decodestep to a driver that replays against a host vector, while
qwen3.cppdeclinesfor exactly that condition. Reading the tree found something larger. Those three
registrations never constructed a
detail::DeviceTokenIdsScope, and neitherqwen3_moe.cpp's nordeepseek_v2.cpp'sEmbedIntoever consulted one, sodevice_token_idsreached NOTHING in either translation unit. On theasynchronous serving path the runner's combine splices each decode row's sampled
token into the DEVICE identifiers on the main queue and leaves the host
token_idsstale by design — the synchronize that path exists to remove — andthe mirror arm is the default. Both models therefore generated from the previous
step's identifiers for every decode row, on the graph path and on the eager path
alike. No decline could have mitigated the eager half, which is why the fix here
is the consumption and not the refusal.
What lands
The three registries publish the scope, which is the mechanism
qwen3.cpp,qwen3_5.cpp,mistral_registry.cpp,internlm2_registry.cppandllama_registry.cppalready use, so every embed in both translation unitsconsumes it.
The two decode-graph drivers take the version of the fix the issue asks for
rather than a fifth private copy. Each padded size slot now owns a
vllm::StepTokenIds(include/vllm/model_executor/models/step_token_ids.h),whose destination is a device buffer with an address that does not move and whose
refresh runs through
vt::PersistentStepInput: the host arm for the paddedvector, then the DEVICE arm over the real prefix, both enqueued on the main queue
so the second is ordered after the combine instead of racing it. That gives
RefreshFromDeviceits first production caller — W4 landed it with none and namedthe gap under
## Owed.last_source()is NOT given a reader, and an earlier draft of this body said itwas. Every caller of
last_source()orStepInputSourceis a test; what thegate reads is the process-wide
vt::GetStepInputStats(). Five ofStepTokenIds'six accessors have no caller in
src/ortests/, and the header now says sorather than leaving a reader to assume the counters it exposes are the ones under
test.
The consumer side is written ONCE. Taking the scope and splicing it over an
embed's device buffer is four lines plus five, and
qwen3.cppandqwen3_5.cppeach spelled both out; this change first added a third and a fourth copy, in a
row whose stated purpose is deleting them.
detail::TakeDeviceTokenIdsanddetail::ApplyDeviceTokenIdsnow sit inqwen3_5_internal.hbeside theDeviceTokenIdsScopethat publishes what they read, and all four models callthem. The refusal messages keep their per-caller wording, so a shape disagreement
still names the model it came from.
What this deliberately does NOT do
qwen3.cpp's decline is untouched. W4 (#1307) measured that decline's recordedCAUSE false, so its failure mode is unexplained, and a refactor does not retire a
mitigation whose mechanism nobody can name. Removing it still needs the two
battery runs the spec records.
The embed still sits OUTSIDE the captured region in every driver, because
vt::Embeddingallocates a device flag and synchronizes the stream. Theidentifiers are therefore read once per step from a stable device address, not
from inside the replay.
StepTokenIdsis the destination the inside-the-capturechange would need; it is not that change. Two production comments that stated the
older facts —
persistent_step_input.honRefreshFromDevicehaving no caller,and
qwen3.cpp's decline on the fix existing in no driver — are corrected here,because a record edit rides in the pull request whose change made it stale.
Evidence
tests/vllm/models/test_moe_async_device_ids.cppenters atModelRegistry::Forwardover a synthetic safetensors checkpoint. A case thatdrove the driver directly would measure the type rather than the registration.
Three runs per case, because two of them cannot separate the cases: right host
identifiers and no mirror as the reference; stale host identifiers and no mirror
as the CONTROL, which must differ; the same stale host vector with the truth
reaching the model ONLY through
device_token_idsas the gate, which must bebit-identical to the reference. Without the control, a model that ignored its
identifiers entirely would satisfy the gate.
RED before the change, for the intended reason: 2 cases, 65 assertions, 10
failed, exit 1, with 800 of 800 logit values differing over four steps on both
architectures and every seam counter at 0. GREEN after: 65/65, exit 0.
A fresh review then proved that gate covered half of what this change claims,
by mutation rather than by reading, and the repair is in this branch. Deleting
the
TakeDeviceTokenIds+d.b.Copyblock from BOTHEmbedInto(const std::vector<int32_t>&)overloads — restoring the pre-fix EAGERbehaviour, the half this body calls the reason the fix is a consumption — left
the gate green at 2/2 cases and 65/65 assertions. Deleting the two-line
DeviceTokenIdsScopefromglm4_moe_lite_registry.cpp, the THIRD of the threeregistries this body says publish a scope, did too; the original reachability
mutation had covered only two.
The gate is now 6 cases, 191 assertions, exit 0: both lanes for all three
registrations, through one A/B/C helper. The lane is chosen by the registry's OWN
predicate rather than by the test — a case that constructs
StaticGraphCpugetsthe decode graph, a case that does not gets
ForwardDevice— andthrough_seamasserts the
vt::PersistentStepInputcounters BOTH ways, moving on the graphlane and at zero on the eager one, so a case cannot drift onto the other lane and
stay green. GLM-4-MoE-Lite gets its own fixture rather than a claim of coverage:
it shares the driver, the model and the weights struct with DeepSeek-V2, so the
only thing it owns is its scope.
Four mutations, each with its compile status printed:
EmbedIntoconsumersdiffering == 0glm4_moe_lite_registry.cpp's scopeStepTokenIds::Refresh'sRefreshFromDevicedevice_refreshesANDdiffering == 0ApplyDeviceTokenIdscopies 0 bytesA fifth deleted that shared copy outright and FAILED TO BUILD under
-Wunused-parameter. Its verdict was DISCARDED, not read as a pass.Neighbours green on the same binary after merging
origin/mainat96ed8346f:test_qwen3_moe_decode_graph_seam228,test_deepseek_v2_decode_graph_seam230,test_qwen3_decode_graph_seam231,test_persistent_step_input66,test_model_registry924,test_qwen3_moe_forward504,test_deepseek_v2_forward1052,test_glm4_moe_lite_load15 — all 0 failed,exit 0.
test_qwen3_dense_async_servingexits 0 over 8 cases and 0 assertions,which is a skip wearing a pass: it needs checkpoints this box does not have.
Component checkers green:
check-agent-record,check-issue-index-append-only,check-surface-coverage,check-test-registration,check-symbol-anchors,check-public-doc-tables,check-runner-routing-consistency,check-fusion-consistency,check-device-leakage,check-now-current,check-doc-checkpoint,check-gate-commands,check-role-discipline,check-prompt-contract,check-commit-style,check-commit-trailers.Continuous integration is
REMOTE_UNVERIFIED: every check on this branch readspendingand none starts (#1376).What is still owed, and why #1305 does NOT close here
The issue splits, and only one half settles. The EAGER half is fixed and
gated on all three registrations and deserves to close. The GRAPH half does not,
and the reason is sharper than "the battery did not run": the mechanism these two
drivers now have is functionally what
qwen3.cppALREADY HAD at338cbbfd1^— aregistry scope, consumed by
EmbedInto, copying the mirror's identifiers over theembed source OUTSIDE the capture — and W4 recorded at
qwen3.cpp:1083-1095thatthe depth-2 graph-ON battery STILL FAILED with exactly that in place. A stable
device address buys nothing while the embed stays outside the capture, which this
body concedes two sections above.
The depth-2 four-concurrent battery against Qwen3-Coder and DeepSeek-V2-Lite on a
real device — #1305's own settlement condition — has NOT been run, and the reason
is a fleet state rather than an intention: at 2026-08-19
rc devicesreaddgx:gpu0 busy, the only box whose HuggingFace cache carries the checkpoint,while
thor:gpu0andorin:gpu0were ready and carry none. So #1305 staysopen, with the
ENG-CUDAGRAPH-BREAKrow as owner and the residual recorded inthe spec's
## Owed.The DEVICE half of the refresh contract is untested on any device, and the gate's
own opening comment used to say the opposite. On CPU
vt::Backend::AllocreturnsHOST-addressable memory, so both refresh arms reduce to the same memcpy from the
same address; swapping the device arm for the host arm leaves every logit
bit-identical at 0 of 800 differing and reds only the
device_refreshes/host_refreshescounters. Those counters are a legitimatestand-in for which arm ran and they are what the file asserts, but they gate the
INSTRUMENT, not the behaviour. Owed with the battery, same window, same owner.
Found on
mainwhile doing this, and not caused by ittest_qwen3_5_decode_graph_seamexits 139 at5f68e60df, which wasorigin/mainexactly, while its assertion line reads 135 of 135 passed and 0 failed. Only the
exit status and a
CRASHED: SIGSEGVline say otherwise. Re-measured on thisbranch: exit 139 at the SAME crash case and site
(
test_qwen3_5_decode_graph_seam.cpp:800,W6: two spec shapes of EQUAL S and different q get two graphs) both with and without this branch's working-treechanges, and its printed counts are not reproducible — three consecutive runs of
ONE unchanged baseline binary gave 6 passed with 2 failed and 141 assertions,
then no summary at all, then no summary at all. The exit code is the only stable
observation there. Filed as #1390 with
ENG-CUDAGRAPH-BREAKas the owner andrecorded under
## Owed; that issue stays open.Records
.agents/specs/eng-cudagraph-break.md## Owed,## Nowand## Outcome,.agents/engine-matrix.md, and one appended.agents/issue-index.mdrow for#1390. No lifecycle state moved, so
docs/STATUS.mdanddocs/BENCHMARKS.mdowe nothing.
An earlier draft of this section said the engine-matrix issue cell "listed #1305
twice" at the base. It did not. The cell carries #1305 ONCE at the merge base and
once at
origin/main; the duplicate was this branch's own, introduced by831f8d3ceand removed by91087e670. That sentence attributed a self-inflictedtransient to the base, and the net state is one mention, unchanged.
Repaired after a fresh re-review
The re-review returned
PASS_WITH_FINDINGSon the code — all six production callsites proved individually, the four-way hoist behaviour-identical to every
original, baseline 6 cases / 191 assertions / exit 0 — plus one blocking record
defect. It is repaired here, and no source line moves.
The blocking one.
## Owedsaidlast_source()andStepInputSource"gaintheir reader with it". They do not, and this change already says so in three
other places: both headers state it, and this body retracts it below.
Re-derived rather than assumed —
grep -rn 'last_source()' src/returns NOTHING,the only readers of a value are six
CHECKs intests/vt/test_persistent_step_input.cpp, andstep_token_ids.h:122forwards tocell_.last_source()but is itself never called. What gains a production calleris
RefreshFromDevice; the arm observable stays unread. A record asserting areader exists is precisely the drift the two header repairs in this branch were
made to end, which is why it blocked.
Four smaller ones.
include/vt/persistent_step_input.hsaid "every caller ofeither is a test", false by exactly one — that forwarding wrapper — and now says
every caller that reads a VALUE is a test, naming the wrapper. The #1390
measurement was recorded three ways: the spec presented
8 cases, 7 passed, 1 failed, 135 of 135as the measurement while this body and the engine-matrix rowalready carried the correction that those printed counts are not reproducible.
The spec now agrees with them and states the general rule plainly: on a
crashing suite no assertion count means anything, because the process dies before
the harness totals it — only the exit code carries a verdict.
.agents/issue-index.mdis append-only and cannot be edited, so it still showsthe original numbers; the spec and the issue are named as the authority over that
row. And a stray
.;in.agents/engine-matrix.mdfield 7, where the appended#1305 block was concatenated onto the #1380 sentence.
Two are RECORDED as bounded residuals in
## Outcomerather than repaired.The shape-refusal hoist moves
__FILE__/__LINE__toqwen3_5.cpp:562for allfour refusals, so three of the four callers now name the wrong file; no test
asserts those strings (grepped for the message and for each of the four
whatvalues), the
whatprefix still carries caller identity, and the audience issomebody reading one refusal out of a log. The two rewritten call sites in files
this branch was not repairing (
qwen3.cpp:213,qwen3_5.cpp:7829) are coveredonly by checkpoint-gated skips reporting
assertions: 0, andtest_qwen3_decode_graph_seam.cpp:341-349gates the DECLINE rather than theconsumption; the shared body they call IS gated, so the ungated surface is two
argument lists, the gap pre-dates this branch, and net the hoist IMPROVES
coverage, because a defect in the shared body now reds
test_moe_async_device_ids.Advances #1305, which stays open on the battery above.
FOLLOWING_AGENTS_PROTOCOL
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [Claude Code]