资讯动态

从DFlash到DSpark:推测解码如何将大模型推理加速2-3倍

发布时间:2026/10/20 17:13:11 来源:尧图企业网站定制
1. 推测解码到底在解决什么问题大模型推理这件事真正在一线部署过的人都知道最贵的从来不是训练而是推理阶段的显存带宽和延迟。你有一张卡模型权重塞进去了KV Cache 也留了空间结果生成一个 token 要等半天用户体验直接崩掉。推测解码Speculative Decoding就是在这个背景下被反复拿出来讨论的方案而 DFlash 到 DSpark 这条演进线恰好是这套思路从能用走向好用的一个典型缩影。先说清楚推测解码的核心逻辑。常规自回归生成是大模型每步算一个 token算完再算下一个串行到底。问题在于大模型前向一次的计算量很大但真正决定速度的往往不是算力而是显存读取权重的带宽。也就是说你花了大价钱算了一次只产出一个 token性价比极低。推测解码的思路是找一个小而快的草稿模型Draft Model让它连续猜出 K 个 token然后把这 K 个 token 一次性喂给大模型做并行验证。大模型一次前向就能验证这 K 个位置接受其中正确的部分遇到第一个错误就截断从那里重新开始。这个机制带来的收益是如果草稿模型猜得准大模型一次前向能接受多个 token等效于把串行步数压缩了。假设草稿模型平均能猜对 3 个 token那理论上生成速度能接近 3 倍。但现实没这么美好因为草稿模型本身也要花时间而且猜得不准的时候验证完发现只对了 1 个那这次推测就是纯亏。所以推测解码的工程难点集中在三个地方草稿模型要足够小、足够快草稿质量要足够高接受率Acceptance Rate要稳验证和调度的开销要压到最低。DFlash 和 DSpark 这两代方案本质上就是在不同阶段对这三个难点的取舍和优化。提示推测解码不是免费加速它是一场关于接受率和额外开销的博弈。接受率低于某个阈值时整体反而会变慢这个阈值通常在 0.6 到 0.7 之间具体取决于草稿模型和大模型的速度比。2. DFlash 的设计取舍与核心机制2.1 为什么叫 Flash轻量草稿与并行验证DFlash 这个名字里的 Flash指向的是轻量和快。它的核心设计是把草稿模型做得非常小同时把验证阶段的并行度拉满。具体来说DFlash 采用的是一种多层草稿结构草稿模型不是简单地复制大模型的浅层而是用一个独立训练的小模型参数量通常控制在大模型的 1/10 到 1/20 之间。这里有个关键决策草稿模型是独立训练还是从大模型蒸馏。独立训练的好处是草稿模型可以针对猜下一个 token这个任务专门优化不必承担大模型的全部能力坏处是它和大模型的分布可能有偏差导致接受率不稳定。DFlash 选择了蒸馏路线用大模型的输出分布来监督草稿模型这样草稿模型的预测分布和大模型更接近接受率更可控。验证阶段DFlash 做了一件很聪明的事它把 K 个草稿 token 的验证做成一次前向。具体做法是把草稿 token 拼接到当前上下文后面一次性送入大模型拿到每个位置的 logits然后逐个位置比对草稿 token 是否落在可接受范围内。这个可接受范围不是简单的 argmax 相等而是基于概率的采样接受准则——如果草稿 token 的概率和大模型分布匹配就接受否则以一定概率拒绝。这套准则保证了最终输出的分布和大模型单独生成时一致不会因为推测而改变模型行为。2.2 接受率是怎么被拉起来的DFlash 提升接受率的手段主要有三个。第一是草稿模型的训练目标对齐前面说的蒸馏就是其中一环。第二是动态 K 值不是固定猜 K 个而是根据当前上下文的可预测性动态调整。比如在代码生成这种结构化强的场景下一个 token 往往很确定K 可以设大一点在开放式对话里K 就收小。第三是草稿模型的温度控制草稿采样时用比大模型更低的温度让草稿更保守减少乱猜。实测下来DFlash 在代码补全和结构化文本生成场景的接受率能到 0.8 以上通用对话大概在 0.6 到 0.7 之间。这个数字看起来不高但配合 K4 到 6 的草稿长度等效加速比能到 1.8 到 2.5 倍。注意这里是等效加速比不是理论值已经扣掉了草稿模型的开销。2.3 DFlash 暴露出来的瓶颈DFlash 用了一段时间后问题逐渐清晰。最突出的是草稿模型和大模型之间的同步开销。每次验证完如果只接受了部分 token草稿模型需要重新对齐上下文这个对齐过程在长上下文场景下开销不小。另外草稿模型虽然小但它也是串行生成 K 个 token 的K 越大草稿阶段耗时越长边际收益递减。还有一个隐蔽的问题显存占用。草稿模型虽然参数少但它需要独立的 KV Cache在长上下文场景下这部分显存和主模型的 KV Cache 叠加容易把显存吃紧。DFlash 的应对是把草稿模型的 KV Cache 做量化但这又引入了精度损失接受率会掉一点。3. DSpark 的改进路径与关键突破3.1 从独立草稿到共享表征DSpark 最大的变化是草稿模型不再完全独立而是和大模型共享一部分底层表征。这个思路的动机很直接草稿模型猜不准很多时候是因为它对上下文的理解不够深。如果让草稿模型复用大模型的底层 hidden states它就能站在大模型的肩膀上做预测接受率自然上去。具体实现上DSpark 让草稿模型接入大模型的第 L 层输出L 通常取总层数的 1/4 到 1/3。这样草稿模型不需要从 token embedding 开始算直接从中层表征接着算计算量大幅下降同时表征质量比独立草稿模型高。这个设计有点像早退机制Early Exit和推测解码的结合但 DSpark 做得更彻底它不是简单早退而是早退之后接一个专门训练的轻量头。这个改动带来的收益很实在草稿阶段的计算量降了大概 40%接受率反而涨了 5 到 10 个百分点。一升一降等效加速比能到 2.5 到 3.5 倍。代价是训练复杂度上去了因为草稿头和大模型的联合训练需要仔细设计梯度流不能让草稿头的梯度污染大模型的主干。3.2 动态草稿树的引入DFlash 是线性草稿猜 K 个 token 就是一条链。DSpark 改成了树状草稿也就是在每个位置不只猜一个 token而是猜多个候选形成一棵树。验证的时候大模型一次前向可以验证树上的多条路径接受其中最长的那条。这个改动的逻辑是线性草稿一旦某个位置猜错后面全废。树状草稿给了容错空间即使某个位置第一候选错了第二候选可能对路径还能延续。实测在开放式生成场景树状草稿的接受长度比线性草稿平均长 1.5 到 2 个 token。但树状草稿的代价是验证时的计算量增加因为要处理树结构而不是简单序列。DSpark 的解法是用注意力掩码把树结构编码成一次前向能处理的形式具体是构造一个块对角加交叉的掩码矩阵让每个树节点只能看到自己的祖先路径。这个掩码构造是 DSpark 工程实现里最精细的部分掩码错了会导致验证结果不对而且这种错误很隐蔽表现为接受率异常低而不是报错。3.3 调度与显存优化DSpark 在调度上做了两件事。一是把草稿生成和验证做成流水线草稿模型在猜第 N1 批的时候大模型在验证第 N 批两者重叠执行把草稿开销藏到验证后面。二是 KV Cache 的分层管理草稿模型和大模型共享底层 KV只有草稿头部分独立显存占用比 DFlash 降了大概 30%。流水线调度听起来简单实现起来坑很多。最大的坑是验证结果不确定——第 N 批验证完可能只接受了部分 token那第 N1 批的草稿就要基于新的上下文重新对齐。如果流水线设计不好这种对齐会导致流水线停顿Bubble反而比串行还慢。DSpark 的做法是让草稿模型维护一个乐观上下文先按全部接受来猜验证结果出来后再做局部修正把停顿概率压到最低。4. 两代方案的核心参数对比把 DFlash 和 DSpark 的关键参数拉出来对比能更清楚地看到演进逻辑。下面这张表是基于常见部署配置整理的具体数值会随模型规模和硬件变化。维度DFlashDSpark变化说明草稿模型参数量主模型的 1/10 到 1/20主模型的 1/20 到 1/30共享底层表征后可以更小草稿结构线性链树状分支因子 2 到 3容错能力提升草稿计算量占比约 35%约 20%共享表征 流水线典型接受率对话0.60 到 0.700.70 到 0.80表征质量提升典型接受率代码0.80 到 0.850.85 到 0.90结构化场景优势更明显等效加速比1.8 到 2.5 倍2.5 到 3.5 倍综合收益KV Cache 开销独立较高共享底层降约 30%显存友好训练复杂度中等较高联合训练需谨慎这张表里最值得关注的是草稿计算量占比这一行。DFlash 时代草稿阶段要吃掉三分之一左右的时间这是加速比上不去的直接原因。DSpark 把它压到 20% 以下加速比才真正拉开。很多人只盯着接受率忽略了草稿开销结果调了半天参数发现速度没变问题就出在这里。注意接受率和加速比不是线性关系。接受率从 0.6 提到 0.7加速比可能只涨 10%因为草稿开销和验证开销也在变。优化时要看端到端的 tokens/s不要只看接受率这一个指标。5. 实操部署中的关键步骤5.1 草稿模型的选型与对齐如果你要自己搭一套推测解码第一步是选草稿模型。最省事的路线是用同系列的小模型比如主模型是 70B 级别草稿用 7B 级别这样 tokenizer 和词表天然对齐不用额外处理。如果找不到同系列的小模型就要自己做词表映射这一步很容易出错映射错了会导致草稿 token 和大模型 token 对不上接受率直接归零。对齐之后是蒸馏训练。训练数据用主模型在目标领域上的输出让草稿模型去拟合主模型的分布。这里有个经验蒸馏数据不要用通用语料要用你实际部署场景的数据。比如你做代码助手就用代码数据蒸馏做客服就用对话数据。领域对齐能让接受率提升 10 到 15 个百分点比调任何超参都管用。训练时的损失函数建议用 KL 散度而不是交叉熵。交叉熵只关心 argmax 对不对KL 散度关心整个分布的匹配度而推测解码的接受准则是基于分布的所以 KL 更合适。实测 KL 训练出来的草稿模型接受率比交叉熵高 3 到 5 个百分点。5.2 验证阶段的掩码构造树状草稿的验证掩码是 DSpark 实现里最容易翻车的地方。假设你的草稿树有 3 层每层分支因子 2那总共有 1247 个节点。验证时要把这 7 个节点拼成一个序列然后构造注意力掩码让每个节点只能看到自己的祖先。掩码构造的逻辑是节点 i 能看到节点 j当且仅当 j 是 i 的祖先包括自己。用代码表示大概是这样的def build_tree_mask(tree_parents): n len(tree_parents) mask torch.zeros(n, n) for i in range(n): j i while j ! -1: mask[i][j] 1 j tree_parents[j] return mask这段代码里tree_parents是每个节点的父节点索引根节点父节点设为 -1。构造出来的掩码是下三角的变体不是标准的下三角因为树结构里节点顺序和深度顺序不一定一致。这个掩码要转成 attention 的 additive mask0 表示可见负无穷表示不可见然后传给模型的 attention 层。踩过的坑掩码的 dtype 要和模型一致用 float16 的时候负无穷要用 -65504 而不是 -inf否则会出 NaN。这个坑很隐蔽因为 NaN 不一定报错可能只是让接受率莫名其妙变低。5.3 流水线调度的实现要点流水线调度的核心是维护两个队列草稿队列和验证队列。草稿模型不断往草稿队列里塞候选验证队列从草稿队列取候选去验证。两个队列的同步用事件Event而不是锁因为 GPU 上的锁开销很大。具体实现时草稿模型的输出要先做一次乐观对齐也就是假设上一批全部被接受基于这个假设生成下一批草稿。验证结果出来后如果发现上一批只接受了部分就要对草稿队列做修正。修正的策略有两种一种是丢弃受影响的草稿重新生成简单但浪费另一种是局部修正只调整受影响的节点复杂但高效。DSpark 用的是后者实现上需要维护一个接受状态数组记录每个位置是否被接受。流水线的深度同时有多少批在飞需要根据硬件调。深度太浅重叠不够加速有限深度太深修正开销大而且显存占用高。经验值是 2 到 3 批具体看草稿模型和大模型的速度比。如果草稿模型很快深度可以大一点如果草稿模型本身就慢深度大了反而拖累。6. 常见问题与排查技巧实录6.1 接受率异常低的排查路径接受率低是最常见的问题排查要按顺序来。第一步查 tokenizer 对齐把草稿模型和大模型的词表拿出来比对看 token id 到字符串的映射是否一致。这一步用脚本跑一遍就行不一致的话接受率会低到 0.1 以下很容易识别。第二步查蒸馏数据分布。如果蒸馏数据和你实际推理的输入分布差太远接受率也会低。比如你用通用语料蒸馏结果推理时全是专业术语草稿模型没见过这些词自然猜不准。解决办法是用目标领域数据做增量蒸馏或者在推理时对草稿模型做在线适配。第三步查温度参数。草稿模型的采样温度如果设得和大模型一样接受率会偏低因为草稿的随机性太大。建议草稿温度设成大模型的 0.5 到 0.7 倍让草稿更保守。但也不能太低太低会导致草稿多样性不足树状草稿的分支就浪费了。第四步查掩码构造。前面说过掩码错了接受率会异常低但不报错。验证方法是拿一个简单的树结构手动算一遍期望的接受结果和实际跑出来的对比。如果对不上就是掩码的问题。6.2 加速比不达预期的原因接受率上去了但加速比没上去通常是草稿开销太大。用 profiler 看一下时间分布如果草稿阶段占比超过 30%就要优化草稿模型。优化的方向有三个一是把草稿模型量化到 int8速度能提 30% 左右接受率掉 1 到 2 个百分点整体划算二是减少草稿层数DSpark 的共享表征设计就是干这个的三是把草稿模型放到更快的硬件上比如主模型在 A100草稿模型放 L4两者并行跑。另一个原因是验证阶段的 batch 效率低。如果 K 值设得小验证的 batch 就小GPU 利用率上不去。这时候要适当增大 K或者用树状草稿增加验证的并行度。但 K 也不能无限大K 太大草稿阶段耗时线性增长边际收益递减。经验值是 K4 到 8树状草稿的分支因子 2 到 3。6.3 显存不足的应对显存不足在长上下文场景很常见。DFlash 时代草稿模型独立 KV Cache长上下文下这部分能占到总显存的 20% 到 30%。DSpark 共享底层 KV 后降到 10% 左右但还是有压力。应对手段有几个。一是 KV Cache 量化把草稿部分的 KV 量化到 int8主模型保持 fp16这样精度损失可控。二是分页管理把 KV Cache 按页分配不用的页换出到 CPU 内存用的时候再换回来。这个方案延迟会增加但能撑住更长的上下文。三是限制草稿的上下文长度草稿模型只看最近 N 个 tokenN 取 512 到 1024这样草稿的 KV Cache 就是固定的不随上下文增长。提示显存优化要优先保主模型的 KV Cache草稿部分可以牺牲。因为主模型 KV 出问题会导致输出质量下降草稿 KV 出问题只是接受率低一点影响可控。7. 不同场景下的参数调优经验7.1 代码生成场景代码生成是推测解码最占优势的场景因为代码的结构化程度高下一个 token 的可预测性强。这个场景下 K 可以设大6 到 8 都行树状草稿的分支因子可以到 3。草稿温度设低一点0.3 到 0.5因为代码不需要太多创造性。实测在代码补全任务上DSpark 的接受率能到 0.9 以上等效加速比 3 倍以上。但要注意代码场景的上下文很长一个文件动辄几千 token草稿的 KV Cache 管理要特别小心。建议草稿只看最近 1024 个 token再往前的用主模型的 KV 做压缩表征。7.2 开放式对话场景对话场景的可预测性低K 要收小3 到 5 比较合适。树状草稿的分支因子 2 就够分支太多反而浪费。草稿温度可以设高一点0.7 到 0.9保持多样性。这个场景下接受率通常在 0.7 到 0.8加速比 2 到 2.5 倍。提升空间主要在草稿模型的领域适配上如果你的对话有特定领域比如医疗、法律用领域数据蒸馏草稿模型接受率能再提 5 到 10 个百分点。7.3 长文本摘要场景摘要场景的特点是输入长、输出短推测解码的收益主要在输出阶段。这个场景下 K 可以设中等4 到 6因为摘要的用词相对固定可预测性中等。要注意的是长输入的 prefill 阶段推测解码帮不上忙那部分是计算密集的。所以整体加速比会被 prefill 稀释端到端可能只有 1.5 到 2 倍。优化方向是把 prefill 和 decode 分开调度prefill 用大 batch 跑满算力decode 用推测解码降延迟。8. 从 DFlash 到 DSpark 的工程启示这条演进线看下来最核心的启示是推测解码的优化不是单点突破而是系统工程。DFlash 把草稿模型做小、把验证做并行解决了能不能用的问题DSpark 通过共享表征、树状草稿、流水线调度解决了好不好用的问题。每一步改进都不是孤立的共享表征降低了草稿计算量才让树状草稿的开销可接受树状草稿提升了接受长度才让流水线的修正开销摊薄。另一个启示是训练和推理的协同设计。DSpark 的草稿头是和大模型联合训练的这种耦合在 DFlash 时代是没有的。联合训练让草稿模型能利用大模型的中间表征这是纯推理侧优化做不到的。所以如果你在做推测解码的落地不要只盯着推理侧的调参训练侧的投入往往回报更高。最后说一个容易被忽略的点推测解码的收益和硬件强相关。在算力过剩、带宽受限的硬件上推测解码收益大在算力本身就紧张的硬件上草稿模型抢走的算力可能让主模型变慢整体反而亏。部署前一定要做端到端的 benchmark不要只看论文里的加速比数字。我个人在实际部署中的体会是推测解码这套东西参数调优占三成工程实现占七成。接受率上不去八成是工程问题而不是算法问题——掩码错了、对齐错了、调度有 bubble这些都比算法本身更常见。把工程细节抠干净DFlash 也能跑出接近 DSpark 的效果工程粗糙DSpark 也发挥不出来。所以别急着追新方案先把基础实现做扎实。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