DFlash 到 DSpark in SGLang:解码提速 4.3×,以及 DeepSeek-V4 支持
DFlash 和 DSpark 是两个新一代的投机解码算法。这篇 blog 详细讲解这两个算法的工作原理。两者都放弃了传统的自回归起草,draft 模型改用 DiT 架构。SGLang 现已完整支持这两个算法:用 DFlash,解码达到非投机基线的 4.3×、MTP 的 1.5×;用 DSpark,混合流量上验证窗口从 5.24 收缩到 2.91 个 token,保持接受上限的 0.88–0.97,DeepSeek-V4-Pro 上 batch 1 解码达到 383.7 tok/s。
Background
先讲投机解码的基本思路。解码是 memory-bound 的:target 模型的一次 forward 大部分时间在搬权重,所以小批次下验证好几个 token 的开销和生成一个差不多。于是让一个小 drafter 先写出 K 个 token,target 用一次 forward 全部检查一遍。通过的前缀被提交,verify forward 还顺带产出一个 bonus token。被拒的草稿只浪费 drafter 的便宜算力:
这笔交易随负载变亏。batch 为 B、每请求 K 个投机 token 时,target 每步要验证 B×K 个 token。小批次下,这些多出来的 token 几乎搭权重搬运的顺风车。批次填满 GPU 后,每个 verify token 都开始消耗真实的步时,过了某个点,验证的开销就超过了它省下的。下图给同样三种选择在不同负载下算账:
本文余下的部分分三块:解这笔交易的两代算法,SGLang 为 serve 它们做的工程,以及这套栈对 DeepSeek-V4 意味着什么。
从 DFlash 到 DSpark
DFlash: parallel drafting with KV injection
大多数投机方案,包括 EAGLE 系列和 DeepSeek-V4 这类模型自带的 MTP head,起草是自回归的:小模型逐 token 生成,草稿越长起草开销越大,为了压延迟只能把 drafter 做得很浅。
Z Lab 开发的 DFlash 用一个轻量的块扩散(block diffusion)模型换掉自回归 drafter,一次 forward 并行产出整块草稿。做到这一点不只是换个 drafter 那么简单。从头训练的小扩散模型接受率很低,拿现成的大扩散 LLM 起草又太贵。DFlash 的答案是 KV injection:最懂 context 的是 target 模型本身,所以把 target 的 hidden states 抽出来,注入 draft 模型每一层的 KV cache。EAGLE 类方法只在 drafter 的输入端喂一次 target 特征,drafter 一深信号就衰减;逐层注入让 context 信号一直在场,drafter 可以完全不建模 context,把全部容量花在预测这一块上:
两个部件的贡献是分开的,论文的消融把这一点摆得很清楚。只用扩散、不用注入,端到端就已经比 EAGLE-3 快,哪怕接受长度更低。只用注入、不用扩散,接受长度反超 EAGLE-3。两个一起用,接受长度保持在 EAGLE-3 的水平,起草却快一个量级:GSM8K 上接受 4.2 个、端到端 3.3×,对 EAGLE-3 的 4.2 个、2.1×。在 Qwen3.5-397B-A17B 上,联合发布的 drafter 在 HumanEval 并发 1 下达到非投机基线的 4.3×、MTP 的 1.5×:
DSpark:sequential head 加验证预算
纯并行的块留下了两件事没解决。第一,块内各位置的起草互相独立,没有块内依赖,质量向块尾衰减;DSpark 论文称之为 suffix decay。第二,验证侧原封未动:每个请求照旧整块 verify,高并发下就是 Background 里那笔 B×K 的账。
来自 DeepSeek 的 DSpark 用 draft 侧的三件东西从两端一起下手。block drafter 仍然一次 forward 产出 gamma 个 token 的块,但一个轻量的 sequential head(Markov 或 RNN)让每一位以前一位为条件,这就是「半自回归」的含义,也正是对付 suffix decay 的那一手。
为了解决高并发下整块验证的开销,DSpark 引入 confidence head。它是搭在 drafter hidden state 上的一个线性层加 sigmoid,给每个草稿打「能通过验证」的分数。训练标签就是 drafter 在这一位的输出分布与 target 分布的实测差距,所以分数直接反映下文好不好猜:公式和代码把下一个 token 锁得很死,分就高;自由发挥的文本分就低。整块连乘是块的存活率,Sequential Temperature Scaling(STS)再把它校准到真实接受率。
有了这些存活分,验证长度才能按请求来调度:沿着块的存活率曲线往下走,走到「期望多接受的 token 盖不住多验证一个 token 的边际成本」就停。下图跟着一个请求走完一次 draft:
在 Qwen3-4B、8B、14B 三个 target 上,论文报告宏平均接受长度比 DFlash 分别高 16.3%、18.4%、18.3%。剩下的是 serving 问题:得有人给边际成本定价、每步切好预算,还要让一批验证长度参差不齐的请求在真实 GPU 上跑得快。这就是 SGLang 的部分。
SGLang 怎么把 DSpark serve 起来
SGLang 在 #30261(2026 年 7 月合入)完成集成,drafter 直接搭在 DFlash 的 draft 模型之上,稠密与稀疏 target 都支持,例如 Qwen3 和 DeepSeek-V4。在 DeepSeek-V4-Flash 的完整并发扫掠中,这套集成保持最优的吞吐/延迟频谱,全程领先 MTP 和非投机基线:
本节余下的部分逐个讲把论文的调度变成真实墙钟时间的工程。
Confidence scheduler 与 verify 模式
一句话本质:这个优化把「验证多少个 token」从全局常数变成每个请求、每一步的单独决策。
SGLang 的 confidence scheduler 每步给批次里的每个请求执行上一节的预算规则:沿存活率曲线往下走,走到期望收益盖不住边际成本就停。边际成本由 SGLang 在这台 server 上实测:每多验一个 token,步时多花多少,存成成本表。窗口长短就是这场比较的结果:草稿分数高的请求保住长窗口,分数掉得快的被裁到两三个 token;负载升高会抬高边际成本,所有窗口一起收紧:
verify 分三种模式,分的是速度和可观测性的取舍。compact 只验 scheduler 给的窗口,是生产路径,预算省下的开销全靠它兑现。它有个盲区:被裁掉的位置从没验过,看不到整块验证本可以多接受几个,也就无从判断 scheduler 裁得好不好。static 整块全验,是不裁剪的对照基线,用来做 A/B。cap-accept 为测量而生:付出和 static 一样的验证开销,但只提交到窗口,产出和 compact 完全一致,同时记下裁剪放弃了多少。实际生产用 compact 跑流量,cap-accept 当审计臂检查裁剪质量;下文混合流量里 0.88–0.97 的利用率就是这么测出来的:
Ragged verify under full CUDA graphs
CUDA graph 要求固定的输入形状,而裁剪后每个请求的验证长度都不一样;padding 在这里不是选项,把大家补齐等于把裁掉的又算回来。SGLang 把调度到的 verify token 首尾相接打包进一个 [总 token 数, hidden] 的 buffer,这正是 transformer 做逐行矩阵乘时本来就在用的布局。cu_seqlens 式的变长输入标出每个请求的分段起点,每个 token 只对自己请求的 KV 做 attention,后端现成的 kernel 就能处理(DeepSeek-V4 上是 flash_mla)。graph 则按打包后的总 token 数选档位,向上取整到最近的已捕获档位。预算一收紧,总数落到更小的档位,回放的就是 attention 和 MLP 行数真正更少的 graph。DP attention 下各 rank 共用最大档位、一起降档:
调度收益
动态预算到底买到了多少?原文把 compact 和 no-trim 对比,后者是把 static 的整块调度跑在同一条 ragged 路径上,比较对象是两个接受率不同的示例负载。收益主要是高批次效应。batch 1 时,target verify 几乎不因多验几个 token 变慢,裁剪省不了什么,两臂打平。并发上去、吞吐开始进入平台期后,裁剪缩短了每一步,compact 拉开领先。在接受率更低的负载上,差距更大、拉开得更早,因为更低的接受率留下了更多可裁的尾巴。
这个 scheduler 是第一版朴素实现,所以结论看趋势,不看绝对数字。同质负载的扫掠还会掩盖它的真正意义:同一批次里,一个请求比另一个可预测得多时,两者不该拿到同样的窗口。在 gsm8k、arena-hard、poetry 的混合流量上,窗口随难度收缩,从 5.24 到 3.78 再到 2.91 个 token,对接受上限的利用率保持在 0.88–0.97。约 55% 的 gsm8k 步用满 6 的窗口,约 80% 的 poetry 步只用 3 个以内:
Zero-overhead scheduling 与 kernel
把调度变成墙钟时间靠两类工程。kernel 侧,SGLang 把 verify 周围成簇的小算子重写成融合的 Triton kernel:compact scatter、SWA page-index、验证长度的 top-k 调度、ragged 窗口打包。block drafter 的采样路径折叠进融合 kernel,矩阵乘做了 shard。在一个示例 profile 里,target verify 之外的部分缩短了 1.7 ms,而 verify 本身是 7.3 ms。
调度侧,DSpark 几乎不加特判就接入了 SGLang 的 zero-overhead(overlap)scheduler。spec-v2 运行时是随 DFlash 集成一起上线的,当时它的 overlap scheduling 在初版实现之上又多拿了 33% 的吞吐。它在独立 stream 上把下一步的调度和当前 forward 重叠,DSpark 作为一等 worker 加入:forward 输出以 async future 返回,跨迭代的顺序靠运行时的 device 侧 barrier,页表在 device 上,每步不需要 host 同步。confidence relay 走同一条通道,读两步之前的值。解码循环于是完全没有逐步气泡,比关掉 overlap scheduler 紧约 1.5×:
用 SGLang serve DeepSeek-V4
DeepSeek-V4 有 1.6T 的 Pro 和 284B 的 Flash 两个尺寸,SGLang 从 Day 0 起就支持这个家族,前缀缓存、kernel、并行方式都为它的混合注意力路径专门做过。投机解码架在这之上,是 serving 时的一个选择:--speculative-algorithm 选 EAGLE(跑 checkpoint 自带的 MTP head)、DFLASH 或 DSPARK,三者都不改 target 模型的架构与输出分布。
| 投机方案 | 目标模型 · 硬件 | 接受长度 | 单请求解码 |
|---|---|---|---|
| EAGLE(MTP head) | V4-Pro · B200 TP8 | ~2.5 | 199 tok/s |
| EAGLE(MTP head) | V4-Flash · H200 TP4 | ~2.5 | 266 tok/s |
| DSpark | V4-Pro · B300 TP8 | ~5 | 383.7 tok/s |
前两行来自 Day-0 blog,第三行来自 DSpark blog。硬件不同代,所以这是参照表,不是严格 A/B;同一台 server 上的对照是本文前面那张吞吐-延迟频谱图。
Acknowledgments
原文致谢 DSpark 作者们与 DeepSeek 提供算法与模型,以及 Z Lab、Modal 和 SGLang 团队带来 DFlash 与 Spec V2。
延伸阅读
- LMSYS Blog: DSpark in SGLang: Speculative Decoding with Confidence-Driven, Variable-Length Verification
- LMSYS Blog: The next generation of speculative decoding: DFlash and Spec V2
- LMSYS Blog: DeepSeek-V4 on Day 0: From Fast Inference to Verified RL with SGLang and Miles
- sglang#30261: [Spec] Add DSpark: confidence-scheduled speculative decoding
- sglang#30344: [Tracking] DSpark in SGLang: roadmap
可视化教学由 Yichi Zhang 写作。