Skip to content

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
localai-bot merged 6 commits into
mainfrom
row/ENG-CUDAGRAPH-BREAK-W3
Aug 19, 2026
Merged

feat(ENG-CUDAGRAPH-BREAK): migrate the last three plain batched decode drivers onto the seam, and pay G1 on a device (#1291, #1163)#1306
localai-bot merged 6 commits into
mainfrom
row/ENG-CUDAGRAPH-BREAK-W3

Conversation

@localai-bot

Copy link
Copy Markdown
Collaborator

W3 of ENG-CUDAGRAPH-BREAK migrates the three remaining PLAIN BATCHED decode
drivers onto the break-point capture seam: Qwen3MoeDecodeGraph (Qwen3-Coder
MoE), VoxtralDecodeGraph (Voxtral text) and DeepseekV2DecodeGraph (MLA, also
constructed by the GLM4-MoE-Lite registration). Each Step opens a
vt::GraphCaptureScope over a per-slot vt::BreakableGraph in kFull and
replays through BreakableGraph::Replay, so three more copies of the hand-rolled
BeginCapture/EndCaptureGraph pair, the raw void* handle, the bool captured flag, the DestroyGraph loop and the driver's own VLLM_CPP_CUDAGRAPH
read 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 baseline item 1 counted
are 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_CUDAGRAPH and VT_DEEPSEEK_CUDAGRAPH are A/B levers
for 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 @ pin 5559679229) is
documented at :630-632 as a FULL graph for DECODE batches and a piecewise one
for prefill and mixed batches, and decode_mode() (:65-66) returns the full
half. 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 each migrated step's
shape 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_captured
moves only when a vt::GraphCaptureScope closes a segment and replays only
inside vt::BreakableGraph::Replay, so those two are the only observables that
separate "captured a graph" from "captured a graph THROUGH THE SEAM". Each gate
drives Step through cold, capture, replay and a second replay and asserts them.

RED FIRST, on four assertions each, before its migration:

gate red green
test_qwen3_moe_decode_graph_seam 222/226, exit 1 3/3, 228/228
test_voxtral_decode_graph_seam 224/228, exit 1 3/3, 230/230
test_deepseek_v2_decode_graph_seam 224/228, exit 1 3/3, 230/230

G2 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_graph 216/216 and W2's test_qwen3_decode_graph_seam 231/231
GREEN. 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 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 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.h holds them once and W2's file
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.

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.cpp runs each migrated driver
COLD, 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 when b
exceeds max_num_seqs, and every migrated Step falls out to its plain forward
on that value, so max_num_reqs == 0 gives the same binary on the same device
taking 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: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, binary resolving libcudart.so.13 and libcublasLt.so.13,
build 489 s at -j4:

[doctest] test cases:    3 |    3 passed | 0 failed | 0 skipped
[doctest] assertions: 1600 | 1600 passed | 0 failed |
G1 Qwen3MoeDecodeGraph  on CUDA: 5 steps x 100 logits, 0 differing, 4 replays
G1 VoxtralDecodeGraph   on CUDA: 5 steps x 100 logits, 0 differing, 4 replays
G1 DeepseekV2DecodeGraph on CUDA: 5 steps x 100 logits, 0 differing, 4 replays

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! over
assertions: 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 Qwen3DenseDecodeGraph is not one of the three cases — it
shares 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 == 0 to hold each capture to vLLM's
decode arm. For these models that assertion is a TAUTOLOGY: it 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. W2's driver runs through it, so for W2
the guard was load-bearing; none of W3's three registers one. Measured, not
reasoned: flipping kFull to kPiecewise in qwen3_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 Step local, the
container is private to the driver's Impl, and a token gate cannot see a
segment count. vt::GraphBreakStats gains full_scopes and piecewise_scopes,
counted in GraphCaptureScope's constructor on the ACTIVE path only — an inert
scope 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.cpp with
the 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
kFull a 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

  • VoxtralDecodeGraph needs none. Its only construction site is
    VoxtralGenerateGreedy, the single-sequence multimodal greedy loop, which is
    not reached from runner.cpp and has no asynchronous device mirror.
  • Qwen3MoeDecodeGraph and DeepseekV2DecodeGraph are a NEW FINDING, filed
    as 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: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 this branch's
    base commit 5d9fe332c, so W3 found it rather than caused it. Adding a decline
    would trade 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 — 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

  • 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. 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.
  • 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 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_graph 29/29 (229),
test_qwen3_decode_graph_seam 4/4 (231), test_qwen3_break_point 2/2 (516),
test_qwen3_moe_decode_graph_seam 3/3 (228), test_voxtral_decode_graph_seam
3/3 (230), test_deepseek_v2_decode_graph_seam 3/3 (230),
test_decode_graph_sizes 5/5 (478), test_qwen3_forward 10/10 (1575),
test_qwen3_moe_forward 3/3 (504), test_deepseek_v2_forward 11/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 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.

Negative mutations, each printed with its git diff --stat, its compile status
and its exit status, each detected and each restored byte-for-byte and verified
by sha256: the three G2 driver mutations above; the capture_failed() branch
disabled in qwen3_moe.cpp (CHECK_THROWS_AS(...) did NOT throw at all!,
225/226, exit 1); and the kFull -> kPiecewise flip on all three drivers,
UNDETECTED before the mode counters and detected after.

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 off
pure_decode.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [Claude Code]

mudler added 6 commits August 19, 2026 05:35
…-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]
@localai-bot
localai-bot merged commit 5c8671c into main Aug 19, 2026
0 of 15 checks passed
@localai-bot
localai-bot deleted the row/ENG-CUDAGRAPH-BREAK-W3 branch August 19, 2026 06:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ENG-CUDAGRAPH-BREAK W3: migrate Qwen3MoeDecodeGraph, DeepseekV2DecodeGraph and VoxtralDecodeGraph onto the seam

2 participants