Skip to content

spec(ENG-CUDAGRAPH-BREAK): the capture seam has no break points, and the nine hand-rolled drivers each re-derive one - #1177

Merged
localai-bot merged 5 commits into
mainfrom
row/ENG-CUDAGRAPH-BREAK
Aug 18, 2026
Merged

spec(ENG-CUDAGRAPH-BREAK): the capture seam has no break points, and the nine hand-rolled drivers each re-derive one#1177
localai-bot merged 5 commits into
mainfrom
row/ENG-CUDAGRAPH-BREAK

Conversation

@localai-bot

@localai-bot localai-bot commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Scopes ENG-CUDAGRAPH-BREAK to READY: one shared vt capture seam that accepts break points, so a forward containing a host-dependent operation is still graphed except at that operation instead of falling out to eager for the whole step. Spec-only. No production code lands here.

The boundary comes from vLLM and the construction comes from SGLang, and the spec keeps those apart. vLLM's v1 default already splits at splitting_ops (vllm/config/compilation.py:60-63,517,1145 @ 555967922), so the boundary is the attention family and this row does not invent one; its mechanism is Dynamo and FX, which we have no way to reach. SGLang gets the same coverage with no compiler, and that is what ports.

Coverage AND correctness, never speed. qwen3.cpp:961-986 already declines its decode graph outright whenever the async device-token mirror is live, on its own measured battery (depth-1, graph ON PASS 78/78; depth-2, graph OFF PASS 82/82; depth-2, graph ON FAIL, slots 1-3 degenerate), because the fix its own comment names lives in one sibling driver as StepDevInputs and nowhere else. A shipped model has already lost its decode graph to this duplication. No throughput gate is declared and the prefill refutation stands unchanged: GB10 measured 3.8% host idle between launches with GPU-busy above 96%, and the 27B prefill gap at 92.5% non-GEMM glue.

Review repair

A fresh reviewer returned FAIL on 35206c295. Ten findings, all repaired in e714dc25e and a647abbe9. The two that were load-bearing:

The upstream test suite the spec said did not exist is 305 lines. ## Tests to port asserted that "SGLang's BCG carries no unit test at the pinned revision that exercises the segment container in isolation". test/registered/cuda_graph/breakable/test_breakable_cuda_graph.py @ f63458b5be carries eleven unit cases across TestBreakableCUDAGraphBasic (:30), TestCopyOutput (:172) and TestBreakGraphHelper (:230). A ## Tests to port section that asserts there is nothing to port deletes the obligation the section exists for. Every case is now mapped to the local test that owes it, with the one harness adaptation stated once. test_gsm8k_accuracy is deliberately excluded, with the reason: it is a distributional accuracy floor on a prefill path, and G1's bit-exactness against the model's own eager forward is strictly stronger.

The primitive dropped the output writeback. ## Port map §3 said eager_on_graph "appends fn to break_fns". It does not. It appends a replay_fn closure (:231-235) that calls the captured inner function and writes the result back into the capture-time destination through _copy_output (:172-201), with _weak_ref_if_tensor (:156-169) keeping that destination alive. On replay N the eager operation returns a fresh allocation whose address is not the one the next segment baked, so a container replaying the raw fn leaves segment i+1 reading capture-time data while the break function writes elsewhere. That is wrong numerics rather than a fault — the same detection class D1 records compute-sanitizer cannot see. Now stated at the GraphBreak declaration, pinned by the ported TestCopyOutput cases, recorded as D9.

The rest:

  • Auxiliary-stream fork was absent everywhere. Upstream auto-joins side streams before closing a segment (:353-361) and maintains the whole wait_stream hook (:101-153, installed :310, removed :332) for that alone. It is live here: SupportsAuxStream() is true on CUDA (cuda_backend.cu:200) and both drivers this row migrates in W4 and W5 fork on it, at qwen3_5.cpp:6254 and laguna.cpp:2572. Stated on GraphCaptureScope, gated by a new test, recorded as D10.
  • Four enumerations and one grep count re-derived with printed instruments. Six drivers read VLLM_CPP_CUDAGRAPH, not five; the Qwen3.5 dense getenv is :9969, not :9974; Laguna's capture switch is VT_LAGUNA_DECODE_GRAPH at :1684, not the KV cluster at :104,116,140, none of which is a capture switch; PadToCaptureSize has six call sites and the diagnostics pair six definitions. The pattern was that the Qwen3.5 dense driver was dropped from three of four enumerations though W4 migrates it. StepDevInputs is on 41 lines and occurs 47 times, not the 33 recorded, which is neither number; the four zeros reproduce under both instruments and the conclusion survives.
  • D6 asserted what W0 said was not our measurement. It claimed the CUDA mid-forward re-begin "holds" and is "cheap" while ## Work breakdown W0 called the same fact strong evidence but not ours. D6 now defers to W1's exit criterion in W0's own words, drops the unmeasured cost claim, and names the A/B that would settle it.
  • Anchor drift fixed (all landed inside the right construct, none exact); the upstream path spelled once in full instead of abbreviated in a table header; Risks/decisions reordered to run D1 through D10.

