第 11 章 · 采样Multi-token prediction

多 token 预测

深入阅读 · 第 11 章 Sampling——模型如何在一次前向中掏出不止一个 token

上一章讲的 sampling,把一行 logits 变成一个 token。要产出下一个 token,你得把它接回提示词,再把整个模型重新跑一遍——全部 24 层、每一个权重。一段 200 token 的回复就是 200 次完整前向,严格一个接一个,因为每个 token 都依赖前一个。这就是自回归税,也是生成感觉慢的最大单一原因。

让它真正难受的,是每一次前向把时间花在哪。产出一个 token 会把模型里每个权重恰好碰一次,所以一个解码步是访存受限的:GPU 这一步的大部分时间只是在把那十亿来个参数从内存里读出来,而那一个 token 实际要做的数学,相比之下只是个零头。芯片大多在闲着、等内存。于是有一个诱人的问题:既然我们反正要付出把这些权重全都搬进来的代价,能不能为这同一笔钱拿到不止一个 token

这个赌注:先 draft,再 verify

推测式解码(speculative decoding)就是那个说「能」的把戏。思路是先廉价地出接下来几个 token,再让真正的模型在一次前向里把所有猜测一次性查完。查 k 个猜测,开销和正常产出一个 token 差不多——都是过一遍权重——所以每个活下来的猜测,都是你白赚的一个 token。猜得好,一次前向就吐出好几个 token;猜得差,你也只是浪费了一点点廉价的草稿,仅此而已。

传统上你需要第二个、更小的「draft 模型」来做猜测——一个额外的模型,要训练、要加载、还要保持同步。多 token 预测(multi-token prediction,MTP)去掉了这笔成本:模型为它自己打草稿。Qwen3.5 随身带着一个小小的额外模块——MTP 头——它唯一的活,就是在主模型还在收尾当前 token 时,先提议下一个 token。

Qwen3.5 随身带的 MTP 头

它刻意做得很小,因为几乎所有东西都从主模型那里借。它接收两个输入:主模型在当前位置的最后一个 hidden state,以及刚刚吐出的那个 token 的 embedding——关键在于,这个 embedding 来自主模型用的同一个 embedding 矩阵(mtp_use_dedicated_embeddings: false)。它把两者都归一化,用一次投影把它们融合,过一层transformer,再通过主模型用的同一个 tied LM head 读出一个 token。整个头就这么多:

一个 MTP 模块的内部
刚生成的 token → embedding1024-d用的是主模型的 embedding 矩阵last hidden state h1024-d主模型在当前位置的最终 hiddenRMSNormpre_fc_norm_embeddingRMSNormpre_fc_norm_hiddenconcat([emb_norm, hidden_norm])2048 = 2 × 1024fc · Linear 2048 → 1024无 bias一个 full-attention 解码层self-attention + 门控 MLP
一个 FULL-attention 层——绝不是 GatedDeltaNet;它不触碰循环状态
RMSNorm最终共享 LM head(权重绑定)草稿 token · 即「下一个之后」的那个

复用主模型的 embedding 与 LM head,所以这个头很小——一个 transformer 层、三个 norm、一个投影。

虚线框与主模型共享——本身没有新增权重。

config.json: mtp_num_hidden_layers: 1 · mtp_use_dedicated_embeddings: false

有两个细节对后面很重要。第一,这个头的那一层是全量注意力层——不是 18 个 GatedDeltaNet 层里的某一层——而且它自带一个小小的注意力 cache;它从不碰主模型的循环 state。第二,因为 embedding 和输出头是共享的,唯一的新权重就是三个 RMSNorm、一次投影,以及那单独一层。在配置里这就是一行——mtp_num_hidden_layers: 1——一个预测下一个之外再多一个 token 的单一模块。

这个循环:draft 个、verify 一次、接受匹配的

现在让这个头干活。一个解码步变成三个动作。Draft(打草稿):廉价的 MTP 头一个接一个地提议D 个候选 token(Qwen3.5 的默认深度是 D = 1,不过引擎可以把它调高)。Verify(核验):完整模型把窗口[最后提交的 token, d₁, …, d_D]一次批处理前向里跑完,一口气在每个位置都给出它自己的意见。Accept(接受):从左到右走过草稿,保留每一个与完整模型本会选出的相符的猜测;在第一个不匹配处,改吐出一个修正 token,然后停下——在 temperature 0 时,这个修正就是完整模型的 top token;而带采样时,它是从一个由两个模型的概率共同构建的 调整后分布中抽取的,而不是简单地取 target 自己选定的那个 token。一步步看:

speculative decoding——draft、verify、accept
提交
draft
verify
accept
统计
已提交MTP draftofFranceis

