跳到主要内容
推理优化

5.2 Draft 模型与 N-gram / Suffix 方案

独立小模型的选择与部署(大小、词表、TP 配置、TLI 跨词表),以及无需额外模型的 N-gram 与 Suffix Decoding 方案,理解接受率与收益的权衡

Draft ModelN-gramSuffix DecodingTLI接受率

5.1 节建立了投机解码的框架:提案器 + 验证器 + Rejection Sampling。这一节回答最实际的问题——提案器从哪来? 两条路线:一是用独立的小模型(Draft Model),二是完全不用模型,用 N-gram 匹配或 Suffix Decoding 这类基于文本模式的算法。前者收益高但引入部署成本,后者几乎零成本但收益有限。选哪条,取决于你的场景对”接受率”的要求。

📑 目录


1. 独立 Draft 模型:最朴素也最通用的提案器

思路一句话:拿一个同架构、同词表、小得多(通常 1/10 到 1/100 参数量)的模型当提案器。它在同一份 prompt 前缀上自回归生成 N 个候选 Token,每个候选附带草稿概率 q(x^i)q(\hat{x}_i),交给 Target 验证。

为什么”同词表”重要?因为 Rejection Sampling 需要把草稿概率 q(x^)q(\hat{x}) 和 Target 概率 p(x^)p(\hat{x})同一个 Token 上比较。词表不同(比如 tokenizer 切分方式不同),两边给出的 Token ID 无法对应,仲裁无从谈起。这也是 vLLM 的 draft_model 方法默认要求草稿与目标共享词表的原因。

实际中最经典的开箱组合(vLLM 官方文档示例):

  • Qwen/Qwen3-8B + Qwen/Qwen3-0.6B(同家族同词表,0.6B 是 8B 的 1/13)
  • meta-llama/Llama-3.1-8B-Instruct + 同家族小模型

💡 提示:同家族的小模型(Qwen3-0.6B 之于 Qwen3-8B、Llama-3.2-1B 之于 Llama-3.1-8B)是最省心的选择——词表、架构、训练分布天然对齐。跨家族的组合不是不行,但要么自己处理词表对齐(TLI,见第 4 节),要么接受接受率偏低。

1.1 收益曲线:为什么”小”是双刃剑

5.1 节的加速比公式 E[L]=1αN+11αE[L] = \frac{1-\alpha^{N+1}}{1-\alpha} 直接决定收益,而 α\alphaTdT_d 都由草稿模型的大小决定:

  • 草稿越大 → 分布 qq 越接近 ppα\alpha 越高 → E[L]E[L] 越大;但同时 TdT_d 变大,每轮草稿成本 NTdN \cdot T_d 升高
  • 草稿越小 → 草稿快但 α\alpha 低,E[L]E[L] 逼近 1,投机等于白干

最优草稿大小是”让每轮净收益最大”的平衡点,实践中靠扫参数(不同草稿大小 × 不同 N)找。经验区间:目标模型 8B 级配 0.5-1B 草稿,70B 级配 1-3B 草稿

📌 关键点:草稿模型的大小不是”越小越好”也不是”越大越好”。α\alphaTdT_d 是同一个旋钮的两端——调参的本质是在”猜得准”和”猜得快”之间找平衡。这也催生了 5.3 节的 Self-Draft 方案:它们用目标模型自己的浅层当提案器,让”猜得准”和”猜得快”不再互斥。


2. 模型选择:大小、词表与能力匹配

选择独立草稿模型时,除了大小还有三个实操维度:

2.1 词表(Vocabulary)对齐

如第 1 节所述,默认要求草稿与目标共享 tokenizer。检查方法很简单:加载两个模型的 tokenizer,对同一段文本编码,看 token ID 序列是否一致。不一致时有两个选择:

  • 换同家族的草稿模型(推荐,零成本)
  • 启用 vLLM 的 use_heterogeneous_vocab: true(TLI 算法,第 4 节,有额外限制)

2.2 能力匹配:草稿只需”像”目标,不必”懂”任务

草稿模型的核心指标只有一个:在目标模型擅长的内容上,草稿分布的接受率。它不需要具备推理能力——需要”推理”的 Token 反正大概率会被拒绝,交给 Target 兜底。但要注意极端不匹配:

  • 草稿是代码模型、目标是对话模型:代码场景接受率尚可,对话场景接受率很低
  • 草稿的知识截止日期远早于目标:新知识相关 Token(人名、新术语)几乎必被拒绝,但这类 Token 占比通常不高

💡 提示:有个反直觉的点——草稿模型不需要做任何微调也能工作(LLM 生成文本的低熵特性决定的),但微调过的草稿(用目标模型的输出分布做蒸馏式训练)接受率显著更高。vLLM 官方维护的 Speculators 库就是干这个的:用 vLLM 离线生成训练数据,训练草稿模型,再无缝接入 vLLM。