Rides with it: #1179

The hand-rolled driver count recorded in 9bc4d7f44 is eight and is actually nine. The ninth is the DFlash draft graph (qwen3_dflash.cpp:771,870,1038,1091,1095,1106), file-local with no header declaration, which is why an enumeration built from the model headers could not see it. #1179 named four locations; the sweep across all of .agents/ found a fifth (sglang-matrix.md:149) and three more inside eng-cudagraph-break.md itself. All eight corrected.

.agents/issue-index.md:354 (#1162) and :355 (#1163) also still say eight and are deliberately not edited — that file is append-only and carries merge=union, so an edited row is duplicated rather than merged. The appended #1179 row is the correction and names the DEDUP row's "times eight drivers" sizing explicitly.

The reframing is the substantive half, and it does not weaken the framing rule: the row still makes no throughput claim.

Verification

./scripts/agent-preflight.shAll gates green. (79 checks ok, 0 red, exit 0). check-agent-record OK, check-commit-trailers OK, check-commit-style OK against origin/main 84b9660c0. Box load 0.86, so test_cpu_x86_llamacpp_floor is a real green and not #618's load-dependent flake.

Negative mutation, both restored byte-for-byte and the branch SHA unchanged throughout. The append-only gate reads merge_base..HEAD and never the working tree, so the mutation was built as a detached scratch commit rather than an unstaged edit — an unstaged mutation reads as a passing test against this checker. Editing an existing index row: baseline rc=0 → mutation rc=1, FAIL: .agents/issue-index.md is append-only, and this range removes or edits lines. Breaking the matrix row's spec link: check-agent-record rc=0rc=1 on five structured-section errors.

Not verified: every anchor in this spec is a static read of the pinned trees; nothing here was executed on a GPU, and W1's exit criterion — that CUDA permits cudaStreamEndCapture followed by cudaStreamBeginCapture mid-forward on our stream configuration — remains unmeasured by design and needs an rc lease.

Closes #1179.
Refs #1163, #1020, #1162, #1164.

FOLLOWING_AGENTS_PROTOCOL

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

mudler added 2 commits August 18, 2026 00:56
…the nine hand-rolled drivers each re-derive one

Scoping spike plus the structured spec for #1163, and no production code. The row
record says "large and structural, spike first", so this commit lands the design
and the inventory and nothing else.

Two defects, both re-grounded against the tree rather than restated. Capture is
all or nothing: `src/vllm/v1/worker/gpu/runner.cpp:1341` admits a step only when
`num_actual_tokens == num_reqs` and there is no prefill, and a step that fails
that predicate runs eager in full. There is no way to spell "capture this forward
except at these three calls", so coverage is a cliff and not a slope, and #1020 is
one reachable instance.

The driver count was EIGHT and is NINE. `src/vllm/model_executor/models/qwen3_dflash.cpp:771,1091`
already wrote the ninth, which is the outcome the issue predicted would happen next.
Seven pieces of machinery are re-derived across them, each cited at two sites in the
spec, and the re-derivation has a measured cost rather than an aesthetic one: the
persistent DEVICE input path `StepDevInputs` (`qwen3_5.cpp:3894`) exists in exactly
one driver, `grep -c` returns 0 in the other four batched drivers, and
`qwen3.cpp:961-986` therefore DECLINES the graph outright when the async
device-token mirror is live, with the measured degeneration recorded in its own
comment. One capability, written once, unavailable to four models.

The boundary comes from vLLM and the construction comes from SGLang, and the spec
keeps those apart. vLLM's `_attention_ops` (`config/compilation.py:764-772` at pin
`5559679229`) is the split set and `FULL_AND_PIECEWISE` is the v1 default, so the
capability is already in our denominator; but vLLM gets the split from
`torch.compile` and we have no compiler. SGLang reaches the same coverage with none
(`breakable_cuda_graph.py:204-241,246-263` at pin `f63458b5be`), so the mechanism is
ported from there.

