DFlash 一次并行出整块 draft,但块内各位置互不商量,而且整块都要送进 target model。DSpark 只做两件事——draft 侧加一个很轻的串行采样循环让位置之间互相看得见,target 侧加一个裁剪决定这一轮到底验多长。采样规则和接受规则都没变,所以依然无损。
归属:DeepSeek-AI 与北京大学,DSpark: Confidence-Scheduled Speculative Decoding with Semi-Autoregressive Generation,arXiv:2607.05147,作者含 Wenfeng Liang(梁文锋)。代码 DeepSpec,权重 DeepSeek-V4-Pro-DSpark,CC BY 4.0。
前置:只讲相对 DFlash 的增量。块扩散 draft、KV 注入、拒绝采样见 DFlash 投机解码。
一、出发点
DFlash 的链条是:draft model 一次前向算出整块 γ 个位置的分数 → 每位置各自取 top-1 → 整块送去验证。这条链有两个口子,正好在两头:
- draft 侧:每个位置独立取 top-1。它是在对「不确定的前驱」取期望,所以可能拼出并不存在的组合,越靠块尾越容易错。
- target 侧:整块都验。尾部位置本来大概率被拒,验它们等于白占 target model 的 batch 容量。
DSpark 的两处改动分别堵这两个口子。
图 1:一轮五步。标绿(串行采样)和标橙(裁剪)是全部新增,其余沿用 DFlash。
二、串行采样:把块内各位置串起来
2.1 它改了什么
draft model 里那层完全不动——仍然是块内双向注意力、一次前向出整块的分数,延迟与 γ 几乎无关。变的只有「怎么从这些分数得到候选」:
- DFlash:每个位置各自取 top-1,位置之间不通信;
- DSpark:从左到右逐位采样,采到第 k 位时,用刚刚采出的第 k−1 位去修正第 k 位的分数,再采样。
修正量就是一个加在分数上的偏置:
是 draft model 在每位给出的分数, 是 draft 侧串行头给的偏置。这是全文唯一的算法公式,读法就一句:draft model 的分数 + 前文修正,再 softmax 采样。
偏置怎么算出来的?查表 + 一次小矩阵乘: 只依赖紧邻的前一位 token,所以维护一张「token → 向量」的表,查出来乘个小矩阵,就得到加在词表上的偏置。没有额外的 Transformer 前向——这就是它「轻」的原因。
论文还试了能记住整块前缀的递归版本(RNN 头),结论是收益只多一点、主要出现在很长的块上,但实现复杂不少,所以生产用的是只看前一位那版。
2.2 例子
假设上文已定,接下来该写「很大程度」。draft model 给位置 2 的 top-1 是「可」0.40,「大」只拿到 0.31 —— 因为它不知道位置 1 会采出什么:若位置 1 是「稍」,这里本该是「微」。两个模式被平均掉了,所以纯并行 draft 在这里容易采偏。
串行采样把「不确定的前驱」换成「已经采出的前驱」:
- 第 1 步没有前文,直接用原始分数,采到 很;
- 第 2 步用「很」查表得到偏置,抬高「大」、压低「可」,采到 大(0.62 vs 0.19);
- 第 3 步用「大」的偏置抬高「程」,采到 程(0.88);
- 第 4 步同理采到 度 —— 但这一位「猜得准不准」是另一回事,见第三节。
一旦「很」被真正采出来,「稍」那条分支就不存在了,概率质量自然集中回「大」。DFlash 在这里可能采到「可」或「慢」,DSpark 不会。
同时这个循环顺手产出每个位置的条件存活概率 ——在第 k 位之前全部被接受的前提下、第 k 位还能活下来的概率。注意它的输入只有 draft model 隐状态和前一位 token。四个位置依次给出 、、,而 只有 。
图 2:上例的完整推演(上半部分是串行采样,下半部分是接着做的裁剪)。
三、裁剪:决定这一轮验多长
3.1 为什么要裁
DFlash 把整块送进 target model。但尾部位置本来大概率被拒,验它们纯属浪费;而且在高并发下,这些浪费的验证位会挤占别的请求的 batch 容量。
论文还有一个观察:结构化任务(数学、代码)的接受率明显高于开放式闲聊,可是验证花费是一样的。所以「写死一个验证长度」天然不合理。
3.2 三个量各自的算法
裁剪要最大化系统总吞吐。所有参与决策的量只有三个,且都能从上一步的输入算出来:
| 量 | 算法 | 说明 |
|---|---|---|
| 累积存活概率 | ,即 | 位置 k 能被验到,前提是前面每位都被接受,所以是连乘 |
| 期望被接受 | 1 是 anchor 自己;被验的位不一定被接受,所以按概率加权 | |
| 吞吐 | 一次前向平均能产出多少 token,单位 tokens/s |
其中 是这次前向的 batch 大小( 个 anchor + 已准入的 draft 位数), 是引擎在该 batch 下的 step 速度(在每次前向B个token时,每秒跑多少次前向)——它随 单调不增,引擎初始化时 profile 一次存成表。
于是判据是:逐个纳入, 不再上升就停。
3.3 接上例子,把每个数字算一遍
沿用上例,四位 draft 的存活概率取 ,并取一条示意的 step 速度曲线(真实曲线由引擎实测得到、有数千档,这里取整数方便逐格验算):
它随 单调不增。先把连乘算出来:,,,。然后逐个纳入:
| 纳入到 | 期望被接受 | 算法动作 | |||
|---|---|---|---|---|---|
| 只验 anchor | 1 | 1000 | 起点,best = 1000 | ||
| 位置 1(很) | 2 | 520 | → 留 | ||
| 位置 2(大) | 3 | 400 | → 留 | ||
| 位置 3(程) | 4 | 340 | → 留 | ||
| 位置 4(度) | 5 | 280 | → break |
判决列只做一件事:新的 有没有超过目前的最好值。
四、采样流程
1 | def dspark_cycle(prefix, target, draft, head, scheduler, gamma=16): |
调度器本体:
1 | def admit(conf, base_batch=1): |
五、效果
| 对比 | 结果 |
|---|---|
| 平均接受长度 vs DFlash | Qwen3-4B/8B/14B 上 +16.3% / +18.4% / +18.3% |
| 块长 4→16 的额外延迟 | 仅 0.2%–1.3%(串行循环几乎不要钱) |
| V4 线上同吞吐下单用户速度 | +60%–85%(Flash)、+57%–78%(Pro) |
参考资料
DSpark 本体
- DSpark: Confidence-Scheduled Speculative Decoding with Semi-Autoregressive Generation — arXiv:2607.05147(CC BY 4.0)
- DFlash 背景:§2.2;半自回归与两种串行头:§3.1;置信头与 STS:§3.2.1;调度器与 Algorithm 1:§3.2.2
- 生产适配(异步、零开销调度、变长 kernel):§5.2–5.3;线上结果与局限:§5.4;选择偏差反例:Appendix A
- 代码 deepseek-ai/DeepSpec 权重 deepseek-ai/DeepSeek-V4-Pro-DSpark
前代与对照
- Chen 等,DFlash: Block Diffusion for Flash Speculative Decoding — arXiv:2602.06036
- Li 等,EAGLE-3 — arXiv:2503.01840
- Leviathan 等,Fast Inference from Transformers via Speculative Decoding — arXiv:2211.17192
- DeepSeek-AI,DeepSeek-V3 Technical Report(MTP-1 基线)— arXiv:2412.19437
站内相关
- DFlash 投机解码:块扩散草稿与 KV 注入 —— 本文前置
- 投机解码发展 —— 三步骤与 EAGLE 三代差异



















从上图可以看出 cuda hardware 的函数执行时间情况和 cpu 侧的执行时间情况。下面是核函数的发射时间,上面 device 侧是核函数实际执行时间,和 gpukernsum 统计的时间一致。
上图是在软件中可以看见的较为详细的统计数据,和命令行结果一致。