Found while fixing #2008. Not fixed in that flow, and owned by specs/dflash2-request-scoped-context.md under ## Owed.
What was measured
src/vllm/v1/worker/gpu/runner.cpp:2939-2945 is the guard the whole DFlash2 draft-context accumulation rests on:
// Invariant: this step's first committed token sits at absolute position L
// (== current context length). A violation means the accumulation lost sync
// (the I5e async-input-combine bug class) — assert rather than corrupt.
const int64_t L = ...;
VT_CHECK(step.positions[static_cast<size_t>(rows[0])] == L,
"propose_drafts_block: context position discontinuity (accumulation "
"out of sync with the target's committed positions)");
It is what turned #2008 from a silent wrong-context draft into a loud refusal, and it is the reason that row could be diagnosed at all.
Deleted, on the pre-#2008 code, tests/vllm/v1/spec_decode/test_dflash2_concurrency stays green: 2 test cases, 30 assertions, 0 failed. With the invariant gone, the row move resets the survivor's store to empty, the survivor drafts from a context that is not its own, and nothing notices.
Nothing notices because the verify is lossless — a draft produced from the wrong context costs acceptance and never a token — so every token-shaped gate in this tree is blind to it by construction.
Why #2008's own gate cannot close this
The natural leg is to compare the drafted blocks against a solo control run. That leg was written and removed, because on the shared DFlash2 fixture it is a tautology: with the invariant deleted and the context reset at every row move, the draft still emits 12 12 12 at every step of both runs. The synthetic draft's block is constant in the context — seeded-noise weights over a 24-token vocabulary, and the selector walk collapses to a single id — so nine passing string comparisons measured nothing.
(test_dflash2_runner_reach's value-sensitivity case is unaffected: it moves the drafts by changing the selector's WEIGHTS, not the context.)
What closing this needs
A fixture whose drafted block is demonstrably sensitive to the draft context, so that "drafted from the wrong context" is observable without a device and without an acceptance-rate measurement. That is a fixture problem before it is a test problem, and it is the same instrument several DFlash2 rows would benefit from: today the tree can prove a draft moves with its weights and cannot prove it moves with its context.
Related
- #2008 — the defect the invariant caught, and where this was found.
- The
## Owed section of the spec above carries the two other DFlash2 items from the same flow.
Found while fixing #2008. Not fixed in that flow, and owned by
specs/dflash2-request-scoped-context.mdunder## Owed.What was measured
src/vllm/v1/worker/gpu/runner.cpp:2939-2945is the guard the whole DFlash2 draft-context accumulation rests on:It is what turned #2008 from a silent wrong-context draft into a loud refusal, and it is the reason that row could be diagnosed at all.
Deleted, on the pre-#2008 code,
tests/vllm/v1/spec_decode/test_dflash2_concurrencystays green: 2 test cases, 30 assertions, 0 failed. With the invariant gone, the row move resets the survivor's store to empty, the survivor drafts from a context that is not its own, and nothing notices.Nothing notices because the verify is lossless — a draft produced from the wrong context costs acceptance and never a token — so every token-shaped gate in this tree is blind to it by construction.
Why #2008's own gate cannot close this
The natural leg is to compare the drafted blocks against a solo control run. That leg was written and removed, because on the shared DFlash2 fixture it is a tautology: with the invariant deleted and the context reset at every row move, the draft still emits
12 12 12at every step of both runs. The synthetic draft's block is constant in the context — seeded-noise weights over a 24-token vocabulary, and the selector walk collapses to a single id — so nine passing string comparisons measured nothing.(
test_dflash2_runner_reach's value-sensitivity case is unaffected: it moves the drafts by changing the selector's WEIGHTS, not the context.)What closing this needs
A fixture whose drafted block is demonstrably sensitive to the draft context, so that "drafted from the wrong context" is observable without a device and without an acceptance-rate measurement. That is a fixture problem before it is a test problem, and it is the same instrument several DFlash2 rows would benefit from: today the tree can prove a draft moves with its weights and cannot prove it moves with its context.
Related
## Owedsection of the spec above carries the two other DFlash2 items from the same flow.