示意 token——讲概念,并非本模型的真实输出。

目前已经提交了 5 个 token。普通解码现在会把全模型跑一遍,恰好再得到 一个 token。speculative decoding 试图一次拿到好几个。

无损 · 与普通解码分布一致最好情况:D+1 = 4 token / 遍 · 最坏情况:1 个 token + 一点点草稿开销
本遍 token:3
遍数:0 · token: 0 / 遍

在本项目原生引擎中实测:约 1.06–1.46× 加速,temperature 0 时每次 verify 约 1.3–1.46 个 token——增益取决于文本有多可预测。Qwen3.5 的默认 draft depth 为 1。

算一算收益。如果 D 个草稿里有 K 个被接受,这一步就吐出 K + 1 个 token——K 个好猜测,加上 verify 这趟白赚的一个 token(第一处失误处的修正,或者当所有草稿都对时、末尾再多送的一个 token)。所以过一遍权重,能产出 1 个 token(全被拒——和朴素解码跑同样一趟完整模型前向,只是多花了草稿那一点点开销)一直到 D + 1 个之间的任意数量。

为什么它恰好是无损的

下面这部分,正是让推测式解码诚实、而非以质量换速度的关键:在 temperature 0 时,一个草稿只有在它与完整模型自己在那个位置本会选出的顶选相符时,才会被保留。带采样时,接受判据换了一套,但同样严格——它比较 target 和 draft 对那个具体草稿 token 各自给出的概率,并以概率 接受,被拒时则从一个修正性的残差分布里重新抽样。不管哪种情况,verify 这趟用的都是真模型的真分布;MTP 头永远说不了最后一句话。配上恰当的接受规则——Leviathan 等人提出、Chen 等人独立提出的推测式采样/解码算法,两者都发表于 2023 年——你吐出的 token 流在分布上与朴素的逐个解码完全一致——每一步的概率、以及 temperature 行为都相同(在 temperature 0 时就是完全相同的文本;带采样时则是完全相同的概率),只是用更少的前向产出。MTP 头永远改不了答案——最坏只是付一点点草稿税,最好则能省下好几趟前向。

这也是为什么猜错很便宜:一个被拒的草稿,只花掉产出它的那一点点 MTP 计算。昂贵的 verify 那趟无论如何都要发生——它就是朴素解码本会跑来产出那一个 token 的同一次前向。推测式解码,最坏不过是带一点点草稿税的朴素解码;最好则是好几倍的速度。

这个想法从哪来

MTP 不是一出场就是成品。我们走到这里用了三步,Qwen3.5 所采用的是第三步的设计:

MTP 谱系——三个里程碑
Meta 2024并行
  • n = 4 个并行 head
  • 彼此独立,相互之间没有因果链
  • 训练:作为辅助任务
  • 推理:自我推测式(self-speculative),最高快 3×
  • 在 13B 上 +12% HumanEval / +17% MBPP
arXiv 2404.19737
DeepSeek-V3 2024串联
  • D = 1(一个额外 token)
  • 串联——保持因果链
  • 共享 embedding 与 output head
  • 训练损失 λ·(1/D)·ΣLᵏ,λ:0.3→0.1
  • 推理:丢弃,或用于 speculative decoding
arXiv 2412.19437
Qwen3.5 / Qwen3-Next 2025串联
本课程使用的模型
  • 一个共享的串联 module,depth 为 1
  • mtp_num_hidden_layers: 1
  • 共享绑定的 embedding(mtp_use_dedicated_embeddings: false)
  • 同时提升预训练效率与推理速度
  • 可在 vLLM / SGLang / 本项目的原生引擎中运行

这个领域从并行的独立 head(Meta)转向一个保持因果链的串联 module(DeepSeek → Qwen)。

Meta 2024 年的版本在一个共享主干上栓了四个并行的头,每个同时预测一个不同的未来 token——作为训练信号很棒,但这些头彼此独立,所以没法互相作为条件。DeepSeek-V3 把形状改成了单个串行模块,它预测多出来的一个 token,同时保住因果链——预测 t+2这个 token 的草稿,能看到已经选定的 t+1。Qwen3.5(建立在 Qwen3-Next 架构之上)采用的正是这种串行、权重共享的设计:一个模块,深度为一。

它把自己的饭钱挣了两次

同一个头在两个截然不同的时刻都有回报。在训练时,要求每个位置去预测接下来两个 token 而不是一个,是一个更稠密的学习信号——每一个 token 的数据里都有更多可学的东西。Meta 报告说他们的多 token 模型在 13B 规模下多解出了 12% 的 HumanEval 和 17% 的 MBPP 题;DeepSeek-V3 把一个 MTP 损失折进预训练,其权重从 起步,在最后一段 token 上降到

Qwen 把自己的 MTP 描述为同时提升预训练效率和推理速度。在推理时,同样的这些权重就变成了上面那个循环里白送的自我 draft 者。一个模块,两份工作:一个训练得更好的模型,外加一个更快的模型。

一座 drafter 动物园

上面所有内容都把 MTP 头当作那份廉价 draft 的来源。但 draft → verify → accept这副骨架并不在乎猜测从哪来——它只在乎猜测够廉价、以及 verify 那趟守住无损保证。换掉 drafter,你就得到一整个 speculative decoding家族,它们全都共享你刚刚走过的那同一趟 verify。

draft-target 方案(由第二个更小的模型来写 draft——最容易加装,但你现在要跑两个模型);Medusa(在模型自身上长出几个额外的预测 head,不需要独立模型);EAGLE(一个专门打造、读取 target hidden states 的微型模型——通用对话的首选);以及 n-gram / lookahead解码(完全不用 draft model——只是查一下通常接什么,在代码和可预测语法上收益巨大)。你刚认识的 MTP 头,就是 Qwen3.5 随身带的那一种。挑一个 drafter,比一比什么变了、什么没变:

挑选你的 drafter——speculative decoding 家族
you are here · Qwen3.5
已提交廉价 drafterD 个 draft tokenisMTP headParis,a全模型 verify · 一遍只有 verify 接受才保留(T=0 时保证严格匹配)

骨架每次都一样——只有 drafter 这个盒子在变。

draft 来源
模型自带的 MTP head
典型 draft 长度
1 个 token(Qwen3.5 默认 depth)
最适合
本课程已经讲过的部分

模型通过自带的 MTP head 为自己起草——没有第二个模型,也无需训练。这正是本课程已经走过的 draft → verify → accept 循环,也是本项目原生引擎真正运行的那一种。

对每一种 drafter 都成立
  • 全部无损。在 T=0 时,verify 只有在 draft 与真实模型自己的顶选一致时才保留;带采样时,它跑的是同一套概率比接受/重采样判据,所以每一种变体只改变速度,绝不改变答案
  • 低 batch 时收益最大(有富余算力去 verify);在高 batch 时会被动态关闭——GPU 已经被占满了。
  • temperature 越高 → token 越难预测 → 接受率越低

浏览器内的演示这些都不跑——它只做普通的自回归解码,每遍一个 token。本项目的原生引擎运行的是 MTP 变体(在那里约 1.06–1.46×)。请把这个选择器看作一台生产服务器会从中挑选的菜单——代码用 n-gram,通用对话用 EAGLE——它们共享着课程已经证明过的同一套无损 verify

有三条规则对整座动物园都成立。它们全部无损——在 temperature 0 时,verify 只有在 draft 与真实模型自己的顶选一致时才保留;带采样时,它跑的是上面那同一套概率比接受/重采样判据。所以每一种变体只改变速度,绝不改变答案。它们都在低 batch 时收益最大,此时 GPU 有富余算力去白跑那趟 verify;在高 batch 时芯片已经被占满(batching 子章节会讲为什么),于是服务器会把推测关掉。而 temperature 越高,下一个 token 越难预测,接受率就越低。空闲且文本可预测时,推测为你买来速度;反之,它会安静地让到一边。

你浏览器里的 Qwen 用它吗?

没有——而且照例,诚实的答案才是有意思的那个。架构是真的:这个具体模型的配置写着mtp_num_hidden_layers: 1,所以一份完整的 Qwen3.5 checkpoint 确实定义了你刚看到的那个头。但有两件事让它在这个演示里没上路。浏览器下载的那份 bf16 checkpoint 在转换时没带上 MTP 权重——它的 474 个 tensor 里,零个mtp.*——而这里的 WebGPU 解码循环是朴素自回归的,一次前向一个 token,完全没有 draft/verify 那套机制。(你不孤单:原版的 Hugging Face Transformers 也会丢掉 MTP 权重;如今主要是 vLLM、SGLang,以及本项目自己的原生 Metal 引擎这类推理引擎,才真的去跑那个循环——在那里实测大约快1.06–1.46×,取决于文本有多好预测。)

所以多 token 预测,和视觉编码器以及完整 checkpoint 里其他「架构有定义、但本次浏览器之旅不会走到」的部分坐在一起:在 Qwen3.5 里是真的,在这里是有意关掉的。你甚至能在架构图里看到它被置灰——那个「MTP head」节点,就在一个 token 在本演示里真正会走的路线之外。