一次前向验好几个 token,凭什么输出分布一点都不变?
从已有出发
第 1 课的账:锚点 batch 1 时,生成 1 个 token 的计算下界约 0.052 ms,读权重的下界约 15.30 ms。如果一次前向同时算 5 个位置,计算下界涨到 0.26 ms,仍远小于读权重的 15.30 ms——多验几个 token 几乎不多花时间。贯穿例句那样的请求,写一个字和验五个字,耗时几乎一样。
问题只剩一个:草稿猜的字,哪些该收?
这一课为什么换例子。要演示「收哪些」,需要两个真模型在同一个前缀上各自给出完整的概率分布,而且词表相同。锚点在浏览器里跑不动,贯穿例句也不在我们小模型的训练分布里。所以换成我们项目里三个同语料、同词表、同训练步数的模型:目标 ref_baseline(3,688,704 参数),草稿 c1_dim64(332,352,约 1/11)与 c1_dim128(1,066,112,约 1/3.5)。全部分布、轨迹与记账由 gen_c13_precomputed.py 离线真跑得到。
先自己设计:什么时候算「猜中」?
旧版课程的演示用的是贪心:草稿猜的 token 等于目标模型的 argmax 就收。温度 0 时这完全正确——我们在 4 个提示、2 个草稿、k = 2 和 4 上各跑了一遍,投机输出与目标自己贪心解码逐 token 相同 16 / 16。可一旦按概率采样,「目标会选哪个」本身就是随机的,没有 argmax 可比。
最自然的推广:草稿按 q 采一个,目标按 p 也采一个,相同才收,不同就用目标采的那个。输出总是目标采的,分布当然对。问题在别处:
- ① 朴素方案
- ② 它在哪失败
- ③ 改进方案
改进:接受概率 min(1, p/q),拒绝时从残差重采
草稿按 q 采到 x 之后:如果目标给 x 的概率不比草稿低(p(x) ≥ q(x)),一定收;如果草稿高估了它(q(x) > p(x)),只以 p(x)/q(x) 的概率收。被拒时,不是从 p 重采,而是从「p 比 q 多出来的那部分」重采:
这是一个恒等式,对任意 p 与 q 成立 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条只要求 q(x) > 0 时才可能抽到 x;不依赖模型大小、数据与温度。p、q 指的是各自经过同样温度与截断之后的分布。 出处Leviathan et al. 2022 附录 A.1(本地 PDF) ↗ 。离线大样本核对(python,完整 2,048 维,提示「孔明曰:「」,200,000 次):正确做法的输出与 p 的总变差距离 0.0218,直接从 p 采同样次数的抽样噪声是 0.0215——两者一样大;「被拒后从 p 重采」是 0.1666(理论值 0.165906)。
在上面「改进方案」一侧的组件里,点「跑一轮」逐项看一次判定,再用三个按钮先预测、再跑 10,000 次:
试四件事:① 点「跑一轮」几次:落在绿 + 蓝那些 token(q ≤ p)上一定收;落在带红色的 token 上按 p/q 的比例收。② 先回答「被拒以后该从哪个分布重采」,再看 10,000 次的黑竖线是不是落在虚线(p)上:选「目标分布 p」时,「孔明」这种目标很看好、草稿却很少猜的 token 明显偏低。③ 换成大草稿:α 从 56.7% 变成 56.5%,换个前缀(「卻說曹操」)差别更大。④ 点「×100,000」,把总变差距离和离线的 0.0218 对照。
目标模型一次前向怎么同时给出 k + 1 个分布?把草稿猜的 k 个 token 接在后面整段喂进去,每个位置的输出就是「假设前面都对,下一个 token 的分布」——这就是一次 prefill:
| 维度 | 含义 | 真实规模 | 小规模 |
|---|---|---|---|
| k1 | k + 1:要验的位置数 | 5 | 5 |
| k | 草稿每轮猜的 token 数 | 4 | 4 |
| V | 词表大小(锚点 vocab_size) | 248,320 | 8 |
一轮能出几个 token:回放与记账
下面是一次真跑的完整轨迹:目标 ref_baseline、草稿 c1_dim64、k = 4、温度 1,提示「孔明曰:「」。每一轮的草稿 token、它在两个模型下的概率、接受概率和这次用到的随机数都在:
试三件事(保留旧版的三条结论):① 一轮一轮点,看「每次目标前向产出」怎么变:这一次 15 轮出了 33 个 token;② 找一轮第一个就被拒的:这一轮照样产出 1 个 token(残差重采的那个)——最坏也就是逐 token 生成,不会更少;③ 找一轮 4 个全收的:再白拿 1 个,一轮 5 个,这是 k = 4 的上限。所以每轮产出 = 接受数 + 1,落在 1 到 k + 1 之间。
每轮的期望产出有一个闭式:若每个位置的接受率都是 α、互相独立,一轮产出 (1 − αk+1) ÷ (1 − α) 个 token 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条截断几何分布的期望;前提是各位置的接受率独立同分布,这是简化假设。 出处Leviathan et al. 2022 式 (1)(本地 PDF) ↗ 。真实文本里每个位置的 α 并不一样(验证集 64 个位置上从 30% 到 96%),所以要拿真跑来对:
试三件事:① 表里「每次目标前向产出(真跑)」和「公式」两列差多少?(k = 4 时小草稿 1.96 对 1.95,大草稿 2.40 对 2.40)② 大草稿每个 token 的接受率更高,可它更贵:看最后一列,小草稿 k = 3 时理论加速最高(1.50×),大草稿 k = 8 时只有 0.75×——比不投机还慢;③ 把 α 拖到 0.8、c 拖到 0:k = 1 时正好 1.8 个——这就是下面 DeepSeek-V3 的情形。
论文与报告里的真实数字(都是提出方自报,只在各自的模型与硬件上成立):
现代:让模型自己当草稿
独立训练、独立部署一个草稿模型很麻烦,还要和目标对得上。于是出现了「自带草稿」的做法:
- MTP 模块当草稿:DeepSeek-V3 训练时就多预测一个 token(C11 岔路讲训练侧),推理时把这个模块当 k = 1 的草稿:第二个 token 接受率 85%–90%,代入公式 1 + α = 1.85–1.90;报告实测 1.8 倍 TPS——比公式略低,差的是草稿本身的开销 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条官方自报,没有给出测量的硬件与 batch。 出处DeepSeek-V3 报告 §5.4.3(本地 PDF) ↗ 。锚点 Qwen3.8-27B 的 config 里同样带着 1 层 MTP(mtp_num_hidden_layers = 1,424,699,392 个参数,占 1.5%);服务框架是否默认拿它投机,以部署文档为准。
- 直接优化接受率:Kimi K3 预训练带 1 层 MTP,之后冻结目标、把它微调成 EAGLE-3 式的草稿(输入融合目标低、中、高层的特征,训练时展开 7 步),损失函数直接取 −log Σ min(p, q)——就是本课的 α 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条官方自报;报告说明用 KL 做代理目标不能保证容量有限的草稿把接受率最大化。 出处Kimi K3 报告 §4.1(本地 PDF) ↗ 。
- 分歧:DeepSeek-V4.1 预训练阶段完全不用 MTP,而是冻结骨干、另训一个 DSpark 草稿器(一次给 5 个位置、按置信度决定验多长),论文报告对 MTP-1 基线每用户快 60%–85%(未经同行评审)。MTP 该和骨干一起预训练,还是作为独立部件后装?2026 年还没有定论(R3 §3.8)。
🔍 线性注意力(GDN / KDA)在投机解码里的麻烦
全注意力层验证被拒时,把多写的 KV 丢掉就行;线性注意力层的状态是原地更新的,验证时已经把几个草稿 token 写进去了,一旦被拒没法简单回退。每个草稿位置都存一份状态快照能解决,可状态本身就大(锚点每请求 151 MB,即 144 MiB),流量会成倍增加。Kimi K3 的做法是只缓存草稿 token 的投影输入,被接受的前缀在芯片上重建状态(报告 §5.4.2)。这是混合架构为投机解码付的一笔工程账。
⚠ 常见误解
「投机解码是近似,质量会降一点。」按 min(1, p/q) 接受、残差重采,输出分布与目标模型严格相同(恒等式,上面的 20 万次抽样只剩抽样噪声)。它改的是「什么时候算」,不是「算出什么」。
「草稿越大、越准越好。」接受率高了,草稿自己的前向也贵了。Leviathan 表 2:T5-small 草稿 α = 0.75 → 3.4 倍,T5-large α = 0.82 → 只有 1.7 倍;我们的大草稿 k = 8 时理论加速 0.75×,比不投机还慢。
「被拒以后,从目标分布重采一个就行。」那会把两个分布共有的部分数两次:总变差距离 0.1666,远大于抽样噪声。必须从残差 max(0, p − q) 重采。
「batch 很大时,投机解码照样白赚。」它白赚的前提是第 1 课的「带宽受限、算力闲着」。batch 大到接近算力受限区时,多验 k 个位置就是实打实多算 k 倍,收益会缩水甚至变负;EAGLE-3 在 SGLang、batch 64 时的吞吐提升只有 1.38 倍(论文自报)。
在演进地图上的位置
- 每一步把整段重新喂进模型
- KV cache
- 批处理:一次解码多个请求
- 连续批处理(迭代级调度)
- PagedAttention:KV 分块按需分配
- 投机解码:草稿猜、目标一次验
- 让模型自己当草稿:MTP 与专用草稿器
- 量化:每个权重少读几位
- 按 TTFT / TPOT / goodput 调度:分块 prefill、P/D 分离
- 采样:温度 → top-k → top-p → min-p
- 少存 KV:GQA / MLA / 线性注意力
证据三问
| 问 | 本课的回答 |
|---|---|
| ① 它解决什么问题? | batch 小时算力闲着:让草稿先猜、目标一次验完,单个用户每次目标前向能拿到多于 1 个 token |
| ② 数学上为什么? | 输出分布等于 p 是恒等式 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条对任意 p、q 成立。 出处Leviathan 2022 附录 A.1 ↗ ;期望产出公式在「各位置接受率独立同分布」下可证 证据等级 · 数学可证明 在写明的前提下可以严格推导出来,不依赖实验。只要前提成立,换数据、换规模仍然成立。 本条我们 12 组真跑与公式的差都在 1% 以内。 出处Leviathan 2022 式 (1) ↗ |
| ③ 实验上呢? | 加速倍数只能实测,强烈依赖草稿、任务、温度与 batch:T5-XXL 1.4–3.4 倍、Chinchilla 70B 2–2.5 倍、DeepSeek-V3 1.8 倍 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条都是提出方自报。 出处Leviathan 表 2;Chen 2023;DeepSeek-V3 §5.4.3 ↗ |
一句话
草稿猜、目标一次验完;按 min(1, p/q) 接受、拒绝时从残差重采,输出分布与目标模型逐字相同,省的只是目标模型的前向次数。
先停在这里:记住「绿 + 蓝 = p」那张叠放图,就能去第 4 课。
再深一层:读 Leviathan 论文的附录 A.1 与定理 3.8(草稿代价 c 进来之后,最优的 k 怎么选)。
下一个缺口
投机解码没有减少任何一次前向要读的字节,它只是让每次读完能多出几个 token。另一条路正好相反:每次前向少读一点。锚点每个权重现在是 2 字节——非要 16 位吗? → 第 4 课:把权重从 16 位压到 4 位,为什么能变快,又会丢什么?
你可能会问
这一课还没有收到真实提问;你的提问会记到 05-问答记录/C13-L3.md。
- 「温度 0 时这套规则还成立吗?」 成立。温度 0 时 p、q 都是只在 argmax 上为 1 的分布:min(1, p/q) 在 argmax 相同时为 1、不同时为 0,残差就是目标的 argmax——正好退化成旧版演示的「贪心精确匹配」。
- 「草稿和目标的词表必须一样吗?」 这套规则要求两个分布定义在同一组 token 上。词表不同时要先做映射,工程上麻烦得多,所以常见的草稿要么是同家族的小模型,要么是目标自带的 MTP / EAGLE 头。
- 「为什么不一次猜 20 个?」 产出的上限是 1 ÷ (1 − α),α = 0.6 时最多 2.5 个;猜得越多,越靠后的越可能作废,草稿的开销却线性增加。上面表里我们的小草稿在 k = 3 时最好。