Running Local LLMs with DFlash for Agents

post May 5, 2026

起因是看到一条关于 Qwen3.5-27B 在 RTX 3090 跑出 207 tok/s 的推文;DFlash 在本地推理上的加速效果很惊艳,而家里的 RTX 4090 平时也经常闲着;LuceBox Hub 的结果基于 RTX 3090,我想看看 RTX 4090 上能跑出什么结果。

原始 DFlash benchmark 复现

我先在 Claude Code 的辅助下尝试复现原始 benchmark。环境初始化、依赖安装、CUDA 扩展构建、数据整理,基本都是通过聊天完成。第一轮复现得到下面这些数据。

DFlash 的原始 benchmark 是 Qwen3.5-27B Q4_K_M target + DFlash draft,n_gen=256,每个数据集 10 个 prompts。

数据集 GPU AR tok/s DFlash tok/s AL Speedup
HumanEval 3090 README 37.78 129.52 8.31 3.43×
HumanEval 4090 实测 42.55 147.90 8.01 3.48×
GSM8K 3090 README 37.65 96.15 6.14 2.55×
GSM8K 4090 实测 42.51 127.94 6.89 3.01×
Math500 3090 README 37.71 110.51 7.04 2.93×
Math500 4090 实测 42.52 132.32 7.12 3.11×

这组数据看起来很好。从 3090 README 到 4090 实测,AR baseline 从 37.7 tok/s 左右上到 42.5 tok/s 左右,DFlash 从 96-129 tok/s 上到 128-148 tok/s——这是硬件差异,不是优化。整体 speedup 仍在 3× 左右。

这个结果看起来激动人心,我就迫不及待想接入到 Pi 中使用。能有这个速度,那作为日常助手的 agent 就不用独立买一个 Kimi Coding Plan,直接使用 Local LLM 就可以了。

为 llama.cpp server 添加 DFlash + DDTree 的支持

LuceBox Hub 仓库里的 server.py 是最简单的 benchmark server。它没法直接接入 Pi 做真实 agent workload 测试。翻查后发现 Luce Team 有一个 fork 版本的 llama.cpp 在推进相关改动,但还不完整。

我当时也查了 llama.cpp 上游的实现状态:server: speculative checkpointing 已经在 #19493 合并;speculative-simple: add checkpoint support 已经在 #22227 合并;但 DFlash 相关的 #22105 还是 draft,并且依赖 EAGLE3 的 #18039。另一个相关 PR 是 GDN partial seq_rm 的 #22400

GPT-5.5 刚上线不久还没尝鲜,我就让 GPT-5.5 协助推进 llama.cpp 这边的实现。Python 参考实现已经存在,社区也有相关工作,一天左右后 Codex 报告“可以了”。

第一轮真实测试体感很差,和原始 benchmark 的差距很大。最初我以为只是参数问题,于是在 Pi 里面启动了 autoresearch,让 GPT-5.5 自动测试各种组合。

Autoresearch 跑了什么

Autoresearch 断断续续跑了 224 个 run:

状态 数量 含义
keep 20 正向有效结论 / 新 best / 保留点
discard 180 负向有效结论 / 证伪方向
crash 24 没有有效 benchmark 结果

换句话说,有效 benchmark 结论是 200 个,其中正向有效结论是 20 个。

压缩后的结论如下:

  1. 1024-token target feature window 太慢。
  2. target_feat_ctx=128 是早期甜点。
  3. Decode-only TPS 比 end-to-end TPS 更适合作为调优指标。
  4. Budget 增大到 32/40 能改善 accepted path。
  5. Proposal temperature 最佳区间在 0.7 左右。
  6. top_k=4 足够,K>4 收益不明显。
  7. Pure best-first / chain_seed=0 更适合当时的 exact path。
  8. Fast rollback 在 64k context / full-draft / 24GB VRAM 下不适合作默认路径。
  9. CUDA graph、batch、KV、q8/q4、offload 等微调都不能稳定改善。

但这 224 轮次 autoresearch 之后的结果是 TPS 10.666,step 214ms,draft 141ms,top-k 15ms - 这个结果不对劲。Python standalone DFlash 能轻松破百 token/s,但跑了一天多 autoresearch 的结果还不如 vanilla llama.cpp。

停下来复核实现后,我发现又被 GPT-5.5 坑了。它把 P0 任务判断成“跑得动”,而不是“完整实现 DFlash + DDTree 的高性能路径”。也就是说,llama_decode 路径还和 DFlash/DDTree 的理想执行路径有很大差距,但 autoresearch 一直在做参数搜索,而不是从更高层的实现路径上调整。

这个教训很明显: GPT-5.5 能发现问题,但不能放心让它排优先级。它容易把文档对齐、小瑕疵、边缘 clean-up 当成 P0/P1。一旦注意力走偏,短时间内很难拉回来。

DFlash draft runtime and DDTree verify

虽然还是 GPT-5.5 敲定了接下来的目标,我还是切换到 Claude Code 推进执行事项:添加 DFlash draft runtime 和 DDTree verifier。

