让 2B 当侦察兵:投机解码的原理、实测数据与失效边界
上一篇我们提到,2B 小模型最"值钱"的用法之一是给大模型当草稿机。这个技术的正式名字叫投机解码(Speculative Decoding)。它的效果相当戏剧化:有人实测把 27B 模型的生成速度从 25 tok/s 提到 45 tok/s,也有人实测把 60 tok/s 掉到 45 tok/s——用的是同一个技术。
本文面向没有推理优化背景的读者,先讲清"为什么能加速",再讲清"为什么有时反而变慢",中间穿插多组真实实测数据。
一、先搞懂一个反直觉的事实:大模型生成慢,不是因为算不过来
这是理解投机解码的唯一前提。
大模型生成文字是一个字一个字往外蹦的(术语叫自回归)。每蹦一个字,都要把整个模型的全部权重从显存里完整读一遍。以 27B 模型 Q4 量化为例,权重约 16 GB——也就是说,每生成一个字,就要搬运 16 GB 数据。
这带来一个关键结论:瓶颈在内存带宽,不在计算能力。显卡的计算单元绝大部分时间在空转等数据。用一张带宽 936 GB/s 的 RTX 3090 举例,理论上限就是 936 ÷ 16 ≈ 58 tok/s,无论这张卡的算力有多强。
所以加速的方向有两条:
- 减少要搬运的数据量(量化、用小模型)——代价是质量。
- 让空转的算力干点活——这正是投机解码的思路,且不损失任何质量。
二、核心思路:让便宜的小模型去"猜",让贵的大模型去"批改"
打个比方。大模型是个写文章很慢但水平很高的资深编辑,小模型是个打字飞快但水平一般的实习生。
- 传统方式:编辑一个字一个字亲自写,写一个字想一下,慢。
- 投机解码:实习生先一口气猜出接下来 5 个字,编辑拿过来一次性扫一眼——“前 3 个对,第 4 个不对”。于是前 3 个字直接采纳(几乎白赚),第 4 个字由编辑自己改,第 5 个字扔掉重来。
关键在于:编辑一次性检查 5 个字的成本,和检查 1 个字的成本几乎一样。因为权重的搬运量在一次前向传播里是固定的,检查 5 个 token 只是把这条数据多算几遍而已——而算力本来就是空闲的。
这个"猜 → 批量验证 → 采纳正确前缀"的循环,就是投机解码的全部机制。三个关键点:
- 小模型(草稿模型)通常比大模型小 5 到 10 倍,猜一个字的速度快得多。
- 大模型(目标模型)逐字验证,因此输出结果与它单独生成时完全一致——这是无损的,不是取舍。
- 收益完全取决于接受率:猜得越准,白赚的字越多。
关于第 2 点值得强调:投机解码改变的是"每个大模型前向传播能产出几个字",而不是"产出什么字"。所以它不是近似、不是投机取巧的质量妥协——这一点让它成为罕见的"免费午餐",前提是草稿猜得准。
三、决定成败的唯一指标:接受率
接受率(acceptance rate)= 被大模型采纳的草稿 token 数 ÷ 草稿总数。
行业里已经形成较一致的经验阈值:
| 接受率 | 结论 |
|---|---|
| 70% 以上 | 值得开启,收益显著 |
| 60% ~ 70% | 收益有限,需实测权衡 |
| 60% 以下 | 通常不划算,建议关掉 |
接受率由两个因素决定:
因素一:草稿模型与目标模型的"血缘关系"。 两者必须同家族、共用同一个分词器。用 Qwen 系列的草稿去猜 Llama 系列的目标,词表都不一样,token ID 无法对应,验证环节直接失效。这是硬性要求,不是优化建议。
因素二:任务的可预测性。 这是一组实测数据(Gemma 4 31B,draft-max 8)在不同任务上的接受率:
| 任务类型 | 接受率 |
|---|---|
| 数学推理 | 61.7% |
| 代码 | 47.9% |
| 翻译 | 40.9% |
| 创意写作 | 34.7% |
| 科学问答 | 34.6% |
规律很清楚:结构化、可预测的内容(代码、JSON、公式、样板文)接受率最高;开放式创意内容最低。原因也很直白——代码的下一个字往往是可以推断的,而小说的下一个字全凭作者心意。
还有一个常被忽略的细节:草稿位置越靠后,接受率越低。实测衰减曲线是这样:
| 草稿的第 N 个 token | 接受率 |
|---|---|
| 第 1 个 | 95% |
| 第 3 个 | 88% |
| 第 5 个 | 79% |
| 第 8 个 | 72% |
这解释了一个重要的调参现象:草稿长度不是越长越好。猜 16 个 token,后面大部分会作废,反而浪费验证算力。
四、真实实测数据(含一个"翻车"案例)
以下数据来自 2026 年公开的多组实测,涵盖不同硬件与方案。
成功案例:
| 配置 | 结果 |
|---|---|
| Qwopus3.6-27B-MTP(Q4_K_M),双 RTX 2080 Ti 22GB | 34.03 tok/s,接受率 63.03%(574/897) |
| Qwen3.6-27B + MTP,单张 A10G 24GB | 25 → 45 tok/s(+78%) |
| Qwen3.6-27B + MTP,Apple Silicon,接受率 82% | 7 → 16 tok/s(约 2.3 倍) |
| Llama 3.1 8B + 1B 草稿(草稿长度 5) | 1.83 倍 |
| Gemma 4 31B,Strix Halo,草稿长度 4 | +103%(该硬件上最优) |
翻车案例(务必记住这个):
| 配置 | 结果 |
|---|---|
| Gemma 4 + 不匹配的 int4 草稿,接受率仅 41% | 60 → 45 tok/s(−25%) |
同一个技术,一头是 2.3 倍,另一头是负 25%。分水岭就是接受率:41% 的接受率下,草稿模型本身的运行开销和验证浪费超过了收益。
还有一个容易被误读的现象,值得单独说明:
| 草稿长度 | Strix Halo(共享内存) | RTX 5090(独立显存) |
|---|---|---|
| 2 | +71% | — |
| 4 | +103%(最优) | — |
| 8 | +47%(反而变差) | 最优 |
| 16 | +65% | 数学任务达 99 tok/s |
同一个草稿长度,在两种硬件上结论相反。 原因在于验证批次的成本随草稿长度增长:在带宽紧张的共享内存设备上,为了验证 9 个 token 而多搬的权重,把收益吃掉了;而在带宽充裕的独立显卡上,验证成本微不足道,草稿越长越是纯赚。
结论:草稿长度必须针对自己的硬件实测调优,没有通用最优值。
五、2026 年的重大变化:命令参数已改名
如果你照着 2024–2025 年的博客或教程抄命令,现在会直接报错。 llama.cpp 在 2026 年重命名了这一组参数,统一到 --spec- 前缀下:
| 旧参数(已移除) | 新参数 | 含义 |
|---|---|---|
--draft-max | --spec-draft-n-max N | 每轮最多草稿多少 token(默认 3) |
--draft-min | --spec-draft-n-min N | 低于此数量则放弃该轮草稿 |
--draft-p-min | --spec-draft-p-min P | 草稿置信度低于此值即停止 |
-ngld | --spec-draft-ngl N | 草稿模型放到 GPU 的层数 |
| (新增) | --spec-type <类型> | 选择投机方式 |
其中 -md / --model-draft(指定草稿模型文件)没有改名,仍然可用。
最容易踩的坑:--spec-type 默认值是 none。 这意味着如果你只写了 -md draft.gguf 而没有指定 --spec-type,llama.cpp 会老老实实把草稿模型加载进内存、占用显存,然后什么也不做。你付出了内存代价,却拿到零收益,而且不会报错。
普通双模型方案用 draft-simple。--spec-type 还支持多种方案,可逗号分隔叠加:
draft-simple | draft-mtp | draft-eagle3 | draft-dflash | draft-dspark
ngram-cache | ngram-simple | ngram-map-k | ngram-map-k4v | ngram-mod
5.1 完整可运行示例
# 目标模型 8B + 草稿模型 0.5B,均同属一个家族
./llama-server \
-m models/target-8b-Q4_K_M.gguf \
-md models/draft-0.5b-Q4_K_M.gguf \
--spec-type draft-simple \
--spec-draft-n-max 5 \
--spec-draft-p-min 0.75 \
-ngl all --spec-draft-ngl all \
-c 8192 --port 8080 --metrics
参数逐个解释:
-m:目标模型(要加速的那个)。-md:草稿模型(小得快跑的那个)。--spec-type draft-simple:启用经典双模型投机——漏了它整个功能不生效。--spec-draft-n-max 5:每轮最多猜 5 个。起步建议 5,再按接受率上下调。--spec-draft-p-min 0.75:草稿模型对下一个字的把握低于 75% 就停手,避免硬猜。-ngl all --spec-draft-ngl all:两个模型都尽量放进 GPU。注意这两个是独立参数,草稿模型不会自动跟随目标模型。
5.2 如何验证"到底有没有变快"
不要凭感觉。llama.cpp 会直接给出接受率统计。用 OpenAI 兼容接口调用后,响应体会附带:
"timings": {
"predicted_per_second": 34.03,
"draft_n": 897,
"draft_n_accepted": 574
}
draft_n_accepted ÷ draft_n 就是接受率(上例为 63.03%)。同一模型内部还提供更细的按位置接受率,可用 --perf 或 Prometheus 指标(--metrics)查看。
正确的工作流是:先用真实业务 prompt 记录基线 tok/s → 开启投机 → 记录新 tok/s 与接受率 → 接受率低于 60% 就果断关掉。
六、优势与局限(如实列出)
优势
| 优势 | 说明 |
|---|---|
| 输出无损 | 结果与直接跑大模型完全一致,不是近似 |
| 不增加显存中的目标模型精度损失 | 只加一个草稿模型的开销 |
| 首字延迟不变 | 只优化生成(decode)阶段,输入处理(prefill)不受影响 |
| 接受率高时收益可观 | 代码/结构化输出常见 1.5–3 倍 |
| 与量化正交 | 可以和 Q4、KV cache 量化叠加使用 |
局限
| 局限 | 说明 |
|---|---|
| 接受率低时是负收益 | 实测出现 −25%;这是最需要警惕的一条 |
| 必须同家族同分词器 | 跨家族基本不可用,选型自由度受限 |
| 多占显存 | 草稿模型自己的权重 + 自己的 KV cache;Q8 级草稿可能吃掉数 GB |
| 显存紧张时更糟 | 若草稿要挤出目标模型的层或上下文长度,得不偿失 |
| 无法与多模态共用 | llama.cpp 明确在加载 --mmproj 时禁用投机解码 |
| 强制单并发 | 需 --parallel 1;草稿模型的 KV cache 会随并发槽位膨胀 |
| 对并行吞吐无帮助 | 优化的是单请求延迟,高并发服务场景收益有限 |
最后两条尤其值得注意:投机解码是一个降低单条请求延迟的技术,不是提升整体吞吐的技术。如果你的场景是大批量并发推理,它不但帮不上忙,还会因为强制单并发而拖累吞吐。
七、2026 年的新路径:不必再找第二个模型
上面讲的都是"两个模型"的方案。2026 年出现了一条更省事的路:MTP(Multi-Token Prediction,多 token 预测)——把草稿能力直接训练进模型本身,模型自己给自己当草稿。
好处非常实际:不需要再挑选、对齐、加载第二个模型,也就没有显存翻倍和家族匹配的问题。代价是必须使用带 MTP 结构的模型文件。
配置方式和普通方案几乎一样,只是换个类型:
./llama-server \
-m models/qwen3.6-27b-MTP-Q4_K_M.gguf \
--spec-type draft-mtp \
--spec-draft-n-max 2 \
--spec-draft-ngl all \
-c 32768 --parallel 1 --perf
注意 MTP 场景下草稿长度建议调小(实测用 2 效果稳),因为这是模型内部的预测结构,不是独立的小模型。
生态跟进也很快:Gemma 4 在 MLX 推理器上默认启用了投机解码,Ollama 也已在 Mac 上为 Gemma 4 加入 MTP 支持——也就是说,你可能已经在不知不觉中用它了。
除 MTP 外,--spec-type 还支持更激进的方案:
- EAGLE-3(
draft-eagle3):训练专门的草稿头,在有利条件下可达 2–4 倍。 - n-gram 系列(
ngram-simple/ngram-mod等):完全不需要草稿模型,改用哈希表匹配历史 token 序列来预测。特别适合"改写源码"“批量重构"这类大量重复片段的场景。--spec-default可以一键启用默认的ngram-mod配置。 - DFlash / DSpark:扩散式与稀疏式草案,属于较新的实验路线。
八、给 2B 小模型的定位
结合本系列的主题,2B 级小模型在投机解码中的定位非常明确:
- 它是天然的草稿模型候选。与目标模型 1:5 到 1:10 的尺寸比正是推荐区间,而 0.5B–2B 的 Q8 量化文件只占 0.5–1.9 GB 显存,成本很低。
- 它不适合当目标模型被加速。一个 2B 模型本身的生成速度已经受带宽限制不多,加草稿的收益微乎其微,反而多占内存。要加速就加速 8B 以上的模型。
- 要注意它的有效上下文。小模型标称上下文往往远大于实际有效上下文,草稿场景下尤其如此——拼接上下文过长会让接受率进一步下降。
九、一句话决策清单
在动手之前,问自己五个问题:
- 我的模型是否完全跑在 GPU 上?——否,则先解决显存不足,投机解码会雪上加霜。
- 我的任务是结构化/可预测的吗?——代码、JSON、公式适合;创意写作不适合。
- 我能接受单并发吗?——需要高并发吞吐的场景请放弃。
- 我有没有同家族的小模型?——没有则考虑 MTP 或 n-gram 方案。
- 我实测过接受率吗?——没有实测就下结论,是这类优化最常见的错误来源。
最后一条建议:投机解码是"调参型"优化,同一个命令在不同机器上可能一个是加速 2 倍、一个是减速 25%。先花十分钟记录基线,再决定是否保留它——这十分钟的投入,比任何推荐参数都值钱。
延伸阅读:别把 2B 当小号 GPT-4:llama.cpp + Qwen 2B 的六种实战玩法 | curl 不止是下载工具:AI 与 Agent 开发中的十二类实战场景