投机解码接受率的诊断与 --speculative-config 取值

投机解码(speculative decoding)用一个更便宜的 draft 模型先提议若干 token,再由 target 模型一次并行前向批量校验,被接受的位置直接采纳,出现分歧的位置重新采样。它能否兑现收益由接受率(acceptance rate)决定:接受率高时,一次 target 校验换回多个 token,draft 前向与采样的成本被摊薄;接受率低时,draft 前向未产生收益,target 校验开销仍需支付,端到端时延反而上升。收益窗口落在中低 QPS、访存受限的场景;高 QPS、算力已经打满时,多出的 draft 前向会挤占本可以服务其它请求的算力。

机制:接受率决定收益是否成立

一次投机步里,target 模型对 draft 提议的 num_speculative_tokens 个位置做一次并行前向,逐位置判定接受或拒绝。这一步涉及两个成本项与一个收益项:

接受率高时,收益项接近提议长度,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。它是独立的配置集合,与采样参数分开,三个易错点先列出:

常用键与取值如下,具体以文档为准:

键 类型 典型值 / 默认 含义
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}'

另有两类版本约束:草稿模型类的投机解码在早期版本并不支持;管线并行在一段版本区间内与投机解码不可组合。配置能解析、服务能起来,不等于这条路在该基础软件栈版本上真的被支持。排查此类问题时,应把框架版本与基础软件栈版本一并记录,再用最小配置复现,避免把版本不支持误判为配置写错。

触发条件:让接受率下降的工况

接受率不是配置项,而是负载与模型组合的函数。让接受率下降的工况有几类典型:

现象:可观测的形态

启动日志可以直接看到方法被解析成了什么,这是排查的第一步:

# 启动阶段:确认方法解析结果与提议步长(字段名示意)
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                              # 质量差异,确认无损性是否被破坏

定位步骤

按顺序执行,每步给出具体动作。

  1. 确认接受率是否真的低。 打开按请求粒度的接受率统计(vLLM 提供 Per-Request Acceptance Metrics 一类能力),看分布而非只看全局均值。区分「普遍偏低」与「少数请求极低把均值拉低」两种情况,后者要按请求类型分组统计。统计时按上下文长度与温度分组:批内低接受率与高接受率请求混在一起时,全局均值无法反映收益是否成立。
  2. 确认收益是否被开销抵消。 接受率不低而收益仍不成立时,问题多在成本侧:draft 前向成本、校验阶段成本,或两者叠加的调度开销。此时按阶段分段记账(prefill / decode),不要只看端到端一个数。
  3. 对照工况。 逐条比对上一节的触发条件:是否长上下文、温度是否偏高、draft 与 target 是否同源、输出是否过短、QPS 是否已经打满算力。把当前负载形态与收益窗口对一遍,通常能定位到其中一条。
  4. 确认无损性。 接受率调优不能以质量为代价,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:                 # 收益成立 / 被开销抵消 / 工况不匹配

处置

小结

接受率的诊断顺序固定:先确认它是否真的低并按请求粒度看分布,再看收益是否被开销抵消并分段记账,再对照工况,最后确认 quality_delta 无损。接受率落在收益窗口之外、或收益被额外开销抵消,都属于需要单独记账的结论;记录时应写清适用的上下文长度、温度、输出长度与 QPS 区间。