Skip to content

DFlash2 speculative decoding serves only ONE sequence: at c=2 the draft context accumulation desynchronises and EngineCore stops #2008

Description

@localai-bot

DFlash2 speculative decoding serves one sequence. At concurrency 2 the engine refuses on its own invariant and EngineCore stops. This is the blocking defect for the #1574 three-engine comparison: vLLM and SGLang were both measured at c=1..32 with the same DFlash2 draft at k=8, and we cannot produce a rung above c=1.

Measured

integ4/1574 @ 3d137890f (main + #1994 + #1997 + #1999 + #2000), artifact-gated (flash_fwd=1792, SpecDecodeFA2Bf16=1, FA2 manifest [121a]), on an idle leased GB10. --num-blocks 3744 --max-num-seqs 16 --max-model-len 8192 --no-enable-prefix-caching --speculative-config '{"method":"dflash","model":"/draft","num_speculative_tokens":8}', via vllm bench serve --backend openai-chat, input 1024 / output 512:

rung result
c=1 24.70 out tok/s, 75.40 total, TTFT mean 1363.78 ms / p99 1388.08, TPOT 37.90 ms, 8/8 ok
c=2 VOID — ok=1, failed=7

The c=2 failure:

engine-fatal: EngineCore busy loop threw: vt: propose_drafts_block: context position
discontinuity (accumulation out of sync with the target's committed positions)
at src/vllm/v1/worker/gpu/runner.cpp:2961

Every later request returns 500 [request submitted to a stopped AsyncLLM]. The ladder's failed==0 assertion recorded the rung as VOID and wrote no row, which is the harness working.

The invariant, and why it is right to assert

src/vllm/v1/worker/gpu/runner.cpp:2955-2963:

// 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 = dflash_ctx_len_[static_cast<size_t>(i)];
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)");

This is a guarded refusal, not a crash — the code deliberately stops rather than drafting from a desynchronised context, and the comment names the failure class it is defending against. The defect is that the accumulation desynchronises at all once more than one request is in flight.

The matching half is the capture gate at src/vllm/model_executor/models/qwen3_dflash.cpp:1577, which admits the capture-safe paged path only when P == 1. Above that the fallback re-materialises each request's context from the paged store every propose step, and dflash_ctx_len_[i] no longer tracks what the target committed for request i.

Relationship to the other rows

Distinct from all four fixes landed today, and it is what remains after them:

It is also the surviving candidate for the crash half of #1983: the same P > 1 region, reached under a configuration where the position accumulation desynchronises rather than the assertion catching it first.

What c=1 says about the rest

Worth recording, because it changes the priority. At c=1 on this build we are competitive: 24.70 out tok/s against vLLM's 24.36 and SGLang's 25.20, with TPOT 37.90 ms against SGLang's 37.93 — per-token decode at parity with the engine we are chasing. Caveats: ours ran at --max-model-len 8192 where the recorded competitor rows used 32768, and at --max-num-seqs 16 with an explicit 3744-block pool against their defaults, so this is a same-band result rather than a like-for-like one.

The gap to close is therefore concurrency, not per-token cost.

Owed

  • A red-before test that drives two concurrent requests through propose_drafts_block and reproduces the discontinuity on CPU, so the fix is gated rather than measured.
  • Whether the same configuration serves at c>=2 with --speculative-config omitted. If it does, that isolates the defect to the draft path and gives an interim comparison point.
  • Reproduction is one command; the binary is at /usr/local/vcpp/build14 and the ladder at /usr/local/vcpp/ladder.sh.

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