自回归大模型生成时,最让人难受的就是“逐 token 串行”。推测解码(Speculative Decoding)的解法是,先让一个更小的草稿模型快速生成一段候选 token,再用目标模型并行验证按概率接受,从而把多次串行生成折成一次大模型前向。可草稿模型本身质量不够,候选经常被拒,加速就成了空谈。xPress 这个主题,核心正是给推测解码中的扩散草稿模型(Diffusion Drafters)加一个并行精炼(Parallel Refinement)阶段:在草稿生成之后、大模型验证之前,用一次轻量并行修正把候选序列往目标模型分布上“拉近一点”,从而提高接受率。如果你正在做推理加速、模型服务化部署,或者研究扩散模型生成和推测解码的结合方式,那 xPress 这类工作真正值得看的不是“又多了一个加速 trick”,而是它把“候选生成”和“候选精炼”拆成了可并行、可优化的独立环节。
下面按实际落地顺序拆开讲。先解释为什么需要这个环节,再拆并行精炼到底在并行什么,接着给复现前的环境准备、实验设计、常见问题排查和最终落地建议。整个链路不算长,但每一步都有不少细节值得单独确认。
1. 推测解码里为什么需要“扩散草稿模型 + 并行精炼”
1.1 先理解推测解码的草稿-验证范式
自回归模型生成时,每一个新 token 都依赖前面已经生成的所有 token。哪怕 GPU 的矩阵计算能力再强,也没法在一个 step 里直接算出多个独立 token,因为它们在计算上天然串行。推测解码的思路,就是在中间加一个“草稿模型”:
- 小模型先自回归或非自回归地生成一段候选 token,数量记为 K。
- 目标模型一次性把这 K 个候选 token 的前向计算做掉,得到每个位置的概率分布。
- 根据目标模型的概率,逐个判断草稿 token 是否可以被接受。
- 如果某个位置被拒绝,就从目标模型重新采样,然后从该位置继续生成。
理想情况下,如果 K 个候选都满足目标模型分布,一次大模型前向就能产出 K 个 token,端到端速度接近原来的 K 倍。但实际瓶颈也在这里:小模型能力通常比较弱,生成的候选和目标模型偏好差距大,接受率低。一旦接受率不高,前面生成候选的时间、验证的时间都白花了,甚至比直接让大模型逐 token 生成还要慢。
所以推测解码的效果,很大程度上不取决于小模型“跑得多快”,而是取决于它的候选“被目标模型接受多少”。草稿质量差,后面的验证就是纯浪费。
1.2 扩散模型当草稿模型,能并行但不够稳
传统推测解码里,草稿模型一般还是一个小规模自回归模型。它的优势是容易和主模型共享 tokenizer 和训练分布,缺点也很明显:生成 K 个 token 仍然需要 K 次串行前向,只是前向计算量比大模型小而已。于是有人开始考虑用扩散模型或非自回归模型当草稿。
扩散模型和自回归模型的核心区别,是它不需要逐 token 生成。它从一个随机初始化的张量出发,经过多步去噪,逐步逼近一个样本。整个过程可以一次更新整个序列,天然适合在 GPU 上并行。把这种模型当草稿时,理论上可以并行产出一整段 token,而不是一个接一个蹦出来。
但问题也随之而来。扩散模型对步数非常敏感:只跑很少的去噪步,候选质量很粗糙,偏差明显;跑很多步,质量上去了,生成耗时又回来了。在推测解码场景里,我们还要额外考虑“目标模型会不会接受”。低步数生成的候选里,可能只有一两个 token 能被目标模型认可,其余都被拒绝。这样一来,扩散模型的并行生成优势就被接受率拖住了。
因此,不能把“扩散草稿模型生成完”和“大模型验证前”之间直接空着。需要有一个成本可控的修正环节,把草稿拉回目标模型偏好的方向。
1.3 xPress 在这条链路里承担什么角色
从标题和这类方法的设计逻辑看,xPress 解决的就是“草稿生成完了,但质量不够高”的中间环节。它不是一个独立的生成模型,而是放在扩散草稿模型之后、目标模型验证之前的一个并行精炼模块。
精炼模块接收扩散模型给出的候选序列,在并行维度上对它做一轮或几轮修正,输出一组更接近目标模型分布的候选,再交给目标模型验证。这样做有两个好处:
- 候选接受率可能提升,平均接受长度变大,端到端延迟下降;
- 精炼过程沿用并行更新方式,不会像自回归那样重新引入串行依赖。
但精炼本身也有成本。额外增加了计算,就要看它带来的“接受率提升”能否覆盖“精炼耗时”。所以 xPress 这类方法能否落地,不是看模块设计有多精巧,而是看收益和成本之间的差值。
2. xPress 的并行精炼到底在“并行”什么
2.1 精炼对象是草稿候选,不是重新生成完整答案
很多初学者容易把“精炼”理解成“让大模型再润色一遍”,这是成本完全不同的两件事。xPress 语境下的精炼,对象是扩散草稿模型产生的候选序列,通常体现为 token 序列、logits 或者隐状态。精炼网络会把候选视为“已经存在、但含有噪声”的序列,通过若干小步修正,让序列更符合目标分布。
举个例子:如果草稿里某个 token 在目标模型偏好分布中概率很低,精炼不只是把这个词单独替换成另一个词,而是要整体调整整个候选块。因为 token 之间存在上下文依赖,单独替换很容易破坏语法和语义连贯性。这和“重新生成答案”不同,精炼是在已有候选附近做局部修正,计算量比从零生成小得多。
2.2 可以从三个维度理解并行
第一个维度:序列内并行。扩散草稿模型一次生成一整段候选序列,精炼时也是整段同时修正,而不是从左到右逐 token 生成。这是和自回归最本质的区别。
第二个维度:批量候选并行。同一个 prompt 可以同时生成多条草稿候选,每条候选的精炼过程相互独立。GPU 对 batch 操作非常友好,一次处理多条路径可以提高吞吐,也能为最终验证阶段提供更多样的候选。
第三个维度:修正分支并行。同一条草稿可以同时跑多个精炼步,或者使用多种修正策略,最后根据置信度合并。这个维度空间更大,但成本也更高。因为后续验证阶段的输入量会成倍增加,显存占用和计算量都会涨上去。
工程上最常用的是前两个维度。增加 batch 是 GPU 上最自然的事;修正分支一旦很多,后续验证开销会线性上升,反而可能得不偿失。
2.3 并行精炼不等于多候选筛选
容易混淆的地方在这里:先随机生成 N 条草稿,再让大模型挑一条最好的,这不叫精炼。多候选筛选是“选择已有结果”,并行精炼是“对已有结果做修正”。前者候选池越大,验证阶段成本越高;后者目标是让每个候选质量变高,候选数量可以保持较小。
做实验时,要特别注意把“候选数增加”和“并行精炼”分开测试。否则你看到端到端速度变慢,很难判断是候选数增加的验证开销导致的,还是精炼模块本身的耗时导致的。我更建议从固定一个较小候选数开始,比如 4 或 8,先测不加精炼和加精炼的差异。如果加精炼后接受率明显上升,端到端变快,再继续调候选数。
注意:不要一上来就开最大并行。先用一条样例确认输入、精炼和输出日志都正常,再逐步增加并行分支。
3. 想复现 xPress 思路,先准备什么
3.1 环境依赖和硬件下限
原始相关资料没有给出统一的具体版本号,所以我这里只给一个通用参考。你需要一个能跑目标模型和小型扩散草稿模型的深度学习环境,PyTorch 或兼容框架都可以。如果参考实现基于 Transformers 或 Diffusers 风格组件,还需要确认对应组件版本之间能互相兼容。不同模型加载方式差异很大,落地前先确认依赖版本,别直接上最新版。
硬件方面,核心是显存和内存。目标模型、草稿模型、精炼模块、候选张量都会同时占显存。一张 24G 左右显卡跑 10B 级别目标模型加一个小草稿模型,通常需要先把候选数量和精炼步数压下来。如果只有 8G 到 12G 显存,建议使用量化后的小目标模型,或者把目标模型降到 3B 到 7B 级别。磁盘和网络在单机推理场景里不是主要瓶颈,但如果需要从远端拉权重,就要保证网络稳定,避免下载一半中断。
3.2 三件最小验证任务
不要一上来就跑完整 pipeline。我建议先做三件最小验证:
- 扩散草稿模型能否独立生成一段 token。确认 prompt 的编码方式、输出 tokenizer、结束符处理都是对的。
- 目标模型能否对这段 token 做批量前向验证。确认能拿到每个 token 位置的 logits,而不是只支持标准自回归生成。
- 精炼模块的输入输出维度是否和草稿模型输出对齐。最容易出错的是 hidden size 不匹配、序列长度 padding 处理不一致、token id 和 embedding 混用。
这三步都正常,再串起来跑完整链路。这样可以避免把“模块对齐”的问题错当成“精炼效果不好”。
3.3 输入输出形态和验收标准
输入一般是文本 prompt 加生成参数。输出最终是文本,但过程日志更重要。你需要记录:草稿生成了哪些候选、精炼后变成什么样、目标模型接受了几步、在哪一步被截断。
验收标准应该分四层,不要只看第一层:
| 验收层级 | 关注点 | 判断标准 |
|---|---|---|
| 第一层 | 能否跑通 | 单条 prompt 能正常生成,不报错 |
| 第二层 | 质量是否接近 | 输出和直接目标模型生成结果语义、流畅度接近 |
| 第三层 | 耗时是否缩短 | 端到端延迟比直接生成更低,或相对不恶化 |
| 第四层 | 是否稳定 | 多条 prompt、多次重复,结果波动不大 |
跑通只代表链路正确,不代表方法有效。很多方案 demo 能跑,但一上指标就不行,问题往往出在验收标准定得太粗。
4. 实验设计:怎么验证并行精炼真的有效
4.1 先设置三组对照
要判断 xPress 这种并行精炼是否有价值,最少需要三组对照:
- A 组:目标模型直接生成,作为 baseline。
- B 组:扩散草稿模型生成候选,目标模型验证,不加精炼。
- C 组:扩散草稿模型生成候选,加入并行精炼,再交给目标模型验证。
如果 B 已经比 A 快不少,那 C 的意义更多是提升质量稳定性;如果 B 比 A 还慢,说明扩散草稿在这套任务里质量不够,C 的目标就是把“慢”拉回来。三组都要固定相同的 prompt 集合、随机种子、候选长度 K、草稿模型去噪步数、目标模型 batch size。否则对比结果没有意义。
4.2 核心指标的计算口径
建议重点看四个指标:
- 接受率:目标模型最终接受的 token 数除以草稿候选 token 总数。接受率越高,草稿质量越好。
- 平均接受长度:一次草稿生成到验证截断之间,被连续接受的 token 数。这个指标比接受率更直观,平均接受长度越长,越接近“一次大模型前向生成 K 个 token”的理想状态。
- 端到端延迟:从 prompt 输入到完整生成结束的墙钟时间。跑多次取中位数,不要只看一次。
- 显存峰值:用 nvidia-smi 记录,避免并行精炼带来隐性 OOM。
如果 C 组的平均接受长度显著高于 B 组,但端到端耗时没有下降,说明精炼阶段耗时占比太高,需要减少精炼步数或减少并行分支。
4.3 参数调整优先级
我建议按这个顺序调整参数:
- 候选块长度 K。K 太小,草稿的优势摊不薄;K 太大,精炼和验证成本线性上涨。
- 扩散草稿模型的去噪步数。步数少,草稿快但噪声大;步数多,草稿慢但质量高。
- 精炼步数。先设 1 到 3 步,不要直接调到 10 步。
- 并行分支数或候选数。显存不足时优先降这个。
每一步只动一个变量,记录耗时和接受率。你很快就能看到收益拐点。例如 K=16 时接受率已经到 0.8,继续调到 32 反而变慢,那就不需要继续加。
5. 常见问题和排查顺序
5.1 输出质量明显劣化
现象是加了精炼后,生成内容不如直接生成,甚至出现重复、语义偏移。
排查顺序是这样的:
- 先看精炼模块是否把本来正确的 token 改错了。
- 再确认精炼用的目标分布和验证模型是不是同一个。如果精炼阶段用了一个不匹配的分布,越修越偏。
- 检查步数。精炼次数太多会过度修正,把原本没问题的表达也改掉。
- 检查采样参数。草稿、精炼、验证三个阶段如果采样随机性不一致,也会出现飘。
我遇到最多的情况是输入了 token id,但精炼模块内部把它当成索引去查词表,和 hidden state 没有对齐,导致修正结果和语义无关。这类问题光看输出很难看出来,必须打印中间张量形状。
5.2 显存或内存不够
批量并行时 OOM,或者单条正常但并发一高就崩,都不是罕见现象。
排查顺序:
- 减少候选数量和并行分支数。
- 精炼时关闭梯度计算,用推理模式。
- 及时释放中间张量,避免多个变量引用同一块显存。
- 验证阶段把 batch 切小一点,不要一次验证太多候选。
如果只是复现,不用纠结必须在一张小显存卡上跑满。换一个小目标模型,先把流程跑通,再逐步把模型放大。
5.3 生成速度不升反降
现象是精炼把耗时拉高,端到端比直接生成还慢。
排查顺序:
- 把一次完整生成拆成草稿、精炼、验证三段,分别计时。
- 如果精炼耗时占比高,减少精炼步数或修正分支。
- 如果验证耗时占比高,降低 K 或候选数。
- 检查是否每次迭代都在 CPU 和 GPU 之间来回拷贝张量。这类隐含开销很耗时,而且还容易被忽略。
这类问题最忌讳只看总时间。总时间变慢,你不拆阶段很难定位是哪里出了问题。
5.4 采样随机性造成的误判
同一 prompt 跑两次,一次快一次慢,一次质量高一次低。这可能不是代码问题,而是采样随机性导致的。
排查顺序:
- 固定随机种子再对比。
- 多准备几条不同 prompt,跑 5 到 10 次取平均。
- 把单次成功结果和单次失败结果都记录下来,避免以偏概全。
- 如果只有一条 prompt 加速明显,其他都不行,说明方法对数据分布有偏好,不能当作稳定收益。
做这种实验时,我一般会固定随机种子,同时固定 prompt 集合,至少覆盖短文本、长文本、问答、翻译等不同场景。否则很容易被个别样例误导。
这里注意:采样随机性和并行精炼的关系很大。草稿模型一旦换了 seed,候选就完全不同,精炼后的接受率也可能变化很大。因此,跑对比实验前先统一随机种子。
6. 落地建议:先跑稳单条,再做批量
6.1 学习、复现和生产场景的区别
学习或复现场景,用一张消费级显卡跑简化版本就够了。候选数量设小一点,精炼步数低一点,核心是理解整个链路每个阶段的输入输出。
生产场景就完全不一样了。要额外考虑并发请求、排队、超时、日志、输出一致性、失败重试。单条速度再好看,一旦多个请求同时打进来,显存和调度都会成为瓶颈。并发越多,单请求能用的显存越有限,候选数量和精炼步数可能还要进一步下调。
建议按照“单条 -> 多条定时 -> 并发压测”的顺序推进。不要刚跑通一条 prompt 就想着上线。
6.2 重点盯住三张日志
第一张日志:每个请求的草稿、精炼、验证耗时。它告诉你时间花在哪。
第二张日志:接受率、平均接受长度、截断位置。它告诉你精炼有没有效果。
第三张日志:显存峰值、是否 OOM。它告诉你资源边界在哪。
没有这三张日志,调参基本靠感觉。尤其是并行精炼这种多阶段链路,阶段耗时一旦混在一起,问题定位会非常痛苦。
6.3 后续可以扩展的方向
如果基础流程已经跑通,后面值得继续尝试的方向包括:
- 把精炼目标设计成显式接近目标模型分布,而不是只用启发式修正。
- 用蒸馏或强化学习训练精炼模块,让修正行为更稳定、可复用。
- 把精炼模块做成可插拔组件,兼容不同扩散草稿模型。
- 支持流式输出和长文本生成。
- 在应用层加入超时、熔断和降级策略。
这些方向都需要先有一个可复现的基础 pipeline。没有基础流程,直接上强化学习或者蒸馏,只会让排查难度成倍增加。
踩过几次之后我发现,很多问题不是 xPress 这个思路本身不行,而是我把草稿、精炼、验证三个阶段混在一起看,导致定位不到耗时和劣化来源。所以在复现和落地前,先把链路拆清楚,固定随机种子,逐阶段计时,比直接堆参数重要得多。如果只是学习,默认配置已经足够;如果要长期使用,一定要把日志、输出目录和任务队列提前整理好。