Skip to content

[Feature]: Paged / KV-manager-resident storage for the DFlash draft context, replacing the dense buffer #16005

Description

@chungen04

🚀 The feature, motivation and pitch

Problem

DFlash's draft model cross-attends over an accumulated bank of projected target hidden states. At upstream it is written as a dense, worst-case pre-allocated GPU buffer (tensorrt_llm/_torch/speculative/dflash.py,_lazy_init_ctx_buffers):

_ctx_k_buf / _ctx_v_buf : [max_batch, L_draft, max_ctx + block_size, n_kv, head_dim]  (bf16, x2 for K and V)

with max_ctx defaulting to min(runtime max_seq_len, draft max_position_embeddings). This reserves max_batch × max_ctx token-slots up front.

When launching the engine with model: MiniMax-M2.5-NVFP4, TP=2, max_batch_size=64,max_seq_len=131072 with draft model: 5 layers, 8 KV heads, head_dim 128:

DFlash: allocated ctx buffers: max_batch=64, max_ctx=131083, dtype=torch.bfloat16
  • Per-token context cost: 2 × 5 × 4 × 128 × 2 B = 10 KB/token/rank
  • Dense reservation: 64 × 131,091 ~ 8.39 M token-slots × 10 KB ~ 80 GB per rank
  • On a 180 GB GPU, the buffer consumes much of the remaining memory. The target KV pool shrinks to the point that admission starves (utilization pins at 100%, new requests can't be scheduled).

Current design is also not able to retrieve the context on a prefix-cache hit.

vLLM's solution for reference

vLLM's DFlash implementation stores the draft context inside the standard paged KV cache system, so no dedicated buffer at all. Based on release v0.24.0:

  • Draft layers are first-class members of the hybrid KV cache manager. Layer grouping explicitly accommodates "speculative decoding drafters that add extra layers to one attention type" (vllm/v1/core/kv_cache_utils.py).
  • Projected context K/V is scattered into the draft layers' paged cache via slot mappings, with norm/RoPE (and optionally quantization) applied at store time: precompute_and_store_context_kv(states, positions, slot_mappings) (vllm/model_executor/models/qwen3_dflash.py,vllm/models/deepseek_v4/nvidia/dspark.py).
  • Per-iteration metadata (context positions, context/query slot mappings) is built on the GPU from the block table by a single Triton kernel (copy_and_expand_dflash_inputs_kernel, vllm/v1/spec_decode/utils.py), making zero host-side table maintenance, also CUDA-graph-clean (vllm/v1/worker/gpu/spec_decode/dflash/cudagraph.py).
  • The scheduler natively accounts for the draft context as ordinary lookahead (num_lookahead_tokens = num_spec_tokens + 1,vllm/v1/core/sched/scheduler.py).
  • Because context lives in ordinary cached blocks, prefix-cache hits restore the draft context.

Constraints discovered when porting this to TensorRT-LLM

  • Upstream flash_attn's paged read requires 256-token pages. DFlash's cross-attention currently calls flash_attn_with_kvcache. Its paged path asserts Paged KV cache block size must be divisible by 256, so it cannot consume the manager's tokens_per_block=32 blocks. (vLLM avoids this because its attention backends read 16/32-token pages natively.)
  • Manager pools share one dtype. Manager-resident context inherits the target's KV dtype (e.g. fp8), so store-time quantization of the projected context is required.
  • Scheduler accounting: once draft-layer context grows inside the manager, admission must budget it.

Proposed solution

Register the draft layers as spec layers of the target manager. Some machinery already exists:

  • get_num_spec_layers / get_pp_layers append spec layers.
  • _build_per_layer_num_kv_heads already builds heterogeneous per-layer head lists
  • Multi-pool handles differing geometry. get_buffers(layer_idx) exposes per-layer paged views for the store path.

Alternatives

No response

Additional context

No response

Before submitting a new issue...

  • Make sure you already searched for relevant issues, and checked the documentation and examples for answers to frequently asked questions.

Metadata

Metadata

Assignees

Labels

Speculative Decoding<NV>MTP/Eagle/Medusa/Lookahead/Prompt-Lookup-Decoding/Draft-Target-Model/ReDrafterfeature requestNew feature or request. This includes new model, dtype, functionality support

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions