feat(ENG-CUDAGRAPH-DEDUP): fold captures that share a topology onto one graph executable - #1178
Merged
Conversation
… per bucket We instantiate a fresh `cudaGraphExec` for every capture and throw the raw graph away (`src/vt/cuda/cuda_backend.cu:222-232`), so the executables multiply along two axes: 7 padded decode buckets at `max_num_seqs=32` or 11 at 64, times eight hand-rolled drivers. `grep -rn "cudaGraphExecUpdate" src include` returns nothing. This spec lands before its implementation, as the protocol requires. It ports the construction from SGLang's dedup mixin at the pinned `f63458b5be`, because vLLM gets its executables from `torch.compile` and therefore has no analogue to mirror, and it records the three places where our seam forces a divergence. The load-bearing one: SGLang holds a second probe executable per group for the whole capture phase and frees it in `seal()`, but our drivers capture lazily and interleaved with replay, so there is no capture-phase end and a persistent probe would hold two executables per group forever — worse than the baseline this row exists to reduce. The probe is therefore transient, and a probe the driver rejects falls back to a private executable instead of aborting, which is what makes an under-specified signature safe rather than wrong. The row moves `INVENTORIED` to `ACTIVE`, which owes `docs/STATUS.md`, `docs/BENCHMARKS.md` and the spec's own `## Now`. The benchmark entry is recorded `PENDING` rather than left absent: the device byte-identity A/B needs a leased CUDA box, and this session has none. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…ne graph executable Every capture used to instantiate its own executable and throw the raw graph away, so a model held one executable per padded decode bucket — 7 at `max_num_seqs=32`, 11 at 64 — and eight capture drivers each held their own set. The decode graphs of two padded batch sizes are usually the same node topology with different parameters, which is the case `cudaGraphExecUpdate` exists for. `src/vt/graph_dedup.h` is the registry: it keys captures by a structural signature and re-points one executable instead of instantiating a second. `src/vt/graph_dedup_runtime.h` binds it to CUDA and to HIP from one source, because the two graph APIs differ only by symbol prefix and by two call shapes, and a second hand-written copy is the parallel path the protocol forbids. Both accelerator backends route `EndCaptureGraph`, `ReplayGraph` and `DestroyGraph` through it, dispatching on `Owns()` rather than a mode flag so a handle minted before the registry existed is still replayed as the plain executable it is. The signature is a lookup key and never an authority. Every candidate fold is probed with the real driver update on a throwaway executable first, and a probe the driver refuses gives that capture its own executable, so a signature that is too coarse costs a wasted probe and can never make a replay wrong. That is also why the probe is transient where SGLang's is persistent: its backend seals the capture phase and frees the probes, and ours has no capture phase to seal, so a persistent probe would hold two executables per group forever. Default OFF behind `VT_CUDA_GRAPH_DEDUP`, and it stays off until measured. A workload that alternates padded buckets every step pays one update per switch, and the device byte-identity A/B that would price that needs a leased CUDA box this session does not have. It is owed under the spec's `## Owed`, together with the ROCm compile: no ROCm hardware or `hipcc` is reachable here and CI has no ROCm job, so the HIP leg is written against the shared header but unverified. Gated RED-first: `tests/vt/test_graph_dedup.cpp` was written and run against an absent header, then 10 cases / 43 assertions green. The suite drives the registry through a fake device whose launch call records which graph the executable actually reflects, so "the right nodes ran" is an observable sequence rather than an assertion about intent, and every case replays more than once per shape — the risk of a shared executable is that the second visit to a shape is the one that has to re-point it. One case had to be added after the fact: the original sibling-destroy case could not see a capture landing on a freed graph's address, so the fake now hands that storage back and the case reuses it deliberately. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
… just entered it `ENG-CUDAGRAPH-DEDUP` reached `ACTIVE` with a spec whose `## Gates` section names commands that can actually fail, so the row entered the runnable gated population and `test_check_gate_commands` went red on the equality that makes the ratchet work in both directions. Growth, re-pinned in the same change and named the way the checker's own message demands. The credit is not inherited: `ctest -R test_graph_dedup` detected 9 of 9 negative mutations of the registry the row adds, which is the strongest kind of credit in this set rather than the weakest. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…erlapped Three commits arrived while this branch was in flight: the block-wise FP8 load refusal (#1172) and two GDN specs (#1173, #1174). None of them touches the vt graph seam, the capture path, or the `ENG-CUDAGRAPH-DEDUP` row, and the only keyed records they move are `.agents/issue-index.md` (append-only) and a kernel matrix row this branch never wrote, so the automatic merge was verified rather than trusted: the engine matrix still carries this row and its summary counts, and `check-agent-record.py` re-runs clean at 160 engine rows. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…ts own mutation `check-pr-size` refuses a change to a governance checker that arrives without semantic mutation evidence in the paired suite, and it is right to: a re-pin that only added a row to a frozenset would be indistinguishable from one that quieted the gate. Two cases, mirroring what `SPEC-MTP-K-GT-1` and `SERVE-RECIPE-ARGS` already carry. The first pins both halves of the credit -- the row is in the exact pin, AND its `## Gates` section really does yield a command that can fail -- and asserts on `ctest -R test_graph_dedup` rather than on the `git diff --stat` the extractor also finds, because that one exits 0 unconditionally in a repo and is one of the weak credits this checker's own header names. It also asserts the spec still says the device leg is owed, so the credit cannot quietly come to rest on the CPU tier while the record goes silent about the arm nobody ran. The second removes the entry and proves the set equality goes red. Verified by mutation: deleting `ENG-CUDAGRAPH-DEDUP` from `RUNNABLE_BASELINE` fails both new cases (7 of 39 in total), and the file restores byte-for-byte. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…erlapped Two more commits arrived while this branch was in flight: the `--speculative-config` key drop (#1175) and the inert `--gpu-memory-utilization` budget (#1176). Neither touches the vt graph seam, the capture path, or the `ENG-CUDAGRAPH-DEDUP` row, and the keyed records they move are `.agents/issue-index.md` (append-only) and rows this branch never wrote, so the automatic merge was verified rather than trusted: the engine matrix still carries this row and its summary counts, and `check-agent-record.py` re-runs clean. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…h changed at CUDA 13 `cuda-fat-build` on `nvidia/cuda:13.3.0` failed with four errors at `graph_dedup_runtime.h:161,168`: CUDA 13's `cudaGraphGetEdges` takes a fifth `cudaGraphEdgeData*` parameter, where CUDA 12 and HIP take four. The local syntax check could not have caught this and did not. It ran against CUDA 12.9 runtime headers and passed, which is the honest limit of a proxy: a header-shape check is only as current as the toolkit it ran against. Recorded in the spec's `## Risks/decisions` beside the `cudaGraphExecUpdate` split, which was foreseen, so the difference between the two is on the record. Both shapes are bound in one `GetEdges` helper rather than at the two call sites, so the topology walk stays one piece of code. The CUDA 13 arm fills a real edge data buffer instead of passing null, because null edge data on the filling call is not a shape the documentation promises; the data is discarded, since an edge annotation is not part of the identity this key needs. Verified locally against CUDA 12.9's `cudaGraphGetEdges_v2`, whose signature is identical to CUDA 13's: both arms compile clean under `-Wall -Wextra -Werror`. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…#1179's corrected driver count `origin/main` moved `ENG-CUDAGRAPH-BREAK` to `READY` and corrected the hand-rolled decode-driver count from eight to nine (#1179: `9bc4d7f44` missed the DFlash draft graph). This branch moved `ENG-CUDAGRAPH-DEDUP` to `ACTIVE`. Both edits land on the same three lines of `.agents/engine-matrix.md`, so the automatic merge conflicted. Resolved by taking both row sets rather than either side: the `BREAK` row is byte-identical to `origin/main`, the `DEDUP` row keeps this branch's `ACTIVE` content with the corrected NINE-driver wording carried onto its baseline field, and the two summary counters compose both moves (`READY` 3 to 4, `ACTIVE` 6 to 7, `INVENTORIED` 7 to 5 for the area; `READY` 12, `ACTIVE` 33, `INVENTORIED` 38 in the total). Every other line of the file is byte-identical to both parents, verified by diffing the resolved file against each parent's version. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…apture happened, not at the first replay Repairs the findings of the fresh review of #1178. Issue #1162. `Register` assigned `ops_.instantiate(raw_graph)` and moved on, while `graph_dedup_runtime.h` returns null on a driver failure. The pre-dedup path wrapped `cudaGraphInstantiate` in `Check()` and threw at the capture site with the driver's code; with dedup on the same failure minted a valid-looking handle over a null executable, counted it live in `ExecCount()`, and surfaced at the first replay as "graph launch failed" -- the wrong site, the wrong message, and the driver's reason already discarded. `Register` now fails there, releasing the capture whose ownership it had taken so the throw does not also leak it. The probe instantiates a SECOND executable, so it can fail independently, and `cudaGraphExecUpdate` has no defined behaviour for a null one. `ProbeAccepts` now answers "cannot fold" instead of asking the driver about nothing, which degrades to exactly today's one-executable-per-capture path. Two guarantees were unpinned and are now tests. A capture the driver cannot instantiate must throw and count nothing; a probe that cannot instantiate must not drive a null executable. Both were run RED against the unfixed source (6 failing assertions) before the fix. The environment polarity gained the values only the terminator check rejects: deleting `&& value[1] == '\0'` left the whole suite green, so "10" enabled a default-off correctness-sensitive path undetected. 12/12 cases, 61 assertions; three negative mutations applied, detected and byte-restored. Three findings are records rather than code, because fixing them would change behaviour a device A/B is measuring on this commit or is not code at all. `Replay` does not issue the fold `Register` probed: the probe tests `(raws.front(), raw_graph)` and the replay issues `(current_raw, entry.raw)`, which diverge from a group's third member onwards. Honouring the probe therefore treats `cudaGraphExecUpdate` compatibility as transitive across a group, which no documentation states and nothing here asserts. The comment claiming the fold was already probed is corrected to say what is actually assumed; the stronger fix -- probe `current_raw` -- is owed rather than taken, because it changes probe behaviour mid-measurement. `src/vt/graph_dedup_runtime.h` has zero executable coverage on any tier. The mutation count is a statement about `graph_dedup.h` and must not be read as covering the signature builder, whose one silent mode is a signature unstable for some topology: it folds nothing while every CPU test stays green. Recorded in the spec and in the matrix row, with the two device-tier tests that would cover it named and owed. The registry lives on a process-singleton per-device backend, so one unsynchronised registry serves every model on a device. That is wider than the "one runner thread" note it carried, and it is now stated where a future capture-widening row will read it. Also merged up: the spec's `Stats()` never existed and is corrected to the shipped `CapturedCount()` / `ExecCount()`, and the decode-driver count moves from eight to nine here too, after #1179 corrected it on main. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…unt correction broke The nine-driver correction was spliced into a wrapped comment paragraph and left one 129-column line in a file wrapped at 88. Reflowed the paragraph; no other change. The signature note also said "described there" of a call site 150 lines below it and then restated its own conclusion, so it now points at the call site and says the thing once. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…ty note now rests on The previous commit corrected the Replay comment to say that honouring Register's probe assumes cudaGraphExecUpdate compatibility is transitive across a group's members, and argued the assumption is survivable because a refusal lands on a VT_CHECK rather than on a silent launch of the executable's previous contents. Nothing gated that argument, and an argument for why a defect is tolerable is exactly the kind of claim that has to be executable. The fake driver can now refuse a (currently-reflected, target) PAIR, which is the shape the target-only rejection set cannot express and the shape that matters. Refusing exactly the pair Register never asks about reproduces the case: all three probes succeed, the group folds to one executable, and the second replay is where the assumption is tested for real. The case asserts the throw AND that the launch log is unchanged. Mutation, detected: replacing the VT_CHECK with `(void)ok` makes the replay launch graph 2's nodes under graph 3's handle, which is the silent wrong answer the check exists to prevent, and fails three assertions. Non-empty `git diff --stat`, clean compile, byte-for-byte restore. 13/13 cases, 65 assertions. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
… counts both sides moved Both sides added to the same keyed records, so none of the three conflicts is a disagreement. `.agents/engine-matrix.md` takes main's KV-cache row and its new total row count, then re-applies this branch's own lifecycle move (ENG-CUDAGRAPH-DEDUP INVENTORIED -> ACTIVE) on top: Engine and scheduling ACTIVE 6 -> 7 and INVENTORIED 6 -> 5, and the totals become 161 rows with ACTIVE 34 and INVENTORIED 38. Every column and every row of the table sums. `docs/BENCHMARKS.md` and `scripts/check-gate-commands.py` each keep BOTH entries, in date order, because a second row arriving does not retire the first. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
… consume the latched error, and this one never did (#1184, #1162) `VT_CUDA_GRAPH_DEDUP=1` could not complete one decode step once a graph was actually captured. 6/6 deterministic on GB10: vt graph dedup: captured 1 graphs, deduped to 1 execs [Qwen3DenseDecodeGraph] captured dense decode graph for padded size S=8 engine-fatal: EngineCore busy loop threw: vt cuda: greedy_argmax launch: invalid device function from a `greedy_argmax` launch that had SUCCEEDED. The OFF and `=0` arms were clean and byte-identical across 7 runs on the same binary and libraries, which is what makes the asymmetry arm-attributable. `greedy_argmax` was never at fault. The whole safety argument for this row is that the signature is a lookup key and `cudaGraphExecUpdate` is the authority, so a probe the driver REFUSES is normal operation, not an exception; the topology walk has five more escapes of the same kind. A CUDA call that fails also latches its code in the runtime's sticky per-thread slot, and none of the twelve fallible calls in `graph_dedup_runtime.h` consumed it -- `grep -n cudaGetLastError` on that file returned nothing. The next unrelated kernel, launched with the ordinary `kernel<<<>>>(); Check(cudaGetLastError())` pattern, then read our routine refusal and reported it as its own failure. Every symptom follows from that and none from the kernel named: it needs BOTH `dedup=1` and a real capture, `CUDA_LAUNCH_BLOCKING=1` does not move it because the latch is host-side and synchronous rather than a deferred async error, and `cudaGraphLaunch` returns success because a return value does not consume the latch. The fix is one type, not twelve clears. Twelve hand-placed `cudaGetLastError()` calls are a fix the thirteenth fallible call silently misses, and a file whose design is "these calls are allowed to fail" will grow a thirteenth. The clear lives in `ScopedLatchClear`'s destructor and is installed at the binding's ENTRY POINTS, which are exactly the six `GraphDedupOps` members the backends reach through `Ops()`. Every exit runs it: a plain return, a degradation escape, and the `VT_CHECK` unwinding out of `Launch`. `MakeLatchGuardedOps` is the table's only constructor and takes the raw functions as template arguments, so no raw address reaches a field; a seventh operation wired anywhere else leaves its field null and `GraphDedupRegistry` refuses an incomplete table. One line covers both arms because there is one source: `VTGD_FN(GetLastError)` resolves to `cudaGetLastError` or `hipGetLastError`. The fold and probe DECISION logic is untouched. This is error-state hygiene. MEDIUM-3, the coverage gap that hid this for a whole review cycle, closes in the same change. `graph_dedup_runtime.h` was reached by no test on any tier, so its Kahn ordering, topological re-index, sorted edge emission, depth-4 child bound and degradation escapes were compile-gated only. The device-free half moves to `src/vt/graph_dedup_signature.h` behind an `Rt` policy and is driven by a fake runtime in the new `tests/vt/test_graph_dedup_runtime.cpp`: 13 cases, 51 assertions, RED-first against the pre-fix guard, where the suite reports 22 failed assertions including the production message reproduced from the mechanism alone. 7/7 negative mutations detected, each with its `git diff --stat`, compile status and exit status; one first attempt failed to build under `-Werror` and was re-run in a compiling form, because a mutation that fails to build reads as a passing test. BE HONEST ABOUT THE LIMIT. A CPU test drives a FAKE runtime. It cannot observe the CUDA runtime's real latched-error state, so it proves the guard's structure and not that #1184 is gone on a device. The device A/B re-run stays owed under #1184, and the five node-payload cases stay device-only. The spec also now records what the device gate found: the shipped async serving path captures no decode graph at all (the #323 mitigation in `DenseDecodeGraphForward`), so dedup engages only under `VT_ASYNC_RUNNER=0`, which materially bounds this row's worth and is owned by #1179. No local CUDA toolkit was reachable. A scratch header-shape stub instantiated `Ops()` at CUDART_VERSION 12090 and 13030 and on the HIP arm under `-Werror`, all three clean, with a negative control proving the instrument can fail. That is a proxy; `cuda-fat-build` remains the gate, exactly as it was for the CUDA 13 `cudaGraphGetEdges` break this file already took. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
Takes the W1 break-point seam (#1192) and the rest of main so the range gates can report and the PR is mergeable. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] # Conflicts: # .agents/engine-matrix.md
localai-bot
added a commit
that referenced
this pull request
Aug 18, 2026
…tioning builder that has no frame-0 branch (#1096) (#1209) Closes #1096. Closes #1191. `pipeline_kind = keyframe_interpolation` resolves `KeyframeInterpolationPipeline` (`keyframe_interpolation.py:55` @ `fd4ded7f`) on all four generations the table keys. Asking for it used to get the generic table refusal. It was the last unported LTX-2.5 pipeline that is neither hardware- nor artifact-blocked. Structurally it is `ti2vid_two_stage`: the same parser, the same guided half-res stage 1 on the UNADAPTED model, the same Euler stepper (derived — neither `self.stage_1(...)` at `:231` nor `self.stage_2(...)` at `:271` passes one, so `utils/blocks.py:524-527` applies), the same frozen three-sigma stage 2, and the same `schedule_tokens = kSchedulerDefault` because `:200` calls `execute(steps=...)` with no latent. **Two fields differ, and both of them render either way.** ## The conditioning builder, which is what this pipeline is named after `:211` and `:260` call `image_conditionings_by_adding_guiding_latent` (`helpers.py:343-367`). Every other pipeline calls `combined_image_conditionings` (`:272-308`). The two differ by one branch: | | `combined_image_conditionings` | `..._by_adding_guiding_latent` | |---|---|---| | `frame_idx == 0` | `VideoConditionByLatentIndex` (`:295-300`) | `VideoConditionByKeyframeIndex` | | any other `frame_idx` | `VideoConditionByKeyframeIndex` (`:301-305`) | `VideoConditionByKeyframeIndex` | | what frame 0 does | REPLACES latent frame 0's clean tokens; the count never moves (`latent_cond.py:38-39`) | APPENDS a latent frame of tokens (`keyframe_cond.py:79-82`) | The second has no branch at all. The first image is a keyframe the model interpolates **from**, not a frame it overwrites. This engine hard-coded the other arm, and both conditioning primitives were already ported and gated — what was missing is the SELECTION, now `Ltx2PipelineRecipe::image_conditioning`, defaulting to today's behaviour so nothing landed moves, read in exactly one place. **Nothing about a render can see this.** The wrong builder returns a clip of the right size, the right frame count and the right sample rate with the image visibly present. It is conditioned; it is conditioned as a different pipeline. The only observable is the sequence length the DiT ran over, and the gate renders the same image at the same geometry on both kinds plus a **bare** control that pins the target grid — without which the comparison passes on a tree where both arms append. Measured: `video_tokens` 12 on `keyframe_interpolation` against 8 on `ti2vid_two_stage` and 8 bare, with `image_tokens` 4 on both. ## `audio_output_phase` is 1, and on `ti2vid_two_stage` it is 0 `:271` binds `video_state, audio_state = self.stage_2(...)` and `:293` decodes that name. `ti2vid_two_stages.py:289` binds `video_state, _` under its own comment at `:287-288`, "Stage 2 refines video only; discard its audio", and decodes the name `:247` bound. Neither file argues the point, so the binding is the statement — and copying the neighbour ships a soundtrack one refinement stage stale, at the right length and the right sample rate. The recipe case asserts both polarities beside `a2vid_two_stage`'s 1 and `res2s_two_stage`'s 0, so it cannot pass because every two-stage recipe happens to agree. ## Two of #1096's three blockers were stale, and the real difference was in none `.agents/issue-index.md:321` named a multi-keyframe request surface, a per-sigma guided denoiser, and two missing checkpoints. All three were re-derived at the pin rather than inherited: - **The per-sigma denoiser is stale on this pipeline's default path.** `main()` passes plain `MultiModalGuiderParams` (`:325-340`), never a factory, so `create_multimodal_guider_factory` takes its last line, `MultiModalGuiderFactory.constant` (`guiders.py:360`), which builds `_params_by_sigma = ((inf, params),)` (`:312-315`) — ONE bin, so `build_from_sigma` returns the same guider at every sigma and `FactoryGuidedDenoiser` delegates to `_guided_denoise`, which is `Ltx2GuidedDenoise` (landed `daeff67f2`). The sigma-BINNED arm, `MultiModalGuiderFactory.from_dict`, is real, is reachable only by a caller who constructs a factory, and is now #1187. - **Both checkpoints are on the NAS**, byte-verified, and #1148 closed the pure-BF16 DiT refusal at `40a796aa9`. What is owed is the RUN. - **The multi-keyframe surface is true and is not the blocker.** Two pinned keyframes at the two ends is what interpolation means at its default configuration. An interior `frame_idx` is #1187. ## `--last-frame` on `ltx2-gen` (#1191, in flow) `vllm_video_params` has carried `last_frame` and the engine has served it since #930; `ltx2-gen` parsed `--first-frame` and never read the field. That only starts to bite on a pipeline whose whole job is the motion between two pinned frames, so this row is the first caller it narrows. One flag, parsed and assigned beside `--first-frame`, sharing the `--image-crf` and strength the two slots already share. ## The gate Whole binaries, never a `--test-case` filter. Focused, at this head: | Binary | cases | assertions | exit | |---|---|---|---| | `test_ltx2_pipeline` | 56 | 3316 | 0 | | `test_ltx2_video` | 87 | 2713 | 0 | RED before the recipe landed, on the same binaries: `test_ltx2_pipeline` 56 / 54 passed / 3183 assertions, `Status: FAILURE!`, exit 1, both new cases throwing `Unsupported LTX pipeline kind/version: 'keyframe_interpolation'/'2.5'`; `test_ltx2_video` 87 / 83 passed / 2593, exit 1. Full gate at `251f76b5e`, after merging `origin/main`: `CONFIGURE_EXIT=0`, `BUILD_EXIT=0`, `: error:` count 0, `ctest -N` **515**, `CTEST_EXIT=0`, `100% tests passed, 0 tests failed out of 515`. `No space left` and `BFD` are each 0 in the build and ctest logs, against injected controls that returned 1. Load average 50 to 76 across the run and 24 GiB free; none of the four load-dependent gates (#618, #294, #1052, #428) went red at that load. The same gate ran green at `1d880bcc5` before the merge, at 511 registered tests. The `READER ANCHORS` instrument in `ltx2_video.cpp` was ARMED rather than assumed correct: inserting one line above `kKnownLoadExtras` shifted every derived anchor by one and the gate went RED naming both lists. On the real tree recorded and derived agree at `[823 833 834 896 992 1008 1056 1147 1172 1277 1318 1360 1362]`, because this row's edits are below the last anchor. The tree was restored byte-for-byte after every mutation, with `os.utime` and a confirmed ninja recompile each time; `git status` is clean at the pushed head. ### Nine mutations, each printing four facts `git diff --stat`, BUILT, the `: error:` count, and the exit code captured directly — with the expected OLD line content asserted unique before every edit. | # | mutation | diff | built | errors | result | |---|---|---|---|---|---| | M1 | `recipe.image_conditioning` assignment deleted | 1 deletion | YES | 0 | video rc=1 (1 case), pipeline rc=1 (2 cases) | | M2 | reader forced to the REPLACE arm | 1+/2- | YES | 0 | video rc=1 (1 case) | | M3 | `audio_output_phase` 1 -> 0 | 1+/1- | YES | 0 | pipeline rc=1 (2 cases) | | M4 | `stage1.schedule_tokens` deleted | 1 deletion | YES | 0 | video rc=1, pipeline rc=1 | | M5 | `stage1.loras = kNoAdapters` deleted | 1 deletion | YES | 0 | pipeline rc=1 | | M6 | `requires_distilled_lora` deleted | 1 deletion | YES | 0 | video rc=1, pipeline rc=1 | | M7 | the dispatch key made UNREACHABLE | 1+/1- | YES | 0 | **all six new cases RED**, by name | | M8 | the COND arm left in velocity space | 3+/1- | YES | 0 | video rc=1, 7 cases / 13 assertions | | M9 | the anchor case's `gen.steps` 3 -> 2 | 1+/1- | YES | 0 | video rc=1 (2 assertions) | **M7 was run twice and the first run is recorded rather than dropped.** Deleting the whole dispatch arm left `KeyframeInterpolationRecipe` unreferenced and `-Werror` killed the build — `BUILT=NO`, 1 error, no test result at all, which is exactly the shape that reads as a passing test. Renaming the dispatch key keeps every line compiled and referenced while making the recipe unselectable through the request surface, which is what the reachability mutation is there to measure. M9 is the mutation `ltx25-ti2vid-recipe.md`'s first head passed. The step count comes back OUT of the render lambda and the trajectory recomputation runs at it, with `rendered_steps > 2` asserted by name, because at two steps `stretch` pins both non-zero sigmas and the schedule is `{1, 0.1, 0}` for every token count. Measured here: `keyframe: 4096 / 4096 res2s: 2 / 8`. ## What is NOT verified **No real-weights render.** Upstream marks this arm `Full + distilled LoRA` (`packages/ltx-pipelines/CLAUDE.md:24`), so stage 1's identity is CFG on the UNADAPTED model and the checkpoint it needs is `ltx-2.5-22b-dev-transformer-bf16.safetensors`. It is on the NAS and loadable. What is owed is a GPU lease and the two renders; another agent holds `dgx:gpu0` and no GPU work is in this row's scope. Running the arm against a **distilled** checkpoint instead would be worse than not running it — the distilled scales are trained into those weights, so a CFG-guided stage 1 samples a trajectory they were never trained for and renders a plausible clip with no diagnostic (#1137). The reach claim rests on the ABI path — `LoadVideoEngine` + `Generate`, which is what `ltx2-gen` drives. #928 does not exclude the HTTP route here, because all three knobs are LOAD extras and `requires_audio_input` is false; that is a claim about the request surface, and no case here drives HTTP end to end. ## Review repair A fresh review of `251f76b5e` returned eight findings. Six are repaired in `0e4357707`; the other two are recorded rather than silently absorbed. The blocking one was a bug this row introduced two hundred lines from the code that carries it. The last-frame arm located its own appended tokens at `positions[target_tokens * 2]`, the first token past the fixed target grid -- which is its own token only while this arm owns the first append. This row put a second appending item in front of it, so with both ends pinned the index named the FIRST frame's keyframe at temporal 0 and the arm threw. That is `docs/USAGE.md`'s worked example for this kind and what `ltx2-gen --help` tells the reader to do, so the documented headline command did not run. Neither assertion is weakened. Each arm now captures the sequence length at the moment of its own append and locates its tokens from that, so no arm depends on being first. The generated-keyframe-slot arm carried the same derivation and is repaired the same way. Three guarantees the review found ungated are addressed, and one of the three turned out to be a different problem than reported. `frame_idx = 0` and the trace's tail slice are now gated. `causal_fix = true` at that call site is INERT rather than merely ungated, measured on a probe: at `num_pixel_frames = 1`, which both production arms pass, flipping it moves 0 of 48 position values, because the temporal start clamps to 0 either way and the `num_pixel_frames == 1` narrow overwrites the end the fix moved. No call-site check can detect that flip. The risk lives in the `frame_idx == 0` gate the argument passes through, and that is gated in `test_ltx2_vae` at `num_pixel_frames != 1`, where it shows. Two records are corrected: `ti2vid_two_stages.py:211` is blank and the real `combined_image_conditionings` calls are `:231` and `:276`; and the claim that this row moved #1150's owed count from six to five was wrong on both numbers and on the premise, since the keyframe arm was unported rather than divergent. Both of those corrections stand. Closes #1219. #1220 is filed and NOT fixed: the two schedule-anchor cases return the request step count where their comment claims to read the render. It still catches the mutation it was built for, so it is a weakened guard rather than a vacuous one, and re-deriving it changes what a landed case measures on both pipelines. Listed under the row spec's `## Owed`. ## The scoped re-review: two off-by-N anchors, deferred whole A scoped re-review of `87e9f0e37` returned two findings with one root cause. An earlier shape of the repair corrected two inherited off-by-N upstream anchors on the seven new lines that restated them. Those corrections are now REVERTED and the whole question is deferred to [#1230](#1230). **Both readings are right, and that is not the point.** Re-derived here at the LTX-2 pin `fd4ded7f` by reading the pinned files rather than inheriting the citation: `latent_cond.py:38` is `latent_state = latent_state.clone()` and `:39` is blank, so the two writes are `:40-41`; `schedulers.py:31` is the return annotation `) -> torch.FloatTensor:`, so the `tokens = math.prod(latent.shape[2:])` read is `:32`. Correcting seven of the twenty-two citations was still wrong, because **a partial correction is strictly worse than none.** A uniformly wrong anchor is one grep from being right, and a whole tree citing `:38-39` is a single mechanical edit for a single reviewer. A file citing BOTH forms is not. `src/vllm/multimodal/ltx2_video.cpp` read `latent_cond.py:38-39` at `:2208` and `:3174` and `latent_cond.py:40-41` a hundred lines later at `:3392`, with nothing in the tree recording which one to believe -- so the one file a reader of this change opens was the one file stating both. The same split ran through `schedulers.py:31` in `tests/vllm/multimodal/test_ltx2_video.cpp`, whose two near-identical schedule-anchor comments at `:7890` and `:8485` disagreed after the correction and agree again now. It also **cost a gate, which is the blocking half.** `include/vllm/model_executor/models/ltx2_pipeline.h:754` was one of the seven, and `USER_USAGE_PREFIXES` in `scripts/check-doc-checkpoint.py:99` is a pure path match on `include/vllm/` with no content analysis. A one-line comment edit in a public header therefore reads as a usage change and demands a `docs/USAGE.md` edit. `.github/workflows/ci.yml:448` runs that gate per commit over the range, so no follow-up commit could clear it -- the commit itself had to change. There is no user-visible usage change here, so writing a `docs/USAGE.md` edit to turn the gate green is the move AGENTS.md forbids. Removing the header edit is the honest fix, and it retires the mixed file in the same stroke. **The repair commit no longer touches `include/` at all.** Measured, on the rewritten repair commit `263f82b67`: | range | `check-doc-checkpoint.py` | |---|---| | `--base 5af6e76 --head 87e9f0e` | exit **1**, `commit 0e43577: changed user_usage but did not update docs/USAGE.md` | | `--base 5af6e76 --head 853384a` | exit **0**, `OK: public documents match the claims this change makes` | The old head is kept as the control so the instrument is shown armed rather than assumed: the checker still reds on the pre-rewrite range from the same working tree that returns 0 on the new one. It is also 0 against today's `origin/main`. **The RECORDS go the other way on purpose and are not reverted.** The spec's port-map table and #1219's index row state `:40-41` and `:32`, because a record's job is to say what is true, and the index is append-only, so a wrong anchor written there could never be swept. What the record now says, and what #1230 carries, is: the anchors are `:40-41` and `:32`, the source cites them uniformly short in twenty-two places, and one row corrects all of them at once. The tree already carried eight citations of the CORRECT form before this row existed -- `include/vllm/multimodal/ltx2_video.h:625` among them -- so the mixture is older than this change and outlives it either way. #1230 is filed, listed under `## Owed` in the row spec and appended to the issue index, and stays OPEN. ## Gate rerun by the operator Rerun at `87e9f0e37`, not taken from the implementer's report. `CONFIGURE_EXIT=0`, `BUILD_EXIT=0`, `: error:` 0, no `No space left`. `ctest -N` = **516** and `ctest -j3` = **100% tests passed, 0 tests failed out of 516**, `CTEST_EXIT=0`. Focused, with ANSI stripped and `Status:` read rather than inferred from the exit code: `test_ltx2_pipeline` 56 cases / 3316 assertions, `test_ltx2_video` **88 / 2755**, `test_ltx2_vae` 43 / 3125, all `Status: SUCCESS!` at exit 0. The video case count moved from the reviewed head's 87 / 2713, which is what proves the new both-keyframes case ran rather than matching nothing -- a doctest filter that selects zero cases prints `SUCCESS!` and exits 0. `windows-msvc-cpu` and `windows-msvc-vulkan` are red. They are red on every PR in this repo -- confirmed on the unrelated #1186 and #1178 -- because those jobs are PR-only with no `main` baseline. Not attributable to this change. ## Gate rerun after the anchor revert Rerun by the repairing session at `853384a8b`, on the merged tree, in its own worktree. `CONFIGURE_EXIT=0`, `BUILD_EXIT=0`, `: error:` count **0**, `No space left` and `BFD` each **0**. `ctest -N` = **516** and `ctest -j3` = **100% tests passed, 0 tests failed out of 516**, `CTEST_EXIT=0`. Focused, whole binaries with no `--test-case` filter, ANSI stripped and `Status:` read rather than inferred from the exit code: | Binary | cases | assertions | `Status:` | exit | |---|---|---|---|---| | `test_ltx2_pipeline` | 56 | 3316 | `SUCCESS!` | 0 | | `test_ltx2_video` | 88 | 2755 | `SUCCESS!` | 0 | | `test_ltx2_vae` | 43 | 3125 | `SUCCESS!` | 0 | Unchanged from the operator's rerun at `87e9f0e37`, which is the expected result: the revert touches eight comment lines and no executable statement. `test_ltx2_video` holding at 88 rather than falling back to 87 is the load-bearing one, because a lost case is how a history rewrite drops work silently. Record gates on the rewritten range: `check-commit-trailers` OK, `check-commit-style` OK, `check-issue-index-append-only` OK, `check-now-current` OK, `check-agent-record` OK. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code] --------- Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
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.
feat(ENG-CUDAGRAPH-DEDUP): fold captures that share a topology onto one graph executable
Refs #1162 and #1184, deliberately not
Closeson either. RowENG-CUDAGRAPH-DEDUPin.agents/engine-matrix.md, spec.agents/specs/eng-cudagraph-dedup.md, derivedby the analysis in #1161. #1162 owns every item under the spec's
## Owed--the device A/B, the executable-count ratio, and the default flip -- and
docs/BENCHMARKS.mdrecords that A/B asPENDINGagainst it. The row staysACTIVE. Closing the issue on squash-merge would delete the tracking the"Nothing lands dead" exception requires while three items are still owed. #1184
stays open for the same reason: the repair below is proven structurally on the
CPU tier and only a device can prove it on a device.
Every capture used to instantiate its own executable and throw the raw graph
away, so a model held one executable per padded decode bucket -- 7 at
max_num_seqs=32, 11 at 64 -- and nine capture drivers each held their ownset.
grep -rn "cudaGraphExecUpdate" src includereturned nothing. The decodegraphs of two padded batch sizes are usually the same node topology with
different parameters, which is the case
cudaGraphExecUpdateexists for.src/vt/graph_dedup.his the registry: it keys captures by a structuralsignature and re-points one executable instead of instantiating a second.
src/vt/graph_dedup_runtime.hbinds it to CUDA and to HIP from one source,because the two graph APIs differ only by symbol prefix and by two call shapes,
and a second hand-written copy is the parallel path the protocol forbids. Both
accelerator backends route
EndCaptureGraph,ReplayGraphandDestroyGraphthrough it, dispatching on
Owns()rather than a mode flag so a handle mintedbefore the registry existed is still replayed as the plain executable it is.
The signature is a lookup key and never an authority. Every candidate fold is
probed with the real driver update on a throwaway executable first, and a probe
the driver refuses gives that capture its own executable, so a signature that is
too coarse costs a wasted probe and can never make a replay wrong. That is also
why the probe is transient where SGLang's is persistent: its backend seals the
capture phase and frees the probes, and ours has no capture phase to seal --
the drivers capture lazily, interleaved with replay -- so a persistent probe
would hold two executables per group forever, which is worse than the baseline
this row exists to reduce.
vLLM has no analogue, because its executables come from
torch.compile. Theconstruction is therefore ported from the secondary oracle SGLang at the pinned
f63458b5be,cuda_graph_dedup_mixin.py:27-37,105-179,219-242,258-275,353-358,with three divergences recorded in the spec's
## Upstream chain.Not a throughput change
A deduped replay launches the same nodes with the same parameters. The
reportable axes are executable count and capture wall time, and the row must not
be sold as speed. It matters because on GB10 unified memory an out-of-memory
event reboots the box, capture time is startup latency, and bucket count is
exactly what #1163 would raise if it widened coverage.
Gates
tests/vt/test_graph_dedup.cppwas written and run before theheader existed:
fatal error: vt/graph_dedup.h: No such file or directory,exit 1. Then green, non-vacuous. Now 13 cases / 65 assertions, after the
review repair below added four cases: two source-behaviour ones run red against
the unfixed source first (6 failing assertions), one pinning the environment
polarity, and one gating the loud-failure claim the transitivity note rests on.
trust the signature without probing; keep a freed graph's address;
Ownsclaims every pointer; wrong counts in the log line; inverted environment
polarity; free the shared executable with the first sibling instead of the
last. Every one printed a non-empty
git diff --statand a successfulcompile before its verdict, and every one restored byte-for-byte -- an earlier
pass had to be re-run because the file was still untracked, so
git diff --statcame back empty and would have hidden a mutation that never applied.launchrecordswhich graph the executable actually reflects, so "the right nodes ran" is an
observable sequence rather than an assertion about intent, and every case
replays more than once per shape: the risk of a shared executable is that the
second visit to a shape is the one that has to re-point it. One case had to
be added after the fact, because the original sibling-destroy case could not
see a capture landing on a freed graph's address; the fake now hands that
storage back and the new case reuses it deliberately.
g++ -std=c++20 -fsyntax-only -Wall -Wextra -Werroragainstreal CUDA 12.9 runtime headers, exit 0, over a translation unit that also
mirrors the three call shapes
cuda_backend.cunow uses. Thecuda-fat-buildjob compiles the real thing on this PR.
scripts/agent-preflight.sh:All gates green., exit 0.Review repair
A fresh review returned FAIL. Three findings were code or tests; three are
records, because fixing them would change behaviour the device A/B is measuring
on this branch, or is not code at all.
cudaGraphInstantiatewas silently accepted.Registerassignedthe result and moved on while the runtime returns null on failure. The
pre-dedup path threw at the capture site with the driver's code; with dedup on
the same failure minted a valid-looking handle over a null executable, counted
it live in
ExecCount(), and surfaced at the first replay as "graph launchfailed" -- wrong site, wrong message, driver reason discarded. It now fails at
the capture site and releases the capture whose ownership it had taken.
executable and can fail on its own;
cudaGraphExecUpdatehas no definedbehaviour for a null one.
ProbeAcceptsnow answers "cannot fold", whichdegrades to today's one-executable-per-capture path.
&& value[1] == '\0'left the whole suite green, so
"10"enabling a default-off,correctness-sensitive path went undetected. The adversarial values are now
asserted, and the deletion is a detected mutation.
Registerprobes(raws.front(), raw_graph);Replayissues(current_raw, entry.raw), and those diverge from a group's third memberonwards -- so honouring the probe treats
cudaGraphExecUpdatecompatibilityas transitive across a group, which no documentation states and nothing here
asserts. The failure polarity is what makes it survivable: a refusal lands on
the
VT_CHECKinReplay, loudly, never on a silent launch of the previouscontents. The comment claiming the fold was already probed is corrected to
say what is actually assumed, and the loud-failure claim it now rests on is
gated rather than asserted: a new case refuses exactly the
(currently-reflected, target)pairRegisternever asks about, so all threeprobes succeed, the group folds to one executable, and the second replay
throws. Deleting that
VT_CHECKmakes the replay launch graph 2's nodes undergraph 3's handle. The stronger fix -- probe
current_raw-- is owed, nottaken: it changes probe behaviour while the A/B is measuring this code.
src/vt/graph_dedup_runtime.hhas no executable coverage (record only).The mutation count is a statement about
graph_dedup.h. The signature builder-- Kahn ordering, topological re-index, sorted edge emission, five node-payload
cases, the depth-4 bound, five degradation escapes -- is reached by no test on
any tier, and
cuda-fat-buildproves only that it compiles. The probe caps theblast radius so it can never cause a wrong replay, but one mode is silent: a
signature unstable for some topology folds nothing while every CPU test stays
green, with the device-side log line as the only observable. Stated in the spec
and the matrix row, with the two device-tier tests that would cover it named
and owed.
CudaBackend::dedup_belongs to a process-singleton per-device backend, so theconstraint is wider than the "one runner thread" note it carried. Correct
today; recorded where a future capture-widening row will read it.
Also in this branch: merged up to
ab6e6521, resolving theengine-matrix.mdconflict by taking both row sets -- #1177's
ENG-CUDAGRAPH-BREAKcontentbyte-identical, this row's
ACTIVEcontent with #1179's corrected NINE-drivercount carried onto it, and both moves composed in the summary counters. Every
other line of that file is byte-identical to both parents. The spec's
Stats()never existed and is corrected to the shipped
CapturedCount()/ExecCount().#1184: the file was allowed to see the runtime fail, and never consumed the latch
The device gate found that
VT_CUDA_GRAPH_DEDUP=1could not complete one decodestep once a graph was actually captured. 6/6 deterministic on GB10:
from a
greedy_argmaxlaunch that had SUCCEEDED. The OFF and=0arms wereclean and byte-identical across 7 runs of the same binary against the same
libraries, which is what makes the asymmetry arm-attributable rather than an
environment artefact.
greedy_argmaxwas never at fault. The entire safety argument for this row isthat the signature is a lookup key and
cudaGraphExecUpdateis the authority --so a probe the driver REFUSES is normal operation, not an exception -- and the
topology walk has five more escapes of the same kind. A CUDA call that fails
also latches its code in the runtime's sticky per-thread slot, and none of the
twelve fallible calls in
src/vt/graph_dedup_runtime.hconsumed it:grep -n "cudaGetLastError\|hipGetLastError"on that file returned nothing. The nextunrelated kernel, launched with the ordinary
kernel<<<>>>(...); Check(cudaGetLastError())pattern this tree uses everywhere, then read ourroutine refusal and reported it as its own failure.
Every symptom follows from the mechanism and none from the kernel named. It
needs BOTH
dedup=1and a real capture, because with no capture there is noprobe and no latch.
CUDA_LAUNCH_BLOCKING=1does not move it, because the latchis host-side and synchronous rather than a deferred async error.
cudaGraphLaunchitself returns success, because reading a return value doesnot consume the latch.
The repair is one type, not twelve clears. Twelve hand-placed
cudaGetLastError()calls are a fix the thirteenth fallible call silentlymisses, and a file whose design is "these calls are allowed to fail" will grow a
thirteenth. So the clear lives in
ScopedLatchClear's destructor(
src/vt/graph_dedup_latch.h, new, device-free) and is installed at thebinding's ENTRY POINTS, which are exactly the six
GraphDedupOpsmembers thebackends reach through
Ops(). Every exit runs it: a plain return, adegradation escape, and the
VT_CHECKunwinding out ofLaunch.MakeLatchGuardedOpsis the table's only constructor and takes the rawfunctions as template arguments, so no raw address reaches a field; a seventh
operation wired anywhere else leaves its field null, and
GraphDedupRegistry'sconstructor refuses an incomplete table, so the bypass does not merely get
discouraged, it does not construct. One line covers CUDA and HIP because there
is one source:
VTGD_FN(GetLastError)resolves tocudaGetLastErrororhipGetLastError.The fold and probe decision logic is untouched. This is error-state hygiene.
MEDIUM-3: the coverage gap that hid it
src/vt/graph_dedup_runtime.hwas reached by no test on any tier --grep -rn "graph_dedup_runtime\|AppendGraphSignature" tests/returned nothing --so its Kahn ordering, topological re-index, sorted edge emission, depth-4 child
bound and degradation escapes were compile-gated only. That is where #1184 sat
through a whole review cycle.
The device-free half moves to
src/vt/graph_dedup_signature.hbehind anRtpolicy, byte-identical in output, and the new
tests/vt/test_graph_dedup_runtime.cppdrives it with a fake runtime: 13 cases,51 assertions.
ScopedLatchClear's destructor emptied to the pre-fixstate, the suite reports 22 failed assertions, including
CHECK_NOTHROW(NextUnrelatedKernelLaunch()) THREW exception: "greedy_argmax launch: invalid device function"-- the production message reproduced fromthe mechanism alone, with no CUDA in the room. Then green, 13/13.
the raw address; the Kahn re-index becomes the identity; the edge sort is
dropped; the depth bound moves 4 to 3; a child graph is noted but never
walked; the unknown-endpoint escape becomes a
continue. Each printed itsgit diff --stat, its compile status and its exit status, and restoredbyte-for-byte. One first attempt failed to build under
-Werrorand wasre-run in a compiling form, because a mutation that fails to build reads as a
passing test.
including an unwinding one, that a refused fold leaves nothing latched for the
next caller, that no table field skips the guard, and that the signature walk
is order-independent, discriminating, bounded and degrading.
cannot observe the CUDA runtime's real latched-error state, so it proves the
guard's structure and not that VT_CUDA_GRAPH_DEDUP=1 dies after one replay: the signature walk latches a CUDA error that the next unrelated kernel reports as its own #1184 is gone on a device. The five
node-payload cases stay device-only behind the policy.
scratch header-shape stub instantiated
Ops()atCUDART_VERSION12090 and13030 and on the HIP arm under
-Werror: all three clean, with a negativecontrol proving the instrument can fail. That is a proxy and not the gate --
the CUDA 13
cudaGraphGetEdgesbreak this file already took was invisible toexactly this kind of local check, and only
cuda-fat-buildreported it.What the device gate also found, and it bounds this row
vllm-benchon the async path captures NO decode graph at all, becauseDenseDecodeGraphForwardreturnsnulloptwheneverinput.device_token_ids != nullptr-- the #323 mitigation that #1179 re-derived. No capture means noregistration, so dedup engages only under
VT_ASYNC_RUNNER=0, which is not thedefault and not what a user serves with. This changes nothing about the row's
correctness argument and it does change its worth: on the default path the
executables this row folds are executables that are never instantiated in the
first place. Recorded in the spec rather than sold around; the repair is the
StepDevInputs-shaped one #1179 already owns.Owed, and named rather than implied
VT_CUDA_GRAPH_DEDUPoff/on A/B proving a dedupedreplay token-identical, and the executable-count ratio. It needs a leased CUDA
box;
dgx:gpu0was held by another session's oracle build for this wholesession, and
orin:gpu0has no toolkit. RecordedPENDINGindocs/BENCHMARKS.md, not omitted.padded buckets every step pays one update per switch, and shipping a default
that trades an unmeasured latency for an unmeasured memory saving would be
the wrong kind of confidence.
current_rawrather thanraws.front(), which retires theupdate-transitivity assumption entirely. Held back because it changes probe
behaviour mid-measurement; it lands with the A/B rerun. Tracked under One cudaGraphExec per decode bucket per model, and no cudaGraphExecUpdate anywhere: graph executables never dedup #1162.
the same trivial graph captured twice signs identically, one that two
deliberately different topologies sign differently, plus the five node-payload
cases. The device-free half is now covered by
tests/vt/test_graph_dedup_runtime.cpp; what is left needs a realcudaGraph_t, so it rides with the leased box the A/B already needs. Trackedunder One cudaGraphExec per decode bucket per model, and no cudaGraphExecUpdate anywhere: graph executables never dedup #1162.
VT_CUDA_GRAPH_DEDUP=1now completes a decode step. The CPU suite proves theguard's structure over a fake runtime; only a device proves that
cudaGetLastErrorclears the real latch. VT_CUDA_GRAPH_DEDUP=1 dies after one replay: the signature walk latches a CUDA error that the next unrelated kernel reports as its own #1184 stays open until that runexists.
runner's absent capture blocks today. Tracked under The decode-graph driver count recorded in 9bc4d7f44 is eight; it is nine, and the ninth predates the record #1179.
hipccis reachable from thissession and CI has no ROCm job, so the HIP wiring is written against the same
shared header but is compile-unverified. Tracked under ROCm (AMD GPU) backend #41.
Scope
No driver internals, no bucket selection, no break points (#1163, parallel
owner), no diffusion path (#1164, blocked).
docs/ENVIRONMENT.mdchanges onlybecause a config key does.
scripts/check-gate-commands.pyre-pinsRUNNABLE_BASELINEin the same change, which is what its own message demandswhen a row enters the runnable population.
What was NOT verified on this head, stated plainly
This lands with two gaps named rather than papered over. Both are owed under #1162
and #1184, which stay open.
The device A/B never ran on the fixed code.
dgx:gpu0was held by anothersession's
oracle-build130for over 90 minutes with this job queued at position 1.The previous run, on the pre-fix head
e4ce5571a, is what FOUND #1184: everydedup=1cell died after exactly one replay. The repair is therefore unverified onhardware. What the repair does have is a red-first test reproducing the exact
production string
greedy_argmax launch: invalid device functionfrom the mechanismalone with no CUDA present, 7/7 negative mutations, and a structural design in which
the bypass does not compile.
cuda-fat-builddid not report on this head. It passed one4ce5571aearlier,before the latch fix added
graph_dedup_latch.hand the signature extraction, so theCUDA leg of the current code is covered only by
-Werrorstub syntax checks atCUDART_VERSION12090 and 13030 plus HIP, each with a negative control proving theinstrument can fail. That job has already caught one real defect on this PR that every
local check passed through, so its silence here is a gap, not a pass.
Why landing anyway is the honest call:
VT_CUDA_GRAPH_DEDUPis default OFF, theunset and
=0arms were byte-identical across 7 runs, and nothing on a defaultconfiguration reaches this code. The "Nothing lands dead" exception is satisfied
literally and was verified by a fresh reviewer. Holding the branch indefinitely on
another session's resource hold would not make any of it more true.
Also bounding this row's value, and measured rather than assumed: the signature
hashes
gridDim/blockDim(graph_dedup_runtime.h:121-128), and decode bucketsdiffer precisely in launch dimensions, so buckets may be structurally unfoldable --
which is the premise #1162 was filed on. The first run logged
captured 1 graphs, deduped to 1 execsin every ON cell, a 1:1 ratio with no fold. Separately, the shippedasync serving path captures no decode graph at all, so dedup engages only under
VT_ASYNC_RUNNER=0. Both are recorded in the spec.FOLLOWING_AGENTS_PROTOCOL
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [Claude Code]