教程·阅读约 4 分钟·
vLLM 投机解码实战:方法选型、参数配置与效果验证

vLLM 投机解码实战:方法选型、参数配置与效果验证

投机解码能把 LLM 推理的吞吐往上提一大截,但 vLLM 里方法有一堆、参数也不少。这篇按官方文档梳理清楚:各种方法适合什么负载、--speculative-config 该怎么写、用哪些指标判断到底有没有提速。

四月·

原创。读完你能明确三件事:自己这套负载该选哪种投机解码方法、--speculative-config 具体怎么写、以及用哪些指标验证提速是否真实发生。

大模型推理有个反直觉的地方:decode 阶段往往不是算力不够,而是显存带宽不够。每生成一个 token,都要把整个模型权重从显存里读一遍,GPU 的计算单元大部分时间在等数据。序列越长、batch 越小,这个浪费越明显。

投机解码(speculative decoding)就是冲着这个浪费去的。思路很朴素:既然一次前向只出一个 token 太亏,那就先让一个便宜的方式猜出后面几个 token,再让主模型一次性并行验证。验证的代价和生成一个 token 差不多,但只要猜中,就能一次吐出多个 token。

本文全部配置与参数说明来自 vLLM 官方文档(当前版本 v0.29.0),涉及出处的部分都附了链接,方便你对照原文。

先搞清楚收益是怎么来的

投机解码的流程可以拆成三步:

  1. 提议:由 drafter 生成 K 个候选 token(K 就是配置里的 num_speculative_tokens)。
  2. 验证:主模型对这段候选做一次前向,并行算出每个位置的输出分布。
  3. 接受:从前往后逐个比对,接受匹配的候选前缀,再额外吐出 1 个由主模型自己决定的"奖励 token"。

关键在于接受长度。如果一次验证平均能接受 3 个草稿 token,加上奖励 token,这一次前向就产出了 4 个 token,相当于 4 倍效率。官方的 per-request 指标里,mean_acceptance_length 的定义就是"每个验证步平均产出多少 token(含奖励 token)",取值范围从 1.0(一个都没接受)到 K+1。

所以投机解码的效果完全取决于两件事:草稿质量(接受率高不高)和额外开销(drafter 自己算得贵不贵)。这两点决定了下面该怎么选方法。

还有一个容易误解的点:投机解码是精确的。它的接受/拒绝机制经过设计,能保证最终输出分布和直接用主模型采样一致——它不是在"近似"模型行为,这一点和量化、蒸馏那些会改变输出的手段有本质区别。真正损失的只有速度:当草稿全被拒绝时,你白付了一次 drafter 的开销。所以它不会让模型变笨,只可能让推理变慢。

—— 广告 ——

vLLM 里有哪些方法

按官方文档,vLLM 目前支持这一组方法,可以分为"基于模型"和"免模型"两类:

  • 基于模型:EAGLE、MTP(多 token 预测)、Draft Model、PARD(并行草稿模型)、MLP speculator
  • 免模型:N-gram、Suffix decoding(后缀解码)
  • 进阶:Custom Proposer(自定义提议器,实验性)、Dynamic Speculative Decoding、Adaptive Verification

官方给了一张定性的选型表,我把它整理成中文:

方法低 QPS(看延迟)高 QPS(看吞吐)说明
EAGLE高收益中到高通用性最强的模型方法
MTP高收益中到高目标模型原生支持 MTP 时最合适
Draft model高收益需要额外一个草稿模型
PARD(并行草稿)高收益中到高草稿模型延迟低
MLP speculator中到高有兼容的 MLP 草稿器时可用
N-gram低到中轻量、开启成本最低
Suffix decoding低到中不需要额外模型,推测深度动态调整
Dynamic Speculative Decoding高收益高于基础方法适合 RL 场景或 QPS 波动大
Adaptive Verification高收益高于基础方法按草稿置信度决定验证规模,目前仅 DSpark

这张表只是起点。真实收益取决于你的模型家族、流量模式、硬件和采样设置——同一套配置换台机器可能就从"很香"变成"更慢",后面讲验证方法就是为这件事准备的。

三种上手指路

所有方法都通过同一个入口配置:命令行上传 --speculative-config 一个 JSON 对象,Python 里则是 LLM(..., speculative_config={...})

上手一:N-gram,零额外模型

最便宜的一档。它不需要任何草稿模型,而是在当前请求已经出现过的文本里做 n-gram 匹配,把匹配到的后续片段当草稿。官方文档对它的定位是"轻量、容易开",适合代码补全、摘要改写、问答这类有明显重复模式的负载。

code
vllm serve <target-model> \
  --speculative-config '{
    "method": "ngram",
    "num_speculative_tokens": 4,
    "prompt_lookup_min": 2,
    "prompt_lookup_max": 5
  }'

