feat(ENG-CUDAGRAPH-BREAK): migrate the last three plain batched decode drivers onto the seam, and pay G1 on a device (#1291, #1163) - #1306
Merged
Conversation
…-point capture seam (#1291, #1163) W3 migrates the three remaining PLAIN BATCHED decode drivers onto the seam W1 landed and W2 first entered from a production step. This commit is the first of them, `Qwen3MoeDecodeGraph` (Qwen3-Coder, full-attention MoE). `Step` now opens a `vt::GraphCaptureScope` over a per-slot `vt::BreakableGraph` and replays through `BreakableGraph::Replay`, so the driver's hand-rolled `BeginCapture` / `EndCaptureGraph` pair, its raw `void*` handle, its `bool captured` flag, its `DestroyGraph` loop and its own `VLLM_CPP_CUDAGRAPH` read are gone — items 1, 2, 5, 6 and part of 4 of the spec's `## Our baseline` re-derivation list, absorbed. kFULL, INHERITED FROM W2 AND NOT RE-ARGUED. vLLM's v1 default `FULL_AND_PIECEWISE` (`vllm/config/compilation.py:63` @ pin `5559679229`) is documented at `:630-632` as a FULL graph for DECODE batches and a piecewise one for prefill and mixed batches; `decode_mode()` (`:65-66`) returns the full half. This is a decode driver, so its capture is ONE segment. W2 measured what the alternative costs: a `kPiecewise` scope over a decode driver does not merely slow the step down, it FAULTS on the first replay, because W1's break closure captures its layer frame by reference. No throughput is claimed here and none was measured; `kFull` is what keeps the migrated step's shape the one it already had. THE QWEN3-CODER-LOCAL KILL SWITCH STAYS, and the FRAMEWORK-WIDE one goes. The driver read TWO environment variables. `VLLM_CPP_CUDAGRAPH` is the shared switch six drivers each read for themselves, which is the defect; it is now `vt::GraphCaptureEnabled()`, read once per process into a function-local static. `VT_QWEN3MOE_CUDAGRAPH` is a per-model A/B lever for exactly this driver and is not a copy of anything, so it stays where it is. GATED, RED FIRST. `tests/vllm/models/test_qwen3_moe_decode_graph_seam.cpp` drives `Qwen3MoeDecodeGraph::Step` through four decode steps — cold, capture, replay, replay — and asserts the SEAM's own counters. It has to be the counters and not the backend log: the seam bottoms out in the same `Backend::` calls the raw driver made, so the log, the logits and `replay_count()` are all identical either way, and `segments_captured` and `replays` are the only observables that separate "captured a graph" from "captured a graph THROUGH THE SEAM". Before the driver change the file failed on exactly those four assertions (`segments_captured 0 == 1` twice, `replays 0 >= 1`, `replays 0 >= 2`), 226 assertions, 222 passed, exit 1. After it, 3/3 and 226/226, exit 0. Two cases in that file were GREEN before the change, deliberately, and they are controls rather than red-first cases. G4 — the capture step bit-identical to `Qwen3MoeModel::Forward`, 100 values, 0 differing — is what makes the migration reversible, so it must not move. The capture-FAILURE case passed against the pre-W3 driver because its `s.graph = b.EndCaptureGraph(...)` was unguarded and propagated; it is here because W2's first head turned exactly that into a silent return of pool-recycled memory, and a migration that reads only `captured()` cannot tell a FAILED capture from an INERT scope. The driver branches on `vt::BreakableGraph::capture_failed()` and rethrows the runtime's own exception. THE HARNESS IS NOW SHARED, for the reason this row exists. W2's gate carried its own capture-capable CPU backend and static-graph CPU platform; W3 adds three more gates of that exact shape. `tests/vllm/models/decode_graph_seam_harness.h` holds them once and W2's file now includes it — a fourth copy inside `tests/` would reproduce the duplication this row removes from `src/`, and two copies of a harness diverge invisibly, because both files stay green while measuring different things. W2's gate is unchanged in behaviour: 4/4, 231 assertions, the same numbers its own commit recorded. NO BREAK POINT IS REGISTERED IN THIS MODEL, and that is a decision. The one production `vt::GraphBreak` in the tree is at `qwen3.cpp`'s dense attention entry (W1). Under `kFull` a break point takes the same pass-through arm it takes outside a scope, so registering one here would land machinery no gate can exercise. Establishing this model's break-point set is what the PIECEWISE arm needs, that arm is blocked on replay-safe closure inputs which W4 owns, and W6 is where the eligibility predicate moves. G1 IS NOT MET HERE. Bit-exactness against a REPLAYED capture needs a real device: a CPU kernel is a direct function call rather than a backend submission, so nothing a CPU harness calls a replay recomputes anything. It stays owed. Refs #1291. Parent #1163. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…point capture seam (#1291, #1163) The second of W3's three plain batched drivers. `VoxtralDecodeGraph::Step` (the Voxtral TEXT backbone, Mistral/Llama full attention) now opens a `vt::GraphCaptureScope` over a per-slot `vt::BreakableGraph` and replays through `BreakableGraph::Replay`, so its hand-rolled `BeginCapture`/`EndCaptureGraph` pair, its raw `void*` handle, its `bool captured` flag, its `DestroyGraph` loop and its own `VLLM_CPP_CUDAGRAPH` read are gone. WHAT DIFFERS FROM THE SIBLINGS, because it is not nothing. The single-sequence multimodal greedy driver constructs this graph with `max_num_reqs == 1`, so `PadToCaptureSize` only ever yields S == 1 and the captured region is `ForwardLastLogits` — the LAST row gathered before an untied lm_head, returning [1, vocab] — rather than the full [S, vocab] its siblings capture. The state machine, the persistent host inputs and the invalidate-on-column-change rule are the same, so the migration is the same, and the shape difference lands in the gate rather than in the driver. kFULL, for the reason W2 established and measured: vLLM's v1 default `FULL_AND_PIECEWISE` (`vllm/config/compilation.py:63` @ pin `5559679229`, documented at `:630-632`, `decode_mode()` at `:65-66`) is a FULL graph for DECODE batches. No throughput is claimed and none was measured. GATED, RED FIRST. `tests/vllm/models/test_voxtral_decode_graph_seam.cpp` drives `Step` through cold, capture, replay and a second replay and asserts the SEAM's own counters, because the backend log, the logits and `replay_count()` are identical whether the driver goes through the seam or calls `Backend::` itself. Before the driver change: 4 failed assertions (`segments_captured 0 == 1` twice, `replays 0 >= 1`, `replays 0 >= 2`), 228 assertions, 224 passed, exit 1. After: 3/3, 228/228, exit 0. G4 IS AGAINST THE DRIVER'S OWN EAGER ARM, and the reason is a visibility one rather than a preference. Voxtral's text forward lives in `voxtral.cpp`'s anonymous namespace and is reachable only through this driver or through the multimodal greedy driver that owns the audio tower, so there is no model-level `Forward` to compare against as the Qwen3 gates do. `Step` on a platform that does not admit capture runs exactly that forward, which is precisely what G4 asks for — "with capture disabled, output bit-identical to the pre-migration eager output". 100 values, 0 differing. THE ASYNC DEVICE-TOKEN DECLINE DOES NOT APPLY TO THIS DRIVER, and it is worth saying so rather than leaving the omission to be read as one. `qwen3.cpp:1106` declines its decode graph while `ModelForwardInput::device_token_ids` is live, because `Step` replays against persistent HOST vectors. That field is set by `src/vllm/v1/worker/gpu/runner.cpp:1523` on the paged-engine path, and this driver is not reached from there: its only construction site is `VoxtralGenerateGreedy`, the single-sequence multimodal greedy loop, which feeds `Step` the token it just sampled on the host. There is no asynchronous device mirror on that path, so there is no race to decline. G1 IS NOT MET HERE and stays owed: a CPU kernel is a direct function call rather than a backend submission, so nothing a CPU harness calls a replay recomputes anything. Refs #1291. Parent #1163. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…ak-point capture seam (#1291, #1163) The third and last of W3's plain batched drivers, and the only one whose capturable region is not dense attention. `DeepseekV2DecodeGraph::Step` now opens a `vt::GraphCaptureScope` over a per-slot `vt::BreakableGraph` and replays through `BreakableGraph::Replay`, so its hand-rolled `BeginCapture`/`EndCaptureGraph` pair, its raw `void*` handle, its `bool captured` flag, its `DestroyGraph` loop and its own `VLLM_CPP_CUDAGRAPH` read are gone. With this the six batched-driver `VLLM_CPP_CUDAGRAPH` reads the spec's `## Our baseline` counted are down to two, both in `qwen3_5.cpp`, which W4 owns. MLA, AND WHY IT CHANGED NOTHING ABOUT THE MIGRATION. This driver captures `vt::MlaDecodeAttention` rather than the shared `dense_attn::AttnBlock`, its KV cache is one latent cache per layer with `num_kv_heads == 1` and no separate V, and its replay branch re-records the W8 MLA split-shape counters from the padded metadata because `BuildMlaStep` does not run on a replay. All of that is model state and stays in the model. What it re-derived was the capture machine, and the capture machine is what moves. `VT_DEEPSEEK_CUDAGRAPH` STAYS. It is the per-model A/B rollback W9 uses for exactly this lever, not a copy of the framework-wide switch, so only the `VLLM_CPP_CUDAGRAPH` read is replaced by `vt::GraphCaptureEnabled()`. kFULL, for the reason W2 established and measured: vLLM's v1 default `FULL_AND_PIECEWISE` (`vllm/config/compilation.py:63` @ pin `5559679229`, documented at `:630-632`, `decode_mode()` at `:65-66`) is a FULL graph for DECODE batches. No throughput is claimed and none was measured. GATED, RED FIRST. `tests/vllm/models/test_deepseek_v2_decode_graph_seam.cpp` drives `Step` through cold, capture, replay and a second replay and asserts the SEAM's own counters, because the backend log, the logits and `replay_count()` are identical whether the driver goes through the seam or calls `Backend::` itself. Before the driver change: 4 failed assertions (`segments_captured 0 == 1` twice, `replays 0 >= 1`, `replays 0 >= 2`), 228 assertions, 224 passed, exit 1. After: 3/3, 228/228, exit 0. `test_deepseek_v2_forward` stays 11/11, 1052 assertions. That gate is also the first CPU decode coverage this model has had: every existing `deepseek_v2` CPU case runs a PREFILL, so the MLA decode path had no CPU-side execution anywhere in the tree until this file. It runs. THE ASYNC DEVICE-TOKEN DECLINE: THIS DRIVER HAS NONE, AND W3 DOES NOT ADD ONE. `qwen3.cpp:1106` returns `std::nullopt` while `ModelForwardInput::device_token_ids` is live, because `Step` replays against persistent HOST vectors that the asynchronous combine has deliberately left stale. `deepseek_v2_registry.cpp` and `glm4_moe_lite_registry.cpp` route a pure-decode step into this driver with no such check, and this driver holds 0 lines of `StepDevInputs`. W3 does not close that: adding a decline would trade a shipped, default-ON capability away on a measurement this stage cannot make, and the fix its sibling's comment names is the persistent device input path as a SEAM capability, which is W4 and which is the only version that reaches this driver at all. It is filed, and recorded under the spec's `## Owed` with W4 as the owner. G1 IS NOT MET HERE and stays owed: a CPU kernel is a direct function call rather than a backend submission, so nothing a CPU harness calls a replay recomputes anything. Refs #1291. Parent #1163. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…ecause the guard for it was a mute switch (#1291, #1163) A W3 mutation found a gate that could not fail. The three drivers this stage migrated assert `breaks_registered == 0` to hold their capture to vLLM's DECODE arm. Flipping `kFull` to `kPiecewise` in `qwen3_moe.cpp` — one token — compiled clean and left that driver's whole gate GREEN at 226 of 226. The same flip in `voxtral.cpp` and `deepseek_v2.cpp` was equally invisible. WHY THE ASSERTION COULD NOT SEE IT. `breaks_registered` moves when a `vt::GraphBreak` registers into a splitting scope. Exactly ONE production break point exists in this tree, W1's, at `qwen3.cpp`'s dense attention entry. W2's driver runs through it, so for W2 the counter genuinely separated the two modes and the assertion was load-bearing. None of W3's three models registers one, so for them `breaks_registered` is 0 in BOTH modes and `== 0` is a tautology. The guard was copied from a gate where it worked into three where it could not, which is the failure mode this row exists to stop. WHY IT MATTERS THAT NOTHING COULD SEE IT. The mode is the difference between one graph and one eager attention call per layer, and no gate anywhere in this tree could read a driver's mode from outside the driver: the scope is a `Step` local, the container is private to the driver's `Impl`, and a token gate cannot see a segment count. So the mode was unobservable rather than under-asserted. WHAT LANDS. `vt::GraphBreakStats` gains `full_scopes` and `piecewise_scopes`, incremented where the mode is decided — in `GraphCaptureScope`'s constructor, on the ACTIVE path only. The inert path is excluded deliberately: a scope that cannot capture makes no backend call in either mode, so counting it would report a mode that never reached a backend. `ResetGraphBreakStats()` clears them like every other G3 counter. This is the G3 observability the spec asks for, extended by the one axis it was missing. G3's own words are a counter reporting "segments captured, break functions registered, and replays run, so the ratio is observable in a run rather than inferred"; the mode is what makes that ratio mean something, and it was inferred. GATED, WITH ITS CONTROLS. `tests/vt/test_breakable_graph.cpp` gains a case that asserts each mode moves its own counter and not the other, that an INERT scope moves NEITHER — the control that stops the counters from degenerating into "scopes constructed" — and that the reset clears them. 29 cases, 229 assertions, exit 0. Each of the three W3 driver gates now asserts `full_scopes == 1` and `piecewise_scopes == 0` beside the `breaks_registered` line, and states in place why the older assertion is kept but is not the discriminator. RE-MEASURED AFTER THE FIX. The same one-token flip, applied to each of the three drivers in turn, compiled clean and now REDS its gate on exactly those two assertions: `qwen3_moe` 226 of 228, `voxtral` 228 of 230, `deepseek_v2` 228 of 230, all exit 1. Each file restored and verified by sha256. Refs #1291. Parent #1163. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…it-exact against eager over three replays, on a device (#1291, #1163) G1 is the one gate `.agents/specs/eng-cudagraph-break.md` has carried as OWED through W1 and W2, and it is owed for a structural reason rather than an oversight: every other gate this row owns runs on a CPU backend that implements the capture vocabulary by LOGGING. A CPU kernel is a direct function call and not a backend submission, so nothing a CPU harness calls a "replay" recomputes anything. Those gates hold the ROUTING and the capture step's numerics. They cannot hold that a REPLAYED graph reproduces the eager forward, which is the whole claim a decode-graph driver makes. `tests/vllm/models/test_decode_graph_seam_g1_cuda.cpp` is that gate, for all three drivers W3 migrated. Each runs FIVE steps at one shape — cold, capture, then THREE consecutive replays — and every step's logits are compared bit for bit against the same step through the eager arm. Three replays and not one is the spec's own wording: a single replay cannot distinguish a correct capture from one that happens to read a buffer nothing has overwritten yet, and the defect class this row's history contains (`.agents/specs/decode-graph-scratch-uaf-2026-07-18.md`) appears on a LATER replay. THE EAGER ARM IS SELECTED WITHOUT AN ENVIRONMENT VARIABLE, and that is what makes this a same-binary A/B rather than two builds. `PadToCaptureSize(b, max_num_seqs)` returns -1 when `b` exceeds `max_num_seqs` (`decode_graph_sizes.h:47-54`), and every migrated `Step` falls out to its plain forward on that value, so a driver constructed with `max_num_reqs == 0` is the same code, on the same device, taking the eager path. `VLLM_CPP_CUDAGRAPH=0` is read once per process into a function-local static and would need a second process; the per-model switches exist for two of the three drivers and not the third. Neither is uniform, and this is. Each arm gets its OWN device KV cache, because the arms are stepped in lockstep and a shared cache would let one arm read the other's writes and call the agreement a result. DeepSeek runs at the REAL V2-Lite MLA geometry (qk 192, V 128, kv_lora 512), because the CUDA MLA launcher is instantiated for head_dim 192 only. READ ITS COUNT AND NOT ITS STATUS. With no CUDA backend every case skips and the file prints `Status: SUCCESS!` over `assertions: 0` — a skip wearing a pass. The header says so, and a G1 result from this file is admissible only with a NON-ZERO assertion count and the device it ran on named beside it. The lease run and its numbers are recorded in the spec. Refs #1291. Parent #1163. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…, and two things the stage was not looking for (#1291, #1305, #1163) The record edits the three W3 migration commits and the mode-counter fix made stale, plus the two findings that came out of doing the work. W3 IS DONE. Four of the nine drivers are on the seam, and the six batched-driver `VLLM_CPP_CUDAGRAPH` reads the spec's `## Our baseline` item 1 counted are down to two, both in `qwen3_5.cpp`, which W4 owns. Recorded in the spec's `## Work breakdown`, `## Now` and `## Owed`, in the engine-matrix row, in `docs/STATUS.md`, and in a new claim. G1 IS DELIVERED, AND IT IS THE ITEM W1 AND W2 BOTH CARRIED FORWARD. Every other gate this row owns runs against a CPU backend that implements the capture vocabulary by LOGGING, and a CPU kernel is a direct function call rather than a backend submission — nothing a CPU harness calls a replay recomputes anything. `tests/vllm/models/test_decode_graph_seam_g1_cuda.cpp` ran on `thor:gpu0` through an `rc` lease: NVIDIA Thor sm_110, driver 595.78, nvcc 13.0.88, source `c905bb536`, CUDA-ON build for arch 110, 32 `.cu.o` objects, the binary resolving `libcudart.so.13` and `libcublasLt.so.13`, 489 s to build at `-j4`. **3 cases, 1600 assertions, exit 0**, reading `5 steps x 100 logits, 0 differing, 4 replays` for each of `Qwen3MoeDecodeGraph`, `VoxtralDecodeGraph` and `DeepseekV2DecodeGraph`. The count is what carries that, not the status line: with no CUDA backend the same file prints `SUCCESS!` over `assertions: 0`. Two limits are recorded beside it rather than left for a reader to discover. The models are the synthetic tiny ones the CPU forward gates already use, so the run exercises the real CUDA kernels and the real capture and replay and NOT a checkpoint's weights or a long context. And W2's `Qwen3DenseDecodeGraph` is not one of the three cases: it shares the seam and the shape, which is an argument rather than a measurement. FINDING 1, A GATE THAT COULD NOT FAIL. The three W3 gates assert `breaks_registered == 0` to hold each capture to vLLM's decode arm. For these models that is a tautology — the counter moves only when a `vt::GraphBreak` registers into a splitting scope, and the one production break point in the tree is W1's, in `qwen3.cpp`. Flipping `kFull` to `kPiecewise` in `qwen3_moe.cpp`, one token, compiled clean and left that gate green at 226/226. Closed in the preceding commit by counting the mode itself; recorded here because a guard that was copied from a gate where it worked into three where it could not is the failure mode this row exists to remove. FINDING 2, FILED RATHER THAN FIXED, WITH AN OWNER. #1305: `qwen3_moe_registry.cpp:107`, `deepseek_v2_registry.cpp:106` and `glm4_moe_lite_registry.cpp:125` route a pure-decode step into a host-vector replay with NO `device_token_ids` check, while `qwen3.cpp:1106` declines for exactly that condition on a measured battery whose own comment calls the hazard "latent for EVERY classic-dense model, since the graph is default-ON". The shape is present at W3's base commit `5d9fe332c`, so W3 found it rather than caused it. Not fixed in flow for two stated reasons: a decline trades a shipped, default-ON capability away on a measurement this stage cannot make, and the fix `qwen3.cpp` names is `StepDevInputs` as a seam capability, which is W4 and is the only version that reaches these registrations at all. Owner W4, with #1179 as the standing tracker. Voxtral is unaffected and the record says why. TWO OWED ITEMS ARE CORRECTED RATHER THAN QUIETLY INHERITED. G5's ROCm and Tenstorrent arm read `Owner: W3` and W3 did not discharge it: the fleet carries no ROCm and no Tenstorrent device, so it is BLOCKED on hardware rather than unattempted, and it moves to W5 — the stage that migrates the driver family whose Tenstorrent recapture path is the only place a ttnn mesh trace meets this seam. And the async serving battery read `Owner: W3`: the lease was obtainable, the CHECKPOINT was not, because the G1 run built from a clean clone on the box's local disk against synthetic weights. It moves to W4, which owns the fix the battery exists to validate. Component checkers green: agent record (`ENGINE=164 MODEL=377 QUANT=84 KERNEL=52 BACKEND=85`), issue-index append-only against `origin/main`, public doc tables (`docs/STATUS.md` inside its size ratchet), symbol anchors (649/649), test registration, gate commands, NOW currency, role discipline, prompt contracts, model checklist, oracle pins. Closes #1291. Parent #1163 stays open: W4 lifts `StepDevInputs` into the seam and owns both the async decline and #1305, W5 takes the three single-shape drivers and G5's other backends, and W6 moves the eligibility predicate. 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.
W3 of
ENG-CUDAGRAPH-BREAKmigrates the three remaining PLAIN BATCHED decodedrivers onto the break-point capture seam:
Qwen3MoeDecodeGraph(Qwen3-CoderMoE),
VoxtralDecodeGraph(Voxtral text) andDeepseekV2DecodeGraph(MLA, alsoconstructed by the GLM4-MoE-Lite registration). Each
Stepopens avt::GraphCaptureScopeover a per-slotvt::BreakableGraphinkFullandreplays through
BreakableGraph::Replay, so three more copies of the hand-rolledBeginCapture/EndCaptureGraphpair, the rawvoid*handle, thebool capturedflag, theDestroyGraphloop and the driver's ownVLLM_CPP_CUDAGRAPHread are gone. Four of the nine drivers are now on the seam, and the six
batched-driver reads of that variable the spec's
## Our baselineitem 1 countedare down to TWO, both in
qwen3_5.cpp, which W4 owns.Each driver landed as its own commit with its own gate, because they share W2's
shape and nothing about one depends on another. The two per-model rollback
switches STAY:
VT_QWEN3MOE_CUDAGRAPHandVT_DEEPSEEK_CUDAGRAPHare A/B leversfor exactly one driver each, not copies of the shared one.
kFULL, INHERITED FROM W2 AND NOT RE-ARGUED. vLLM's v1 default
FULL_AND_PIECEWISE(vllm/config/compilation.py:63@ pin5559679229) isdocumented at
:630-632as a FULL graph for DECODE batches and a piecewise onefor prefill and mixed batches, and
decode_mode()(:65-66) returns the fullhalf. W2 measured what the alternative costs: a
kPiecewisescope over a decodedriver does not merely slow the step down, it FAULTS on the first replay, because
W1's break closure captures its layer frame by reference. No throughput is
claimed here and none was measured.
kFullis what keeps each migrated step'sshape the one it already had.
Each driver owes its own gate, because nothing else can see the difference
A driver that kept its raw pair produces IDENTICAL logits, an IDENTICAL backend
log and an identical
replay_count().vt::GraphBreakStats::segments_capturedmoves only when a
vt::GraphCaptureScopecloses a segment andreplaysonlyinside
vt::BreakableGraph::Replay, so those two are the only observables thatseparate "captured a graph" from "captured a graph THROUGH THE SEAM". Each gate
drives
Stepthrough cold, capture, replay and a second replay and asserts them.RED FIRST, on four assertions each, before its migration:
test_qwen3_moe_decode_graph_seamtest_voxtral_decode_graph_seamtest_deepseek_v2_decode_graph_seamG2 REACHABILITY MUTATION, one per driver: restore the pre-W3 driver file in a
scratch copy (25/102, 23/92 and 25/94 changed lines; each compiled clean, exit
0). Each reddens ONLY its own gate on those four assertions and leaves
test_breakable_graph216/216 and W2'stest_qwen3_decode_graph_seam231/231GREEN. Each file restored and verified by
sha256sum -c.Two cases per file were green BEFORE the change, deliberately, and they are
controls rather than red-first cases. G4 — the capture step bit-identical to the
eager arm, 100 values, 0 differing — is what makes each migration reversible, so
it must not move. The capture-FAILURE case passed against each pre-W3 driver
because
s.graph = b.EndCaptureGraph(...)was unguarded and propagated; it ishere because W2's first head turned exactly that into a silent return of
pool-recycled memory, and a migration that reads only
captured()cannot tell aFAILED capture from an INERT scope.
THE HARNESS IS NOW SHARED. W2's gate carried its own capture-capable CPU backend
and static-graph CPU platform, and W3 adds three more gates of that shape.
tests/vllm/models/decode_graph_seam_harness.hholds them once and W2's fileincludes it — a fourth copy inside
tests/would reproduce the duplication thisrow removes from
src/, and two copies of a harness diverge invisibly becauseboth files stay green while measuring different things. W2's gate is unchanged in
behaviour: 4/4, 231 assertions, the same numbers its own commit recorded.
G1 is DELIVERED, on a leased device
This is the item W1 and W2 both carried as owed, and it is owed for a structural
reason: every other gate this row owns runs against a CPU backend that implements
the capture vocabulary by LOGGING, and a CPU kernel is a direct function call
rather than a backend submission, so nothing a CPU harness calls a replay
recomputes anything.
tests/vllm/models/test_decode_graph_seam_g1_cuda.cppruns each migrated driverCOLD, CAPTURE and THREE consecutive replays and compares every step bit for bit
against that driver's own EAGER arm. The eager arm is selected WITHOUT an
environment variable:
PadToCaptureSize(b, max_num_seqs)returns -1 whenbexceeds
max_num_seqs, and every migratedStepfalls out to its plain forwardon that value, so
max_num_reqs == 0gives the same binary on the same devicetaking the eager path. Each arm gets its OWN device KV cache, so neither can read
the other's writes and call the agreement a result.
Measured on
thor:gpu0through anrclease — NVIDIA Thor sm_110, driver595.78, nvcc 13.0.88, source
c905bb536, CUDA-ON build for arch 110, 32.cu.oobjects, binary resolvinglibcudart.so.13andlibcublasLt.so.13,build 489 s at
-j4:The assertion count carries that claim and the status line does not. With no
CUDA backend every case skips and the same file prints
Status: SUCCESS!overassertions: 0, which the file's own header names as a skip wearing a pass.TWO LIMITS, recorded rather than left to be discovered. The models are the
synthetic tiny ones the CPU forward gates already use: the run exercises the real
CUDA kernels and the real capture and replay, and NOT a checkpoint's weights or a
long context. And W2's
Qwen3DenseDecodeGraphis not one of the three cases — itshares the seam and the shape, which is an argument and not a measurement.
W3 found a gate that could not fail, and closing it is part of this stage
The three gates assert
breaks_registered == 0to hold each capture to vLLM'sdecode arm. For these models that assertion is a TAUTOLOGY: it moves only when a
vt::GraphBreakregisters into a splitting scope, and the ONE production breakpoint in the tree is W1's, in
qwen3.cpp. W2's driver runs through it, so for W2the guard was load-bearing; none of W3's three registers one. Measured, not
reasoned: flipping
kFulltokPiecewiseinqwen3_moe.cpp— one token —compiled clean and left that driver's whole gate GREEN at 226/226.
The mode was UNOBSERVABLE from outside a driver: the scope is a
Steplocal, thecontainer is private to the driver's
Impl, and a token gate cannot see asegment count.
vt::GraphBreakStatsgainsfull_scopesandpiecewise_scopes,counted in
GraphCaptureScope's constructor on the ACTIVE path only — an inertscope makes no backend call in either mode, so counting it would report a mode
that never reached a backend. Gated in
tests/vt/test_breakable_graph.cppwiththe inert arm as the control (29 cases, 229 assertions). RE-MEASURED after the
fix: the same one-token flip now REDS each of the three driver gates on exactly
those two assertions (226/228, 228/230, 228/230, all exit 1).
NO BREAK POINT IS REGISTERED IN THESE THREE MODELS, and that is a decision. Under
kFulla break point takes the same pass-through arm it takes outside a scope,so registering one would land machinery no gate can exercise. Establishing each
model's break-point set is what the PIECEWISE arm needs; that arm is blocked on
replay-safe closure inputs, which W4 owns, and W6 is where the eligibility
predicate moves.
The async device-token decline, stated per driver
VoxtralDecodeGraphneeds none. Its only construction site isVoxtralGenerateGreedy, the single-sequence multimodal greedy loop, which isnot reached from
runner.cppand has no asynchronous device mirror.Qwen3MoeDecodeGraphandDeepseekV2DecodeGraphare a NEW FINDING, filedas Three decode-graph registrations route async steps into a host-vector replay with no device_token_ids decline #1305 and NOT fixed in flow.
qwen3_moe_registry.cpp:107,deepseek_v2_registry.cpp:106andglm4_moe_lite_registry.cpp:125route apure-decode step into a host-vector replay with NO
device_token_idscheck,while
qwen3.cpp:1106declines for exactly that condition on a measuredbattery whose own comment calls the hazard "latent for EVERY classic-dense
model, since the graph is default-ON". The shape is present at this branch's
base commit
5d9fe332c, so W3 found it rather than caused it. Adding a declinewould trade a shipped, default-ON capability away on a measurement this stage
cannot make, and the fix
qwen3.cppnames isStepDevInputsas a SEAMcapability — W4, and the only version that reaches these registrations at all.
Owner W4, with The decode-graph driver count recorded in 9bc4d7f44 is eight; it is nine, and the ninth predates the record #1179 as the standing tracker.
Two owed items are corrected rather than quietly inherited
Owner: W3and W3 did not discharge it.The fleet carries no ROCm and no Tenstorrent device, so it is BLOCKED on
hardware rather than unattempted, and it moves to W5 — the stage that
migrates the driver family whose Tenstorrent recapture path is the only place a
ttnn mesh trace meets this seam. What W3 can say instead: the seam's CUDA arm
now runs on TWO architectures, sm_110 here and sm_121a for W1's exit criterion.
Owner: W3. The lease was obtainable; theCHECKPOINT was not, because the G1 run built from a clean clone on the leased
box's local disk against synthetic weights. It moves to W4, which owns the
fix the battery exists to validate.
Gates run
Focused, on the final head:
test_breakable_graph29/29 (229),test_qwen3_decode_graph_seam4/4 (231),test_qwen3_break_point2/2 (516),test_qwen3_moe_decode_graph_seam3/3 (228),test_voxtral_decode_graph_seam3/3 (230),
test_deepseek_v2_decode_graph_seam3/3 (230),test_decode_graph_sizes5/5 (478),test_qwen3_forward10/10 (1575),test_qwen3_moe_forward3/3 (504),test_deepseek_v2_forward11/11 (1052),test_graph_safe_scratch(4).ctest -R 'voxtral|glm4|deepseek|qwen3_moe|graph'46/46 passed. Full CPU build exit 0.
Component checkers green: commit trailers and commit style over
origin/main..HEAD, agent record (ENGINE=164 MODEL=377 QUANT=84 KERNEL=52 BACKEND=85), issue-index append-only againstorigin/main, public doc tables(
docs/STATUS.mdinside its size ratchet), symbol anchors (649/649), testregistration, gate commands, NOW currency, role discipline, prompt contracts,
model checklist, oracle pins.
Negative mutations, each printed with its
git diff --stat, its compile statusand its exit status, each detected and each restored byte-for-byte and verified
by sha256: the three G2 driver mutations above; the
capture_failed()branchdisabled in
qwen3_moe.cpp(CHECK_THROWS_AS(...) did NOT throw at all!,225/226, exit 1); and the
kFull->kPiecewiseflip on all three drivers,UNDETECTED before the mode counters and detected after.
Closes #1291. Parent #1163 stays open: W4 lifts
StepDevInputsinto the seam andowns both the async decline and #1305, W5 takes the three single-shape drivers
and G5's other backends, and W6 moves the eligibility predicate off
pure_decode.FOLLOWING_AGENTS_PROTOCOL
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [Claude Code]