Medusa 完全拆解:多头树式候选如何用一次验证解码多个 Token

7594 字
38 分钟
Medusa 完全拆解:多头树式候选如何用一次验证解码多个 Token

问题:为什么自回归解码总在等待下一个 Token#

大语言模型的生成阶段有一个很难绕开的依赖:只有得到第 tt 个 token,才能把它送进模型并计算第 t+1t+1 个 token。假设最终要生成 NN 个 token,标准自回归解码就要串行执行大约 NN 次模型前向。GPU 可以在一次前向内部并行计算矩阵乘法,却不能提前知道尚未生成的 token,这使时间维度上的并行性几乎为零。

单请求、低 batch 的 decode 往往又不是算力受限,而是显存带宽受限。每一步只处理一个新 token,但线性层仍要读取庞大的模型权重。以参数量为 PP、权重每参数占 bb 字节为例,忽略缓存命中与张量并行通信,一次解码前向至少要搬运约 PbPb 字节权重。每次只产出一个 token 时,权重读取的成本无法被更多 token 摊薄。

这也是投机解码的基本机会:让一个便宜的提议器先猜多个 token,再由目标模型一次并行验证。若一次目标模型前向最终提交了 AA 个 token,理想情况下串行步数就从 NN 降为约 N/AN/A。真正的加速比还要扣除提议器、候选验证、采样和 KV Cache 整理的开销,所以关键不是“猜得越多越好”,而是让平均接受长度的增益超过单步延迟的增长。

传统投机解码通常还要维护一套独立草稿模型。草稿模型太小,预测与目标模型分布相差大,接受率低;草稿模型太大,自己就会消耗可观的算力和显存带宽。多模型部署还会带来额外权重、并行切分和调度复杂度。Medusa 的切入点是:不再运行第二个 Transformer,而是在目标模型最后一层隐藏状态上挂几个很轻的预测头。

Medusa 由普林斯顿大学、Together AI、伊利诺伊大学厄巴纳-香槟分校等团队提出,论文发表于 ICML 2024。它把一次迭代拆成三个动作:多头产生候选、Tree Attention 并行验证、接受一条候选路径的最长前缀。论文报告的重点是 batch size 1 的低延迟场景;这点很重要,因为高并发吞吐服务的瓶颈与收益曲线并不相同。

Medusa 的核心思路与系统位置#

Medusa 不是“一个头直接吐出一句话”。它仍然保留目标模型的自回归校验,只是把未来几个位置的候选组织成树,让目标模型在一次前向中同时判断多条路径。整个数据流如下图所示。

Medusa 从隐藏状态生成多头候选并由目标模型验证
Medusa 从隐藏状态生成多头候选并由目标模型验证

图源:Medusa 论文与官方 arXiv 源文件。蓝色区域是原模型,红色区域是附加的 Medusa Heads。所有头读取同一个末层隐藏状态,却分别预测不同的未来位置;右侧 Top-k 结果组合成候选,随后目标模型选出可接受的最长路径。图中一次提交了 “It is difficult”,因此一次验证替代了三次普通解码迭代。

给定当前上下文 x1:tx_{1:t},目标模型最后一层在位置 tt 的隐藏状态记为 ht∈Rdh_t\in\mathbb{R}^d。原 LM Head 给出下一 token 的分布:

pt(0)=softmax⁡(WLMht).p_t^{(0)}=\operatorname{softmax}(W_{\mathrm{LM}}h_t).

Medusa 再添加 KK 个头。第 kk 个附加头预测更远的 xt+k+1x_{t+k+1},论文采用带残差的一层前馈变换:

pt(k)=softmax⁡(W2(k)(SiLU⁡(W1(k)ht)+ht)),k=1,…,K.p_t^{(k)}=\operatorname{softmax}\left(W_2^{(k)}\left(\operatorname{SiLU}(W_1^{(k)}h_t)+h_t\right)\right),\qquad k=1,\ldots,K.

