系列总览见 投机解码方案对比。上一篇:EAGLE-3。

问题:串行 draft 的 k 开不大

从 EAGLE 到 MTP 再到 EAGLE-3,draft 都是自回归的:猜 k 个 token 就要 k 次 draft 前向。这带来两个互相牵制的限制:

DFlash(Chen、Liang、Liu,UCSD,arXiv 2602.06036)的出发点是:如果 k 个 token 能在一次前向里出来,draft 的代价就和 k 基本无关,这两个限制同时解开。

insight:扩散模型单独用不行,当 draft 刚好

块扩散(block diffusion)语言模型可以一次前向去噪出一整块 token,但作为独立模型,它的质量明显不如自回归模型。论文的观察是:投机解码里 draft 的质量不决定结果,target 的 verify 保证了无损,所以扩散模型最大的短板在这里不要紧。甚至不需要多轮去噪,一次去噪就够了:猜得不完美的位置交给 verify 剔掉。

光靠扩散还不够。一个从零理解上下文的小扩散模型猜不准,论文里不用 target 特征时 GSM8K 上只有 2.83×。第二个 insight 被作者概括为 the target knows best:target 的 hidden 里已经编码了后续多个 token 的信息(EAGLE-3 的多层融合也是这个思路),draft 只要能充分读到这些特征即可。

把两者合起来:并行块让 draft 加层变便宜,于是 draft 可以有 5 层甚至 8 层;但前提是每一层都能直接看到 target 特征,否则加层也没用。这引出了 DFlash 的核心结构 KV 注入。

draft 结构

输入融合与 KV 注入

和 EAGLE 系比,draft 的状态维护也换了一种方式:上下文全部在 draft KV 里,verify 之后只需要把新被接受位置的 target hidden 投影成 K/V 写进去,不需要再跑一次 draft 前向。

训练

结果

局限也很直接:并发越高收益越小;块越大,后面那些大概率被拒绝的位置越浪费 verify 算力;超过 4K 的上下文需要再微调。下一篇的 DSpark 正是从「块内越往后越不准」和「大 batch 下 verify 浪费」这两点切入的。

在 SGLang 里

DFLASH 是独立的算法和 worker(speculative/dflash_worker_v2.py 里的 DFlashWorkerV2),不再复用 EAGLE 的 worker。

draft 模型是 models/dflash.py 的 DFlashDraftModel,结构和论文一一对应:

取哪几层:build_target_layer_ids(speculative/dflash_utils.py)返回的层数和 draft 层数相同。draft 只有 1 层时取中间层 N//2;否则在 [1, N−3] 上均匀取。SGLang 在「第 i 层之前」捕获,hook 里会再加 1。

每个 decode step:

1 次 draft 块前向 + 1 次 target verify + 1 次「投影写 KV」

prefill 时把整个 prompt 的 target hidden 投影写进 draft KV;verify 之后按 commit_lens 只写被接受的位置。这里没有 EAGLE 那样的 draft extend 前向。

参数:DFlash 强制 num_steps = topk = 1,这两个字段对它没有意义,只是通用的显存计算要用;block size 就是 num_draft_tokens,不指定时从 draft 配置推断,默认 16。限制是只支持 CUDA、不支持 DP attention、要求 PP = 1。draft 是 sliding window 时可以打开 --speculative-draft-window-size,draft KV 只按窗口分配(draft_swa_pool.py),见 总览里的说明。

后续工作

DFlash 只验一条链,块内每个位置的分布里其实还有别的候选。两篇后续分别在这两处继续推:

小结

DFlash 把两个看似不相关的想法拼在了一起:块扩散让 k 个 token 一次前向出来,于是 draft 可以加深、k 可以开到 16;KV 注入让每一层都直接读 target 的多层特征,于是加深真的有用,状态维护也只剩投影写 KV。它的代价在块内:同一次前向里的各位置看不到彼此最后取了什么,越往后越容易各说各的,而大 batch 下这些后面的位置还要白白占 verify 的算力。这两点是 DSpark 要补的。