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