vLLM 投机解码之 Draft Model 实践指南:离线/在线配置、跨词表 Token-Level Intersection(TLI)与源码剖析
投机解码(Speculative Decoding)是 vLLM 降低中低 QPS、访存受限场景下逐 Token 生成延迟的关键特性。本文以 vLLM 的 Draft Model(独立草稿模型) 方法为主线,完整讲解其在离线(LLM)与在线(vllm serve)两种模式下的配置方式、--speculative-config 的统一参数语义,以及当草稿模型与目标模型词表不一致时的 Token-Level Intersection(TLI)解决方案,并深入 vllm/config/speculative.py 与 vllm/v1/spec_decode/vocab_mapping.py 等源码,说明参数校验与底层实现原理。读完本文,你将能够独立为自有目标模型挑选并配置一个兼容的草稿模型,跑通端到端加速,并理解"同词表"约束的来龙去脉与突破方法。
一、Draft Model 方法定位:何时值得引入一个独立小模型
在 vLLM 的投机解码方法矩阵中,模型驱动型方法(EAGLE、MTP、Draft Model、PARD、MLP)通常能带来最大的延迟收益,而 n-gram、后缀解码等"免额外模型"方法则更轻量。见 投机解码总览 README 的方法选择对照表:
| 方法 | 低 QPS(延迟导向) | 高 QPS(吞吐导向) | 备注 |
|---|---|---|---|
| EAGLE | 高收益 | 中高收益 | 通用型模型驱动方法 |
| MTP | 高收益 | 中高收益 | 目标模型原生支持 MTP 时最佳 |
| Draft model | 高收益 | 中收益 | 需要单独加载一个草稿模型 |
| Parallel Draft Model | 高收益 | 中高收益 | 草稿模型推理延迟低 |
| MLP speculator | 中高收益 | 中收益 | 需有兼容的 MLP speculator |
| N-gram | 低中收益 | 中收益 | 轻量、易开启 |
| Suffix decoding | 低中收益 | 中收益 | 无需额外模型、动态投机深度 |
Draft Model 的核心思路:让一个体积远小于目标模型(通常是同系列的小尺寸版本)的草稿模型快速自回归地推测出 K 个候选 Token,再由目标模型对这 K 个 Token 做一次前向验证,通过拒绝采样(rejection sampling)保证输出分布与直接解码一致,从而在保持"无损"的前提下减少目标模型的串行解码步数。该机制在 vLLM 中由 SpeculativeConfig(method='draft_model') 驱动,每次解码最多追加一个额外 drafting slot(见下文源码分析)。
需要量化评估收益时,可参考仓库自带的复现脚本 examples/features/speculative_decoding/spec_decode_offline.py,它以
--method draft_model与--draft-model参数支持多数据集、多参数的对比测量。
二、离线模式(Offline)配置:LLM + speculative_config
以下代码来自 draft_model 文档,在离线模式下配置 vLLM 使用草稿模型进行投机解码,每次最多投机 5 个 Token:
from vllm import LLM, SamplingParams
prompts = ["The future of AI is"]
sampling_params = SamplingParams(temperature=0.8, top_p=0.95)
llm = LLM(
model="Qwen/Qwen3-8B",
tensor_parallel_size=1,
speculative_config={
"model": "Qwen/Qwen3-0.6B",
"num_speculative_tokens": 5,
"method": "draft_model",
},
)
outputs = llm.generate(prompts, sampling_params)
for output in outputs:
prompt = output.prompt
generated_text = output.outputs[0].text
print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")
配置要点解读:
method: "draft_model"显式声明投机方法为"独立草稿模型";model指向草稿模型标识(Hugging Face Hub 模型 ID 或本地权重路径),例如用Qwen/Qwen3-0.6B为Qwen/Qwen3-8B服务;num_speculative_tokens: 5表示草稿模型每步推进 5 个候选 Token,需要大于 0(Field(gt=0)校验,见 vllm/config/speculative.py);tensor_parallel_size是目标模型的并行度,草稿模型的并行度另由draft_tensor_parallel_size单独指定(默认继承目标模型设置)。
参数自动推断与必填约束
源码 vllm/config/speculative.py 中的模型校验器说明了两条硬性规则:
- 若
speculative_config中同时提供了model,vLLM 会尽量自动推断method;只有无法推断时才要求必须显式给出method。 num_speculative_tokens在大多数情况下必须显式提供,除非草稿模型自身的 Hugging Face 配置中带有n_predict之类的元数据(例如部分官方 MTP 类检查点)可以由框架自动推导。
从源码结构看,draft model 类投机在 vLLM 中仍属于"显式提供候选数"的路径,因此建议始终在配置里写清楚 num_speculative_tokens,避免歧义。
三、在线模式(Online)配置:vllm serve 与 --speculative-config
与离线模式等价的在线启动方式同样来自 draft_model 文档:
vllm serve Qwen/Qwen3-4B-Thinking-2507 \
--host 0.0.0.0 \
--port 8000 \
--seed 42 \
-tp 1 \
--max-model-len 2048 \
--gpu-memory-utilization 0.8 \
--speculative-config '{"model": "Qwen/Qwen3-0.6B", "num_speculative_tokens": 5, "method": "draft_model"}'
各服务端参数的作用:
--host/--port/--seed:控制 API 监听地址、端口与随机种子(保证可复现);-tp 1:目标模型的张量并行度;--max-model-len 2048:上下文窗口上限;--gpu-memory-utilization 0.8:允许框架使用 80% 的显存。由于投机解码需要同时驻留目标模型与草稿模型两套权重(以及草稿模型的一份 KV cache),实践中通常需要为此预留更多显存预算;--speculative-config '{"model": ..., "num_speculative_tokens": 5, "method": "draft_model"}':以 JSON 字符串统一传入全部投机解码配置。
重要:CLI 应使用 --speculative-config 而非旧参数
文档末尾给出的 重要告警 明确指出:与投机解码相关的全部配置都应通过 --speculative-config 设置。此前通过 --speculative-model 指定模型、再单独追加 --num-speculative-tokens 等参数的老式写法已弃用(deprecated)。支持的 key 列表与示例请查阅 --speculative-config schema 章节。
--speculative-config 在命令行接收一个 JSON 对象;在 YAML 配置文件中则使用嵌套映射而非转义 JSON 字符串。同一组 key 既可用于 CLI,也可通过 Python LLM(..., speculative_config={...}) 传入,二者语义完全一致。
客户端代码保持不变
启用投机解码后,作为客户端的请求代码完全不需要改动。以 vLLM 的 OpenAI 兼容 API 为例(代码同样来自 draft_model 文档):
from openai import OpenAI
# 修改 OpenAI 的 API key 与 base,使其指向 vLLM 的 API server
openai_api_key = "EMPTY"
openai_api_base = "http://localhost:8000/v1"
client = OpenAI(
api_key=openai_api_key, # 默认读取 os.environ.get("OPENAI_API_KEY")
base_url=openai_api_base,
)
models = client.models.list()
model = models.data[0].id
# Completion API
stream = False
completion = client.completions.create(
model=model,
prompt="The future of AI is",
echo=False,
n=1,
stream=stream,
)
print("Completion results:")
if stream:
for c in completion:
print(c)
else:
print(completion)
投机解码对服务端调用方完全透明:加速逻辑只发生在引擎内部,模型 ID、采样参数、流式行为都不受影响。
四、草稿模型选型与"同词表"默认约束
4.1 默认要求目标与草稿模型词表完全一致
使用 method='draft_model' 时,vLLM 默认要求草稿模型与目标模型共享同一套词表(tokenizer)。该约束体现在 verify_equal_vocab_size_if_draft_model 中:
- 若未开启
use_heterogeneous_vocab,引擎会比较target_model_config.get_vocab_size()与draft_model_config.get_vocab_size(); - 若两者不相等,会直接抛出
ValueError,提示"Target and draft model should have the same vocabulary size……使用不同 tokenizer 的模型会在投机解码期间引发越界错误(out-of-bounds)"。
这一约束背后是概率语义的完整性:拒绝采样需要在目标与草稿的输出空间上做逐 Token 的概率比较与 ID 对齐,词表不一致会导致草稿采出的 token ID 在目标词表中无意义,甚至访问越界。因此最稳妥的选型是目标模型同系列的小尺寸版本(如 Qwen3-8B → Qwen3-0.6B,Llama-8B → Llama-1B),它们共享 tokenizer,词表天然一致。
4.2 草稿模型的张量并行约束
在投机配置中,目标模型与草稿模型的并行度是分开管理的:
- 目标模型用
tensor_parallel_size(-tp); - 草稿模型用
draft_tensor_parallel_size,其校验为ge=1(见 vllm/config/speculative.py)。
特别注意:tensor_parallel_size 不是 speculative_config 的合法 key。若误传,配置校验器会直接报错并提示改用 draft_tensor_parallel_size(见 vllm/config/speculative.py)。而在当前 draft-model 路径的实现中(见 DraftModelProposer),草稿与目标模型的 TP 大小必须相等——源码注释说明,当目标 TP>1 而草稿 TP=1 时,各 rank 会在 rank 0 上重复编译 TP=1 的草稿模型,导致 torch.compile 缓存被覆盖损坏,因此当前实现直接拒绝两者不一致。
4.3 调度槽位的影响
从 max_num_new_slots_for_drafting 这一属性可以清楚看到各方法对调度预算的额外占用:标准(非并行)draft model 每条解码请求只额外占用 1 个 drafting 槽位(草稿输入保留一个未切分的 token);而若开启 parallel_drafting: true(PARD 式并行草稿),所有 K 个候选位置都需要额外槽位,占用变为 K。因此 parallel_drafting 需与经过专门训练、支持并行草稿的模型配合(可参考 PARD 说明)。
五、speculative_config 常用 key 速查
下表提炼自 README 的 common keys 表,并与 SpeculativeConfig 字段定义 逐一对齐:
| Key | 类型 | 默认值 | 含义 / 取值 |
|---|---|---|---|
method |
string | None |
投机方法:常见 draft_model、ngram、suffix、mtp、eagle3、dflash 等;若提供 model 可自动推断 |
model |
string | None |
草稿模型 / EAGLE head / 辅助模型标识;对 ngram、suffix、mtp 通常可省略 |
num_speculative_tokens |
integer > 0 | None |
每步投机的 Token 数;无法从模型元数据推断时必填 |
draft_tensor_parallel_size |
integer >= 1 | None |
草稿模型的张量并行度 |
max_model_len |
integer >= 1 | None |
草稿模型的最大上下文长度 |
parallel_drafting |
boolean | false |
并行草稿生成,仅兼容 EAGLE 与 draft-model 方法 |
rejection_sample_method |
string | standard |
standard / synthetic / block |
synthetic_acceptance_rates |
list[float] | None |
synthetic 拒绝采样下按位置的接受率,元素在 [0,1] 且长度须等于 num_speculative_tokens 并单调不增 |
synthetic_acceptance_length |
float | None |
synthetic 的目标平均接受长度,位于 [1, num_speculative_tokens + 1],与上者互斥 |
use_heterogeneous_vocab |
boolean | false |
允许词表不同的草稿/目标模型;初始化时构造 token 级交集并约束草稿 logits,仅兼容 method=draft_model |
同时有几个常见的"非字段"值得提醒(源码中均有对应校验):
temperature、top_p属于采样参数(SamplingParams),不是--speculative-config的字段;target_model_config、draft_model_config、target_parallel_config、draft_parallel_config、draft_load_config等内部字段由 vLLM 在初始化时填充,用户不应自行设置;- 草稿模型的采样方式由
draft_sample_method控制:默认greedy(取 argmax,草稿概率在拒绝采样中被视作 one-hot),probabilistic则从草稿分布随机采样并使用完整草稿 logits 做概率比检验,代价是额外显存开销(见 draft_sample_method 定义)。
特别注意:文档提示 Gemma 4 的 assistant 检查点应按 MTP speculator 处理而非通用草稿模型(
"method": "mtp",详见 MTP 指南)。若启动日志中出现对 Gemma 4 assistant 检查点使用SpeculativeConfig(method='draft_model', ...)的记录,说明当前安装的 vLLM 版本缺少对应支持,应升级版本而非强行走通用 draft-model 路径。
六、进阶:跨词表草稿模型与 Token-Level Intersection(TLI)
前文提到默认约束要求同词表,这限制了草稿模型的选型范围——例如某些任务上不同模型家族的更优小模型无法被利用。为突破该约束,vLLM 提供 use_heterogeneous_vocab: true,启用 Token-Level Intersection(TLI) 算法,允许使用来自不同模型家族、tokenizer 不同的草稿模型。
6.1 示例配置
以下代码同样来自 draft_model 文档:用 SmolLM2-135M 为 Qwen3-8B 做草稿推理,尽管二者词表不同:
from vllm import LLM, SamplingParams
prompts = ["The future of AI is"]
sampling_params = SamplingParams(temperature=0.8, top_p=0.95)
llm = LLM(
model="Qwen/Qwen3-8B",
speculative_config={
"method": "draft_model",
"model": "HuggingFaceTB/SmolLM2-135M-Instruct",
"num_speculative_tokens": 3,
"use_heterogeneous_vocab": True,
},
gpu_memory_utilization=0.5,
)
outputs = llm.generate(prompts, sampling_params)
for output in outputs:
prompt = output.prompt
generated_text = output.outputs[0].text
print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")
6.2 当前能力边界:仅支持 greedy 草稿采样
文档明确指出:当前 use_heterogeneous_vocab 要求 draft_sample_method='greedy'(即默认值)。概率式草稿采样暂不支持,计划在未来版本中补全。这一限制在源码校验器中有明确反映(见 vllm/config/speculative.py):
use_heterogeneous_vocab且方法不是draft_model→ 报错"仅支持 method='draft_model'";use_heterogeneous_vocab且draft_sample_method != 'greedy'→ 报错并要求显式使用 greedy 或省略该项;- 反过来,只要未开启
use_heterogeneous_vocab,就会强制执行前文的同词表大小校验。
6.3 TLI 的底层实现机制
从源码可以还原 TLI 的完整工作流程。当 use_heterogeneous_vocab=True 时,引擎在初始化阶段分别加载目标与草稿模型的 tokenizer,构建 VocabMapping(见 vllm/v1/spec_decode/draft_model.py 与 vllm/v1/spec_decode/vocab_mapping.py):
- Token 字符串归一化:对两个词表中的 token 字符串做归一化(
_normalize_token,见 vocab_mapping.py),剔除空格前缀差异等噪声; - 求交集:对归一化后的 token 取两个词表的公共集合
common_tokens,构建target→draft与draft→target的双向 ID 映射表,并生成一张草稿侧布尔掩码intersection_mask_draft标记哪些草稿 token 在交集中;初始化日志会输出交集规模(占草稿/目标词表的百分比),若交集过小(<100 个 token)会给出告警; - 约束草稿 logits:草稿采样前,用
constrain_draft_logits将不在交集内的 token 的 logits 掩码为-inf(见 vocab_mapping.py),使草稿只能从"双方共有且语义对齐"的 token 中采样; - ID 双向翻译:将目标侧输入映射为草稿侧 ID 喂给草稿模型(
map_target_to_draft_ids),草稿采出的 ID 再映射回目标词表(map_draft_to_target_ids),未命中交集的 ID 回退到各自词表的 unk_token(见 vocab_mapping.py)。
正是"把 logits 约束到交集后再采样、再把 ID 翻译回目标词表"这一设计,使得即使词表不同,拒绝采样依然在双方共享的 token 子空间上进行,从而保持投机解码的无损性。也正因如此,概率式草稿采样与 TLI 的组合目前尚不可行——概率式采样需要完整草稿分布上的概率比检验,在映射后的交集子空间上的实现仍在规划中。
七、无损保证与可复现性提示
投机解码的价值前提是"加速但不改变输出分布"。vLLM 将其分解为理论、算法与数值三层来看待(详见 README 无损保证章节):
- 理论无损:投机采样算法本身在硬件数值精度范围内无损;
- 算法无损:vLLM 的拒绝采样器收敛到目标分布、且"开启/关闭投机时贪心采样结果一致",仓库中
tests/v1/spec_decode下的端到端测试大量覆盖了该性质; - 数值层面:vLLM 不保证跨运行完全一致的 logprob,浮点精度与批量大小都可能造成轻微输出差异,讨论见 FAQ。
因此,即使配置正确,开启投机后与关闭投机的输出在极少数边界情形下可能出现微小差异,这不属于正确性问题,而是硬件数值行为。
八、小结:接入 Draft Model 的检查清单
- 选一个与目标模型同 tokenizer 的小模型作为草稿;若必须跨家族,使用
use_heterogeneous_vocab: true并保持draft_sample_method='greedy'; - 在
speculative_config中同时给出method: "draft_model"、model、num_speculative_tokens(>0); - 在线模式统一使用
--speculative-configJSON 传参,废弃--speculative-model旧写法;YAML 配置使用嵌套映射; - 勿将
tensor_parallel_size、temperature、top_p等误放入投机配置;草稿模型并行度用draft_tensor_parallel_size,且当前 draft-model 路径要求其与目标 TP 相等; - 为"目标 + 草稿"两份权重预留显存,并参考 examples/features/speculative_decoding/spec_decode_offline.py 做同环境下的加速比测量后再上线。
相关深入资料:Draft Model 原始文档、投机解码方法总览与参数 schema、EAGLE 指南、PARD 并行草稿说明、配置字段完整定义 vllm/config/speculative.py、运行时提案器实现 vllm/v1/spec_decode/draft_model.py、词表映射实现 vllm/v1/spec_decode/vocab_mapping.py。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00