返回博客

/ DeepSeek

[DeepSeek-6] DSpark:把投机解码从草稿模型推进到服务调度

系统拆解 DSpark 的半自回归草稿模型、Markov/RNN sequential head、置信度头、STS 校准、硬件感知 prefix scheduler、训练目标、DeepSpec 实现与 DeepSeek-V4 线上部署取舍。

18 minDeepSeek · DSpark · Speculative Decoding · LLM Inference

DSpark 表面上是一篇投机解码论文,真正有意思的地方却不只是“又训练了一个更好的 draft model”。它把问题拆成了两半:

  • 草稿怎样更像 target model:parallel drafter 一次前向能出很多 token,但 block 内 token 彼此独立,后缀很容易崩;
  • 草稿应该验证多少:长 block 可以提高潜在加速,但在高并发服务里,低置信度后缀会占用 target model 的 batch capacity,反而拉低吞吐。

一句话概括:

DSpark 是一个把“半自回归草稿生成”和“负载感知验证调度”绑在一起的投机解码系统。

它既不是 DeepEP/FlashMLA 那类底层 kernel,也不是 DeepSeek-V4 的主模型结构,而是部署在 Decode 循环旁边的加速层:先用轻量 draft model 预测一段候选 token,再让 DeepSeek-V4 一次性验证一个经过调度的 prefix。官方 V4-Flash-DSpark 模型卡也把它描述为同一 checkpoint 叠加 DSpark speculative decoding 模块,而不是一个新的基础模型。模型卡 论文报告中,DSpark 已被部署到 DeepSeek-V4-Flash 和 DeepSeek-V4-Pro preview 的线上服务系统,相比此前 MTP-1 生产基线,在匹配吞吐下带来 60%–85%(V4-Flash)和 57%–78%(V4-Pro)的单用户生成速度提升。DSpark 论文

本文基于 DSpark 论文和官方开源的 DeepSpec 训练/评测仓库来拆解它的算法与系统边界。DeepSpec 仓库

1. 先从投机解码的目标函数看 DSpark#

标准自回归解码每生成一个 token 都要跑一次 target model。投机解码把一次循环改成两步:

当前上下文
   |
   | 轻量 draft model 生成 gamma 个候选 token
   v
x1, x2, ..., x_gamma
   |
   | target model 一次前向并行验证这些 token
   v
接受最长合法 prefix + 生成一个 bonus token

如果一次循环中接受的 token 数为 τ\tau,draft 前向耗时为 TdraftT_{draft},target 验证耗时为 TverifyT_{verify},那么平均每个生成 token 的延迟可以近似写成:

L=Tdraft+Tverifyτ.L=\frac{T_{draft}+T_{verify}}{\tau}.

所以加速只有三个方向:

方向对应问题DSpark 的做法
降低 TdraftT_{draft}草稿生成不能太慢保留 parallel backbone,一次前向生成整段 hidden/logits
提高 τ\tau草稿要尽量被 target 接受在 parallel backbone 后加轻量 sequential head,注入 block 内依赖
降低有效 TverifyT_{verify}不要验证注定失败的后缀confidence head + hardware-aware prefix scheduler

早期 autoregressive drafter 的优点是质量高,因为第 kk 个草稿 token 能看到前面已经采样出的 token;缺点是草稿也要一步一步生成,TdraftT_{draft} 随 block size 线性增长。Parallel drafter 则相反:一次前向预测所有位置,draft latency 对 block size 不敏感,但每个位置都在对所有可能前缀做边缘化,容易出现“前两个 token 各自合理,连起来却不合理”的 mode collision。1

DSpark 的核心判断是:不要在 heavy backbone 上恢复完整自回归,只在输出端加一点很便宜的局部自回归。

2. 为什么纯 parallel drafter 会出现 suffix decay#

考虑一句自然语言续写:

没问题,可以 ...

后面可能接“继续处理”,也可能接“帮你看看”。纯 parallel drafter 在同一次前向里同时预测多个位置:

position 1: 继续 / 帮你 / 直接 / ...
position 2: 处理 / 看看 / 执行 / ...

问题在于 position 2 并不知道 position 1 最后采样成了哪个 token。它学到的是“所有可能 position 1 的混合条件分布”,因此可能把不同模式的后缀拼在一起。投机解码的验证又是严格 prefix survival:第一个 token 被拒绝,后面的 token 再好也全部作废;越靠后的 token 受到前面不确定性的连锁影响,条件接受率越容易下降。