prompt_lookup_minprompt_lookup_max 控制匹配窗口的下限和上限,都是整数、最小为 1。如果两个都不填,默认是 5。

上手二:Draft model,配一个独立草稿模型

这是最直观的一种:找一个小模型当草稿器,同一个家族最好。官方给的最小示例:

code
vllm serve <target-model> \
  --speculative-config '{
    "method": "draft_model",
    "model": "<draft-model>",
    "num_speculative_tokens": 5
  }'

这里有个容易踩的坑:默认情况下 vLLM 要求草稿模型和目标模型共享同一个词表。如果你手上的小模型来自别的模型家族、分词器不同,就得打开跨词表支持:

code
from vllm import LLM, SamplingParams
 
llm = LLM(
    model="<target-model>",
    speculative_config={
        "method": "draft_model",
        "model": "HuggingFaceTB/SmolLM2-135M-Instruct",
        "num_speculative_tokens": 3,
        "use_heterogeneous_vocab": True,
    },
    gpu_memory_utilization=0.5,
)
 
params = SamplingParams(temperature=0.0, max_tokens=256)
out = llm.generate(["用一句话解释投机解码。"], params)
print(out[0].outputs[0].text)

use_heterogeneous_vocab 打开的是官方叫 Token-Level Intersection(TLI) 的算法:初始化时在两个词表之间建立 token 级交集,把草稿 logits 约束到共享 token 上。要注意它只兼容 method=draft_model,而且开启之后暂时不支持概率式的草稿采样(draft_sample_method='probabilistic')。

上手三:EAGLE-3,走草稿头

EAGLE 系列是目前通用性最强的模型方法。它不下载一个独立的完整模型,而是用一个轻量的草稿头(EAGLE head)。配置里把 method 设成 eagle3model 指向配套的草稿检查点即可。如果目标模型自带 MTP 支持(比如 Gemma 4 的 assistant 检查点),则应该用 "method": "mtp",把 assistant 检查点填到 model

这里有个官方明确点出的诊断点:如果你用的是 Gemma 4 assistant 检查点,但启动日志里显示 SpeculativeConfig(method='draft_model', ...),说明你装的 vLLM 版本还不包含这条路径的 Gemma 4 MTP 支持。正确做法是升级 vLLM,而不是硬把一个 assistant 检查点塞进通用草稿模型流程里跑。

常用参数逐个说清

--speculative-config 里有一批跨方法通用的键,官方文档列出的主要几个:

类型默认含义
methodstring推测方法,常见值:draft_modelngramsuffixmtpeagle3dflash。不填时 vLLM 会尽量从配置推断
modelstring草稿模型、EAGLE 头或辅助模型标识。ngramngram_gpusuffixmtp 通常可以省略
num_speculative_tokens整数 > 0每步提议的草稿 token 数。对不能从模型元数据推断的方法,这一项必填
draft_tensor_parallel_size整数 ≥ 1草稿模型的张量并行大小
max_model_len整数 ≥ 1草稿模型的最大上下文长度
parallel_drafting布尔false并行生成草稿 token,只兼容 EAGLE 和 draft model
rejection_sample_methodstringstandard可选 standardsyntheticblock
use_heterogeneous_vocab布尔false允许草稿与目标模型词表不同(TLI),仅 draft_model 可用

另外,n-gram 的 prompt_lookup_min / prompt_lookup_max 见上一节;suffix decoding 也有一组专属键,默认值都写在文档里:suffix_decoding_max_tree_depth(默认 24)、suffix_decoding_max_cached_requests(默认 10000,设 0 关闭全局缓存)、suffix_decoding_max_spec_factor(默认 1.0)、suffix_decoding_min_token_prob(默认 0.1)。

num_speculative_tokens 怎么定

这个参数最需要你按自己的负载调,没有万能值。官方示例里出现过 3、4、5、8 等不同数值,说明它跟方法和模型强相关。实践中有两条经验值得记:

一是别一上来就调大。 草稿越长,被接受的比例通常越低,而验证开销基本是固定的一次前向。草稿长度从 5 加到 15,收益可能先涨一点然后进入平台期,甚至因为接受率下降而变差。官方在 AMD 上的那篇实测也提到过这个现象:增大 num_speculative_tokens 在最初几个档位可能提升吞吐,更大的值会导致平台期或吞吐下降。

二是用指标说话,不要凭感觉。 vLLM 提供了 per-request 的投机解码指标,开启方式是在启动服务时加 --per-request-spec-decode-metrics summary(或 detailed)。开启后,响应里会带上 metrics.speculative_decoding 字段,其中最关键的两个是:

  • mean_acceptance_length:平均每步产出多少 token(含奖励 token),1.0 表示一个都没接受。
  • draft_acceptance_rate:草稿 token 被接受的比例。