2.3 采样配置要与目标一致

草稿模型的采样参数(temperature、top_p)需要和 Target 的采样配置协调。vLLM 中 draft_sample_method 支持 greedy(贪心草稿,默认)和 probabilistic(概率草稿)。贪心草稿通常接受率更稳定,也是 TLI 模式唯一支持的选项。


3. 部署要点:TP 配置与显存

独立草稿模型是第二个模型,部署时它占用一份独立显存,这直接影响你的显存规划:

3.1 Tensor Parallel 配置

vLLM 中草稿模型的 TP 由 draft_tensor_parallel_size 控制,只能取 1 或与 Target 相同

  • draft_tensor_parallel_size=1:草稿模型完整放单卡,不参与 TP 通信。适合小草稿模型,省通信开销(默认)
  • 与 Target 相同(如 tp=4 + draft_tp=4):草稿也切 4 份。适合草稿模型大到单卡装不下的场景,例如 70B 目标配 8B 草稿、8 卡部署时

📌 关键点:TP 的直觉是”模型越大越要切”,但草稿模型追求的是低延迟——TP 引入的 AllReduce 通信可能比草稿模型本身的推理还慢。所以除非显存放不下,草稿模型用 draft_tensor_parallel_size=1 通常延迟最优。这和第 6 章”TP 不跨节点”的理由一脉相承。

3.2 显存规划

草稿模型、KV Cache、激活三者共享 GPU 显存。vLLM 的显存规划以 Target 模型为基准自动预留,但如果 gpu_memory_utilization 设得太高,草稿模型可能装不下——启动日志里会出现”draft model loading”阶段的 OOM。经验做法:给草稿模型留出与其参数量匹配的余量(如 8B 目标 + 0.6B 草稿,把利用率从 0.95 降到 0.9 左右),或使用 max_model_len 限制草稿的上下文长度。

3.3 与量化叠加

草稿模型可以独立量化(vLLM 的 speculative_config 里有 quantization 字段指定草稿的量化方式)。但注意第 5.4 节会详细讨论:量化 Target 会改变 pp,草稿的 qq 没变,接受率可能小幅下降。实践中常做的是”Target 量化、草稿保持 FP16/BF16”——草稿小,量化省下的显存有限,但保留精度对维持接受率更有利。


4. 跨词表方案:TLI 算法

现实中有时候你就是想用不同家族的草稿模型(比如手头只有某个现成的小模型)。vLLM 为此实现了 **Token-Level Intersection(TLI)**算法,通过 use_heterogeneous_vocab: true 开启:

  1. 初始化:对两个 tokenizer 的全部 Token 做字符串归一化,计算词表交集,建立草稿 Token ID → 目标 Token ID 的映射表
  2. 草稿采样:把草稿模型的 logits 约束到交集 Token 子集上,只在共享 Token 内采样
  3. 仲裁:草稿概率与 Target 概率在共享 Token 上做 Rejection Sampling;被接受的草稿 Token ID 映射回目标的词表

代价与限制:

  • 交集之外的 Token(通常是生僻词、特殊符号)永远不会被草稿猜中,接受率上限被压缩
  • 目前仅支持贪心草稿采样(draft_sample_method='greedy'),概率草稿尚不支持
  • 两个 tokenizer 差异越大,交集越小,收益越差

💡 提示:TLI 是”退而求其次”的方案。如果同词表的草稿模型不存在(比如目标模型很新、生态里没有配套小模型),TLI 能让你先用任意小模型顶上——但接受率大概率不如同家族组合。SGLang、TensorRT-LLM 也有类似机制,名字各有不同,思路一致。


5. N-gram 方案:零模型的匹配式提案

5.1 原理:从 prompt 里抄作业

N-gram 提案器的想法极其朴素:LLM 生成的内容经常在 prompt 里出现过(用户给了示例、few-shot 上下文、代码片段、重复的格式)。那么——直接在 prompt 的历史 Token 里找”最近 N 个 Token”的重复出现,把那次出现后面的 Token 抄下来当候选:

Prompt 历史:  ... A B C D E F ...  (某个位置出现过 "B C D")
当前上下文:   A B C  ← 以 "B C" 结尾
匹配:         找到历史中的 "B C",后面跟 "D E F"
候选:         D, E, F  ← 直接抄

vLLM 的实现由两个参数控制:

  • prompt_lookup_max:匹配窗口上限(默认 5)
  • prompt_lookup_min:匹配窗口下限(默认与 max 相同)

每个位置在 [min,max][\text{min}, \text{max}] 范围内找最长可匹配的窗口,把后续 Token 作为候选。找不到匹配就停止本轮提案。

5.2 优劣:零成本,但天花板低