论文把这个现象称为后缀接受衰减。离线分析里,DFlash 这类 parallel drafter 的首 token 接受率很高,但后面位置下降明显;Eagle3 这类 autoregressive drafter 首 token 容量较弱,后续位置却更稳定。DSpark 要组合两者:用 parallel backbone 抢首 token 和整体容量,用 sequential head 修补后缀一致性。1

3. Semi-Autoregressive:重前向并行,轻输出串行#

DSpark 的生成结构可以分成两级:

anchor token x0
      |
      v
+-----------------------------+
| Parallel Backbone            |
| 一次前向产生 h1...h_gamma    |
| 和 base logits U1...U_gamma  |
+-----------------------------+
      |
      v
+-----------------------------+
| Lightweight Sequential Head  |
| 按位置采样,并给 logits 加   |
| prefix-dependent bias        |
+-----------------------------+
      |
      v
candidate block x1...x_gamma

Parallel backbone 在论文实现中以 DFlash 为基础。一个小改动是:DFlash 原本输入“anchor token + gamma 个 mask”并预测 mask 位置;DSpark 把 anchor 本身作为第一个预测位置,因此输入“anchor + gamma-1 个 mask”即可得到 gamma 个草稿 logits,减少一格草稿计算。1

Sequential head 不重新跑 Transformer,而是在每个位置的 base logits UkU_k 上加一个很轻的偏置:

pk(vx0,x<k)=exp(Uk(v)+Bk(x0,x<k,v))uVexp(Uk(u)+Bk(x0,x<k,u)).p_k(v\mid x_0,x_{<k}) =\frac{\exp(U_k(v)+B_k(x_0,x_{<k},v))} {\sum_{u\in V}\exp(U_k(u)+B_k(x_0,x_{<k},u))}.

这样仍然能给出每个位置的精确 token probability,满足投机解码 rejection sampling 需要的 draft distribution;同时采样阶段只是在输出层做一个很短的串行循环,不把整个 draft backbone 变成自回归模型。

4. Markov Head:用低秩转移矩阵补一阶依赖#

DSpark 默认使用 Markov head。它假设第 kk 个 token 的额外偏置只依赖上一个 token xk1x_{k-1}

B(xk1,)=W1[xk1]W2.B(x_{k-1},\cdot)=W_1[x_{k-1}]W_2.

如果直接存一个 V×VV\times V 的 token transition 矩阵,词表一大就不可接受;DSpark 用低秩分解:

prev token id
      |
      v
W1 embedding: [vocab] -> r
      |
      v
W2 projection: r -> [vocab]
      |
      v
transition bias 加到 base logits 上

论文默认 rank 为 256。DeepSpec 的 VanillaMarkov 实现也正是一个 embedding 加一个无 bias linear projection:markov_w1 查上一个 token 的低维向量,markov_w2 投到 vocab 维度,然后与 backbone logits 相加。Markov head 源码

这件事的工程含义很直接:

  • heavy computation 仍然是一次 parallel backbone forward;
  • 串行部分只做 gamma 次低秩投影和采样;
  • 每一步的 draft probability 都是普通 softmax,不需要 CRF/CTC 那样的全局归一化或 latent marginalization;
  • 对投机解码来说,概率可计算比“生成起来像一个序列模型”更关键。

DeepSpec 还实现了 GatedMarkovHeadRNNHead。RNN head 能维护 block 内 recurrent state,理论上看到更长前缀;论文实验中 RNN 在较长 proposal length 上有边际收益,但默认仍选择 Markov head,因为它更简单、更容易部署。3

5. Confidence Head:预测“这个 prefix 还能活多久”#

即使 DSpark 能生成更长、更好的草稿,也不意味着每次都应该把全部 token 交给 target model 验证。投机解码只接受连续 prefix,因此真正有价值的不是单个 token 是否看起来靠谱,而是:

P(x1...xj 整个 prefix 被接受)=ijci.P(x_1...x_j\ \text{整个 prefix 被接受}) =\prod_{i\le j}c_i.