这里 dd 是隐藏维度,VV 是词表大小;按列向量记法,W1(k)∈Rd×dW_1^{(k)}\in\mathbb{R}^{d\times d},W2(k)∈RV×dW_2^{(k)}\in\mathbb{R}^{V\times d}。W1(k)W_1^{(k)} 负责对共享隐藏状态做轻量变换,残差项 +ht+h_t 保留原模型已经形成的语义表示,W2(k)W_2^{(k)} 再把它投影到词表。

要区分两个容易混淆的“多头”。Transformer 的 multi-head attention 是把同一层注意力拆成多个 Q/K/V 子空间;Medusa Heads 是挂在模型输出端、预测不同未来位置的词表分类器。二者所处层级和职责完全不同。

作为背景,论文给出只适配附加头的 Medusa-1 与主干共同适配的 Medusa-2 两种配置;后文只讨论两者共享的推理路径。

多个 Medusa Head 如何产生候选树#

若每个未来位置只取 argmax,KK 个头只能给出一条长度 KK 的候选。问题是各头并不是条件自回归的:第 2 个附加头在产生 xt+3x_{t+3} 的分布时,并没有先看到第 1 个附加头猜出的 xt+2x_{t+2}。一个早期位置猜错,后面的整条链都会失效。Medusa 因而对每个头保留多个高概率 token,并把组合看成候选树。

设第 kk 层保留 sks_k 个候选。如果机械取笛卡尔积,深度 kk 会有

nk=∏i=1ksin_k=\prod_{i=1}^{k}s_i

个节点,整棵树需要验证的新增节点数是

Ntree=∑k=1K∏i=1ksi.N_{\mathrm{tree}}=\sum_{k=1}^{K}\prod_{i=1}^{k}s_i.

例如两层分别保留 2 和 3 个候选,会形成 2+2×3=82+2\times3=8 个节点,对应 6 条长度为 2 的完整路径。它比把 6 条序列各自展开为 12 个 token 少,因为相同的第一层前缀只保存和计算一次。这种前缀共享正是“树”比“候选 batch”更合适的原因。

但规则笛卡尔积很快爆炸。若 4 层都取 top-4,节点数为 4+16+64+256=3404+16+64+256=340。目标模型虽然只前向一次,却要处理 340 个查询位置,线性层和注意力都会从“顺手多算一点”变成显著计算负担。因此论文实际采用校准集统计不同头、不同排名的命中概率,并在固定节点预算下构造不平衡稀疏树。

记第 kk 个头第 ii 名预测恰好正确的概率为 ak(i)a_k^{(i)}。在论文的独立性近似下,一条排名路径 (i1,…,ik)(i_1,\ldots,i_k) 的概率估计为

q(i1,…,ik)=∏j=1kaj(ij).q(i_1,\ldots,i_k)=\prod_{j=1}^{k}a_j^{(i_j)}.

每加入一个合法节点,它对期望接受长度的边际贡献可用对应的 qq 估计。于是可以从根开始,每次加入“父节点已存在且估计概率最大”的节点,直到耗尽节点预算。这样产生的树通常在浅层较宽、深层只保留少数强路径,因为越远的头越不确定,概率连乘也会快速衰减。

校准概率构造出的不平衡 Medusa 稀疏树
校准概率构造出的不平衡 Medusa 稀疏树

图源:Medusa 论文 arXiv 源文件中的稀疏树示例。灰边表示被并行验证的候选,红线是最终接受路径。它直观展示了固定预算不该平均分给每个分支:高概率前缀值得继续向深处扩展,低概率分支保留一两层即可。

手算一棵两层候选树#

用符号例子可以把索引关系说清楚。设原 LM Head 给出的根 token 是 aa,第一个 Medusa Head 的 top-2 是 (b,u)(b,u),第二个头的 top-2 是 (c,v)(c,v)。规则组合会得到四条路径:

(a,b,c),(a,b,v),(a,u,c),(a,u,v).(a,b,c),\quad(a,b,v),\quad(a,u,c),\quad(a,u,v).