The spike established that the existing `vt` vocabulary already expresses a
segmented capture with NO new backend virtual, because `EndCaptureGraph` stores
nothing (`src/vt/cuda/cuda_backend.cu:225-232`), and that a break point is
expressible with one `thread_local` capture pointer plus a free function. A
throwaway proof-of-concept, deliberately not committed, produced 3 segments and 2
break functions from a 2-layer forward whose break site sits three frames below the
entry, replayed in a stable order twice, and made zero backend calls when no capture
scope was active. It did NOT establish that CUDA permits re-beginning capture on the
same stream mid-forward on our stream configuration; that needs a GPU, this box has
none, and it is named as W1's exit criterion rather than skipped.

Row moved `INVENTORIED` to `READY`, not `SPIKE`: the nine sections are committed and
no claim is active, so no `CLAIM-*` owner would be honest. Summary counts follow.

This is a COVERAGE row and the spec says so in three places. Prefill idle between
launches measured 3.8% with GPU-busy above 96%, the 27B prefill gap is 92.5%
non-GEMM glue GPU work, and decode already banked its launch-overhead win, so no
throughput gate is declared and `## Gates` states the two conditions any later speed
claim has to satisfy first.

FOLLOWING_AGENTS_PROTOCOL

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

`origin/main` moved from the pinned base `9bc4d7f4` to `469f38395` while this
spike was in progress, and `scripts/agent-preflight.sh` SKIPPED both commit gates
for that reason: "origin/main is not an ancestor of HEAD, so this branch is behind
it and the trailer gates did NOT run. Neither gate reported anything about this
tree." A skipped gate is not a passing gate, so the merge is what makes the gates
report.

No conflict, and no overlap: main added three specs, a kernel-matrix row, an
issue-index append and the block-wise FP8 refusal, and none of those files is one
this branch edits. `.agents/engine-matrix.md` and
`.agents/specs/eng-cudagraph-break.md` come through untouched, `ENGINE_ROWS` stays
160, and the record checker is green on the merged tree.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [Claude Code]
mudler added 3 commits August 18, 2026 01:30
…eport

