我想做一个范围很小的 speculative decoding 实验:不用 serving framework,也不追求完整系统,只实现 greedy speculative decoding 最核心的 draft、verify 和 KV rollback,然后实际看看它在我的笔记本 GPU 上能不能比 target-only decoding 更快。
最后没有。最好的 fixed-K 配置只有 target-only 的 0.82x。后来我又试了基于 draft confidence 的 early stop:它减少了大约 33% 的 draft forward,也让 fixed K=8 路径快了约 26%,但最终仍只有 target-only 的 0.759x。
这个 repo 记录实现、benchmark,以及我对这个结果的分析。
我故意把范围控制得很小:不做 serving、batching、quantization 或 compile,只测最朴素的 greedy speculative decoding 在单卡单请求下值不值得。
| 项目 | 配置 |
|---|---|
| Draft | Qwen3-0.6B |
| Target | Qwen3-1.7B |
| GPU | RTX 5060 Laptop 8GB |
| Runtime | PyTorch BF16 / Transformers 5.15 |
| Mode | batch=1, greedy |
核心想看两件事:fixed-K speculation 在这套环境里到底值不值得;draft 自己的 confidence 能不能帮助决定什么时候停止继续 proposal。
draft: d1 -> d2 -> ... -> dK
target: [last committed, d1, ..., dK]
|
one forward verify
|
accept prefix / correction / bonus
|
crop KV cache
- target-only incremental greedy 作为 baseline
- draft 自回归生成 proposal,每个 token 一次 forward
- target 一次 multi-token verification(
logits_to_keep=M+1) - longest-prefix acceptance:mismatch 用 target correction,全匹配取 bonus
DynamicCache.crop(-n)回滚,zero repair replay
tokenizer 全映射兼容、cache invariant、synthetic 边界 case 都有测试覆盖(31 项 unittest)。BF16 下还有一个有限精度问题,后面单独说明。
| 配置 | 相对 Target-only |
|---|---|
| Target-only | 1.000x |
| Fixed K=1 | 0.820x |
| Fixed K=2 | 0.793x |
| Fixed K=4 | 0.715x |
| Fixed K=8 | 0.584x |
| Adaptive 0.4 | 0.649x |
| Adaptive 0.6 | 0.759x |
这和最开始的预期相反。K 变大以后,每次 target verification 确实能对应更多输出 token(K=1 时约 1.9 个,K=8 时约 4.5 个),但 draft 也必须顺序执行更多 single-token forward(每请求中位数从 27.5 涨到 70.5)。代表性 profile 里,draft execution 占 measured E2E 的 69%~85%,最后增加的 draft 工作比 target verification 省下来的更多。sequential single-token draft execution 是主要可观测成本(没有独立 profiler,我没有断言具体是哪一层 overhead)。
在这组实验中,没有一个 speculative 配置快过 target-only。
看到 K 越大越慢以后,我想知道 draft 能不能自己判断哪些 proposal 不值得继续生成。做法:每生成一个 draft token,直接从已有 logits 算 top-1 probability,低于 threshold 就停止继续 proposal;当前这个低 confidence token 本身仍然交给 target verify。不增加额外 model forward。
confidence 本身确实有排序价值(frozen protocol 下复采,n=3774 valid observations):
- Spearman(prob, accepted) = 0.486
- prob < 0.4 时 acceptance 约 31%
- prob >= 0.8 时 acceptance 约 96%
adaptive 的实际效果(threshold 0.6):
- draft forward:70.5 → 47.5 / request,约 -33%
- E2E vs Fixed K=8:1.263x
- E2E vs Target-only:0.759x
confidence 确实让我少做了一部分低价值 draft,但省下来的工作还不足以让 speculative decoding 整体超过 target-only。完整五档 bin 表在 benchmarks/RESULTS.md。
实现过程中还遇到了一个和 cache/state machine 无关的问题。BF16 下,multi-token target verification 和 sequential single-token target decoding 走不同的浮点路径,在极低 logit margin 的位置偶尔会得到不同 argmax。
- 最终 measured speculative runs 中 222/252 与 sequential target-only token-identical
- divergence 集中在 low-margin positions
- synthetic state-machine / cache invariant tests 仍然通过
- 切换 attention backend 没有完全消除这个现象
详细诊断和实验计数修正记录见 benchmarks/RESULTS.md。
uv venv .venv && source .venv/bin/activate
uv pip install torch transformers tokenizers huggingface_hub accelerate safetensors
# 模型放本地目录 models/Qwen3-1.7B、models/Qwen3-0.6B(或改脚本中的 MODEL 路径)
python -m unittest discover -s tests # 31/31
python benchmarks/collect_observations.py benchmarks/results/observations_phase6.jsonl
python benchmarks/analyze_confidence.py benchmarks/results/observations_phase6.jsonl
python benchmarks/benchmark_phase5.pyspecdecode/ 核心实现(baseline / speculative / model_utils)
tests/ unittest(31 项)
benchmarks/ 实验脚本、fixed prompts、RESULTS.md(详细协议与数据)