树式存储不复制公共前缀,可以展平为 7 个节点:a, b, u, c|b, v|b, c|u, v|u。竖线后的字符表示父节点,不属于 token 内容。若把四条路径直接作为 batch,则要放入 4×3=124\times3=12 个 token;树布局只需 7 个节点。

设已确认上下文长度为 LL,这些节点的位置编号依次是 LL、两个 L+1L+1、四个 L+2L+2。节点 c|u 的可见候选集合只能是 {a, u, c|u};它不能看到 b,也不能看到另一个 c|b。目标模型验证后,若它在处理 aa 时认为下一 token 应为 uu,在处理路径 (a,u)(a,u) 时认为再下一 token 应为 cc,那么本轮就提交 (a,u,c)(a,u,c)。候选 (a,b,c)(a,b,c) 即便最后的 cc 概率很高,也会因为 bb 在前一位置失败而只能接受根部 aa。

Tree Attention:把一棵树压进一次并行前向#

候选树解决了“猜哪些 token”,接下来要解决“怎样让目标模型一次验证整棵树”。不能直接把树节点当普通序列送入因果注意力。普通下三角 mask 会让排在后面的节点看到所有更早节点,包括其他分支;这样得到的条件概率对应一条根本不存在的混合序列。

Tree Attention 的规则是:一个节点只能看到既有上下文、自己以及自己在树上的祖先,不能看到兄弟、堂兄弟或其他分支的后代。将树节点按深度展开成一维张量后,可以定义验证区内部的布尔掩码

