vLLM 推理服务的推测解码
目录
简介在决定之前本指南在 Alauda AI 上验证过的方法推荐起始点内部验证快照 — N-gram内部验证快照 — EAGLE-3前提条件配置面在 Alauda AI 上提供 Model Artifacts单 artifact 模式(N-gram)双 artifact 模式(EAGLE-3 及类似方法)选项 A — KServestorageUris(可用时推荐)选项 B — 单个 OCI Modelcar,其中包含两个 artifact选项 C — 预先放置在共享 PVC 上在 A / B / C 之间选择端到端示例示例 1 — N-gram示例 2 — 在共享 PVC 上同时放置 target + draft 的 EAGLE-3验证并衡量影响1. 确认配置已应用2. 确认推测解码正在实际运行3. 衡量端到端影响4. 如何报告或比较数据回滚故障排查注意事项和已知限制参考资料简介
推测解码允许 vLLM server 在每个 decode 步骤中提出多个 token,并通过目标 model 的单次 forward pass 对其进行验证,从而在不改变输出分布的情况下,降低交互式工作负载的单 token 延迟。
本页重点介绍如何在 Alauda AI 上为运行中的 InferenceService 启用、配置、验证以及回滚推测解码。关于该技术本身以及 vLLM 支持的完整方法列表,请参阅 vLLM speculative decoding 文档。
推测解码涉及对 runtime 版本敏感的 flags。下面提到的 --speculative-config JSON keys、支持的 method 值以及 metric 名称,都取决于你 runtime image 中的 vLLM 版本。本文中的所有示例都应视为起点,并请以你实际发布的 vLLM 版本为准进行确认。
在决定之前
当单个请求的 decode loop 主导端到端延迟,并且提议的 token 能够以足够高的接受率被采纳,从而摊薄提议开销时,推测解码才会带来收益。
它通常适用于:
- 交互式 chat / agent 循环,且后续输出相对可预测。
- 摘要、RAG answers 和 code completion,此类场景中输出与 prompt 有较高重叠。
在以下场景中,它可能带来负收益或基本无影响:
- 高温度采样,此时接受率会大幅下降。
- 高 QPS / batch 饱和的服务,此时 decode 容量已不再空闲。vLLM 团队在 2024 年的 V0-engine benchmarks 中报告,在高 QPS 下,同一数据集上出现了 1.4×–1.8× 的变慢。V1 engine 的调度方式不同,因此在你的 runtime 上幅度可能不同,但风险方向是相同的。
- 非常小的 target model,此时验证步骤本身已经很便宜。
在将推测解码设为默认值之前,请先在具有代表性的 workload 上进行验证。请参见 验证并衡量影响。
本指南在 Alauda AI 上验证过的方法
下面两个方法是本指南覆盖的内容,并且已在 Alauda AI 上完成端到端验证。vLLM upstream 还支持其他方法(例如适用于带有 multi-token-prediction heads 的 model 的 MTP、Medusa、MLP Speculator、Suffix、Draft Model),这些方法也可能通过相同的 --speculative-config flag 在 Alauda AI 上使用。它们不在本文范围内,因此请参考 upstream 文档,并在你自己的环境中验证后再推广到生产。
说明:
- vLLM upstream 将 N-gram 描述为“适用于摘要和问答等用例,因为 prompt 与 answer 之间存在显著重叠”。
- vLLM upstream 将 EAGLE-3 描述为“当前推测解码算法的 SOTA”(摘自最新 features 页;请按每个 release 重新确认)。
推荐起始点
不存在一种适用于所有 workload 的最佳方法。以下是保守的起始点,用于降低试错成本。在推广到生产之前,请始终结合你自己的流量进行验证。
内部验证快照 — N-gram
上面的起始点是建议,不是保证。下面的测量结果来自 Alauda AI 内部实验室的一个具体数据点,目的是帮助你对类似的单 GPU serving 场景形成预期。你自己的 model、GPU、runtime 版本和流量会产生不同结果——在推广到生产之前务必进行 benchmark。
- 硬件: NVIDIA A30 24 GB × 1
- Model: Qwen3-8B(BF16,HuggingFace
Qwen/Qwen3-8B) - Runtime: vLLM 0.19.1(V1 engine)
- 请求参数:
temperature=0、seed=42、max_tokens=1024、enable_thinking=false、单个并发请求、1 次 warmup 丢弃 + 3 次计时运行(报告中位数)
基线命令(不使用 spec decode):
N-gram 命令(仅 --speculative-config 不同):
Workloads:
- code refactor(高 prompt-output 重叠): 要求 model 为一个 30 行的 Python class 添加 docstring 和类型注解,并返回完整更新后的 class
- general chat(无 prompt-output 重叠): 要求 model 用不少于 800 个词解释一个概念
结果:
解读:
- 在这个单 GPU 8B 场景中,N-gram 在 code refactor workload 上表现为轻微回退,而在 chat 上则出现了明显的约 15% 回退。提议器的 CPU 工作、每步对 5 个候选 token 的验证,以及 vLLM 在 N-gram 下禁用 async scheduling,这些开销加在一起超过了被接受 token 带来的收益。
- 对高重叠 code workload 而言,接受率是健康的(在更早的非正式探测中,平均接受长度约为 3),但仅有接受率并不能预测端到端加速——每步开销必须与 target model 的实际 decode 时间摊销后才有意义。在单 GPU 的小型 target model 上,decode 本身已经很便宜,可供摊销的空间很小。
- chat 结果验证了 注意事项 中关于缺少 prompt-output 重叠的 workload 的判断。
同样的方法在更大的 target model 上(此时每次 verify step 的成本更高)、在 multi-GPU tensor parallelism 下,或在更高并发下,可能表现得非常不同。请把这个快照当作“需要测量”的提醒,而不是对 N-gram 本身的结论。
内部验证快照 — EAGLE-3
上面的起始点是建议,不是保证。下面的测量结果来自 Alauda AI 内部实验室的一个具体数据点,目的是帮助你对类似的单 GPU EAGLE-3 场景形成预期。你自己的 model、GPU、runtime 版本和流量会产生不同结果——在推广到生产之前务必进行 benchmark。
- 硬件: NVIDIA A30 24 GB × 1
- Model: Meta-Llama-3.1-8B-Instruct(BF16,HuggingFace
meta-llama/Meta-Llama-3.1-8B-Instruct),EAGLE-3 draft 为yuhuili/EAGLE3-LLaMA3.1-Instruct-8B - Runtime: vLLM 0.19.1(V1 engine)
- 请求参数:
temperature=0、seed=42、max_tokens=1024、单个并发请求、1 次 warmup 丢弃 + 3 次计时运行(报告中位数)
基线命令(不使用 spec decode):
EAGLE-3 命令(仅 --speculative-config 不同):
Workloads:
- code refactor(高 prompt-output 重叠): 要求 model 为一个 30 行的 Python class 添加 docstring 和类型注解,并返回完整更新后的 class
- general chat(无 prompt-output 重叠): 要求 model 用不少于 800 个词解释一个概念
结果:
Speedup 是 tok/s 的比值(与 completion length 无关)。Wall delta 直接比较中位数墙钟时间;chat 运行产生的输出数量不同(基线 588 token,而 EAGLE-3 为 709 token),因此在该场景下 Speedup 是更可靠的指标。
推测解码行为(EAGLE-3 侧,来自 SpecDecoding metrics 日志窗口):
平均接受长度和接受率是按各 benchmark 运行所覆盖的 SpecDecoding metrics 日志窗口中的 draft 加权结果;按位置的数值来自每次运行中的持续负载窗口。
解读:
- 在这个单 GPU 8B 场景中,EAGLE-3 在 code refactor 上带来了 约 1.84× 的加速,而在 general chat 上则基本持平(约 0.99×)。两个基线运行的 tok/s 都稳定在约 47.8,这与预期一致——基础 decode 速率是 model 与硬件的属性,不依赖 prompt 内容。所有可观察到的差异都来自 EAGLE-3 一侧。
- 为什么 code 有收益而 chat 没有——接受率数据直接揭示了机制。在 code 场景中,draft head 在每个 decode step 平均能命中约 2.54 个 token,接受率约 51%,因此大多数 step 会输出多个 token;按位置接受率下降得很慢(0.50 / 0.40 / 0.33),因此即使第 3 个 speculative slot 仍然有约三分之一的时间产生收益。在 chat 场景中,平均接受长度只有约 1.19,接受率约 6%,并且按位置接受率在第 2 个 slot 就迅速崩塌(0.16 / 0.02 / 0.01)——几乎每一步都只输出经过验证的 token,而 draft 出来的 token 都被丢弃。
- 实际效果 vs 理论上限。 在没有 proposer 开销的理想情况下,平均接受长度是理论上的最大加速上限。code 场景实际达到 1.84×,而理论上限为 2.54×(约 72% 转化),也就是说 proposer 的 CPU 工作、对被拒绝提议的验证,以及 async scheduling 的成本,吞掉了大约四分之一的可用空间。chat 场景的 1.19× 理论上限则完全被开销吃掉,甚至略微转为回退。这与 Caveats 中的说明一致:在单 GPU 的小模型上,每步开销几乎没有空闲 decode 容量可供隐藏。
同样的方法在更大的 target model 上(此时每次 verify step 的成本更高)、在 multi-GPU tensor parallelism 下,或在更高并发下,可能表现得非常不同。请把这个快照当作“需要测量”的提醒,而不是对 EAGLE-3 本身的结论。
前提条件
- 一个已安装 KServe 的 Kubernetes 集群,以及可以创建
InferenceService资源的 namespace。 - 平台上已注册一个 vLLM serving runtime,并且其 vLLM 版本支持你计划使用的 speculative method。要检查版本,请进入该 runtime 的运行中 pod:
kubectl exec <pod> -- python3 -c "import vllm; print(vllm.__version__)"。 - 目标 model 可通过其 storage source(model repository、PVC 或 OCI image)供服务访问。
- 对于 EAGLE-3:一个 draft head,其 architecture、tokenizer 和 base version 与精确的 target model 匹配。若 head 不匹配,通常会悄悄降低接受率,而且不一定会以启动错误的形式暴露。
- 对于 EAGLE-3:一种可以将 target 和 draft 同时送入同一 pod 的 model-artifact 加载机制。请参阅 在 Alauda AI 上提供 Model Artifacts。
配置面
在 vLLM v1 中,推测解码通过一个参数启用:
常见 keys:
method:要使用的 proposer。本指南使用的值为ngram和eagle3。upstream 还存在其他值(例如medusa,或模型特定的 MTP 名称如deepseek_mtp)——请在 vLLM speculative decoding 文档中确认你方法对应的确切值。num_speculative_tokens:每步提议多少个 token。更高的值可能提升 speedup,但也会在被拒绝的提议上浪费计算。model:对于加载单独 draft artifact 的方法(例如 EAGLE-3),这是容器内该 artifact 的路径。- 方法特定的 keys,例如 N-gram 的
prompt_lookup_max/prompt_lookup_min。这些名称在不同 vLLM release 中发生过变化——请以你发布的版本为准进行验证。
其他所有 vLLM 参数(--model、--tensor-parallel-size、--gpu-memory-utilization,等等)都与非推测解码部署中的用法相同。
在 Alauda AI 上提供 Model Artifacts
不同的方法需要 predictor pod 中的不同文件。
单 artifact 模式(N-gram)
N-gram 只需要 target model。像其他 inference service 一样,直接使用 storageUri 即可:
model 会落到 /mnt/models,并通过 --model 传递给 vLLM。
双 artifact 模式(EAGLE-3 及类似方法)
EAGLE-3 需要将 target model 以及 匹配的 draft head 加载到同一个 pod 中。共有三种受支持的交付方式。请根据平台版本、网络访问条件和运维偏好进行选择。
选项 A — KServe storageUris(可用时推荐)
storageUris 是一个 KServe 字段,支持多个 storage location,并将每个 location 挂载到声明的路径。当你的平台 KServe 版本支持它时,这是最简洁的选择(KServe 0.16 及更高版本)。
然后将 vLLM 指向这两个路径:
需要注意的约束:
storageUri(单数)和storageUris(复数)互斥。- 所有
mountPath值都必须是绝对路径,并共享一个公共父目录(例如/mnt/models/target和/mnt/models/draft)。 - 对于私有仓库,请将相应的 credentials secret 绑定到 predictor pod 使用的 service account 上。
如果你平台上的 KServe 版本尚不支持 storageUris,请使用选项 B 或选项 C。
选项 B — 单个 OCI Modelcar,其中包含两个 artifact
将 target model 和 draft head 打包进同一个 OCI image 中,并放在可预测的子目录下(例如 /models/target 和 /models/draft),然后使用 storageUri: oci://... 进行部署。打包步骤请参阅 使用 KServe Modelcar 存储模型。可写入镜像的示例磁盘布局如下:
随后 vLLM 命令引用同样的路径:
对于离线 / air-gapped 集群,此选项非常适合,因为这些 artifact 会一起版本化,并从你自己的 registry 拉取。
选项 C — 预先放置在共享 PVC 上
将两个 artifact 预先放置到某个 PVC 的已知目录结构中,挂载该 PVC,并在 vLLM 命令中引用本地路径。如果你已经在共享文件系统上管理 model 文件,这是最简单的方案。
在 A / B / C 之间选择
端到端示例
下面两个示例覆盖了 Alauda AI 上可用的方法 中列出的方法。请将 <your-namespace>、<your-vllm-runtime> 和 storage URIs 替换为你环境中的实际值。
示例 1 — N-gram
- 请替换为你的实际 model 名称;该注解由平台用于展示。
prompt_lookup_*keys 属于 n-gram proposer。这些名称在不同 vLLM release 之间发生过变化——请以 runtime image 内的版本为准进行验证。
示例 2 — 在共享 PVC 上同时放置 target + draft 的 EAGLE-3
该 manifest 与上文 内部验证快照 — EAGLE-3 中使用的 setup 一致。target model 和 EAGLE-3 draft head 都已预先放置在同一个 PVC 中的可预测子目录下;PVC 通过 storageUri: pvc://... 挂载到 /mnt/models/,而 vLLM 命令直接引用这两个子目录。
- vLLM 命令中的两个路径(
--model和--speculative-config内的modelkey)必须与 PVC 内部 的目录名完全一致。如果你的 PVC 使用了不同的目录名,请同步调整这两个路径。 - EAGLE-3 head 会占用
--gpu-memory-utilization预算之外的 GPU memory。保留余量(这里用0.8而不是0.9)可以在两个 artifact 同时加载时降低 OOM 风险。 pvc://<your-pvc-name>/期望一个已预先放置目标 model 和 EAGLE-3 draft head 的 PVC;PVC 根目录会挂载到/mnt/models/,因此两个 artifact 必须分别位于/mnt/models/<target-subdir>/和/mnt/models/<draft-subdir>/。请参见下方期望布局。如果你更希望使用声明式多 URI 挂载(KServe 0.16+),或者改为将 target + draft 打包进单个 OCI image,请参阅 在提供 Model Artifacts 中的选项 A 或选项 B。
PVC 内部的期望布局(在 pod 中挂载到 /mnt/models/):
pod 启动后,从 predictor pod 内部验证布局:
使用以下命令应用上述任意 manifest:
验证并衡量影响
确认已配置推测解码是一回事;确认它确实改善了你的 workload则是另一回事。
1. 确认配置已应用
在 predictor command 中查找 --speculative-config,并确认 readiness 状态:
2. 确认推测解码正在实际运行
启动时的第一个信号是 engine-config 日志行;它会打印 engine 解析出的 speculative_config,因此你可以据此验证 method 和 draft path 是否生效:
对于实时 counter,vLLM 会在 /metrics 暴露 Prometheus metrics。确切的 metric 名称取决于 vLLM 版本,因此应先进行广泛匹配:
如果没有返回任何内容,说明 pod 还没有处理过任何请求(counter 只会在第一次 generation 完成后才发布),或者你所用 vLLM build 中的 metric 名称不同——此时应回退查看 predictor 日志。
vLLM 会打印一条按窗口汇总的摘要行,这是最直观的实时视图。以下是 vLLM 0.19.1 在 num_speculative_tokens=3 时的真实输出格式:
如何阅读它:
- Mean acceptance length — 每个 decode step 平均交付的 token 数。基线为
1。这是你在该 workload 上可期望获得的实际加速上限。 - Avg Draft acceptance rate — 被接受的提议 token 占所有提议 token 的整体比例。它回答的是“proposer 大多在发挥作用,还是大多在浪费计算?”这一问题。
- Per-position acceptance rate — 第
1..num_speculative_tokens个 slot 的逐位接受率。你会看到恰好num_speculative_tokens个值——上面的示例之所以有 3 个值,是因为运行时使用了num_speculative_tokens=3;而一个num_speculative_tokens=5的ngram运行会打印 5 个值。健康的曲线下降应较缓;如果在第 2 个 slot 附近就降到接近 0,说明该 workload 不适合这个 proposer。
3. 衡量端到端影响
对同一个具有代表性的 workload 运行两次:
- 移除
--speculative-config(基线)。 - 启用它(其他配置完全相同,包括
--seed)。
每次运行记录三个数值:
- 首 token 时间(TTFT)。
- 单 token 延迟(或固定输出长度下的端到端延迟)。
- 在你实际服务的 QPS 下的吞吐量(tokens/second)。
如果在目标 QPS 下这三项都得到改善,那么就值得保留推测解码。常见失败模式是:低 QPS 时有提升,但在生产 QPS 下反而回退——因此务必在你真实运行的负载下进行测量。
4. 如何报告或比较数据
如果没有上下文,性能数据既无法复现,也无法被信任。每当你发布一组对比——无论是内部、客户报告,还是反馈给平台团队——都应包含下面五项字段。缺少任意一项的数据都应被视为轶事,而非证据。
Spec-decode 命令(仅 --speculative-config 不同):
结果:
进行对比时,有两个实用原则:
- 两侧都使用相同的
--seed和temperature=0,并在计时前对每个 service 先进行 3 次丢弃的 warmup 请求——否则采样噪声和 compile-cache 噪声会主导你测到的差异。 - 基线和 spec-decode 必须针对同一组固定 prompt 列表,且顺序相同,每个 prompt 至少运行 5–10 次,并比较中位数而不是平均值。
回滚
如果要在不改动其他内容的情况下禁用推测解码,只需从 predictor command 中移除 --speculative-config 这一行并重新应用:
或者重新应用一个不包含该 flag 的 manifest:
服务会以不含 speculative proposer 的新 revision 进行滚动更新。对于 N-gram,不需要更改任何 model artifact。对于 EAGLE-3,draft head 会继续挂载但不会被使用——如果你想回收磁盘空间,请在下一次变更时移除 draft-head artifact(删除选项 A 中对应的 storageUris entry、在不包含 draft 目录的情况下重新构建选项 B 的 OCI image,或者在选项 C 中删除 PVC 的 draft 子目录)。
故障排查
对于 pod 级别的问题,可以使用标准的 inference-service 故障排查命令:
注意事项和已知限制
- 结果会随 workload 形态大幅波动——回退和加速都是真实存在的。 upstream V0 benchmarks 在高 QPS 下报告了 1.4×–1.8× 的变慢。我们自己的 A30 + Qwen3-8B N-gram 测试(参见 内部验证快照 — N-gram)即使在高重叠的 code workload 上也出现了轻微回退。在相同硬件上,Llama-3.1-8B 的 EAGLE-3(参见 内部验证快照 — EAGLE-3)在 code-refactor 上达到了 1.84× 加速,但在 chat 上基本持平(约 0.99×)——同一个 model、同一种方法、同一个 pod,仅仅因为 prompt 形态不同,实际收益就出现了 2 倍的差异。请始终结合你的生产流量 profile 进行验证。
- N-gram 会禁用 async scheduling。 在最近的 vLLM 版本中,启用
ngram方法会强制关闭 async scheduling(predictor 日志会显示Async scheduling not supported with ngram-based speculative decoding and will be disabled)。如果你的 service 依赖 async scheduling 来提升吞吐量,优先考虑 EAGLE-3,或者明确测量这种权衡。 storageUris的可用性。 该字段从 KServe 0.16 开始提供。较旧的平台版本必须使用 Modelcar 或 PVC 方案。- Draft head 不匹配不会报错。 与 target model 不完全匹配的 draft head 通常也能正常启动并提供服务,但接受率会非常低。启用后务必检查接受率。
- 采样参数会影响接受率。 高温度会降低接受率;请使用与生产使用一致的采样配置进行 benchmark。
gpu-memory-utilization预算。 draft artifact(EAGLE-3 head、MLP speculator、draft model)不计入--gpu-memory-utilization预算;在添加 draft artifact 时应降低该值。- 镜像依赖。 runtime image 必须包含所选方法所需的库。如果某个方法初始化失败,请重建或替换 runtime image——参见 扩展 Inference Runtimes。
min_p和logit_bias会被静默忽略。 在推测解码下,vLLM 在 engine init 期间会记录警告min_p and logit_bias parameters won't work with speculative decoding.。传入这两个采样参数的请求仍会收到 200 响应,但参数不会被真正执行——如果你的流量依赖它们,请验证这一点是否符合客户端预期。- 与其他功能的组合。 推测解码可以与 tensor parallelism 和 continuous batching 组合使用,但它与 autoscaling 以及 EP / advanced parallelism 的交互会随 vLLM 版本而变化。带有 draft artifact 时冷启动尤其昂贵:在我们 A30 + Llama-3.1-8B + EAGLE-3 head 的实验环境中,predictor 从 container-ready 到
Application startup complete约耗时 5 分钟(weights load 约 45 秒,draft weights 约 5 秒,torch.compilebackbone 约 48 秒,torch.compileEAGLE head 约 17 秒,CUDA-graph capture 和 warmup 约 10 秒,外加约 2 分钟的 engine profiling 和 KV-cache sizing)。readiness deadline 应按这个时间设置,而不是按非推测解码基线来设置:在Standardmode 下,predictor readiness probe 必须能容忍 5 分钟启动;在Knativemode 下,serving.knative.dev/progress-deadlineannotation 应设置为1800s。任何 autoscaling 的 scale-from-zero SLO 也应按同样的时间来设定。 - 输出等价性。 vLLM 表示,推测解码不会改变输出分布。这是 vLLM 的属性,不是 Alauda AI 的保证——如果你的 runtime image 需要严格的等价性,请将其作为验收测试的一部分进行验证。