Accelerating Speculative Decoding with Block Diffusion Draft Trees

Liran Ringel, Yaniv Romano (Technion) · 2026-04-14 · arXiv:2604.12989
关键词: speculative decoding · block diffusion drafter · DFlash · draft tree · best-first heap · OPT-Tree · tree attention

速读卡片 (TL;DR)

一句话:把 OPT-Tree 那套"在节点预算 B 下选 prefix 概率最大的子集"的方法,搬到 block diffusion drafter (DFlash) 的一次前向 per-position marginals 上 — 用一个简单的 best-first heap 算法,O(B log B) 构造出最优 surrogate 树,验证用 SpecInfer 风格的 ancestor-only tree attention,在 DFlash 之上再 +30~60% 加速。

+30~60%
over vanilla DFlash 加速增量
8.2×
最高速度比 (Qwen3-30B-MoE / HumanEval)
B=256~512
最优 node budget (Qwen3-8B)

立场:不是新 drafter,而是连接"块扩散 drafter"与"树验证"的桥梁。核心贡献是一条优雅的等价 — block diffusion 的 factorized 分布让 tree 选择 = 选 top-B prefix probabilities,而 best-first heap 恰好枚举这些 prefix。

前置阅读:本文构建在 DFlash (块扩散 drafter) 的概念之上,如果对 DFlash 的 5 层 K/V 注入与单序列输出还有印象,直接读 §3 就行;否则建议先翻 07_DFlash02_EAGLE_evolution_DFlash 的 DFlash 段。 关于 lossless 的 rejection sampling 论证,见 15_Lossless_Proof_Tutorial


§1 动机:为什么 DFlash 丢掉树是一种浪费

1.1 历史脉络

回顾 spec decoding 这条线在"draft → tree"上的演化,可以画成两根并行的轨道:

DDTree 就是在这两条轨道的交叉点上写的:把 OPT-Tree 风格的"top-B prefix 选择"原理性地搬到 block diffusion 的单次 forward marginals 上,得到一个不需要多次前向的最优树构造算法。

1.2 别的方案为什么不够

方案drafter 类型每轮 drafter 前向次数是否用树缺陷
Vanilla SDAR small modelk 次 (每个深度一次)否,单链 分支被忽略,acceptance length 上限低
SpecInfer / 固定形状树AR~ tree depth固定 topology 的树 每个位置硬塞 top-K, 浅层 expand 浪费预算
EAGLE-2 dynamic treeAR (feature-space)~ tree depth动态 expand 仍然需要每一深度跑一次 drafter,latency 累加
OPT-Tree (over AR)AR~ tree depth动态,显式优化期望接受长度 原理优雅,但要多次 drafter forward 拿 path-conditioned probs
Vanilla DFlashBlock diffusion1 次,单链 per-position marginals 信息浪费, 只验证一条 trajectory
DARTBlock diffusion1 次是 (N-gram-aware pruning) 需要外部 N-gram trie + continuity 评分,引入辅助开销
DDTreeBlock diffusion (DFlash)1 次是 (best-first heap) 纯靠 drafter 自身 logits, 无外部数据结构

1.3 为什么这事不平凡

表面上看,DFlash 已经一次前向出 L 个 marginals,要做树似乎只是"在每个位置 top-K 笛卡尔积一下"。但有三个非平凡问题:

  1. 组合爆炸: 真要枚举所有长度 ≤ L 的 prefix, 有 ∑|V|^d ≈ |V|^L 个; 即便每位置截到 top-K, 还有 K^L 个 combination, 在 B = 16~1024 这个预算下还是太多。需要一个不枚举完全集的枚举方法
  2. 验证 topology: 树要让 verifier 一次 forward 就能跑完, 必须配合 SpecInfer 的 ancestor-only attention mask + tree-aware position ids; 否则 KV cache 就乱了。
  3. Lossless 保留: 树验证的 lossless 性来自 verifier 用它自己的 decoding rule 沿着 drafted children 走一步; 不依赖 drafter 分布是不是 "正确的"。这一点恰好让 surrogate 选择没有 lossless 风险。
  4. Surrogate 是否能优化: 真目标是 target model 的 expected acceptance length, 但 target 的 path-conditioned probs 我们拿不到 (要拿到就得跑 target, 等于解题要先求解); 必须用 drafter 的 factorized Q 做 surrogate, 还得证明这个 surrogate 上的最优解可以被简单算法 recover。

论文的优雅之处恰好是: 因为 drafter 分布 Q 是 factorized 的 (每个位置独立, 不带 path conditioning), prefix probability 自然分解成乘积, surrogate 目标分解成 prefix mass 之和,这个加性结构让"top-B prefix"成为最优解,而 best-first heap 又能严格按 prefix 概率降序枚举它们。

两条轨道的交叉点 AR drafter 轨道 Vanilla SD → SpecInfer → EAGLE-1/2/3 → OPT-Tree (top-B prefix) 每深度一次 drafter forward Block diffusion 轨道 BlockDiff → DFlash (一次前向出整块) → DART (N-gram trie 剪枝) drafter 只跑一次 / 但通常无树 DDTree block diffusion + OPT-Tree 风格 top-B
DDTree 是把 OPT-Tree 在 AR drafter 上证明的"top-B prefix 即最优"原理,搬到 block diffusion 一次前向出的 marginals 上;只有 factorized 分布让搬运变得简单且 exact。

§2 背景速查

符号 / 术语含义
c当前 context (prompt + 已生成 tokens)
b (bonus token)上一轮 target 选出的 token, 但 target 还没在它之上 forward 过, drafter 可以 condition 在它上
Lblock diffusion 一次出多少个 future positions (论文实验用 L=16)
q_i(v)drafter 在第 i 个位置预测 token v 的边际概率
Q(y_{1:L})= ∏ q_i(y_i), 即 drafter 视角下整块的 factorized 分布 (式 2)
p(y_i | c, b, y_{1:i-1})target 模型的 path-conditioned 分布 (式 1, 我们拿不到)
q(u | c, b)= ∏_{i=1}^{|u|} q_i(u_i), prefix u 的 factorized 概率 (式 7)
α_T (y_{1:L})continuation y 在树 T 中匹配的最长 prefix 长度
Btree node budget (不计 root b)
K = min(B, |V|)每个 depth 只用 top-K tokens 就足够 (Lemma 1)
ρ = (ρ_1, ..., ρ_d)排名而非 token id 表示一个 prefix; ρ_i = 第 i 位上选第几高概率的 token
tree attentionSpecInfer 引入: drafted node 只 attend 自己 + ancestors (+ past KV)

核心式子小抄

真目标 (无法计算): maxT 𝔼Y~pT(Y)]

Surrogate (Eq. 6): maxT 𝔼Y~QT(Y)] = ∑u∈T q(u | c, b)

解 (Prop 2): TB = {top-B prefixes by q(u)},自动 prefix-closed


§3 关键观察: AR drafter 的分布 vs Diffusion drafter 的分布

这一节是整篇文章的立论根据。两类 drafter 给出的"分布对象"在数学上是不同的:

AR drafter (EAGLE 等) forward 1: p(y₁ | c, b) forward 2: p(y₂ | c, b, y₁) forward 3: p(y₃ | c, b, y₁, y₂) → 每个分支重新 sample 都要再 forward → 拿到 path-conditioned p 树深度 D ⇒ D 次 drafter forward Block diffusion drafter (DFlash) 一次 forward 同时输出: q₁(· | c, b) position 1 的 marginal q₂(· | c, b) position 2 的 marginal q₃(· | c, b) position 3 的 marginal 注意: q_i 不 condition on y₁..y_{i-1} → 只能定义 Q = ∏ q_i (factorized) 树深度 D ⇒ 仍只有 1 次 drafter forward 所有 prefix prob 都已就绪
左:AR drafter 想构造深度 D 的树要 D 次前向,因为 p 是 path-conditioned;右:block diffusion drafter 一次前向出 L 个 marginals,任意 prefix 的概率立刻 = ∏ q_i, 不需要再前向。这就是 DDTree 几乎"白嫖"的部分。

这件事的两面性

好处: drafter forward 次数恒为 1,无论 tree budget 是 16 还是 1024,drafter cost 不变。

代价: Q 是 factorized 的 — 它假设位置之间独立, 而 target p 不是。如果在 position 1 选了 token "积分", target 在 position 2 上的真实分布会被 strongly conditioned, 但 drafter 给的 q_2 看不到这件事。所以 Q 是真目标 p 的近似,DDTree 不在 Q 上回头去做 conditioning, 而是承认它就是 surrogate, 在 surrogate 上做 exact 优化。

反向论证: 如果有人想"再多跑几次 drafter, 把 path conditioning 补回来", 那就退化成 AR drafter 了 — block diffusion 的速度优势就消失。DDTree 的设计选择是: 承认 surrogate gap, 但拿 1 次 forward 的速度去换


§4 Surrogate 目标与 Proposition 1/2

4.1 把目标改写成 prefix mass 之和

设树 T 包含若干 prefix u, 每个 prefix 的 q-概率为 q(u) = ∏ q_i(u_i)。论文 Proposition 1 把"期望接受长度"展开成按深度的指示函数和,然后用 disjoint events 论证:

αT(Y) = ∑d=1..L 𝟙{Y1:d ∈ T}
⇒ 𝔼[αT(Y)] = ∑d Pr[Y1:d ∈ T] = ∑u∈T q(u)

关键步骤是: 在 fixed depth d, 事件 {Y_{1:d} ∈ T} 等于 ∪_{u 是深度 d 节点} {Y_{1:d} = u}, 这些事件互斥 (一个 sequence 只可能是一个 specific u),所以概率直接相加。Q 是 factorized 才让 Pr[Y_{1:d} = u] = q(u)。

4.2 解最优:top-B prefix 自动 prefix-closed

目标是 max_T ∑_{u∈T} q(u) s.t. |T| ≤ B 且 T prefix-closed。Proposition 2 的精彩之处:

  1. 由于 q_i ∈ (0, 1), 任何严格后代 v 满足 q(v) = q(u) · ∏ q(...) < q(u)。即祖先严格大于后代
  2. 所以在 q 降序排列里, 祖先必然出现在后代之前。
  3. 取 q 最大的 B 个 prefix, 自然包含每个被选 prefix 的所有祖先 ⇒ prefix-closed 自动成立, 不用额外约束。
  4. 由于目标加性 + 非负 + 无约束 (除了 prefix-closure 已经被自动满足), top-B 就是最优。
为什么 top-B 自动 prefix-closed depth 1 prefixdepth 2 prefix (扩展自 depth 1) q 关系 u q(u) = 0.6 (u, v) q(u,v) = 0.6 × q₂(v) ≤ 0.6 × 1 < 0.6 ⇒ q(u, v) < q(u), 祖先永远比后代大 取 top-B ⇒ 包含所有被选的祖先 ⇒ prefix-closed 自动成立 ✓
q ∈ (0,1) 的简单不等式让"top-B prefix"和"prefix-closed tree"等价。这是论文最核心的几何观察。

4.3 Lemma 1: 只需要每深度的 top-K tokens

搜索空间还是大: 全空间 ∑|V|^d 在 |V| ≈ 150K 下天文数字。Lemma 1 证: 存在最优树 T* ⊆ S_K, 其中 S_K = {prefix 每位仅用各深度 top-K 的 tokens}, K = min(B, |V|)。直觉: 如果 T 中有一 prefix 在某位用了排名 > B 的 token, 那这个位置上排名 1..B 的 token 替换上去后, 都能得到更高 q 值的 prefix, 矛盾它已经在 top-B 中。


§5 Best-first heap 算法 (Algorithm 1)

5.1 用 rank tuple 做索引

关键技巧: 不存 token id, 存每位的排名。一个 prefix ρ = (ρ_1, ..., ρ_d) 表示"第 1 位选第 ρ_1 高概率的 token, 第 2 位选第 ρ_2 高的, ..."。这样邻居关系特别简单:

5.2 算法骨架

heap H = {((1,), σ((1,)))}     # 初始: 第 1 位排名 1 的 prefix
T = ∅
while |T| < B and H 非空:
    pop ρ = (ρ_1,...,ρ_d) with max σ(ρ)
    T += prefix(ρ)
    if ρ_d + 1 ≤ K:
        push (ρ_1,...,ρ_d + 1) with score σ(ρ) − log q^(ρ_d) + log q^(ρ_d+1)
    if d < L:
        push (ρ_1,...,ρ_d, 1) with score σ(ρ) + log q^{(1)}_{d+1}
return T

5.3 为什么这个 heap 不会漏 / 不会重

论文 Proposition 3 的证明套路:

  1. 每个 ρ 有唯一 predecessor pred(ρ): 若 ρ_d > 1, pred = (ρ_1, ..., ρ_d - 1); 若 ρ_d = 1 且 d > 1, pred = (ρ_1, ..., ρ_{d-1})。
  2. 因此每个 ρ 只会被 push 一次 (作为它 predecessor 的 sibling 或 first-child),不会重复。
  3. q(pred(ρ)) ≥ q(ρ) 永远成立: sibling case 是把 q^(ρ_d) 替成更大的 q^(ρ_d-1); child case 是去掉一个 < 1 的因子。
  4. 从而 heap 在任意时刻总包含所有 unpopped tuple 的当前最大者: 沿任意 unpopped ρ 的 predecessor chain 往上找, 第一个 predecessor 已 popped 的祖先 ¯ρ 现在在 heap 里, 且 q(¯ρ) ≥ q(ρ)。所以下一次 pop 一定取到全局最大的 unpopped。
  5. 归纳: 第 k 次 pop = 第 k 大的 prefix (在 S_K 内)。B 次后得到 top-B。
heap 的 sibling/child 扩展 ρ = (1, 2) σ = log q₁⁽¹⁾ + log q₂⁽²⁾ sibling: (1, 3) +log q₂⁽³⁾ − log q₂⁽²⁾ first child: (1, 2, 1) +log q₃⁽¹⁾ pred(ρ) = (1, 1) pred 已被 popped → 当前 ρ 被 push 进 heap q(pred) ≥ q(ρ) 保证 ρ 不会先于 pred 被 pop
每个 rank tuple 有唯一 predecessor (虚线), 唯一被 push 一次。当 ρ 被 pop, 立刻产生 sibling (改最后位排名) 与 first child (新增一层 rank=1)。这两条规则枚举出整个 S_K, 且天然以 q 降序输出。

5.4 复杂度

最多 B 次 pop, 每次至多 push 2 个 ⇒ heap 总规模 O(B), 总操作 O(B log B)。跟 |V| 无关, 跟 L 无关。这意味着即便 vocab=150K, L=16, B=1024, 也只是几千次 heap 操作 + log factor, 几乎零开销。


§6 完整 worked example (vocab=4, L=3, B=6)

设小词表 V = {A, B, C, D}, drafter 一次前向给出 3 个位置的 marginals:

positionrank 1rank 2rank 3rank 4
i=1A: 0.6B: 0.25C: 0.10D: 0.05
i=2B: 0.5A: 0.3C: 0.15D: 0.05
i=3C: 0.4D: 0.35A: 0.15B: 0.10

取 B = 6 ⇒ K = min(6, 4) = 4 (不剪)。我们用 rank tuple 跑算法:

steppopprefix tokensq (prefix prob)push siblings/children
initheap = { (1,) : log 0.6 = −0.51 }
1(1,)[A]0.60(2,) → q=0.25; (1,1) → q=0.6×0.5=0.30
2(1,1)[A,B]0.30(1,2) → q=0.6×0.3=0.18; (1,1,1) → q=0.30×0.4=0.12
3(2,)[B]0.25(3,) → q=0.10; (2,1) → q=0.25×0.5=0.125
4(1,2)[A,A]0.18(1,3) → q=0.6×0.15=0.09; (1,2,1) → q=0.18×0.4=0.072
5(2,1)[B,B]0.125(2,2) → q=0.25×0.3=0.075; (2,1,1) → q=0.125×0.4=0.05
6(1,1,1)[A,B,C]0.12(1,1,2) → q=0.30×0.35=0.105 (heap 不再 pop, 已满)

得到树 T (6 个节点):

b bonus A q=0.60 B q=0.25 B q=0.30 A q=0.18 B q=0.125 C q=0.12
worked example 的最终 tree: root b 下 6 个 drafted node, 树是front-heavy的 (depth 1 两兄弟, 最深路径仅 [A,B,C]); ∑ q 之和 = 0.60+0.25+0.30+0.18+0.125+0.12 = 1.575, 这就是 surrogate 期望接受长度 (Proposition 1)。

验证 walk 的具体追踪

把树打平送给 target:序列 [b, A, B, B, A, B, C], position ids = [0, 1, 1, 2, 2, 2, 3], attention mask 设为 ancestor-only。target 一次 forward 输出每个节点位置的 logits, 假设 target 的 greedy decoding 给出:

对照 vanilla DFlash: 同样 marginals 下它会取 argmax-per-position = [A, B, C]; 但 target 第 3 步给的是 D, 所以也只接受 [A, B], 同样 3 token / 轮。在这个例子里 DDTree 没赢; 但如果 target 在 [A, ?] 时给的是 "A" (rank 2), DFlash 因为只验证了 [A,B,C] 这一条会在第 2 步就停下 (接受 1), 而 DDTree 因为同时挂了 [A,A] 这条分支, 还能继续接受 → 这是 DDTree 的结构性优势


§7 验证: tree attention + verifier walk + lossless

7.1 tree attention

SpecInfer 的标准做法: 把树 flatten 成 1D 序列, 给每个节点配:

这样 target 一次 forward 就给出每个 drafted 节点上的 logits — 等价于"假设 walk 走到这里, target 的下一步分布是什么"。论文实验里只能用 PyTorch SDPA, 因为 FlashAttention-2 不支持这种 token-level mask。drafter 自己仍然可以用 FA-2 (它的 forward 不带这个 mask)。

7.2 verifier walk

从 root b 起, 每步用 target 的 decoding rule (greedy 或 temperature sampling) 选一个 token; 看它是不是当前节点在树里某个 child; 是 → 接受, walk 到该 child; 否 → 停下, 把这个 (不在树里的) token 当作 next bonus。 这一步不需要再 forward target, 因为所有节点的 logits 都在那一次 forward 里出来了。

7.3 是否 lossless?

关键: verifier 用它自己的 decoding rule, 跟 drafter 提议的具体分布无关。如果走到一个节点, target 的分布是 p, 它按 p sampling; sample 出的 token 恰好是 children 之一就接受, 否则停下用这个 token 作 next bonus。 整个过程的输出分布 = "在 target 的 p 下连续 sample 直到首次 mismatch" — 这跟 vanilla autoregressive 完全等价, 即和 drafter 选了什么 prefix 无关(drafter 选得好不好只影响速度, 不影响分布)。 因此 DDTree 是 lossless。 详见 15_Lossless_Proof_Tutorial

论文用的 sampling 路线属于 SpecInfer "verifier 走自己的 sampling, 树作为 hint" 这一支, 不是 Leviathan 那种 importance-weighted rejection sampling。 这两种 lossless 路径在 spec decoding 里都成立, 区别在于前者要求 verifier 必须 sample 自己, 后者用 q(·) 提议 + 接受/拒绝。 DDTree 选前者是因为更天然适合树。


§8 实验关键结果

8.1 主表速读 (T=0.0)

datasetQwen3-8B DFlash+DDTreeτ DFlashτ DDTree
MATH-5005.56×7.50×7.7910.73
HumanEval4.84×6.90×6.619.67
AIME 20245.38×7.35×7.4610.42
MT-Bench2.56×4.10×4.286.58
SWE-bench Lite2.65×4.23×3.605.91
Alpaca2.07×3.36×3.125.09

读法: 加速增量在每个数据集 × 模型 × 温度组合 (10×3×2=60 项) 上都为正。τ (mean acceptance length, 含 bonus) 在 reasoning / code 上能从 7~8 涨到 ~10, 说明 DDTree 不仅在尾部分布上塞更多长 prefix, 也在中位数上系统性提升。Alpaca / MT-Bench 这种开放对话任务整体 τ 较低, 因为 target 自身的分布更分散, drafter 也更难命中。

8.2 budget-quality 曲线

MATH-500 / Qwen3-8B / T=0.0 16 32 64 128 256 512 1024 node budget B 11× speedup τ (acc len) DFlash baseline ≈ 5.5×
budget-quality 曲线 (示意): τ 随 B 单调上升, 但 speedup 在 B=256~512 触顶, B=1024 又下降 — verifier cost 的边际开销超过 acceptance length 增量。 实务建议: 实际部署时按硬件 sweep 一下选一档 B。

8.3 acceptance length 直方图