Muv={0,v∈Ancestors⁡(u)∪{u},−∞,其他情况.M_{uv}=\begin{cases} 0, & v\in\operatorname{Ancestors}(u)\cup\{u\},\\ -\infty, & \text{其他情况}. \end{cases}

其中 uu 是当前 query 节点,vv 是候选区中的 key 节点。加上原提示词的 KV Cache 后,每个候选节点还可以访问全部已确认历史。将 MM 加到缩放点积注意力的分数上,softmax 后不合法边的概率为零:

Attention⁡(Q,K,V)=softmax⁡(QK⊤dh+M)V.\operatorname{Attention}(Q,K,V)=\operatorname{softmax}\left(\frac{QK^\top}{\sqrt{d_h}}+M\right)V.

dhd_h 是单个注意力头的维度。这个掩码不是近似;它保证每个节点的隐藏状态与“单独把从根到该节点的序列送入目标模型”得到的因果上下文一致。多路径因此可以共享一次批量矩阵计算,同时保持条件依赖正确。

Medusa 候选树与 Tree Attention 掩码
Medusa 候选树与 Tree Attention 掩码

图源:Medusa 官方论文图。左边两枚第一层节点是不同前缀,右侧掩码中的每一行对应一个 query。第二层的 “is” 只连接自己和所属的 “It”,另一个分支的第二层 “is” 则只连接自己和 “I”。图中对角线与祖先位置有勾,其他候选节点之间全部屏蔽。

位置编码也必须随树深度而不是展平下标递增。若已确认上下文长度为 LL,第一层候选的位置都是 LL,第二层都是 L+1L+1,第 kk 层都是 L+k−1L+k-1。对于 RoPE,这意味着同一深度的兄弟节点使用相同旋转位置。若错误地采用展平后的连续位置,兄弟节点会被伪装成先后出现的 token,验证结果就不再等价于各自的真实路径。

官方实现把这套静态结构预编译成四类缓冲:

  • medusa_attn_mask:记录节点到祖先的可见关系;
  • tree_indices:从各头 top-k 的扁平候选表中取出每个树节点的 token;
  • medusa_position_ids:按节点深度生成位置编号;
  • retrieve_indices:把展平树重新整理成一条条根到叶路径,供接受阶段比较。

这四个张量只取决于树形配置,不依赖提示词内容,可以在启动时生成并驻留 GPU。在线迭代只需填入本轮 top-k token,避免在 Python 侧反复遍历树。可参考官方仓库中 generate_medusa_buffers 的实现。

Tree Attention 并没有消灭计算,它把多条候选的验证变成一个更宽的目标模型前向。其价值来自现代 GPU 的一个不对称事实:低 batch、单 token 前向通常没有喂满计算单元,多处理几十个候选节点增加的延迟远小于重复读取几十次权重。候选继续增大后,线性层和注意力转为 compute-bound,单步延迟会明显上升,所以树大小存在最优点。

接受机制:从无损拒绝采样到典型接受#

目标模型完成树式前向后,每条根到叶路径都得到了逐位置的目标分布。此时不能因为某个深层 token 概率高就跳过它前面的错误 token;可提交部分必须是连续前缀。这是维持自回归语义的核心约束。

在温度为零的贪心解码中,规则最简单。设候选路径为 c1,c2,…,cmc_1,c_2,\ldots,c_m,目标模型在对应位置的 argmax 为 g1,g2,…,gmg_1,g_2,\ldots,g_m。该路径的接受长度为

A(c)=max⁡{a:cj=gj, ∀j≤a}.A(c)=\max\left\{a: c_j=g_j,\ \forall j\le a\right\}.

在所有路径中取 A(c)A(c) 最大者;若多条路径一样长,可按目标模型似然择优。由于只接受与目标模型逐步 argmax 完全一致的前缀,输出 token 与普通贪心解码相同。所谓 Medusa-1 的“无损”首先应在这个明确口径下理解,而不是把任何放宽规则都称为无损。

对于随机采样,若目标是严格保持目标分布 pp,可以采用标准投机解码的拒绝采样。草稿分布为 qq 时,候选 xx 的接受概率是

α(x)=min⁡(1,p(x)q(x)).\alpha(x)=\min\left(1,\frac{p(x)}{q(x)}\right).

候选被拒绝后,从校正分布中补采样:

pcorr(x)=max⁡(0,p(x)−q(x))∑ymax⁡(0,p(y)−q(y)).p_{\mathrm{corr}}(x)=\frac{\max(0,p(x)-q(x))}{\sum_y\max(0,p(y)-q(y))}.

这套规则的价值是最终样本严格服从 pp,代价是温度升高时,即使 pp 与 qq 很接近,两次独立采样也可能选择不同 token,接受长度随之下降。多分支树还能增加候选覆盖,但不能取消概率校正本身。

Medusa 另给出 typical acceptance。它不要求逐样本精确复现目标分布,而是接受目标模型看来“足够典型”的 token。对位置 jj 的目标分布 pjp_j,先计算熵

H(pj)=−∑v=1Vpj(v)log⁡pj(v),H(p_j)=-\sum_{v=1}^{V}p_j(v)\log p_j(v),

再定义阈值

τj=min⁡(ϵ,δexp⁡(−H(pj))).\tau_j=\min\left(\epsilon,\delta\exp(-H(p_j))\right).

当 pj(cj)>τjp_j(c_j)>\tau_j 时,候选 token 通过该位置。epsilonepsilon 是硬概率上限,deltaexp⁡(−H)delta\exp(-H) 是随不确定性变化的阈值:分布熵高时,exp⁡(−H)\exp(-H) 小,系统允许更多合理分支;分布尖锐时阈值升高,只有少数高概率 token 会通过。官方实现中的默认参数名是 posterior_threshold=0.3 和 posterior_alpha=0.09,分别对应这里的 ϵ\epsilon 与 δ\delta,但它们是需要按模型、温度和质量目标校准的工程参数,不是普适常数。

为保证每轮至少推进一步,根部 token 来自原 LM Head,并无条件提交;typical acceptance 只判断其后的附加头候选。系统最终选择通过阈值的最长连续前缀,长度相同时再比较路径似然。

这里存在必须说清的质量边界:typical acceptance 改变了原始采样过程,因此不是分布意义上的无损解码。论文在 MT-Bench 的 writing 与 roleplay 子集上观察到相近质量,但这属于经验结果,不能推出所有任务、温度与阈值下都等价。要求可复现、严格保持目标分布时,应使用贪心精确匹配或带校正的拒绝采样;允许在质量预算内换取更低延迟时,才考虑 typical acceptance。

一次完整解码迭代#

把数据结构和概率规则合起来,一轮 Medusa 解码可以沿以下路径执行:

  1. 用已确认序列的最后隐藏状态同时计算原 LM Head 和 KK 个 Medusa Head。原头决定根 token,附加头各自产生未来位置的 top-k 候选。
  2. 根据预先搜索好的稀疏树配置,用 tree_indices 把 top-k 候选填入展平树。树结构本身不在热路径上动态搜索。
  3. 给候选节点配置按深度递增的 position ID,并启用只允许访问祖先的 Tree Attention mask。目标模型对整棵树执行一次前向,得到每个节点后继位置的 logits 和临时 KV。
  4. 用 retrieve_indices 恢复各条候选路径,按贪心匹配、拒绝采样或 typical acceptance 求每条路径的连续接受长度,选出最佳路径。
  5. 把被接受路径的 token 追加到输出,并把对应节点的 KV 从树式临时布局紧凑复制到连续已确认区。未接受分支的 KV 作废;最后一个已接受位置的 logits 与 Medusa logits 成为下一轮候选生成的起点。

第 5 步经常被概念图省略,却是实现正确性的关键。验证整棵树时,KV Cache 中会短暂出现不同分支的状态;下一轮只允许看到选中路径。如果只是移动长度指针而没有按路径索引整理 KV,模型会读到兄弟分支的缓存。官方 update_inference_inputs 会用 retrieve_indices[best_candidate] 计算源位置,再把被接受节点的 KV 原地复制到已确认区尾部,最后更新当前长度。

还要留意一个计数细节:代码中的 accept_length 往往表示附加匹配的边数,而本轮实际追加 token 数是 accept_length + 1,因为根部原 LM Head token 总会提交。比较不同实现的“平均接受长度”时,必须先确认指标是否包含这个保底 token。

速度为什么会提升:收益、成本与上限#

评价 Medusa 不能只看“每轮接受几个 token”,还要看一轮变慢了多少。设普通自回归一步延迟为 TART_{\mathrm{AR}},Medusa 一轮平均提交 Aˉ\bar A 个 token,其延迟可拆成

TM=Theads+Ttree verify+Taccept+TKV commit.T_{\mathrm{M}}=T_{\mathrm{heads}}+T_{\mathrm{tree\ verify}}+T_{\mathrm{accept}}+T_{\mathrm{KV\ commit}}.

忽略首轮预填充,近似加速比为

S≈Aˉ TARTM=Aˉγ,γ=TMTAR.S\approx\frac{\bar A\,T_{\mathrm{AR}}}{T_{\mathrm{M}}} =\frac{\bar A}{\gamma},\qquad \gamma=\frac{T_{\mathrm{M}}}{T_{\mathrm{AR}}}.

Aˉ\bar A 是平均每轮实际提交数,γ\gamma 是 Medusa 相对普通一步的延迟膨胀。扩大候选树通常会提高 Aˉ\bar A,也会提高 γ\gamma;只有前者的相对增幅更大时,总延迟才会下降。例如平均提交 3.47 个 token、单轮开销为普通一步的 1.22 倍,对应的粗略上限是 3.47/1.22≈2.843.47/1.22\approx2.84 倍,而不是 3.47 倍。

论文在 Vicuna-7B 与 Vicuna-13B、ShareGPT 适配数据、MT-Bench 生成场景上报告了以下 wall-time 结果。基线是当时的 Hugging Face 默认实现,因而数字不应直接外推到今天不同 kernel、量化方式或服务框架。

Medusa-1 与 Medusa-2 在 Vicuna 7B 和 13B 上的速度对比
Medusa-1 与 Medusa-2 在 Vicuna 7B 和 13B 上的速度对比

图源:ICML 2024 Medusa 论文。Medusa-1 在 7B 和 13B 上分别达到 2.18 倍与 2.33 倍,Medusa-2 在两种尺寸上均为 2.83 倍。后者命中率更高,但需要改变主干模型;前者只加头,部署边界更清晰。

论文的组件消融给出一条更有解释力的链条:仅有 Medusa Heads、没有树注意力时约 1.5 倍;加入树注意力约 1.9 倍;再采用优化稀疏树约 2.2 倍;使用命中率更高的 Medusa-2 约 2.8 倍。这说明收益不是“多几个输出层”自动产生的,候选覆盖、共享前缀验证和树形预算缺一不可。

为什么“多算几十个节点”可能只慢一点#

以一个形状为 m×nm\times n 的线性层权重和 qq 个候选输入为例,矩阵乘大约需要 2mnq2mnq 次浮点运算。若权重元素占 bwb_w 字节,并暂时忽略激活与缓存,读取权重约需 mnbwmnb_w 字节,算术强度近似为

I(q)≈2mnqmnbw=2qbwFLOP/Byte.I(q)\approx\frac{2mnq}{mnb_w}=\frac{2q}{b_w}\quad\text{FLOP/Byte}.

普通单 token 解码对应 q=1q=1,每读一次权重只服务一个输入向量;树式验证把 qq 提高到几十,权重 tile 可在片上被多个候选复用,算术强度随 qq 增长。这就是 Medusa 用闲置算力换串行步数的硬件基础。

这个近似也解释了为什么收益不会无限增长。候选激活、词表 logits 和中间结果会随 qq 增加;当 GEMM 跨过 Roofline 的转折点成为算力受限后,再扩大树会近似线性增加计算时间。注意力部分还要让多个 query 分别读取历史 KV,其带宽并不会像权重那样完全摊薄。模型越小、batch 越大、上下文越长,转折点通常越早到来。

树大小的消融也揭示了上限:在论文设置中,64 节点的优化稀疏树可以取得比 256 节点随机稠密树更好的接受率。节点继续增加时,接受率的改善逐渐趋缓,tokens/s 却因矩阵乘与注意力计算增长而下降。换句话说,候选树优化的目标不是最大化命中率,而是在给定硬件上最大化 Aˉ/γ\bar A/\gamma。

Medusa 对低 batch 更有吸引力,因为普通解码此时算术强度低,树式验证可利用闲置计算资源。batch 已经很大时,目标模型的 GEMM 本来就接近饱和;再把每个请求扩成几十个树节点,可能直接推高计算量和 KV 工作集。论文主实验聚焦 batch size 1,虽然框架后来支持更大 batch,但生产服务仍应分别测量单请求延迟、不同并发下吞吐与尾延迟,不能拿单流加速比代替集群容量结论。

工程实现与参数选择#

Medusa 的参数并不少,但可以按“先保证正确,再找性能点”的顺序调试。

头数与树深#

论文经验上认为最多配置 5 个附加头已足够,在线推理常只使用其中 3 到 4 个。远期头预测难度更高,增加的候选深度若很少被接受,只会扩大验证前向。已有 5 个头的模型可以在部署时忽略尾部头。

附加头并非零成本。每个头包含 d×dd\times d 残差变换和 d×Vd\times V 词表投影;当词表很大时,后者的参数、显存读取与 logits 写出都不可忽略。实现若能复用、分片或融合词表投影,收益会明显不同。多卡张量并行下,还要让 Medusa Head 采用与原 LM Head 一致的词表切分与归约语义,否则“轻量头”可能引入新的跨卡通信。

树形不是一组 top-k 数字#

规则树可以用 (s1,s2,…)(s_1,s_2,\ldots) 描述,但优化稀疏树本质上是一组路径,例如 [0]、[1]、[0,0]、[0,1]、[1,0]。路径中的整数表示该深度选用第几名候选。必须满足前缀闭包:若 [0,1,0] 存在,则 [0] 和 [0,1] 都必须存在,否则节点没有合法祖先。

校准时应使用接近线上提示词、温度和采样策略的数据。代码生成中标点、关键字和缩进可预测性高,长路径更容易命中;开放式写作的合理后继更多,同一棵树的边际收益会不同。论文也观察到 extraction 和 coding 类别加速更高,这不是模型计算量变了,而是 token 条件熵与候选可预测性不同。

Mask、位置和 KV 必须一起验证#

单元测试不应只比最终文本,还应逐节点比对隐藏状态和 logits:把每条树路径单独做一次普通前向,再与 Tree Attention 对应节点比较。若出现差异,优先检查三处:祖先 mask 是否包含自身、position ID 是否按深度设置、RoPE 或 ALiBi 后端是否真正使用传入的位置。

KV Cache 最好预留最大树节点的临时区域,并在接受后只提交选中路径。静态地址有利于 CUDA Graph 和减少分配开销,但会增加显存占用;动态申请更省空间,却可能把 CPU 分配与同步重新带回热路径。可采用“固定最大槽位 + 实际节点数 mask”的方式折中。分页 KV 系统还要区分临时页和已确认页,避免失败分支被前缀缓存或其他请求引用。

把 Python 控制流赶出热路径#

官方参考实现便于理解,但生产系统需要关注 top-k、树索引、接受长度归约和 KV compact 的 kernel 启动次数。若每轮都在 CPU 上构树、同步读取接受长度,再发起多次小复制,节省的模型前向可能被控制开销抵消。静态树缓冲、设备端接受判定、融合索引与 KV 提交,以及可重放执行图,通常比继续增加一个预测头更值得优先实现。

调优时至少记录以下量,而不只记录 tokens/s:

  • 每轮提交 token 数的分布,而非只有均值;
  • 候选树节点数、最大深度与每层宽度;
  • 头部计算、目标验证、接受判定、KV 整理各自延迟;
  • 不同输入长度下验证注意力的耗时,因为 KV 读取随上下文增长;
  • batch、温度、top-p、任务类别与输出长度;
  • 严格接受与 typical acceptance 各自的质量口径。

没有这些分项,看到加速下降时无法判断是候选命中变差、目标验证转为 compute-bound,还是控制面同步过多。

与 EAGLE、传统投机解码的边界#

三类方法都遵循“便宜提议、昂贵验证”,差别主要在提议器如何获得条件信息。

方法提议器候选之间的依赖额外系统状态典型优势主要代价
独立草稿模型更小的完整语言模型草稿阶段自回归,后一个候选看到前一个第二套权重与 KV Cache分布建模完整,严格拒绝采样成熟草稿延迟、显存和多模型维护
Medusa共享末层隐藏状态的多个输出头各未来头并行,彼此不条件依赖附加头、候选树缓冲无需第二个 Transformer,提议延迟低远期头相关性弱,树验证会膨胀
EAGLE预测目标模型下一步特征的轻量自回归模块每步使用前一步 token 与特征特征草稿模块及其状态条件依赖更强,通常有更好的远期提议草稿仍需逐步运行,集成更复杂

Medusa 的并行头在延迟上很干净,但它们都只看同一个 hth_t。第 3 个头无法根据第 1、2 个头实际选中的 token 修正自己,这是一种条件独立近似。候选树用多分支覆盖这种不确定性,却以目标验证节点数为代价。

EAGLE 则在特征空间自回归地产生草稿,每一步能够条件于前面的候选,因此更擅长建模 token 间依赖。它用顺序草稿开销换更高质量的候选。关于特征草稿、树验证和后续 EAGLE-2/3 的演进,可参考 EAGLE 完全拆解。两者不是简单的“头多”和“头少”,而是在提议器延迟、条件建模能力与验证树规模之间选择不同工作点。

DeepSeek 的 MTP 也预测多个未来 token,但其预测模块按未来深度顺序连接,会融合前一未来 token 的表示,并且作为模型原生结构参与适配;它不是把多个完全独立词表头并排挂到同一个隐藏状态上。相关结构可参考 MTP 完全拆解。在工程上,原生 MTP 与 Medusa 都可充当自投机提议器,但 checkpoint 兼容性、头部计算图和接受统计并不相同。

Tree Attention 也不是 Medusa 独有。SpecInfer 等工作可把一组草稿序列自底向上合并成树;Medusa 则利用多头 top-k 组合自顶向下生成树。树式验证是通用执行技术,Medusa Heads 是一种候选来源,二者应分开理解。

局限与适用场景#

Medusa 最适合以下组合:低 batch 或交互式延迟敏感服务、权重读取占主导、模型输出具有较强局部可预测性,并且可以接受为目标模型准备专用附加头。代码、结构化抽取和模板化文本往往比高熵创作更容易获得长接受前缀。

它不是“给任意 checkpoint 打开一个开关”。附加头必须与目标模型、词表和输出风格匹配;领域或对齐风格发生偏移后,远期头的排名统计与最优树形都可能变化。即使主干模型不改,也仍需拿到与该 checkpoint 匹配的附加头。

长上下文下,树中每个 query 都要读取历史 KV。虽然不同候选共享权重前向,注意力部分的流量仍随上下文长度和节点数增长。模型权重占主导时树验证很划算;历史 KV 已经成为主要瓶颈时,扩大树可能放大带宽压力。实际最优节点数应随上下文长度变化,固定一棵树未必覆盖从短对话到十万 token 上下文的全部区间。

typical acceptance 的速度来自放宽分布保持要求。开放式对话中“质量相近”不等于逻辑、代码或安全约束下等价;阈值必须以任务级指标验证。若业务要求目标分布不变,应把它关掉,并接受严格规则下更短的平均接受长度。

最后,论文数字基于特定模型、软件基线和硬件环境。更强的目标 kernel 会降低普通自回归的基线延迟,也可能改变 γ\gamma;量化减少权重带宽后,候选验证更容易进入算力受限;高并发调度又会改变空闲算力。Medusa 是一种把串行步数换成单步并行宽度的方法,而不是与硬件和工作负载无关的固定倍数加速器。

小结#

Medusa 的关键不是“同时猜几个 token”这句口号,而是一条完整的推理闭环:附加头从同一个隐藏状态低成本提出多个未来位置的候选;稀疏树把有限验证预算放到高概率路径;Tree Attention 用祖先 mask 和深度 position ID 保持每条路径的因果语义;接受器只提交一条路径的连续前缀;KV Cache 再从临时树布局收拢为已确认序列。

从性能模型看,真正要优化的是平均提交数 Aˉ\bar A 与单轮膨胀 γ\gamma 的比值。更多头、更宽树、更松阈值都可能提高 Aˉ\bar A,也分别带来词表投影、目标验证和质量偏移的代价。Medusa 的工程价值,正在于它把投机提议器压缩成目标模型上的几个头,并把剩余复杂性明确暴露为树形、掩码、接受规则与缓存提交四个可测量模块。

参考资料#

  1. Medusa: Simple LLM Inference Acceleration Framework with Multiple Decoding Heads(ICML 2024)
  2. Medusa arXiv 论文与源文件
  3. FasterDecoding/Medusa 官方实现
  4. Together AI:Medusa 技术解读
  5. Fast Inference from Transformers via Speculative Decoding
  6. Accelerating Large Language Model Decoding with Speculative Sampling
  7. SpecInfer: Accelerating Generative Large Language Model Serving with Speculative Inference and Token Tree Verification
  8. Blockwise Parallel Decoding for Deep Autoregressive Models
  9. Truncation Sampling as Language Model Desmoothing

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Medusa 完全拆解:多头树式候选如何用一次验证解码多个 Token
https://pinghaoyang.com.cn/aigc/posts/medusa-speculative-decoding/
作者
平昊阳
发布于
2026-10-13
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
平昊阳
乘长风,破巨浪, 展鸿图于未央!
--
总访问量
--
访客数
公告
欢迎来到我的个人博客!欢迎关注交流吖!
更多相关公告,见
「社交-留言」。
音乐
封面

音乐

暂未播放

0:000:00
暂无歌词
站点统计
文章
212
分类
26
标签
291
总字数
2,407,536
运行时长
0 天
最后活动
0 天前

文章目录