投机解码接受率的诊断与 --speculative-config 取值
投机解码(speculative decoding)用一个更便宜的 draft 模型先提议若干 token,再由 target 模型一次并行前向批量校验,被接受的位置直接采纳,出现分歧的位置重新采样。它能否兑现收益由接受率(acceptance rate)决定:接受率高时,一次 target 校验换回多个 token,draft 前向与采样的成本被摊薄;接受率低时,draft 前向未产生收益,target 校验开销仍需支付,端到端时延反而上升。收益窗口落在中低 QPS、访存受限的场景;高 QPS、算力已经打满时,多出的 draft 前向会挤占本可以服务其它请求的算力。
机制:接受率决定收益是否成立
一次投机步里,target 模型对 draft 提议的 num_speculative_tokens 个位置做一次并行前向,逐位置判定接受或拒绝。这一步涉及两个成本项与一个收益项:
- 成本项一:draft 模型为提议这些 token 付出的前向与采样成本。
- 成本项二:target 模型为校验这些位置付出的前向成本,无论接受与否都要支付。
- 收益项:一次 target 前向能够确认下来的有效 token 数。
接受率高时,收益项接近提议长度,target 的一次前向换回多个 token,成本项一被有效摊薄;接受率低时,收益项退化为接近 1,即退回「一次前向产生一个 token」,而成本项一并不随之消失。两者之间存在一个临界区间:接受率低于该区间时,draft 前向加校验的总开销超过节省下来的部分,收益不成立,甚至转负。临界区间的具体位置取决于 draft 与 target 的相对规模、每步提议长度与硬件的访存特性,不能跨配置照搬。
收益窗口的边界需要写清。投机解码的定位是降低中低 QPS、访存受限场景下的单 token 延迟,用更多算力换取更少的前向次数。在一组常见规格(8 卡 ****,单卡 64GB HBM,卡间互联约 392 GB/s,BF16;规格为估算示例,非实测)下,decode 阶段的单 token 生成受显存带宽约束、算力常有闲置,这构成投机解码的收益空间。高 QPS、算力已经打满时,多出的 draft 前向直接挤占本可以服务其它请求的算力。
参数与取值:--speculative-config 的键
投机解码的配置通过一个专用入口传入。vLLM 的 CLI 参数为 --speculative-config(传入 JSON 对象),Python 侧对应 speculative_config。它是独立的配置集合,与采样参数分开,三个易错点先列出:
temperature、top_p属于采样参数,不是投机配置里的字段。- 张量并行不在该配置里写
tensor_parallel_size,draft 侧使用draft_tensor_parallel_size。 target_model_config、draft_model_config、draft_load_config是运行时自行填充的内部字段,不应手动设置。
常用键与取值如下,具体以文档为准:
| 键 | 类型 | 典型值 / 默认 | 含义 |
|---|---|---|---|
method |
string | draft_model / ngram / suffix / mtp / eagle3 等 |
投机方法,省略时运行时尝试推断 |
model |
string | 草稿模型或投机头标识 | ngram、suffix、mtp 常可省略 |
num_speculative_tokens |
int > 0 | 每步提议数,文档示例中为 3、4、5、8 | 每步提议的 token 数 |
draft_tensor_parallel_size |
int >= 1 | 无默认 | draft 模型的张量并行度 |
max_model_len |
int >= 1 | 无默认 | draft 模型的最大上下文长度 |
parallel_drafting |
bool | 默认 false |
并行草稿生成,仅 EAGLE 与 draft-model 支持 |
rejection_sample_method |
string | standard / synthetic / block,默认 standard |
拒绝采样方式 |
synthetic_acceptance_rates |
list[float] | 长度须等于 num_speculative_tokens,非递增 |
synthetic 模式下逐位置的无条件接受率 |
use_heterogeneous_vocab |
bool | 默认 false |
允许 draft 与 target 词表不同(TLI),仅 method=draft_model |
轻量方法各有自己的键。n-gram 使用 prompt_lookup_max、prompt_lookup_min(两者都省略时默认均取 5)。suffix 使用 suffix_decoding_max_tree_depth(默认 24)、suffix_decoding_max_cached_requests(默认 10000)、suffix_decoding_max_spec_factor(默认 1.0)、suffix_decoding_min_token_prob(默认 0.1)。
rejection_sample_method 取 synthetic 时配合 synthetic_acceptance_rates 使用:后者给出一组假定的逐位置接受率,长度须等于 num_speculative_tokens 且非递增,用于在没有合适草稿模型时先估算收益窗口,判断该方法是否值得接入。
# 模型类方法:draft 模型 + 每步提议数
vllm serve <target-model> \
--speculative-config '{"method":"draft_model","model":"<draft-model>","num_speculative_tokens":5}'
# 轻量方法:n-gram,不需要单独草稿模型
vllm serve <target-model> \
--speculative-config '{"method":"ngram","num_speculative_tokens":4,"prompt_lookup_min":2,"prompt_lookup_max":5}'
另有两类版本约束:草稿模型类的投机解码在早期版本并不支持;管线并行在一段版本区间内与投机解码不可组合。配置能解析、服务能起来,不等于这条路在该基础软件栈版本上真的被支持。排查此类问题时,应把框架版本与基础软件栈版本一并记录,再用最小配置复现,避免把版本不支持误判为配置写错。
触发条件:让接受率下降的工况
接受率不是配置项,而是负载与模型组合的函数。让接受率下降的工况有几类典型:
- 长上下文。 draft 模型的
max_model_len决定它能看到多长的历史。上下文变长后,draft 对远距离依赖的建模减弱,提议质量下降,接受率随上下文长度衰减。同时,校验阶段的 target 前向在长上下文下更贵,KV cache 更大、注意力成本随长度上升,draft 的相对成本被重新放缩,收益窗口整体偏移。 - 高温采样 / 高随机性。 采样温度越高,target 的分布越平坦,draft 与 target 逐 token 对齐的概率越低,接受率随随机性上升而下降。
- draft 与 target 分布不一致。 这是根因。接受率本质上是 target 在同一下文下愿意接受 draft 提议的概率,取决于两者预测分布的接近程度。draft 若来自不同训练数据、不同词表或不同模型族,分布错位,接受率就低。
use_heterogeneous_vocab允许跨词表草稿,但词表交集处的约束会改变 draft 的可选空间,对接受率并不免费。 - 短输出。 输出只有几十个 token 时,收益还没有摊开请求就结束,开销占主导。
- 批量构成变化。 批里混入长短不一、随机性不一的请求时,接受率是各请求的混合值,均值会掩盖少数低接受率请求把整体收益拉低的事实。
现象:可观测的形态
- 投机配置按要求传入,服务正常启动,但开启后吞吐或时延反而变差。
- 接受率随上下文长度衰减:短上下文有效,长上下文收益归零甚至转负。
- draft 阶段成本吃掉收益:draft 前向本身不便宜,接受率不足以把它摊掉。
- 端到端收益与接受率不成比例:接受率看着不低,收益却没有兑现,说明瓶颈不在提案环节,而在校验阶段,或落在被均值掩盖的混合批上。
启动日志可以直接看到方法被解析成了什么,这是排查的第一步:
# 启动阶段:确认方法解析结果与提议步长(字段名示意)
SpeculativeConfig(method='draft_model', model='<draft-model>', num_speculative_tokens=5, ...)
# 运行阶段:按请求看接受情况,而不是只看全局平均
accept_rate # 接受率
throughput_before / throughput_after # 关闭 / 开启两组吞吐
latency_p95_before / latency_p95_after # 关闭 / 开启两组尾延迟
quality_delta # 质量差异,确认无损性是否被破坏
定位步骤
按顺序执行,每步给出具体动作。
- 确认接受率是否真的低。 打开按请求粒度的接受率统计(vLLM 提供 Per-Request Acceptance Metrics 一类能力),看分布而非只看全局均值。区分「普遍偏低」与「少数请求极低把均值拉低」两种情况,后者要按请求类型分组统计。统计时按上下文长度与温度分组:批内低接受率与高接受率请求混在一起时,全局均值无法反映收益是否成立。
- 确认收益是否被开销抵消。 接受率不低而收益仍不成立时,问题多在成本侧:draft 前向成本、校验阶段成本,或两者叠加的调度开销。此时按阶段分段记账(prefill / decode),不要只看端到端一个数。
- 对照工况。 逐条比对上一节的触发条件:是否长上下文、温度是否偏高、draft 与 target 是否同源、输出是否过短、QPS 是否已经打满算力。把当前负载形态与收益窗口对一遍,通常能定位到其中一条。
- 确认无损性。 接受率调优不能以质量为代价,
quality_delta必须落在可接受范围内。
把这几个字段固定成记录模板:
# 投机解码结论记录清单(字段名示意)
target_model: # 目标模型
draft_model: # 草稿模型 / 方法
method: # draft_model / ngram / suffix ...
num_speculative_tokens: # 每步提议数
context_window: # 上下文长度区间,长/短分开记
accept_rate: # 接受率,按请求粒度
throughput_before: # 关闭时吞吐
throughput_after: # 开启时吞吐
latency_p95_before: # 关闭时尾延迟
latency_p95_after: # 开启时尾延迟
quality_delta: # 质量差异
verdict: # 收益成立 / 被开销抵消 / 工况不匹配
处置
- 先小步调
num_speculative_tokens。 它是收益与开销的调节旋钮:调大,理论上限更高,但 draft 成本与校验负担同步上升,接受率不够时亏损更快;调小,回到保守区间。从保守值起步,按接受率朝一个方向推,不要一次拉满。 - 接受率结构性偏低时,换方法而非调参数。 分布错位无法靠增大提议数修好。此时可考虑不需要单独草稿模型的轻量方法(n-gram、suffix),它们开销小、对高 QPS 更友好,代价是收益上限较低。中低 QPS、访存受限且目标模型有原生 MTP 支持时,MTP 一类方法更容易拿到收益。
- 收益窗口不成立时关闭,并记成一条结论。 以长上下文为主、温度偏高、输出很短,或 QPS 已经打满算力的场景,收益窗口本就不成立。关闭并写明「在什么工况下不成立」,比为维持开启而拿一个更差的数更有价值。
- 注意与其它特性的叠加取舍。 在 MoE 模型上,专家相关的算子路径优化与投机解码各自的收益窗口不同,叠加时收益不是简单相加,两边的开销可能落在同一条资源上,出现内存收益与吞吐收益不同时成立的情况,需要分别对照、分别记账。
小结
接受率的诊断顺序固定:先确认它是否真的低并按请求粒度看分布,再看收益是否被开销抵消并分段记账,再对照工况,最后确认 quality_delta 无损。接受率落在收益窗口之外、或收益被额外开销抵消,都属于需要单独记账的结论;记录时应写清适用的上下文长度、温度、输出长度与 QPS 区间。