DiffuTester: Accelerating Unit Test Generation for Diffusion LLMs via Mining Structural Pattern
Lekang Yang, Yuetong Liu, Yitong Zhang, Jia Li
cs.SE, cs.CL
2025-09-30
清华DiffuTester在扩散解码时按AST公共结构多解token。DiffuCoder在TestEval-C++、batch=3时算力1217→430 TFLOPs,耗时14.4→6.0秒。
扩散大语言模型(dLLM)一步可以填多个 [MASK],理论上比自回归更快。落到单元测试生成上,这个优势经常被关掉:现有工作每步只解 1 到 2 个 token,速度跟普通 LLM 差不多。把每步解的 token 加多,测试质量会掉,句法都不一定过关。附录 Fig. 8 把这条权衡画出来了:吞吐上去,句法正确率跟着掉。
单元测试有个很适合钻的空子。对着同一个焦点方法写的几条测试,骨架常常几乎一样,差的是字面量。Fig. 1 左边两条 Java 的 testshortestPath,除了网格常量和断言期望值,AST 结构是同一套。右边一条复杂 Python 测试里,addnode 这种调用也会反复出现。结构重复,多样性靠数字和字符串。
DiffuTester 是训练无关的解码插件。给定一个焦点方法,batch size 设成 n(实验里 3、5、7),一次并发生成 n 条测试。每一步先按标准置信度解码(top-k),再做基于结构模式的额外解码。
结构从 AST 里挖。叶子节点是词法(变量名、数字),非叶节点是结构。把当前 batch 里各条测试的 AST 尽量合并,能合成非空树就认为抓到了公共结构,对应 token 这一步直接留下。早期步句法经常是坏的,整棵树解析会把错误传下去,所以实际按行建 AST,不按整份测试。
多样性靠故意不合并。测试覆盖依赖输入数据的变化,变化又主要来自整型、浮点这些字面量。字面量对应的 AST 节点不参与合并,即使两条测试这会儿构造了相似的数据,这些 token 也会被重新 mask 掉,留给后面的步去改。
另外两个工程细节。直接留下合并树上所有 token 会轻微伤句法,因为偶尔会留下置信度极低的位置;所以只保留置信度高于阈值 τ 的 token,主实验 τ=0.02。每步都跑 AST 对齐有开销,而相邻步的树几乎不变,于是隔步调用,主实验每 2 步跑一次。消融里隔 2 步比每步都跑更好,动态间隔也没赢过固定 2。
生成长度 L 固定 128。默认 remask 策略每步解 2 个 token,不加加速是 64 步。加上 DiffuTester 之后,步数近似正态分布,大多数明显小于 64,并且随焦点方法自适应。
模型是 Apple 的 DiffuCoder-7B-cpGRPO 和港大 NLP 的 Dream-v0-Instruct-7B。基准是 TestEval 的 210 道 LeetCode Python 题,作者按同一批题补了 C++ 和 Java,覆盖用 pytest、Maven、gcov。对照就是同一 dLLM 不用 DiffuTester。
主表(同一批测试条数,n=3):
| 设置 | 算力 TFLOPs | 耗时 s | 吞吐 tps |
| DiffuCoder / Python | 1016 → 580(1.75×) | 12.2 → 7.8(1.57×) | 17.0 → 26.9 |
| DiffuCoder / C++ | 1217 → 430(2.83×) | 14.4 → 6.0(2.42×) | 9.7 → 23.8 |
| DiffuCoder / Java | 1259 → 668(1.89×) | 14.9 → 8.9(1.67×) | 16.1 → 29.2 |
C++ 加速最狠,作者归因为 C++ 常用句法比 Python、Java 更齐,公共结构更好挖。Dream 上趋势相同,幅度略小,Python n=3 算力 1.68×、耗时 1.51×。
行覆盖在 TestEval-Python 上几乎不掉。n=3/5/7 时 DiffuCoder 基线是 91%/94%/94%,加 DiffuTester 是 92%/93%/94%。通用加速器对覆盖不这么客气:EB-Sampler 同设置掉到 86%/88%/88%;SlowFast Sampling 在 n=10 时 7.7 秒跑完,覆盖却卡在 77%,连 n=3 基线的 91% 都不如。DiffuTester 用了更多时间(n=10 时 24.5 秒),覆盖仍是 94%。
跟同规模自回归模型比,对照是没开投机解码的 Qwen2.5-7B-Instruct。作者说多数情况下 Dream+DiffuTester 达到同样覆盖更省时间和算力。这组对照偏瘦:投机解码同样可以叠到 AR 上,公平性有限。
迁到别的代码任务,加速还在,但小得多。HumanEval-X 上 DiffuCoder 每题 4 个样本:Python 5.68 秒 / pass@1 50% → 3.87 秒 / 49%;C++ 5.73/11 → 4.21/13;Java 6.32/11 → 5.36/11。代码翻译(UniTrans)耗时大约少 1 到 2 秒,pass@1 有升有降,Python→C++ 从 42.9 到 46.2,C++→Python 从 96.4 到 95.5。这些任务里重复结构没有单测那么密,加速天花板更低。C++/Java 的 pass@1 只有十来个点,底座模型自己就不强。
给已经在试 dLLM 写代码的人,这是一条任务特化的采样加速,不是又一个 KV cache。它跟 cache 类方法正交,论文只和采样类比了,没把 cache 叠上去。训练无关,换 Dream 和 DiffuCoder、换三种语言都走得通。
适用面很窄,也是它能保质量的原因。单测这种「同一 API、换数据」的分布,AST 对齐才有东西可挖。拿到 HumanEval 那种一题一答,加速就剩两成到三成。如果生产环境的测试是长文件、多 fixture、长度远超 128 token,这篇的数字不能直接外推。
作者自己写的局限就一句:时间和算力不够,没在更多模型和代码任务上测。下面这些是读完以后站不住的地方。
生成长度锁死 128,真实仓库里的单测经常更长。210 道 LeetCode 不是工业代码,TestEval-C++/Java 还是同一批题换语言,和真实 repo 仍不是一回事。AR 对照关掉了投机解码,结论只能读成「裸 dLLM + 结构加速 vs 裸 AR」。HumanEval C++/Java pass@1 垫底,说明加速保的是覆盖不掉,不是把弱模型变强。阈值 0.02、隔 2 步这两个人工超参,换模型要不要重搜,论文没给出迁移实验。