The branch base fell behind origin/main, and check-commit-style.py --range
origin/main..HEAD refuses when the base is not an ancestor of the head.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [Claude Code]
…ist is 305 lines, and the primitive it described drops the output writeback (#1163, #1179)

A fresh review returned FAIL on the spec-only change. Two findings were
load-bearing and both were verified against the pinned SGLang tree at
f63458b5be before repair.

`## Tests to port` asserted that "SGLang's BCG carries no unit test at the
pinned revision that exercises the segment container in isolation". That file
is test/registered/cuda_graph/breakable/test_breakable_cuda_graph.py, 305
lines, and it carries eleven unit cases across TestBreakableCUDAGraphBasic
(:30), TestCopyOutput (:172) and TestBreakGraphHelper (:230). A section that
asserts there is nothing to port deletes the obligation the section exists
for. Every case is now mapped to the local test that owes it, with the one
harness adaptation stated once: upstream skips without CUDA, and ten of the
eleven run here against a recording backend. test_gsm8k_accuracy is
deliberately excluded, with the reason, because it is a distributional
accuracy floor on a prefill path and G1's bit-exactness is strictly stronger.

`## Port map` §3 said eager_on_graph "appends `fn` to break_fns". It does not.
It appends a replay_fn closure (:231-235) that calls the captured inner
function and then writes the result back into the capture-time destination
through _copy_output (:172-201), with _weak_ref_if_tensor (:156-169) keeping
that destination alive. The writeback is the guarantee, not sugar: on replay N
the eager op returns a fresh allocation whose address is not the one the next
segment baked, so a container replaying the raw fn leaves segment i+1 reading
capture-time data while the break function writes elsewhere. That is wrong
numerics rather than a fault, the same detection class D1 records
compute-sanitizer cannot see. The contract is now stated at the GraphBreak
declaration, pinned by the ported TestCopyOutput cases, and recorded as D9.

Third, the auxiliary-stream fork was absent everywhere. Upstream auto-joins
side streams before closing a segment (:353-361) and maintains the whole
wait_stream hook (:101-153, installed :310, removed :332) for that alone. It
is live here: SupportsAuxStream() is true on CUDA (cuda_backend.cu:200) and
both drivers this row migrates in W4 and W5 fork on it, at qwen3_5.cpp:6254
and laguna.cpp:2572. Stated on GraphCaptureScope, gated by a new test, and
recorded as D10.

Four enumerations in `## Our baseline` and one grep count were re-derived with
printed instruments and corrected. Six drivers read VLLM_CPP_CUDAGRAPH, not
five; the Qwen3.5 dense getenv is :9969, not :9974; Laguna's capture switch is
VT_LAGUNA_DECODE_GRAPH at :1684, not the KV cluster at :104,116,140, none of
which is a capture switch; PadToCaptureSize has six call sites and the
diagnostics pair six definitions. The pattern was that the Qwen3.5 dense
driver was dropped from three of four enumerations though W4 migrates it.
StepDevInputs is on 41 lines of qwen3_5.cpp and occurs 47 times, not the 33
the spec recorded, which is neither number; the four zeros reproduce under
both instruments and the conclusion survives.

D6 asserted the CUDA mid-forward re-begin "holds" and is "cheap" while W0 said
the same fact is not our measurement. D6 now defers to W1's exit criterion in
W0's own words, drops the unmeasured cost claim, and names the A/B that would
settle it. Anchors that landed inside the right construct but not on it are
now exact, the D-entries run D1 through D10 in order, and the upstream path is
spelled once in full rather than abbreviated in a table header.

Riding with it, #1179: the driver count recorded in 9bc4d7f is eight and is
actually nine, corrected in all four places that state it. The DFlash draft
graph (qwen3_dflash.cpp:771,870,1038,1091,1095,1106) is file-local with no
header declaration, which is why it was missed. The substantive half is the
reframing. ENG-CUDAGRAPH-BREAK was recorded as a coverage row and it is also a
correctness row: qwen3.cpp:961-986 declines its decode graph outright while
the async device-token mirror is live, on its own measured battery, because
the fix its comment names lives in one sibling driver as StepDevInputs and
nowhere else. A shipped model has already lost its decode graph to this
duplication. The framing rule is unchanged: coverage and correctness, never
speed, and the prefill refutation stands.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [Claude Code]
…pec I had just corrected said it three more times (#1179)

The #1179 sweep named four locations. It missed `.agents/sglang-matrix.md:149`,
where the `SGLANG-BCG` row's local-mapping column enumerates the drivers by
name and stopped at `laguna.cpp`. Re-running the sweep across all of
`.agents/` then found three more inside `eng-cudagraph-break.md` itself: the
`## Our baseline` heading sentence, the cost sentence that closes the
divergence paragraph, and the W0 inventory bullet. All four now read nine,
each naming the DFlash draft graph
(`src/vllm/model_executor/models/qwen3_dflash.cpp:771,870,1038,1091,1095,1106`)
and why it was missed: it is file-local with no header declaration, so an
enumeration built from the model headers cannot see it.

`.agents/issue-index.md:354` (#1162) and `:355` (#1163) also still say eight
and are deliberately NOT edited. That file is append-only and carries
`merge=union`, so an edited row is duplicated rather than merged. The #1179
row appended at `:361` is the correction, and it names the DEDUP row's "times
eight drivers" sizing explicitly so a reader of either row reaches it.

Instrument, run from the worktree after the edits:
`grep -rniE '\beight\b' .agents/ | grep -viE 'completed/' | grep -iE
'driver|hand-rolled|capture|seam|cudagraph'`. Every surviving hit in the
capture domain is either one of my own corrective clauses ("`9bc4d7f44`
recorded eight") or one of the two append-only index rows above.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [Claude Code]
@localai-bot
localai-bot merged commit ab6e652 into main Aug 18, 2026
11 of 19 checks passed
@localai-bot
localai-bot deleted the row/ENG-CUDAGRAPH-BREAK branch August 18, 2026 01:52
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.

The decode-graph driver count recorded in 9bc4d7f44 is eight; it is nine, and the ninth predates the record

2 participants