Support multimodal speculative decoding in draft_model mode - #35714
EanWang211123 wants to merge 28 commits into
Conversation
Signed-off-by: EanWang211123 <wangyiheng@sangfor.com.cn>
|
Hi @EanWang211123, the pre-commit checks have failed. Please run: uv pip install pre-commit
pre-commit install
pre-commit run --all-filesThen, commit the changes and push to your branch. For future commits, Tip Is
|
There was a problem hiding this comment.
Code Review
This pull request adds support for multimodal speculative decoding in draft_model mode, covering both dual-VLM and cross-modal (VLM target + text-only draft) scenarios. The changes primarily focus on correctness by fixing issues related to positional encoding (M-RoPE), input handling for draft models, and relaxing vocabulary size validation. My review found the changes to be well-implemented and thoroughly explained. I have one suggestion to improve the clarity of an error message related to vocabulary size validation.
|
Hi @EanWang211123, the pre-commit checks have failed. Please run: uv pip install pre-commit
pre-commit install
pre-commit run --all-filesThen, commit the changes and push to your branch. For future commits, Tip Is
|
|
Hi @EanWang211123, the pre-commit checks have failed. Please run: uv pip install pre-commit
pre-commit install
pre-commit run --all-filesThen, commit the changes and push to your branch. For future commits, Tip Is
|
benchislett
left a comment
There was a problem hiding this comment.
Please try to simplify your comments. Many of them are diluting the usefulness by repeating what the code is clearly doing
Signed-off-by: EanWang211123 <wangyiheng@sangfor.com.cn>
|
Hi @EanWang211123, the pre-commit checks have failed. Please run: uv pip install pre-commit
pre-commit install
pre-commit run --all-filesThen, commit the changes and push to your branch. For future commits, Tip Is
|
Signed-off-by: EanWang211123 <wangyiheng@sangfor.com.cn>
…211123/vllm into multimodal-draft-support
Signed-off-by: EanWang211123 <wangyiheng@sangfor.com.cn>
|
This pull request has merge conflicts that must be resolved before it can be |
Signed-off-by: EanWang211123 <wangyiheng@sangfor.com.cn>
| if self.supports_mm_inputs: | ||
| if self.supports_mm_inputs and self.pass_hidden_states_to_model: |
There was a problem hiding this comment.
Why is this change necessary? How is passing the hidden_states related to multimodal support?
There was a problem hiding this comment.
After this PR, self.supports_mm_inputs is derived from the draft model config, so it's True whenever the draft itself is multimodal. But only EAGLE-style drafts (which share the target's architecture) actually receive target-precomputed mm_embeds via self.inputs_embeds — a standalone DraftModelProposer wrapping a VLM does not (it treats image placeholders as regular input_ids, and self.inputs_embeds is never populated on that path).
pass_hidden_states_to_model is the existing discriminator: True for EAGLE/DFlash, False for DraftModelProposer. Without the extra guard, a standalone multimodal draft would read uninitialised self.inputs_embeds and the dummy_run-captured cudagraph would diverge from the real propose path (input_ids=None vs tensor).
Let me know if there is anything I'm missing 👍 |
| # Populate mrope_positions for draft models that use M-RoPE. | ||
| self._populate_mrope_positions_after_copy_expand_inputs( | ||
| cad, | ||
| batch_size, | ||
| total_num_input_tokens, | ||
| total_num_output_tokens, | ||
| target_positions, | ||
| query_end_loc, | ||
| token_indices_to_sample, | ||
| ) |
There was a problem hiding this comment.
Could we use this structure? This way the reader knows the function is not even triggered in text-only SD.
if self.uses_mrope:
self._populate_mrope_...()Signed-off-by: EanWang211123 <wangyiheng@sangfor.com.cn>
|
This pull request has merge conflicts that must be resolved before it can be |
Signed-off-by: EanWang211123 <wangyiheng@sangfor.com.cn>
Multimodal Speculative Decoding UpdateThe relevant code has been refactored to support draft-model mode for multimodal speculative decoding, and additional tests have been conducted on H20.
Key Serving Benchmark Results
This shows around 30% throughput improvement when using speculative decoding on H20 TP1. Reason for Limited Acceleration in Previous ExperimentsRegarding the previously observed limited acceleration with the Qwen3-32B + Qwen3-0.6B setup, I believe the main reason was multi-GPU communication overhead. The previous experiments were conducted on 4 × RTX 4090 with TP4, without NVLink. In contrast, the current experiments use H20 TP1, which avoids the same level of inter-GPU communication overhead and achieves around 30% throughput improvement. |
|
@EanWang211123 thanks for this PR! I tried to reproduce the benchmark with multimodal Setup: Symptom: the benchmark request returns Traceback (most recent call last):
File ".../vllm/v1/engine/core.py", line 463, in step
model_output = self.model_executor.sample_tokens(grammar_output)
...
File ".../vllm/v1/worker/gpu_model_runner.py", line 5050, in propose_draft_token_ids
draft_token_ids = self.drafter.propose(
File ".../vllm/v1/spec_decode/llm_base_proposer.py", line 575, in propose
model_kwargs, slot_mapping_size = self.build_model_inputs_first_pass(
File ".../vllm/v1/spec_decode/llm_base_proposer.py", line 960, in build_model_inputs_first_pass
self.inputs_embeds[:num_tokens] = self.model.embed_input_ids(
File ".../vllm/model_executor/models/interfaces.py", line 405, in embed_input_ids
return _merge_multimodal_embeddings(
File ".../vllm/model_executor/models/utils.py", line 492, in _merge_multimodal_embeddings
inputs_embeds[is_multimodal] = mm_embeds_flat.to(dtype=input_dtype)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^
IndexError: The shape of the mask [282] at index 0 does not match the shape of the indexed tensor [283, 2048] at index 0This looks like an off-by-one between Environment: engine self-reports Attached shell scripts and logs: |
|
This pull request has been automatically marked as stale because it has not had any activity within 90 days. It will be automatically closed if no further activity occurs within 30 days. Leave a comment if you feel this pull request should remain open. Thank you! |
Purpose
This PR follows the needs discussed in the following issues and adds support for multimodal speculative decoding in vLLM’s
draft_model(both parallel and non-parallel draft mode) mode:Currently, vLLM does not support speculative decoding for multimodal models in
draft_modelmode. This PR adds support for the following scenarios:Target model + draft model are both multimodal models (VLMs)
Example:
Qwen2.5-VL-32B-Instruct + Qwen2.5-VL-3BCross-modal speculative decoding (target model is VLM, draft model is text-only)
Example:
Qwen2.5-VL-32B-Instruct + Qwen3-0.6BQwen3-VL-8B-Instruct + amdPARD-Qwen3-0.6BThe cross-modal adaptation is implemented with reference to the speculative decoding design in SGLang standalone mode.
Background and Problem Statement
There are several key issues in the current vLLM
draft_modelpath for multimodal scenarios:Multimodal support is determined using the target model config, which causes misclassification in the “VLM target + text draft” case
M-RoPE / positional encoding cache and kernel position handling are incorrect in some paths, which can lead to:
NotImplementedError/AttributeError)When both target and draft are VLMs (both using M-RoPE), the 3D M-RoPE positions are not preserved correctly
Heterogeneous vocab-size combinations are overly restricted, blocking some practically safe combinations (e.g., using
Qwen3-0.6Bas draft forQwen2.5-VL-32B)This PR focuses on correctness and functionality enablement, so that multimodal / cross-modal speculative decoding can run correctly in
draft_modelmode.Changes
1.
eagle.py(6 changes)Use draft model config (instead of target model config) to determine
supports_mm_inputsAvoids misclassifying the draft model as multimodal in the
VLM target + text draftscenario and entering the wrong code path.Always initialize
self.positionsFixes potential
AttributeErrorwhen the Triton kernel writes position buffers in the M-RoPE draft-model path.Fix incorrect broadcasting in
_set_positionsunder concurrencyPrevents 1D
positionsfrom being accidentally reduced to a scalar and broadcasting request-0’s position to the entire batch.Fix
kernel_positionscomputation inset_inputs_first_passfor cross-modal casesIn the
target(M-RoPE) + draft(1D RoPE)scenario, computes the real sequential positions instead of using compressed M-RoPE time positions, preventing draft position / KV-cache misalignment.Fix
mrope_positionsfilling for dual-VLM (dual M-RoPE) scenariosDirectly copies the target’s 3D M-RoPE positions for input tokens, avoiding corruption of image-token H/W dimensions caused by kernel-written 1D positions.
Complete positional-encoding compatibility paths for multimodal / cross-modal speculative decoding
Unifies position propagation and cache alignment logic across M-RoPE and 1D RoPE combinations for target/draft pairs.
2.
speculative.py(1 change)Only raises when
draft_vocab > target_vocab(possible out-of-bounds risk); allows safe combinations wheredraft_vocab < target_vocab(may slightly reduce acceptance rate).Summary: Supported Scenarios
NotImplementedErrorAttributeError, incorrect M-RoPE positionsTest Plan
Dataset: MMStar
Environment: 4090D
Target model:
Qwen2.5-VL-32B-InstructDraft models:
Qwen3-0.6B,Qwen2.5-VL-3BCommands
CUDA_VISIBLE_DEVICES=4,5,6,7 \ vllm serve /models/Qwen2.5-VL-32B-Instruct/ \ --host 0.0.0.0 \ --port 30000 \ -tp 4 --max-model-len 10240 \ --gpu-memory-utilization 0.9 --max-cudagraph-capture-size 32 \ --speculative_config '{"model": "/models/Qwen3-0.6B/", "num_speculative_tokens": 3, "method": "draft_model"}'CUDA_VISIBLE_DEVICES=4,5,6,7 \ vllm serve /models/Qwen2.5-VL-32B-Instruct/ \ --host 0.0.0.0 \ --port 30000 \ -tp 4 --max-model-len 10240 \ --gpu-memory-utilization 0.9 --max-cudagraph-capture-size 32 \ --speculative_config '{"model": "/models/Qwen2.5-VL-3B/", "num_speculative_tokens": 3, "method": "draft_model"}'vllm bench serve \ --backend openai-chat \ --port 30000 \ --host 127.0.0.1 \ --endpoint /v1/chat/completions \ --model /models/Qwen2.5-VL-32B-Instruct/ \ --dataset-name hf \ --dataset-path /workspace/datasets/mmstar/ \ --hf-name Lin-Chen/MMStar \ --hf-split val \ --num-prompts 16 \ --max-concurrency 1 \ --temperature 0 \ --top-p 1.0 \ --seed 123Test Result
baseline
draft model qwen3-0.6B
draft: qwen2.5-vl-3b
Key Serving Benchmark Results
This shows around 30% throughput improvement when using speculative decoding on H20 TP1.
Result Summary and Notes
qwen2.5-32b-vl + qwen3-0.6Baverage acceptance length: 2.01qwen2.5-32b-vl + qwen2.5-vl-3baverage acceptance length: 2.29The actual throughput improvement is limited because ITL increases significantly (from ~20 ms to ~60–90+ ms).
Based on analysis, this large increase does not appear in SGLang, and the same behavior is also observed in text-only speculative decoding tests (e.g.,
Qwen3-32B + Qwen3-0.6B). Therefore, this is likely not caused by the multimodal logic introduced in this PR, but instead may be related to the draft model path not yet supporting full CUDA Graph.Essential Elements of an Effective PR Description Checklist
supported_models.mdandexamplesfor a new model.