判断逻辑很直接:如果接受率长期低于一半,说明这个 drafter 跟你的负载不匹配,换方法或换草稿模型比继续调长度更有效;如果接受长度稳定在 2 以上,再考虑加大草稿长度往上压榨。

效果验证:别只用"感觉快了"

投机解码最常见的翻车方式是:单请求演示时很快,一上生产、并发一高反而更慢。原因是 drafter 也要占算力和显存,高并发下它会和目标模型抢资源。

所以验证要分两种负载各测一遍:

低 QPS、看延迟:单请求、batch 小,重点看首 token 之后的生成速度(TPOT)和端到端时间。这时模型方法(EAGLE、draft model)通常收益最大。

高 QPS、看吞吐:拉满并发,看整体 token/s。这时免模型方法(n-gram、suffix)的优势在于不增加峰值负载——它们不引入额外模型,代价只在你已经有匹配模式时才产生。官方选型表里这两类方法在"高 QPS 吞吐"一列被标成"中"收益,就是这个意思。

官方给了两个可复现的测量入口:仓库里的 examples/features/speculative_decoding/spec_decode_offline.py,以及基准测试 CLI 指南。前者可以直接跑离线对比,后者适合做服务端压测。

服务端还有一组聚合指标可以对账。per-request 的字段和 Prometheus 计数器是能对上的(在全部请求 n=1 的负载下):num_spec_steps 对应 vllm:spec_decode_num_drafts_totalnum_draft_tokens 对应 vllm:spec_decode_num_draft_tokens_totalnum_accepted_draft_tokens 对应 vllm:spec_decode_num_accepted_tokens_total。用 /metrics 拉这几个数,就能在不改客户端的前提下看线上接受率。

它和其他加速手段怎么配合

投机解码不是唯一一条提速路径,理解它和另外几种手段的关系,能少走弯路。

量化(把权重压到 FP8/INT4)降的是"每读一个权重花多少带宽",属于直接缓解瓶颈;投机解码降的是"每个 token 需要几次前向"。两者是正交的,可以叠加,而且量化的收益更稳、更容易预估。如果你还没做过量化,先做它。

前缀缓存(automatic prefix caching)让多轮对话里重复的前缀不用重算,省的是 prefill。投机解码省的是 decode。真实 agent 负载通常两者都占大头,值得都开。

连续批处理(continuous batching)提升的是 GPU 利用率,会让"一次前向处理多个请求"更划算。这里有个交互点值得注意:batch 越大,decode 阶段本身越接近计算受限,投机解码能榨出的相对收益就越小——这解释了为什么官方选型表把免模型方法在高 QPS 一栏标成"中",模型方法在后一栏从"高"降到"中到高"。

所以一个务实的顺序是:先把量化和前缀缓存开了,把批处理调好,让基础吞吐接近这台机器的上限;再用投机解码去优化剩下那部分延迟。反过来做,很容易在一条本来就受限的路径上叠加复杂度,最后分不清收益来自哪里。

几个容易踩的坑

temperaturetop_p 不是 --speculative-config 的字段。 它们是采样参数,别塞进这个 JSON 里。

YAML 配置文件里不要写成转义字符串。 --speculative-config 在命令行上收的是 JSON 对象;在 YAML 配置文件里应该写成嵌套 mapping,而不是把 JSON 转义成一行字符串。

别去设置 vLLM 内部字段。 target_model_configdraft_model_configtarget_parallel_configdraft_parallel_configdraft_load_config 这些是运行时自己填的,用户不该碰。

草稿模型和目标模型分布差太远,接受率会很低。 这是推断测解码收益的第一性原因。同家族、同分词器、或者至少在同类数据上训过的草稿器,表现会明显好于随便找个小模型顶上。

流式响应里指标在最后一个 usage chunk 上。 想拿到 speculative_decoding 字段,得开启 usage 上报——请求里设 stream_options.include_usage: true,或者服务端启动时加 --enable-force-include-usage

一条建议的上手路径

如果你只是想知道投机解码值不值得开:先在你真实的流量样本上跑一遍 n-gram,成本几乎为零,能立刻看出这套负载里有多少可复用的重复模式。n-gram 没收益,说明文本重复度低,直接跳到模型方法。

要上模型方法,就从和你目标模型同家族的 EAGLE 草稿头或官方 MTP 支持开始,这是接受率的上限所在。开完之后固定住并发和输入分布,只看 mean_acceptance_length 和端到端速度这两组数,再决定要不要调 num_speculative_tokens

最后提醒一句:投机解码是用显存和算力换延迟的技术。如果你的瓶颈本来就在显存容量或者并发度上,它可能会让情况更糟。先确认瓶颈在带宽,再考虑它。

参考:

分享到
微博Twitter

© 2026 四月

原文链接:https://www.aprilzz.com/tutorials/vllm-speculative-decoding