Skip to content

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

Description

@localai-bot

VT_CUDA_GRAPH_DEDUP=1 (PR #1178, ENG-CUDAGRAPH-DEDUP, #1162) cannot complete a
single decode step
once a CUDA graph is actually captured. Found by the owed device
gate on dgx:gpu0, which the change had not yet run.

Measured

Commit e4ce5571a, GB10 / sm_121a, driver 580.173.02, boot id
1cf6179f-0150-4052-b507-506fd6751953, leased through rc. Model
Qwen3ForCausalLM 0.6B bf16, model.safetensors sha256 11293257…6f6641. Same
binary in every cell (sha256 2f689337…f824d2); arms differ only by
VT_CUDA_GRAPH_DEDUP.

Every dedup=1 run dies in this exact order:

vt graph dedup: captured 1 graphs, deduped to 1 execs
[Qwen3DenseDecodeGraph] captured dense decode graph for padded size S=8 (real B=8)
engine-fatal: EngineCore busy loop threw: vt cuda: greedy_argmax launch: invalid device function
[Qwen3DenseDecodeGraph] dense decode graph: 1 total replays across 1 captured size(s)
cell VT_CUDA_GRAPH_DEDUP exit decode-graph stats ids sha256
s8_off_a / s8_off_b unset 0 92 replays / 1 size a2ed218d…acdeac
s8_zero 0 0 92 replays / 1 size a2ed218d…acdeac
s8_on_a / s8_on_b 1 1 1 replay / 1 size MISSING
s16_off_a / s16_off_b unset 0 140 replays / 1 size a7cee2a5…7aeb55
s16_on_a / s16_on_b 1 1 1 replay / 1 size MISSING

Isolation, 6 cells, one job, same binary: the failure needs both dedup=1
and a graph that is actually captured. QWEN3_DENSE_DECODE_GRAPH=0 → clean;
VLLM_CPP_CUDAGRAPH=0 → clean; dedup off with the graph on → clean (59 replays);
dedup on with the graph on at batch 1 → dies after 1 replay. CUDA_LAUNCH_BLOCKING=1
does not move it, so it is not a deferred asynchronous error from an earlier
operation. 6/6 deterministic.

Consequences. Byte-identity cannot be established at all — the deduped arm
produces no artifact. And the benefit is unmeasured too: every ON run reports
captured 1 graphs, deduped to 1 execs, a 1:1 ratio, so no fold ever happened.

Cause: a latched CUDA runtime error, misattributed to the next kernel

greedy_argmax is not at fault. src/vt/graph_dedup_runtime.h walks the retained raw
graph with runtime APIs and checks each return code but never clears the runtime's
latched error
. grep -n "cudaGetLastError\|hipGetLastError" src/vt/graph_dedup_runtime.h
returns nothing. The next raw launch using the ordinary
kernel<<<>>>(); Check(cudaGetLastError()) pattern then reports that stale code as its
own, which is why it names an unrelated kernel and why cudaGraphLaunch itself returns
success — it is checked on its return value, which does not consume the latch.

Twelve sites swallow a non-success return without clearing:
graph_dedup_runtime.h:98,100,115,130,137,146,154,173,179,281,307,317.

Two of those are the worst, because failure there is by design, not exceptional:

The signature walk's five degradation escapes (:115,130,137,146,154) are the same
class. This is precisely the risk the fresh review recorded as MEDIUM-3: those 356 lines
have zero executable coverage, and the predicted invisible mode was "a signature
unstable for one topology folds nothing while every CPU test stays green, and the only
observable is the device-only log line". The review called the class; the device gate
found the instance.

Why nothing else caught it

Green at the time of discovery: the full local agent-preflight.sh, cuda-fat-build,
both sanitize-cpu legs, build-test-cpu, 13 focused cases / 65 assertions, and 4/4
negative mutations — plus a fresh adversarial review. No CPU-tier instrument can see
this
, because the tests drive a fake ops table and never touch the CUDA runtime's
latched-error state.

Scope

  • Clear the latched error at the boundary of every runtime call in
    graph_dedup_runtime.h that is allowed to fail, so a by-design refusal cannot be
    misattributed downstream.
  • Re-run the device A/B on dgx:gpu0 and establish byte-identity, which is still owed.
  • The exec-count ratio is also still owed: the workload held a steady concurrency, so
    only one padded size was ever captured and the multi-bucket fold was never exercised.

Not a production regression: VT_CUDA_GRAPH_DEDUP is default OFF, and the unset and
=0 arms are byte-identical across 7 runs.

Two caveats on the environment, recorded rather than hidden: the worker container carries
CUDA 13.3.73 against the recorded dgx gate stack of 13.0.88, and its own cuBLASLt could
not capture in either arm, so the runs used a locally staged cu130 libcublasLt. The
OFF arm is clean under that identical library in 7 runs, which is what makes the
ON/OFF asymmetry arm-attributable, but the absolute stack is not the gate stack and this
wants a re-run on 13.0.88.

Evidence: dedup-gate/EVIDENCE.md, logs4/, logs5/, out4/, mirrored on the NAS at
/mnt/nas_share/rc/dedup-gate/.

Owned by ENG-CUDAGRAPH-DEDUP. Blocks #1162.

FOLLOWING_AGENTS_PROTOCOL

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions