Files
xserv/docs/25-speculative-methods-comparison.md
Gahow Wang 6485c87c5b docs: Phase 25 — three speculative-decoding paradigms compared
Contrast Small-Model (Phase 22-24, done), EAGLE3 (this phase's target),
and Multi-Token Prediction (DeepSeek-V3 style, not applicable here).

Includes the actual EAGLE3-Qwen3-8B weight tensor listing pulled from
AngelSlim/Qwen3-8B_eagle3 on dash5:
- 1 midlayer (attention + mlp) with hidden_size=4096
- fc.weight (4096, 12288) fusing 3 target hidden-state levels
- q_proj (4096, 8192) taking concat(embed, fused_h) as input
- lm_head only over draft_vocab_size=32000, mapped back with d2t table
- ~750 MB total (vs 1.2 GB for Qwen3-0.6B), draft cost ~1/10 of target

Also captures Qwen3-8B + EAGLE3 speedup benchmark on vLLM: ~1.97-2.02x
across MT-bench/HumanEval/GSM8K/Alpaca. That's the number to beat.

Next commits will implement Eagle3Head in xserv-model + hook target
hidden states out of Qwen3::decode_core.
2026-07-01 16:53:37 +08:00

14 KiB
Raw Permalink Blame History

Phase 25: 三种投机解码方法对比 — Small Model / EAGLE / MTP

目标:把 speculative decoding 三种主流范式(本项目已试过一种,另两种未实现) 讲清楚,并把 EAGLE3-Qwen3-8B 的实际权重结构展开来看。

1. 为什么需要多种范式

Speculative decoding 的核心公式:

speedup = tokens_generated / target_forward_passes
        ≈ (1 + α·γ) / (1 + draft_cost/verify_cost)
  • α = acceptance ratedraft 每 token 被接受的概率)
  • γ = draft window size每轮生成的 draft 数)
  • draft_cost / verify_cost = draft 一次前向 vs target 一次前向的耗时比

speedup > 1,两条路:把 α·γ 做大,或把 draft_cost/verify_cost 做小。 三种范式的本质区别就是在这两个变量上的取舍

范式 draft 模型 draft cost α (Qwen3) 需要训练 目标模型是否要改
Small-Model 独立小 LM 中 (~20% target) 40% (γ=4)
EAGLE (1/2/3) 1-layer head 读 target hidden 低 (~10%) 70%+ (γ=6+) 蒸馏训练 无 (推理路径加 hook)
MTP target 内嵌多 head 极低 (∈ target 前向) 70%+ 预训练时就要有 是(架构层面就是这样的)

结论

  • Small-Model 是 v0配置最简单但天花板低。
  • EAGLE3 是当前性价比最高的落地方案draft cost 极低,α 高,需要一次蒸馏训练(约 100k tokens 数据),但对目标模型无侵入。
  • MTP 是 DeepSeek-V3 / DeepSeek-R1 那种"模型天生就懂"的方案,加速比最高但必须在预训练时就设计进去,无法事后加装到 Qwen3。

2. Small-Model Speculative本项目 Phase 22-24 已实现)

结构

  • Draft: 独立的、小得多的同族 LM。要求tokenizer 完全一致vocab 也一致)。
  • Verify: target 用 batched forward 一次算 γ 个位置的 logits从左往右比较 draft_tokens[i] == target_argmax[i],接受最长匹配前缀。

算法伪代码

for _ in gen_tokens:
    round_start = len(committed)
    # 1. draft γ steps
    draft_tokens = [draft.decode(prev) for _ in range(gamma)]
    # 2. target verify all γ positions in one forward
    verify_logits = target.forward(committed + draft_tokens[:γ])
    # 3. accept longest matching prefix
    accepted = 0
    while accepted < γ and draft_tokens[accepted] == argmax(verify_logits[accepted-1]):
        accepted += 1
    # 4. correction: use target's answer as the next token
    correction = argmax(verify_logits[accepted-1] if accepted>0 else prev_target_logits)
    committed.extend(draft_tokens[:accepted] + [correction])