DSpark 的 confidence head 为每个位置输出 ck(0,1)c_k\in(0,1),语义是:在前面 token 都已被接受的条件下,第 kk 个 token 通过 target 验证的概率。论文用 draft distribution 和 target distribution 的 total variation distance 构造软标签:

ck=112pkdpkt1.c_k^*=1-\frac{1}{2}\|p_k^d-p_k^t\|_1.

这和投机解码接受概率直接相关:draft 越接近 target,理论接受率越高。DeepSpec 的训练代码也会先对 draft_logitsaligned_target_logits 做 softmax,再计算 10.5pdpt11-0.5\|p_d-p_t\|_1 作为 accept_rate_3d;confidence loss 则是 binary_cross_entropy_with_logits(confidence_pred, accept_rate_3d)loss 源码

这里有一个容易忽略的点:confidence head 不是只为了“过滤低分 token”。DSpark 后面的 scheduler 要计算 expected throughput,因此需要 confidence 的绝对数值接近真实接受率,而不仅是排序正确。论文因此引入 Sequential Temperature Scaling(STS):在验证集上从左到右校准 cumulative survival probability,降低过度自信,让 ci\prod c_i 更接近经验 prefix 接受率。1

6. Hardware-Aware Prefix Scheduler:验证长度是系统问题#

如果每个请求都固定验证 7 个草稿 token,低负载时可能没问题,因为 target GPU 还有空闲算力;高负载时就会伤害吞吐,因为被拒绝概率很高的后缀仍然占用 batch slot。

DSpark 把验证长度选择写成一个全局吞吐最大化问题。设当前有 RR 个活跃请求,第 rr 个请求的第 jj 个 prefix survival probability 为:

ar,j=ijcr,i.a_{r,j}=\prod_{i\le j}c_{r,i}.

若给请求 rr 分配验证长度 r\ell_r,target 本轮要处理的 token batch size 近似为:

B=r=1R(1+r),B=\sum_{r=1}^{R}(1+\ell_r),

其中 1 是 target 自己产生 bonus/corrected token 的位置。期望产出的 token 数为:

τ=r=1R(1+j=1rar,j).\tau=\sum_{r=1}^{R}\left(1+\sum_{j=1}^{\ell_r}a_{r,j}\right).

服务引擎在不同 batch size 下的 steps-per-second 可以通过 profiling 得到,记为 SPS(B)SPS(B)。于是 scheduler 的目标是最大化:

Θ=τSPS(B).\Theta=\tau\cdot SPS(B).

直觉上,所有候选 prefix extension 都可以按 ar,ja_{r,j} 从高到低排队:高 survival probability 的 token 更值得验证,低 survival probability 的 token 只有在系统很空的时候才值得拿来填 GPU。由于 prefix probability 随 jj 单调不增,这个排序天然不会破坏单个请求内部的 prefix 依赖。1

这就是 DSpark 比静态阈值更系统化的地方:

场景静态阈值DSpark scheduler
低并发可能过早截断,浪费空闲算力放宽验证预算,多吃可接受 token
高并发可能继续验证低价值后缀收紧预算,保护 batch capacity
不同领域同一个阈值难适配 math/code/chat按每个请求的 survival probability 排序
不同硬件/engine不知道 batch size 对 SPS 的影响直接查 engine-specific throughput curve

7. 理论 scheduler 到线上系统之间还有一层现实#

论文中的 Algorithm 1 为了保持 lossless speculative decoding,需要注意 non-anticipating property:是否验证第 kk 个 token,不能偷看未来 token 的 realization。否则 admission decision 会引入选择偏差,破坏 target distribution 的精确恢复。

但线上系统还有两个现实约束:

  1. 真实 SPS(B) 不是平滑曲线,而是离散、锯齿状的硬件容量曲线;
  2. 高性能 decode 引擎通常依赖 CUDA graph replay 和 Zero-Overhead Scheduling,下一步 batch size 需要提前知道,不能等本步全部结束后再慢慢调度。

DSpark 的生产适配采用异步近似:当前候选 token 仍按最新 confidence 排序,但用两步之前的 confidence 输出预测下一步可用的 dynamic truncation length,也就是一个动态 top-KK 选择。这个两步延迟看起来像近似,实际也形成了一个 causal barrier:调度容量来自历史信息,不依赖当前 token 的未来 realization,因此可以在隐藏 scheduling latency 的同时维持 lossless 性质。1