论文 Figure 4 (这里只口述): vanilla DFlash 的 τ 分布峰在 5~7, 长尾在 13~16 较稀; DDTree (B=512) 的峰右移, 长度 ≤ 3 的比例显著降低, 长度 = 16 (full block accept) 显著上升。 这跟 §6 worked example 中"多挂一条 [A,A] 备选分支"导致更难全 reject 的直觉一致。


§9 与同类工作对比

工作drafter每轮 drafter forwardtree 构造需要外部数据结构lossless典型 τ
Vanilla SDsmall AR LMk~3
SpecInferAR + heuristic 树~depth固定 topology~4
EAGLE-2EAGLE-1 head~depth动态 expand (按 confidence)~5
EAGLE-3fused-feature head~depth动态 expand5~7
OPT-TreeAR (任选)~depthtop-B prefix (期望 acceptance 最大)5~7
DFlash (vanilla)block diffusion (DFlash)1无 (单链)5~8
DARTblock diffusion1动态剪枝需要 N-gram trie
DDTreeblock diffusion (DFlash)1top-B prefix, best-first heap9~10.7

立场与定位


§10 局限 / 个人 take / 待验证问题

局限

个人 take

待验证问题

  1. FlashInfer 是否已经支持 token-level tree attention mask? 如果支持, DDTree 在 30B-MoE 上的 8.2× 还能再涨多少?
  2. budget-quality 曲线在不同 GPU (A100 / B200 / H200) 上的最优 B 漂移有多大? Appendix B 说"can shift across hardware",但没量化。
  3. Alpaca / MT-Bench 上 τ 提升相对小,是 surrogate gap 的体现, 还是 drafter 本身在开放对话上 q_i 的就高 (top-1 概率低)? 这两者影响 DDTree 收益的方式不同。
  4. 如果把 DFlash drafter 换成 PARD (AR-mimicking diffusion), DDTree 是否还成立? PARD 的"diffusion"是模拟, 实际给的是不是真 factorized marginals?
  5. DART 论文的 N-gram continuity score 跟 q(u) 的差异在 ablation 上有多大? 是不是 DART 的开销其实没换来什么?
  6. Lemma 1 假设 q_i ∈ (0,1), 当 logits 出现 −∞ (constrained decoding / banned tokens) 时, 算法还需要处理 ρ_d+1 不存在的边界 — 工程上是否要清洗 vocab 后再跑 heap?

Memory points

立场 DDTree = block diffusion drafter 上的 OPT-Tree 等价物, 把 DFlash 单链验证升级成 top-B prefix 树, 1 次 drafter forward 不变。
为什么对 drafter 给出的 Q 是 factorized (per-position marginals 独立) ⇒ prefix prob = ∏ q_i ⇒ surrogate 目标加性 ⇒ top-B 自动 prefix-closed (Prop 2)。
算法 Best-first max-heap 按 σ(ρ) = log q(prefix) 降序 pop, 每次 push 至多 2 个邻居 (sibling = 改最后位排名+1; child = 加一层 rank=1)。 复杂度 O(B log B), 与 |V|, L 无关。
几何核心 q_i ∈ (0,1) ⇒ q(祖先) > q(任何严格后代) ⇒ q 降序排列里祖先永远先到 ⇒ top-B 自动 prefix-closed。 这是论文最优雅的一行不等式。
验证 SpecInfer 风格 ancestor-only tree attention; verifier 用自己的 decoding rule, 沿 children 走到 mismatch 为止; first unmatched token 自动当作 next bonus。 lossless 不依赖 drafter。
数字 Qwen3-8B / MATH-500 / T=0: τ 7.79 → 10.73, speedup 5.56× → 7.50×。 60 个 cells (10 ds × 3 model × 2 T) 全胜 vanilla DFlash。
最优 B Qwen3-8B 上 256~512 触顶, 1024 因 verifier cost 反降。 不同硬件会漂。
局限 FA-2 不支持 tree mask (target 只能 SDPA); surrogate ≠ target distribution, 在 Alpaca / MT-Bench 这种高熵任务上收益变小; B 仍需外部 sweep。
谱系 AR drafter 树进化: SpecInfer → EAGLE-2 → OPT-Tree (top-B); Block diffusion drafter 进化: BlockDiff → DFlash → DDTree (= OPT-Tree on diffusion marginals)。 DDTree 是连接两条线的桥。