优点

  • 零训练。任何同 tokenizer 的两个 LM 组合都能跑。
  • 语义正确性直接保证:只要 accept 逻辑严格,输出等价于纯 target greedy。
  • 代码简单,是理解 speculative decoding 最好的教学入口。

缺点(本项目实测在 dash5 上)

Qwen3-0.6B / Qwen3-8B 组合:

γ acceptance speedup_e2e
1 66.5% 0.57×
4 40.3% 0.49×
8 25.1% 0.36×

即使加上deterministic gemv/attention、batched GEMV verify kernel、 Qwen3 whole-step CUDA graph for draft仍然 speedup < 1

根本原因两点:

  1. Draft 太贵0.6B 一次 decode ~ 2.5 mstarget 8B ~ 12 ms → draft/verify ≈ 20%。 γ=4 时draft 4×2.5=10 ms 单独就占了 verify (13 ms) 的 77%。
  2. Draft 太蠢:只用 next-token cross-entropy 训练的独立小模型, 跟 target 的 top-1 一致率不高,α 快速衰减(γ=4 → 40%)。

理论上限(假设 verify 免费):speedup ≤ (1 + α·γ) ≈ 2.6×。 实际上 verify 花掉了绝大部分预算,跑到 0.5× 就到头了。

什么时候能赢?

只有当 draft_cost / verify_cost < acceptance_rate 时才可能 >1×。 Qwen3-0.6B 的 draft_cost 太高,需要 draft 是 target 的 ~1/40 才行 8B target 需要 ~200M draft。Qwen3 没有官方 200M 的成员。


3. EAGLE3本 Phase 要做的方案)

3.1 一句话概括

EAGLE3 = 用 target 自己的 hidden states 当作 draft 的输入 draft 头只有 1 层 decoder + 1 个 FC 融合层,参数量 ~750Mvs Qwen3-0.6B 的 1.2 GB 且更重要的是draft 前向不需要重跑 embedding、不需要多层 attention 累积 成本大约是 target 一次 decode 的 ~1/10

3.2 权重结构dash5 上下载的 AngelSlim/Qwen3-8B_eagle3 实测)

d2t: (32000,)                   int64    # 每个 draft-vocab id → 加多少变成 target-vocab id
t2d: (151936,)                  bool     # target-vocab id 是否在 draft 频繁词表中
midlayer.self_attn.q_proj.weight: (4096, 8192)  bf16
midlayer.self_attn.k_proj.weight: (1024, 8192)  bf16
midlayer.self_attn.v_proj.weight: (1024, 8192)  bf16
midlayer.self_attn.o_proj.weight: (4096, 4096)  bf16
midlayer.mlp.gate_proj.weight:    (12288, 4096) bf16
midlayer.mlp.up_proj.weight:      (12288, 4096) bf16
midlayer.mlp.down_proj.weight:    (4096, 12288) bf16
midlayer.hidden_norm.weight:      (4096,)       bf16   # 融合特征的 pre-attn norm
midlayer.input_layernorm.weight:  (4096,)       bf16   # draft 嵌入的 pre-attn norm
midlayer.post_attention_layernorm.weight: (4096,)  bf16
norm.weight:                      (4096,)       bf16
fc.weight:                        (4096, 12288) bf16   # 3×hidden → hidden fusion
lm_head.weight:                   (32000, 4096) bf16   # 输出 draft-vocab

关键观察

  • fc.weight (4096, 12288)输入是 target 三个不同层的 hidden state 拼起来 low + mid + high level一次 FC 融合成 EAGLE 内部的 hidden dim。这是 EAGLE3 跟 EAGLE1/2 最大的区别(前两代只用 target 最后一层)。
  • q_proj.weight (4096, 8192)8192 = 4096 × 2。attention 输入是 concat(embed(draft_token), fused_target_hidden),两个 4096 拼起来。 也就是每次预测下一个 token 时,"prompt" 是"上一个 draft token 的 embedding" +"target 对上一个位置的隐状态"。
  • lm_head.weight (32000, 4096)只输出 32000 个高频 tokenvs target 的 151936。 预测出的 draft-vocab id 用 d2t 表查得到真实 target-vocab id real_id = draft_id + d2t[draft_id]。这一步把 lm_head 从 622 MB 压到 131 MB。