另外,变长 prefix 会给 kernel 执行带来麻烦。标准 decode kernel 常假设 batch 内 query length 较规整;如果直接 padding 到同一长度,scheduler 刚刚省下的验证预算又会被 padding 吃掉。论文提到 DeepSeek-V4 部署中把物理执行和逻辑序列跟踪解耦:kernel 层把不同请求的 token flatten 成独立元素处理,序列内依赖通过 marker tensor 传给稀疏 attention;在 V4 架构上主要需要改 index-attention 和 compress kernels。1

8. DeepSpec 代码里 DSpark 是怎样落地的#

开源仓库 DeepSpec 的定位不是生产推理引擎,而是用于训练和评测 speculative decoding draft models 的代码库,包含数据准备、draft model 实现、训练和评测脚本。README 列出的工作流是:准备数据、训练 draft model、在 benchmark 上评测接受长度;默认配置假设单节点 8 GPU,目标缓存可能非常大,例如 Qwen3-4B 默认设置下约 38 TB。2

config/dspark/dspark_qwen3_4b.py 为例,开源配置展示了几个关键选择:config 源码

block_size = 7
num_draft_layers = 5
target_layer_ids = [1, 9, 17, 25, 33]
num_anchors = 512
markov_rank = 256
markov_head_type = "vanilla"
confidence_head_alpha = 1.0
confidence_head_with_markov = True
ce_loss_alpha = 0.1
l1_loss_alpha = 0.9

这些参数和论文主线基本对应:

  • draft backbone 取 target model 多个层的 hidden states,经投影后作为上下文特征注入 draft attention;
  • 训练时随机采样多个 anchor position,每个 anchor 形成一个 block;
  • 输入 embedding 中 block 第一个位置放 anchor token,其余位置放 mask token;
  • attention mask 允许 draft token 看 anchor 之前的 target context,以及同一个 draft block 内的位置;
  • embedding 和 LM head 从 target model 拷贝并冻结,只训练 draft backbone、sequential head 和 confidence head;
  • loss 由 CE、distribution matching 的 L1/TV 项、confidence BCE 项组成,且靠前位置权重更高。

这也解释了 DSpark 为什么和“直接外挂一个小模型”不同。它不是完全独立地读 raw token context,而是利用 target model 的中间 hidden features 对齐目标分布;训练时还需要 target logits 或 LM-head 前 hidden states 来做分布匹配监督。2

9. 训练目标:不只预测正确 token,还要贴近 target 分布#

如果 draft model 只做交叉熵,目标是预测数据中的 ground-truth token。但投机解码关心的是 draft distribution 和 target distribution 的匹配程度,因为 rejection sampling 的接受概率由 pdp_dptp_t 的关系决定。

DSpark 因此使用三类损失:

L=αceLce+αtvLtv+αconfLconf.L=\alpha_{ce}L_{ce}+\alpha_{tv}L_{tv}+\alpha_{conf}L_{conf}.

其中:

  • LceL_{ce} 让 draft 预测真实下一个 token;
  • LtvL_{tv} 惩罚 pdpt1\|p_d-p_t\|_1,直接推动接受率;
  • LconfL_{conf} 训练 confidence head 预测软接受率 10.5pdpt11-0.5\|p_d-p_t\|_1
  • 位置权重 wk=exp((k1)/γ)w_k=\exp(-(k-1)/\gamma) 强调靠前 token,因为 prefix 验证中前面位置的拒绝会让后面全部失效。

DeepSpec 源码里对应的是 ce_loss_alpha=0.1l1_loss_alpha=0.9confidence_head_alpha=1.0。命名上源码叫 l1_loss,论文里讨论的是 total variation distance;两者只差一个 1/21/2 系数,对优化方向一致。1

10. 结果应该怎样解读#

离线评测里,DSpark 在 Qwen3-4B/8B/14B 和 Gemma4-12B 上,相比 Eagle3 与 DFlash 都提升了 accepted length。论文报告 Qwen3 三个规模上,DSpark 相比 Eagle3 的 macro-average accepted length 提升分别为 30.9%、26.7%、30.0%;相比 DFlash 分别提升 16.3%、18.4%、18.3%。1

