[MRV2][Spec Decode] Adaptive Speculative Decoding - Initial Support - #48692
benchislett wants to merge 17 commits into
Conversation
Signed-off-by: Benjamin Chislett <bchislett@nvidia.com>
Signed-off-by: Benjamin Chislett <bchislett@nvidia.com>
Signed-off-by: Benjamin Chislett <bchislett@nvidia.com>
Signed-off-by: Benjamin Chislett <bchislett@nvidia.com>
Signed-off-by: Benjamin Chislett <bchislett@nvidia.com>
Signed-off-by: Benjamin Chislett <bchislett@nvidia.com>
Signed-off-by: Benjamin Chislett <bchislett@nvidia.com>
… mixed prefill/decode) Signed-off-by: Benjamin Chislett <bchislett@nvidia.com>
Signed-off-by: Benjamin Chislett <bchislett@nvidia.com>
Signed-off-by: Benjamin Chislett <bchislett@nvidia.com>
Signed-off-by: Benjamin Chislett <bchislett@nvidia.com>
Signed-off-by: Benjamin Chislett <bchislett@nvidia.com>
|
Documentation preview: https://vllm--48692.org.readthedocs.build/en/48692/ |
| "attention_backend": "FLASH_ATTN", | ||
| "draft_sample_method": "probabilistic", | ||
| }, | ||
| gpu_memory_utilization=0.8, |
There was a problem hiding this comment.
Was getting OOM here due to probabilistic drafting taking up lots of memory for logits, not accounted for by profiler. Not sure if this is safe for CI or not
| return self.num_speculative_tokens_per_batch_size is not None | ||
| return ( | ||
| self.num_speculative_tokens_per_batch_size is not None | ||
| and not self.adaptive_verification |
There was a problem hiding this comment.
Bit of a confusing override here; this is so the scheduler treats adaptive verification as a separate mode (and the model runner decides how many decode tokens to run)
| scheduled_spec_token_ids | ||
| and (generated_token_ids or self.num_sampled_tokens_per_step == 0) | ||
| and request.async_tokens_to_discard == 0 | ||
| if scheduled_spec_token_ids and ( |
There was a problem hiding this comment.
All of this diff is just shuffling things around to fix a bug in the specdec metrics.
Previously we were skipping the metrics observation in cases where request.async_tokens_to_discard == 0, and clearing the entry when num_draft_tokens == 0.
This overcounts the num_drafts since we're only skipping the collection of num_draft_tokens and num_accepted_tokens, throwing off the scores.
There was a problem hiding this comment.
Needed for the test where we assert that num_drafts == K * num_drafted_tokens in the adaptive SD case.
Signed-off-by: Benjamin Chislett <bchislett@nvidia.com>
Signed-off-by: Benjamin Chislett <bchislett@nvidia.com>
Signed-off-by: Benjamin Chislett <bchislett@nvidia.com>
| query_start_loc = self.input_buffers.query_start_loc[: num_reqs_padded + 1] | ||
|
|
||
| if draft_tokens: | ||
| expanded_idx_mapping, expanded_local_pos = expand_idx_mapping( |
There was a problem hiding this comment.
Much of this here is rearranging, since expanded_idx_mapping depends on cu_num_logits, which we construct explicitly in the adaptive verification pathway
| max_num_logits: int, | ||
| vocab_size: int, | ||
| device: torch.device, | ||
| mask_stride: int, |
There was a problem hiding this comment.
We update structured outputs so it can apply the bitmask to the varlen batch. mask_stride effectively makes the bitmask tensor into one that includes padding: and then we read the varlen indices in the kernel, using position_is_active to skip any slots for tokens we did not choose to verify
|
This pull request has merge conflicts that must be resolved before it can be |
|
One thought on the CUDA graph design: SGLang's DSpark integration handles per-request variable verify lengths by packing the ragged batch into a compact varlen buffer and keying the graph purely on the total token count. This seems to allow much finer-grained capture tiers and noticeably less padding waste than the approach here, where decode graphs are rounded up to multiples of the scheduled query length. Might be worth considering whether a similar packed-layout design could reduce compute overhead in vLLM's adaptive verify path. For example, a verify batch [8, 3, 1, 5] (4 reqs, 17 real tokens) with scheduled qlen 8 needs the 32-token FULL graph here (tiers are multiples of 8, and the 24-tier only has 24/8=3 request slots) — 15 padded rows. SGLang's ragged approach packs tokens compactly and keys the graph on total count only, fitting an 18-token tier — 1 padded row, still FULL graph. cc @benchislett |
|
Closing this PR, we will move forward and try to merge #47808. |
|
Also, @cyLi-Tiger I believe that #47808 does it as you suggest, if I am understanding you properly |
Purpose
This PR implements initial support for Adaptive Speculative Decoding using DSpark's confidence head: we enable fully variable-length per request speculative decoding with FULL CUDA Graph support. This PR does not implement online dynamic scheduling of the verification budget; instead, we rely on user-provided
num_speculative_tokens_per_batch_sizeto determine what size pool of draft tokens to verify at each batch size.This PR fleshes out a number of fundamental aspects of variable-length speculation, such as:
Testing
Since only FLASH_ATTENTION is currently functional, I run with Qwen3-8B-FP8 and the corresponding DeepSeek-trained DSpark. Since verification is so cheap on an 8B model, no significant speedup is measurable; that is not the objective of this PR. However, I do observe an increase in acceptance rate under a fixed verification budget:
Acceptance Rate Study: SPEED-Bench, All Categories, Concurrency 128, Shuffled
SPEED-Bench, All Categories, Concurrency 128, Shuffled
Measured using NVIDIA SPEED-Bench harness, thinking off, vLLM config:
I also implement various unit tests, including E2E coverage of acceptance length, GSM8k correctness, and an assert that we're observing almost-exactly
N*Kdraft tokens at each step.Essential Elements of an Effective PR Description Checklist
supported_models.mdandexamplesfor a new model.