3.3 推理时的数据流

target 前向(正常执行):
    tokens t_0..t_n
    → embed → layer0 → layer1 → ... → layer35 → norm → logits
                 ↓        ↓          ↓
              h_low   h_mid      h_high        (在特定层 hook 出来)
    logits → sample → t_{n+1}

EAGLE draft γ 步:
    输入:三个 hidden state h_low[n], h_mid[n], h_high[n] target 已经算好了)
    输入t_{n+1} target 刚采样出来的下一个 token

    for k in 0..γ:
        fused_h = fc(concat(h_low[n+k], h_mid[n+k], h_high[n+k]))     # 4096
        emb = embed_tokens(t_{n+k+1})                                  # 4096
        # 这里 embed_tokens 和 target 共享EAGLE 不重复存 embedding
        x_attn_in = concat(embed_norm(emb), hidden_norm(fused_h))     # 8192
        x = self_attn(x_attn_in) + emb                                 # residual is emb
        x = mlp(post_norm(x)) + x
        x = norm(x)
        draft_logits_small = lm_head(x)                                # 32000
        draft_id_small = argmax(draft_logits_small)
        t_{n+k+2} = draft_id_small + d2t[draft_id_small]              # → target vocab

        # 关键EAGLE 自己会预测下一步的 hidden state 逼近
        # target 在该位置的 hidden state供下一 draft 步用。
        h_low[n+k+1] = h_mid[n+k+1] = h_high[n+k+1] = x

为什么快?

  1. 只有 1 层 decodervs Qwen3-0.6B 的 28 层)。
  2. 每步计算量 = attn(hidden=4096, kv_heads=8) + mlp(intermediate=12288) + lm_head(V=32000) ≈ 1 层 Qwen3-8B decoder + 一个小 lm_head。整个 draft 步 ≈ target 单层 forward + 半个 lm_head 远小于 target 完整 forward。
  3. Draft 的 KV cache 也只有 1 层vs 28 或 36
  4. Embedding 表复用 target 的(不重复算)。

Acceptance rate 高的原因draft 直接使用了 target 的隐状态, 不是"用另一个小模型独立猜"α 通常 ≥70%。

3.4 与本项目现有 speculative 架构的集成点

保留 Phase 22-24 的所有状态机verify + accept-reject + correction 只把 draft 换成 EAGLE3 head。API 契约:

// 现在 (Qwen3 draft)
let draft_logits = draft_decoder.decode(&draft, &[token], &[pos], &[slot], draft_cache);
let draft_next = last_argmax(&draft_logits);

// EAGLE3 draft
let draft_logits = eagle.step(&target_hidden_low, &target_hidden_mid, &target_hidden_high, token, pos);
let draft_next_small = last_argmax(&draft_logits);
let draft_next = draft_next_small + eagle.d2t[draft_next_small as usize];

新增到 xserv 的东西

  1. Target 侧:改造 Qwen3::decode_core 让它在特定 3 层(比如 1/3、2/3、末层的 post_attention_layernorm 之后)把 hidden state export 出来。
  2. 新模块 eagle3.rs:加载 AngelSlim/Qwen3-8B_eagle3 权重,暴露 step() 方法。
  3. bench-speculative 增加 --drafter eagle3 分支draft 改用 EAGLE head。

不变的东西verify path、accept-reject 逻辑、near-tie fallback、CUDA graph 框架、matched=true 的正确性验证。

3.5 Acceptance 上限

按 EAGLE3 paper 的报告Qwen3-8B 上 γ=6 acceptance ≈ 0.75speedup 通常 2-3×。 本项目实测目标:speedup_e2e > 1 是保底,> 2 是 stretch goal。


4. Multi-Token Prediction (MTP)

4.1 一句话概括

MTP = 在 target 模型的最后加 N 个"预测未来第 k 步"的 head 每个 head 都在预训练阶段和主 head 一起联合训练。推理时这些 head 天然可以并行 生成 γ 个 draft然后主 head 一次前向验证。

代表实现:DeepSeek-V3/R1、Meta MTP 论文Gloeckle et al., 2024

