投机解码:从拒绝采样到 Omni 模型

22 July 2026 • 2 minute read

投机解码(speculative decoding)常被介绍成“用小模型猜、大模型验“。这个说法不错,但会让人错过两件更要紧的事:它在数学上是无损的,输出分布与大模型逐字节等价;以及它的收益有一个由分布本身决定的硬上限,和草稿模型做得多好无关。本文从这两点出发,最后落到 Omni 类模型上会遇到的具体问题。

一、瓶颈不在算力

生成一个 token,需要把整个模型的权重从显存读一遍。30B 模型 bf16 是 60GB;B300 的 HBM 带宽约 8TB/s,光读权重就要 7.5ms,与你算了多少 FLOP 无关。

而这一步实际做的算术量呢?batch=1 时每个权重只参与一次乘加,算术强度约 2 FLOP/byte。Blackwell 要 300+ FLOP/byte 才能喂饱张量核。也就是说 decode 阶段 GPU 的算力闲置 99%,纯粹在等内存。

由此得到一个关键推论:

一次前向验证 k 个 token,和生成 1 个 token 的耗时几乎一样。

权重只读一次,多出来的 k 倍算术是免费的。投机解码全部的物理基础就在这里——用闲置算力换内存带宽。

二、为什么它是无损的

对每个草稿 token xᵢ,按顺序执行:

以概率 min(1, p(xᵢ)/q(xᵢ)) 接受
若拒绝:从残差分布 norm(max(0, p − q)) 重采一个,并丢弃后面所有草稿
若 k 个全接受:再从 p 白送一个 token

其中 p 是目标模型分布,q 是草稿模型分布。证明输出精确等于 p:

P(x) = q(x)·min(1, p(x)/q(x)) + P(拒绝)·norm(max(0,p−q))(x)

第一项化简为 min(q, p)。第二项需要两个事实:

P(接受) = Σₓ min(p,q) = 1 − TV      其中 TV = ½Σₓ|p−q|
Σₓ max(0, p−q) = TV                 (残差的总质量恰好是 TV)

于是 P(拒绝) = TV,而残差归一化要除以 TV,两个 TV 正好抵消:

P(x) = min(p,q) + max(0, p−q) = p(x)   ✓

最后一步分两种情况:若 p ≥ q,min=qmax=p−q,和为 p;若 p < q,min=pmax=0,和还是 p

顺带得到本文最重要的一个等式:

单 token 接受率 α = 1 − TV(p, q)

走一个数

词表 {A,B,C},p = [0.5, 0.3, 0.2],q = [0.4, 0.4, 0.2]TV = ½(0.1+0.1+0) = 0.1,理论接受率 0.9。

草稿抽到 B(概率 0.4),p(B)/q(B) = 0.75,以 0.75 接受。若拒绝,残差 max(0, p−q) = [0.1, 0, 0],归一化后必出 A。验算:

计算结果p
A0.4·min(1,1.25) + 0.1·10.50.5 ✓
B0.4·0.750.30.3 ✓
C0.2·min(1,1.0)0.20.2 ✓

三、加速比,以及一个我算错过的地方

草稿是链式的:第 i 个被接受的前提是前 i−1 个都被接受。若各位置独立且接受率为 α:

tokens/pass = 1 + Σᵢ₌₁ᵏ αⁱ = (1 − α^{k+1}) / (1 − α)
speedup     = tokens_per_pass / (1 + k·c)      c = 草稿成本 / 目标成本

独立假设是错的,而且是保守的。看一份真实的 DSpark 草稿模型验证指标(block=7,目标是 Qwen3-Omni-30B-A3B):

位置0123456
接受率0.7140.6190.5530.5080.4740.4460.423

按独立假设累乘求和得 1.62,而模型自报的 accept_len3.16。反解可知要产生 3.16,等效恒定 α 需达 0.80——比任何单个位置的实测值都高。

结论:各位置的接受事件是正相关的。直觉上说得通——容易的上下文一口气全接受,难的一个都不接受,而不是每个位置独立掷骰子。所以独立假设给出的是下界,不是估计。

用真实的 3.16 重算(每次目标前向产出 4.16 个 token):

草稿成本 c净加速
0.053.08×
0.102.45×
0.152.03×
0.201.73×

四、一个常见的对不上

工程上经常看到 accept_rateTV 两个数凑不成 1,进而怀疑实现有 bug。上面那份指标同时报了两个口径,正好解释:

raw_tv            = 0.4655
1 − raw_tv        = 0.5345
accept_rate_epoch = 0.5345    ← 完全相等

α = 1 − TV单位置的等式。若你手上的 accept_rate 是 k 个位置的链式平均,它天然低于 1 − TV,因为后面位置的条件接受率更低。比之前先确认口径,再去查实现。

另一个真实原因是截断词表:EAGLE3/DSpark 类方法常把草稿词表从 151k 截到 32k(靠 d2t/t2d 映射表还原)。词表外的 token 必然拒绝,但若 TV 只在截断支撑集上算,两者就会系统性对不上。

五、草稿从哪来

方式草稿来源成本特点
独立 draft model同族小模型通用,要多存一个模型
EAGLE / EAGLE3复用目标模型的 hidden state目前主流,共享表征所以 α 高
MTP目标模型自带多输出头极低需训练时就设计好
n-gram / prompt lookup从上下文查重复串~0免训练,只在有重复时有效

六、落到 Omni 模型

Qwen3-Omni 是三级流水:

stage 0  thinker    LLM_AR          标准文本 LLM
stage 1  talker     LLM_AR          生成音频 codec token
stage 2  code2wav   LLM_GENERATION  声码器,非自回归

投机解码只对 LLM_AR 有意义,所以候选是前两级——而难度天差地别。

Thinker:几乎是白送的

它继承自 Qwen3MoeForCausalLM,是标准文本 LLM,推理框架里那套 proposer 直接可用。上面那份 DSpark 草稿模型配的正是 Thinker,accept_len 3.16 也是在这一级上测的。

Talker:真正的难点

Talker 每步产出一个 codec 帧,即 num_code_groups 个残差 VQ 码——第 0 个由主干出,其余由一个小的 code predictor 补齐。

这里有个命名陷阱:代码里那个 code_predictor_mtp 文件名带 “MTP”,但它不是投机解码,只是补齐同一帧内剩下的码本,没有 draft-verify 结构,和加速无关。

Talker 难在分布本身。回到 α = 1 − TV:RVQ 码本训练时就是要最大化码本利用率,分布天生平坦。

top-1 概率典型值可达 α
文本~0.90.6–0.8
audio codec~0.2

分布越平,再好的草稿接受率也上不去。 这是信息论层面的约束,不是工程问题。所以在 Talker 上投入草稿模型之前,先量它输出分布的熵和 top-k 集中度——一次前向就能出结果,而这个数直接决定后续工作值不值得做。

不过 Talker 也有文本 LLM 没有的结构可以利用:帧率低(25–50 Hz)所以单帧延迟预算更宽松;同一帧的第 1..N 个码本高度依赖第 0 个;音频有强时间连续性,免训练的 n-gram 类草稿可能意外有效。

小结

  • 投机解码是无损的,不是近似加速;两个 TV 在推导中抵消是关键
  • α = 1 − TV 是单位置等式,和链式平均口径不同,对不上时先查口径
  • 独立假设算出的加速是下界;真实场景中各位置正相关,实际更好
  • 收益上限由目标分布的平坦程度决定,这是选择在哪一级做投机的第一判据

本文的数值来自 Qwen3-Omni-30B-A3B-Instruct 及其 DSpark 草稿模型的公开验证指标;端到端实测另文再述。