在开始之前,我记录下以下基准数据;这个版本能通过 OpenAI-compatible API 生成 token,也能跑 thinking 和 tool use:

step_ms=214.29
draft_ms=140.90
topk_ms=14.71
exact_ms=57.53
acceptance=2.357

Speculative decoding(先让 draft model 猜一段,再让 target model 验证)要有专用 draft path,不能继续走通用 llama_decode。在加入专用 DraftBackend 和 draft runtime 后,数据明显改善:

step_ms=86.31
draft_ms=27.71
topk_ms=0.00
exact_ms=58.58
acceptance=1.941
指标
step_ms 214.29 86.31
draft_ms 140.90 27.71
topk_ms 14.71 0.00
exact_ms 57.53 58.58
acceptance 2.357 1.941

draft_ms 从 140.90ms 降到 27.71ms,topk_ms 也被消掉。

接下来就是 DDTree,把 DDTree verify path 改成更接近论文的路径:

DFlash draft top-K
-> DDTree proposal
-> target tree attention verify
-> posterior tree walk
-> KV compact + recurrent/conv rollback
-> commit + bonus token

DDTree verifier 跑通并保持 bit-equal,这是工程上很重要的节点。但初版 DDTree verifier 的 default exact path 在短测中的结果退化了,target_tree_ms 增加了。

真正的大提升来自 grammar-free DDTree verify。agent traffic 里 tool-call grammar 的 sampler callback 很慢。instrumentation 显示:

n=4247 cb samples
sync avg = 0.004 ms
work avg = 47.978 ms
p90 work = 105 ms

也就是说,慢的不是 GPU sync,而是 grammar-active tool-call schema 下的 host-side sampler/grammar work。DFlash/DDTree reference verify 本来就是 target greedy argmax,不应该在 verify walk 里跑 grammar。grammar 只需要在主 sampler commit path 推进。

关掉 verify walk 的 grammar 后:

agent TPS API: 8.90 -> 14.15 (+59%)
accept_path: 30-110 ms -> 0.01 ms

这就是 speculative mode 内部最好的结果。

真正的 baseline:vanilla llama.cpp

到这里还不能说 DFlash 对 agent 有收益,因为我们一直在跟旧 spec path 比。真正的 baseline 应该是不开 spec 的 vanilla llama.cpp。

继续在 RTX 4090 上进行测试,同一个 Qwen3.5-27B Q4_K_M,同类 agent traffic,plain llama-server 的结果是:

TASK TPS 32.57 tok/s API
wall 24.60 tok/s
single-token eval ~= 24 ms/token

横向对比:

阶段 Agent TPS API
Earliest DFlash recorded 6.96
DFlash draft runtime chain mode 11.07
DDTree grammar verify 8.90
DDTree budget=22 9.28
DDTree grammar-free verify 14.15
Vanilla AR 32.57

这时对比口径变了:DFlash draft runtime 和 DDTree verify 的优化都是真的,但它们只是在 speculative mode 内部变快。面对 vanilla AR,14.15 tok/s 仍然低于 32.57 tok/s。

原因是 acceptance length 太低。Acceptance length (AL) 指一次 spec step 平均能提交几个 token。原始 HumanEval / GSM8K / Math500 benchmark 的 AL 都大于 6,而当前 agent workload 的 AL 大概只有 2。

break-even 很简单:

spec step_ms ≈ 110 ms
vanilla per-token ≈ 24 ms
spec wins iff 110 / AL < 24
AL > 4.6

agent traffic 实测 AL 大概只有 2,差太远。

为什么 agent traffic 的 AL 低

后面我借助 AI 学习了一轮论文,也回头看了 DFlash 的训练分布。根因是 draft model 针对自然聊天和常规 benchmark 训练,没有针对 agent traffic 训练。

agent traffic 包含以下模式:

DFlash draft 没在这种分布上训练。因此 draft 预测出来的很多 token 在 verify 阶段都会被丢掉。

我还做了三组排除实验:

  1. 剥掉 chat/tool template 后,AL 从 2 提到 3.2 左右,但仍低于 break-even 4.6。
  2. 用 oracle gate 提前知道每一步 AL 后,最优策略仍然是不走 spec,因为没有一步 AL 达到 break-even。
  3. reference safetensors path 在 agent prompt 上也只有 AL 2.7-3.2,说明问题不是 GGUF port 单独造成的。

这三组实验说明,agent AL 低不只是 GGUF port 或 llama.cpp fork 的问题。实现有二次损失,但 draft training distribution mismatch 是更大的因素。

此时如果我们去翻查 z-lab 的 Hugging Face collection和公开资料,目前并没有公布他们的 DFlash draft model 训练方法;虽然 SGLang 社区有一个实现,但是要针对 agent traffic 进行训练,这显然不是本地 RTX 4090 能完成的事情了。

vLLM 和 SGLang 已经支持 DFlash 了,结果如何?

为了排除“是不是 llama.cpp fork 不行”,我又看了 vLLM / SGLang 的 DFlash 路线。

结果更差:

所以这两条都没进核心对比。vLLM 那 0.94 tok/s 主要是 CPU offload 的拖累,target 大头权重在 CPU 上、每层 forward 都要搬一次,这数字反映不了 DFlash 本身好不好。它只能说明 24GB 单卡装不下 AWQ-VLM target + DFlash draft 这套组合。SGLang 装得下但跑不起来,没东西可比。

把 Gemma 4 加入评测

最后我加了一个 cross-architecture 对比。这也符合我日常的偏见:我一直觉得 Qwen3.5-27B 在 agent 使用中没有 Gemma-4-31B 好使。

同样是 llama.cpp vanilla AR,同类 agent workload。这里 Qwen vanilla 是 13.22 tok/s。和前面 vanilla baseline 跑出来的 32.57 不一样:前面那次是 driver 560 下的长会话样本,这次是 driver 595 升级后跑的短会话(2 reqs / 311 token 输出),prefill 占比高把 API TPS 压下来了。两个数都对,是不同 sample。Gemma 这次跑的也是短会话,跟 Qwen 的 13.22 同口径:

Model API tok/s Wall tok/s Raw decode Prefill VRAM 配置
Qwen3.5-27B Q4_K_M 13.22 10.06 43.7 tok/s 521-865 tok/s 15.5 GB driver 595
Gemma-4-31B Q4_K_M 29.30 24.97 35.0 tok/s 366-2416 tok/s 18.0 GB ngl 61, SWA 1024

这个对比很有意思。Qwen 的 raw decode 更快:43.7 tok/s vs 35.0 tok/s。但 Gemma 的 agent API TPS 高很多:29.30 vs 13.22,约 2.22×。

原因是 agent workload 不只是 decode。它有大量 prompt ingest、cache revalidation、tool roundtrip 和短输出。Gemma 4 的 sliding window attention(SWA)和 shared KV layers 让长 context prefill 更便宜,所以 agent 层吞吐反而更高。

这条数据说明,在同一类 agent workload 下,Gemma-4-31B 的 API TPS 明显高于 Qwen3.5-27B。

什么是 Speculative Decoding?

既然已经碰到自己的盲点,那自然需要恶补一番。感谢 Codex 客户端,感谢 GPT Image 2。通过对话,我整理这个图来帮助理解(记忆)什么是 speculative decoding。

在 decode 环节,把原本一次只吐一个 token 的流程,变成一次吐多个 tokens、并把结果送进 commit queue 的流程优化,而这个算法需要一个额外的 draft model

What is Speculative decoding

这里需要注意的细节点:

  1. 除了原本的推理模型,你还需要一个 draft 模型。
  2. draft 模型本身也消耗 VRAM。
  3. draft 模型需要和推理模型匹配,否则是负增益。
  4. Speculative decode 会引入额外的 draft/verify 开销,这里就有一个潜在的收益评估,就是图中的 effective AL (acceptance length)。

Speculative decoding 还有一些细节概念,包括 accepted tokens, bonus token, correction token, 这里可以理解为在 target verify 阶段进行的行为,也可以用来衡量 draft model 的“效能”。我们把 draft model 产生的 token 叫做 draft tokens,那么:

简单来说,统计 bonus token 和 correction token 的比例可以用来评估 draft model 的效果。

什么是 DFlash 和 DDTree?

Speculative decoding 是一个框架,而 DFlash 和 DDTree 就是这一框架下的两个组件。

How DFlash & DDTree work

DFlash 全称是 Block Diffusion for Flash Speculative Decoding,是一个 draft 方法,通过一个额外的 draft 模型,在单次 draft forward 尽可能产出多个 token。

DDTree 全称是 Diffusion Draft Tree,是把原来的链状的数据变为基于 DFlash 给出的 top-k 分布来组织为一个树状的数据结构,让 target model 在 verify 的时候做更多可能性的验证。

DFlash + DDTree 做的是优化 proposal 和 verify 两步的吞吐量;串行太慢了,GPU 本身就是对批量计算友好的,应该尽可能改为批量处理。

换一个角度说,为了并行处理我们有额外的前置计算量,作为 trade-off,如果这些前置计算量的结果不被接受(effective acceptance length 的值不高),那么这会是负优化。

这里 DFlash 还有一个约束:DFlash 模型。目前 z-lab 官方训练 DFlash 模型的方法还没有公开,可用的 DFlash 模型目前只有 z-lab 官方提供的这些。SpecForge 提供了 DFlash 模型的训练方法,但结果和 z-lab 的结果还是有差距。

现在我能用 Local LLM + DFlash / DDTree 驱动我的 agent 了吗?

tl;dr 不能。主要的制约因素还是目前 draft model 的训练方法和训练成本还没有公开,而已经公开的(至少 Qwen3.5-27B 的这个版本)并没有为 agent 使用准备好。而除此以外,要使用 DFlash,我们还得注意计算 VRAM 的占用,draft model 也需要占用显存。RTX 3090/4090 的显存只有 24GB,这次 vLLM DFlash 路线使用的 AWQ target + BF16 draft 已经超过 24GB。最后再贴一个通过 GPT Image 2 绘制的图作为我的总结。

Speculative decoding decision chart