4.2 架构

DeepSeek-V3 的做法:

       [ target 主 decoder61 层 ]
                    ↓
         final hidden h  (2048)
       /              \
      main_head        MTP_head_1
      (predict t_{n+1}) (predict t_{n+2}
                          given h and t_{n+1})
  • 每个 MTP_head 是一个完整的 transformer block + linear head含 embedding proj + attention + MLP
  • 训练时MTP_head_k 的 target 是 t_{n+k+1}loss 加权求和DeepSeek-V3 训练时权重 0.3)。
  • 推理时main_head 得到 t_{n+1} 后,用 MTP_head_1 得到 t_{n+2}(作为 draft 可以级联 MTP_head_2 得到 t_{n+3}……然后 target 主前向一次性验证。

DeepSeek-V3 论文arxiv 2412.19437)报告:

  • MTP module 1 层depth=1参数占总模型 ~2%。
  • MTP accept rate ≈ 85-90%。
  • 端到端 tps 提升 1.8×。

4.3 与 EAGLE 的对比

维度 EAGLE3 MTP
加装时机 蒸馏训练(一天量级 GPU-hour 必须预训练时就设计进去
Draft 模型独立性 独立文件target 不用改 是 target 的一部分
深度 递归自回归,可 γ=6+ 通常最多深度 = MTP 头数 (DeepSeek=1)
训练开销 蒸馏,用 target 输出当监督 预训练时加多任务 loss
落地到 Qwen3 已有开源权重可直接用 需要重新预训练,不可行

4.4 为什么我们不做 MTP

  • Qwen3-8B 没有预训练的 MTP head。要 MTP 就得自己重新预训练 Qwen3,不现实。
  • 若要用现成 MTP只能换到 DeepSeek-V3 这种自带 MTP 的模型;那对整个 xserv 目标 (Qwen3 + gpt-oss serving) 是绕道。

5. 三者选型表

给未来的自己或读者一个简明选型:

场景 选谁
已有小同族模型,想快速验证 spec framework Small-Model本项目 Phase 22-24
已有 target 模型,希望加速但不想改 target 训练 EAGLE3(如有开源 head
有充足资源自己预训练一个新 target MTP内嵌加速比最高
目标模型是 DeepSeek-V3/R1 用它自带的 MTP head
目标模型是 Qwen3 / LLaMA / GPT-OSS 找 EAGLE3 蒸馏权重(本 Phase 走这条)

6. 本 Phase 的实施计划

  1. 写这份文档(正在做)。
  2. xserv-model 新增 eagle3.rs:定义 Eagle3Head 结构,加载 AngelSlim/Qwen3-8B_eagle3 权重。
  3. 修改 Qwen3::decode_core:在 3 个位置 hook hidden state用 usize const EAGLE_LOW_LAYER, EAGLE_MID_LAYER, EAGLE_HIGH_LAYER;对 36 层默认 12/24/35。 返回值改成 (Tensor, Option<[Tensor; 3]>),第二个 tuple 只在开启 eagle 时填。
  4. 新增 Eagle3Head::step(hidden_states, token, pos) -> Tensor:一层 attention+ MLP + lm_head输出 draft-vocab logitscaller 做 d2t 映射。EAGLE 自己也有 一个 1-层的 KV cache每轮 spec 结束时清空)。
  5. bench-speculative--drafter [qwen3|eagle3] 开关。EAGLE 分支复用现有 verify+accept 逻辑,只替换 draft 环节。
  6. γ:预期 γ=6 时 acceptance > 0.7、speedup_e2e > 1.5×。

Sources

  • EAGLE-3 paper (arxiv 2503.01840): "Scaling up Inference Acceleration of Large Language Models via Training-time Test"
  • SafeAILab/EAGLE GitHub: reference implementation
  • AngelSlim/Qwen3-8B_eagle3 on ModelScope/HuggingFace: pre-trained head we're using
  • DeepSeek-V3 Technical Report (arxiv 2412.19437): MTP architecture
  • Gloeckle et al. 2024 "Better & Faster Large Language Models via Multi-token Prediction"