但我认为更值得关注的是线上结果。DeepSeek-V4 生产部署里,MTP-1 是此前稳定基线;静态 MTP-3/5 在高并发下可能因为验证开销过大而降低总吞吐。DSpark 的价值不是“永远验证更多 token”,而是:

低负载:多验证,利用闲置 target compute 提升用户 tok/s
高负载:少验证,把 batch capacity 留给更有希望的 token/请求

论文在 live traffic 下报告:

模型中等 SLA 下吞吐收益匹配吞吐下单用户速度收益严格 SLA 下的含义
DeepSeek-V4-Flash80 tok/s/user SLA 下 +51% throughput+60% 到 +85% TPS120 tok/s/user 下 baseline 接近边界,DSpark 扩展可行区间
DeepSeek-V4-Pro35 tok/s/user SLA 下 +52% throughput+57% 到 +78% TPS50 tok/s/user 下 baseline 进入低并发退化区

这些数字不应简单理解为所有场景固定提速几倍。更准确的解读是:DSpark 改变了吞吐与交互速度之间的 Pareto frontier,尤其是在高并发和严格 interactivity 目标同时存在时,负载感知调度比固定 draft length 更稳。1

11. 它和 MTP、DFlash、DeepSeek-V4 的关系#

可以把几条线放在同一个坐标里:

名称角色关键特征
MTP-1DeepSeek 早期生产 baseline每轮只额外预测/验证少量 token,稳定但加速空间有限
DFlashparallel drafter一次前向出长 block,首 token 强,后缀独立性导致 suffix decay
DSparksemi-autoregressive + scheduled speculative decoding用 Markov/RNN head 修补后缀,用 scheduler 控制验证预算
DeepSeek-V4target model / serving systemDSpark 服务于 V4-Flash/V4-Pro decode,不改变 target 分布
DeepSpec开源训练评测仓库复现 Eagle3/DFlash/DSpark 训练与 accepted-length 评测

这也说明 DSpark 的边界:它不是替代 target model,而是借助 target model 的验证规则保持输出分布不变;它不是只靠一个更深的 draft model,而是把 draft quality、confidence calibration、engine profiling 和 online scheduling 合在一起。

12. 局限与工程启发#

DSpark 仍有一个固定成本:无论最终验证几个 token,parallel backbone 都要先生成最大 block。对于本身极难预测、接受率很低的请求,这部分 draft compute 可能无法回收。论文也提到未来可以做 difficulty-aware early exiting,让低收益请求跳过完整草稿生成。1

另外,开源 DeepSpec 目前更适合研究和训练 draft model;论文中的 DeepSeek-V4 生产 scheduler、异步 ZOS 集成、变长 query kernel 支持并不是完整开源推理栈的一部分。因此读 DSpark 时最好区分三层:

  1. 算法层:semi-autoregressive draft + confidence-scheduled verification;
  2. 开源训练层:DeepSpec 里可训练 Qwen/Gemma 目标模型的 DSpark draft checkpoints;
  3. 生产系统层:DeepSeek-V4 服务中的异步 scheduler、capacity profiling、CUDA graph/ZOS/variable-length kernel 适配。

对推理系统的启发则很清晰:投机解码不应只问“draft model accepted length 多高”,还要问“这些额外验证 token 在当前系统负载下值不值得”。当 decode 服务进入高并发、低空闲算力区域时,验证预算本身就是一种调度资源。

13. 总结#

DSpark 的设计可以压缩成三个关键词:

  • Semi-autoregressive:用 parallel backbone 保持草稿吞吐,用 Markov/RNN head 在输出端补充 block 内依赖;
  • Confidence-scheduled:用 calibrated confidence 估计 prefix survival probability,而不是固定验证长度;
  • Serving-aware:把 engine throughput curve、并发负载、CUDA graph/ZOS 约束纳入验证预算选择。

如果说 FlashMLA 和 DeepEP 体现的是 DeepSeek 在 kernel 与通信层的系统优化,那么 DSpark 展示的是另一种系统能力:让模型侧的 speculative decoding 算法进入真实服务引擎,并且按线上负载动态改变行为。

这也是它值得放进 DeepSeek 系列的原因。它提醒我们,大模型推理加速的关键不一定是单个算子更快,也可能是把“哪些 token 值得被算”这件事做成一个可学习、可校准、可调度的系统问题。

References#