维度评价
部署成本——不需要第二个模型,不需要额外显存,一条配置即可开启
接受率中低。只在”内容确实重复”时有效:代码补全、JSON/XML 格式、log 解析、few-shot 模仿
适用场景离线批处理、成本敏感、不想引入第二个模型的环境
天花板无法猜”没出现过”的内容——对话、创意写作等场景基本无效

📌 关键点:N-gram 的适用性可以用一句话判断——“输出是否会大量复现输入中出现过的片段”。代码生成(函数体重复)、结构化输出(重复的字段模式)、Agent 循环(重复的工具调用格式)都是好场景;开放式对话不是。

5.3 vLLM 配置

vllm serve Qwen/Qwen3-8B \
  --speculative-config '{
    "method": "ngram",
    "num_speculative_tokens": 5,
    "prompt_lookup_max": 4,
    "prompt_lookup_min": 2
  }'

💡 提示:N-gram 提案器有一个隐藏优点——它完全不占显存。在显存已经吃紧(比如量化后 KV Cache 才是大头,见 4.4 节)的部署里,N-gram 是唯一”免费”的投机手段。这也是 vLLM 官方在”方法选型总览”里把它列为”peak traffic 下不增加负载”选项的原因。


6. Suffix Decoding:比 N-gram 更聪明的模式匹配

Suffix Decoding(arXiv:2411.04975,vLLM v0.26 支持)是 N-gram 的进化版,解决 N-gram 的两个痛点:

痛点N-gramSuffix Decoding
匹配范围只在 prompt 里找prompt 和之前生成的历史都能匹配
匹配策略找到”最长窗口”就抄频率计数提出最可能的延续(统计”B C”后面跟过哪些 Token、各多少次)
草稿长度固定 N每轮自适应——根据匹配质量和接受率动态决定草稿多长

Suffix Decoding 在 vLLM 中的关键参数:

参数默认含义
suffix_decoding_max_tree_depth24前缀匹配 + 投机树的最大深度
suffix_decoding_max_cached_requests10000全局后缀树缓存的请求数上限(0 = 关闭全局缓存)
suffix_decoding_max_spec_factor1.0投机长度上限 = 前缀匹配长度 × 该系数
suffix_decoding_min_token_prob0.1提议一个 Token 的最低估计概率阈值

它的典型高收益场景:高重复性任务——代码编辑(diff 格式大量重复)、Agentic 循环(self-reflection、self-consistency 反复生成相似内容)、RL rollout(同一策略反复采样)。

💡 提示:Suffix Decoding 需要安装 arctic-inference 包(pip install arctic-inference)。另外注意它的 num_speculative_tokens上限——实际每轮草稿长度自适应,官方建议设大(16 或 32),让算法自己决定。


7. 三种方案怎么选

方案接受率部署成本显存开销典型场景
独立 Draft 模型高(同家族)中(第二个模型)中(0.5-3B 权重)追求最大加速、有配套小模型
N-gram中低内容重复度高、显存吃紧、不想加模型
Suffix Decoding中(重复任务中高)低(一个 pip 包)代码编辑、Agent 循环、RL rollout

决策直觉:

  1. 有同家族小模型 → Draft 模型,收益最大
  2. 没有小模型、但任务重复度高(代码/结构化/Agent)→ Suffix Decoding(> N-gram)
  3. 只想要个”免费的”兜底、或者显存已满 → N-gram

📌 关键点:三者的共同归宿是 5.1 的公式——加速比由 α\alphaTdT_d 决定。Draft 模型用”更贵的 TdT_d“换”更高的 α\alpha“;N-gram/Suffix 用”零 TdT_d“换”更低的 α\alpha“。没有绝对最优,只有场景适配。


📝 总结

  • 独立 Draft 模型:同家族同词表小模型,接受率最高;大小是”猜得准 vs 猜得快”的平衡点;TP 默认 1,显存需单独规划
  • 词表对齐是硬约束:共享词表是默认前提;跨词表用 TLI(Token 级交集 + logits 约束),但有接受率上限和贪心限制
  • N-gram:零成本零显存的匹配式提案,在内容重复场景有效,适合成本敏感/显存吃紧环境
  • Suffix Decoding:N-gram 的进化版——匹配历史生成、频率计数、自适应草稿长度,适合代码编辑与 Agent 循环
  • 选型直觉:有小模型选 Draft,重复任务选 Suffix,兜底选 N-gram

🎯 自我检验清单

  • 为什么草稿模型必须和 Target 共享词表?TLI 是怎么绕过这个约束的?
  • draft_tensor_parallel_size 为什么”只能取 1 或与 Target 相同”?为什么默认 1 通常更好?
  • 草稿模型大小为什么不是越大越好?α\alphaTdT_d 如何此消彼长?
  • N-gram 的匹配窗口参数(min/max)分别控制什么?
  • Suffix Decoding 相对 N-gram 的三个改进是什么?
  • 显存吃紧、任务重复度高、追求最大加速——三种情况分别选什么方案?

📚 参考资料