跳转至

Co-Evolving LLM Coder and Unit Tester via Reinforcement Learning

会议: NeurIPS 2025
arXiv: 2506.03136
代码: GitHub
领域: 代码智能 / LLM推理
关键词: 自演化, 强化学习, 单元测试生成, 代码生成, 自博弈, 奖励精度

一句话总结

提出 CURE 框架,让同一个 LLM 同时扮演代码生成器和单元测试生成器两个角色,通过生成代码与生成测试的交叉执行构建成对奖励矩阵,用基于理论推导的奖励信号进行强化学习,在完全不需要 ground-truth 代码标注的情况下实现代码生成能力和单元测试生成能力的共同进化,在五个编程基准上大幅超过同规模的专用 Coder 模型。

研究背景与动机

领域现状:近年来,大语言模型在数学推理和代码生成方面取得了显著进步,主要得益于后训练优化(如 RL)和测试时缩放(test-time scaling)技术的发展。在代码生成领域,单元测试被认为是一种极有前途的辅助信号——它既可以作为 RL 训练的奖励信号,也可以在推理时通过 Best-of-N(BoN)策略筛选最优代码。与标量/生成式奖励模型不同,生成的单元测试可以高效复用于所有候选解,避免了二次复杂度的问题。更重要的是,生成一个单元测试本身不需要模型产出完整的解法——一个合理的 assert 语句远比解出整道题简单得多,这使得单测生成在逻辑上是一个"更容易"的任务。

现有痛点:尽管单元测试的价值已被广泛认可,但现有的单元测试生成器训练方法(如 O1-Coder、UTGEN)依赖于 ground-truth 代码标注——需要先收集正确的代码解来构造训练数据。这种方式存在两个根本问题:一是标注成本高、数据收集困难,限制了训练规模和领域多样性;二是训练数据的静态性质使得单测生成器无法从动态产生的错误模式中学习。

核心矛盾:训练单元测试生成器需要 ground-truth 代码,但收集 ground-truth 代码本身就是一个昂贵、难以扩展的过程。这形成了一个鸡生蛋蛋生鸡的困境:要得到好的单测需要好的代码做监督,而好的过滤/筛选又需要好的单测做奖励。更深层的问题是,即使有部分 ground-truth 代码,这些代码只代表"一种正确的实现方式",而单测应该具有泛化性——不依赖于特定实现,而是对所有正确实现都通过。用特定代码训练出来的单测可能过度拟合到那个实现的细节,丧失泛化能力。

本文目标 这篇论文要回答一个核心问题:单元测试生成器和代码生成器能否在完全没有 ground-truth 代码的情况下有效共同进化? 具体分解为三个子问题:(1)如何设计无监督的奖励信号让两者互相学习?(2)如何保证奖励的精确度,避免生成平凡或错误的单测?(3)如何在 long-CoT 模型上应用这一框架并保证推理效率?

切入角度:作者的关键观察是:在 RL 训练过程中,代码生成器自然会产出正确和错误的解。错误的代码恰好暴露了典型的失败模式,这些失败案例对单测生成器来说是极有价值的学习材料——单测需要学会区分好代码和坏代码,而坏代码正好提供了区分的训练样本。反过来,改进后的单测又能更好地筛选代码,形成正反馈循环。这个观察打破了对 ground-truth 的依赖。

核心 idea:用代码和单测之间的交叉执行矩阵构建自博弈奖励,通过理论推导的奖励精度目标 \(\mu\) 来指导单测生成器的优化方向,实现无监督的 coder-tester 共同进化。

方法详解

整体框架

CURE 是一个基于自博弈强化学习的协同进化框架。整体 pipeline 如下:给定一组编程任务(每个任务附带少量 ground-truth 单测),同一个策略 LLM 对每个任务分别生成 \(n\) 个候选代码和 \(m\) 个候选单元测试。然后将所有代码在所有单测上执行,生成一个二进制评估矩阵 \(\mathcal{B}^{\star} \in \{0,1\}^{n \times (m+t_q)}\),其中最后 \(t_q\) 列对应 ground-truth 单测。基于这个矩阵,分别为每个代码和每个单测估计奖励值,然后用 GRPO/PPO 风格的策略优化算法交替优化代码生成能力和单测生成能力。整个过程迭代进行,代码和单测的质量持续提升。

这里的关键设计直觉是:ground-truth 单测用来判断代码的正误(作为代码的奖励来源),而代码的正误信息又被用来估计每个生成单测的质量(作为单测的奖励来源)。两者通过执行矩阵实现了无需额外标注的相互监督。整个 pipeline 的计算开销主要在 rollout 生成和交叉执行阶段,策略优化本身与标准 GRPO 相同。框架的一个工程亮点是使用 vLLM 进行高效的批量推理,使得 16×16 的 rollout 生成在 8 卡 A100 上仍然可行。

关键设计

  1. 奖励精度(Reward Precision)的理论推导:

    • 功能:定义并分析"奖励精度"——即一组生成的单测能多准确地区分正确代码和错误代码——作为优化单测生成器的理论目标。
    • 核心思路:作者首先定义奖励精度为 \(P(\mathcal{R}_{s_{j_1}} > \mathcal{R}_{s_{j_2}} \mid s_{j_1} \text{ 正确}, s_{j_2} \text{ 错误})\),其中 \(\mathcal{R}_{s_j} = \sum_{l=1}^{m} \mathcal{B}_{j,l}\) 是代码 \(s_j\) 通过的单测数量。然后引入生成模型:单测正确的概率为 \(p_u\),正确代码通过正确单测的概率为 \(p_{11}=1\),错误代码通过正确单测的概率为 \(p_{01}\),错误代码通过错误单测的概率为 \(p_{00}\),正确代码通过错误单测的概率 \(p_{10}=0\)。通过 Hoeffding 不等式,作者证明了奖励精度趋向 1 的充要条件是 \(\mu > 0\),其中 \(\mu = p_u(1-p_{01}) - (1-p_u)p_{00}\),且收敛速度满足指数界 \(P(\mathcal{R}_{s_{j_1}} > \mathcal{R}_{s_{j_2}}) \geq 1 - e^{-\mu^2 m / 8}\)。这意味着 \(\mu\) 越大,需要的单测数量越少就能可靠区分代码质量。
    • 设计动机:\(\mu\) 不仅是奖励精度的收敛条件,还直接控制收敛速率。因此将 \(\mu\) 作为每个单测的个体级别优化目标是理论上最优的选择。直觉上,优化 \(\mu\) 等价于同时提高单测正确率 \(p_u\)、降低错误代码的逃逸概率 \(p_{01}\) 和误报概率 \(p_{00}\)
  2. 基于执行矩阵的个体奖励估计:

    • 功能:从执行矩阵中为每个生成的单元测试估计其个体贡献的奖励值 \(\mathcal{R}_{u_k}^{\star}\)
    • 核心思路:对于代码的奖励,直接使用通过 ground-truth 单测的数量 \(\mathcal{R}_{s_j}^{\star} = \sum_{l=1}^{t_q} \mathcal{B}_{j,m+l}^{\star}\)。对于单测的奖励,核心公式为 \(\mathcal{R}_{u_k}^{\star} = -\sum_{l=1}^{n}(1-\mathcal{I}_{s_l})\mathcal{B}_{l,k}^{\star} + (\prod_{l=1}^{n} \mathcal{I}_{s_l} \mathcal{B}_{l,k}^{\star})(\sum_{l=1}^{n}(1-\mathcal{I}_{s_l}))\),其中 \(\mathcal{I}_{s_j} = \prod_{l=1}^{t_q} \mathcal{B}_{j,m+l}^{\star}\) 表示代码 \(s_j\) 是否通过了所有 ground-truth 单测(即是否被判断为"正确")。推导过程是先用执行结果估计 \(p_u\)\(p_{01}\)\(p_{00}\) 三个参数,再代入 \(\mu\) 的表达式得到。
    • 设计动机:这个奖励函数的直觉非常清晰——当单测 \(u_k\) 能通过所有正确代码时,第二项为正且与错误代码数量成正比,表示它有能力区分好坏代码,获得正奖励;当单测 \(u_k\) 使某些正确代码失败时,只剩第一项的负贡献,表示它是一个错误的测试,被惩罚。如果简单地用"是否通过所有正确代码"(即 \(p_u\) 的估计)作为奖励,会导致模型生成过于宽松的平凡测试——因为只要不拒绝正确代码就能拿高分,但这样的测试也放过了大量错误代码。CURE 的奖励设计避免了这个陷阱。
  3. 协同进化的策略优化:

    • 功能:使用 GRPO 风格的策略梯度方法交替优化代码生成和单测生成两个目标。
    • 核心思路:同一个模型在两个角色间交替优化。对于代码角色,用 \(\mathcal{J}(\theta, \{s_j\}_{j=1}^n)\) 更新参数,奖励来自通过 ground-truth 单测的数量;对于单测角色,用 \(\mathcal{J}(\theta, \{u_k\}_{k=1}^m)\) 更新参数,奖励来自上述基于 \(\mu\) 的理论推导。两阶段在每个迭代步中顺序执行。优化目标采用标准的 PPO clipped surrogate objective 加 KL 散度正则化:\(\mathcal{J}(\theta) = \mathbb{E}[\min(\frac{\pi_\theta}{\pi_{\theta_{old}}} A, \text{clip}(\frac{\pi_\theta}{\pi_{\theta_{old}}}, \epsilon) A)] - \beta D_{KL}[\pi_\theta \| \pi_{ref}]\)
    • 设计动机:共用同一个模型而非两个独立模型,是因为代码生成和单测生成在底层的编程理解能力上是共享的——理解代码逻辑的能力既帮助写代码也帮助写测试。交替优化使得两种能力可以在共享表示上互相促进。KL 正则化防止模型偏离太远导致训练不稳定,这在自博弈场景中尤其重要,因为奖励信号本身是非静态的。
  4. 长链推理模型的效率优化:

    • 功能:为 long-CoT 推理模型设计响应长度引导的奖励变换,在不损失单测质量的前提下大幅缩短生成长度。
    • 核心思路:对于标准化后的奖励 \(\{r_i\}\) 和对应的响应长度 \(\{l_i\}\),先做长度变换 \(\hat{r}_i = -l_i + T_l\)(若 \(r_i > 0\))或 \(\hat{r}_i = -l_{max} + T_l\)(若 \(r_i \leq 0\)),其中 \(T_l = \text{median}\{l_j \mid r_j > 0\}\)。然后通过正负样本之间的平衡因子 \(\alpha\) 和标准差 \(\sigma\) 进行归一化。最后截断超过 8K token 的响应,只保留前 8K 用于训练。
    • 设计动机:Long-CoT 模型(如 Qwen3-4B)的推理能力极强但推理速度极慢,在单测生成这种需要大量调用的场景中成本过高。这个变换巧妙地在保持奖励区分度(正的基础奖励保持正、负的保持负)的前提下,引入长度惩罚——正确但较短的回复获得更高奖励,鼓励模型学会用更少的推理步骤达到同样的准确度。实验证明这一策略将平均响应长度缩短至原来的 64.8%。

损失函数 / 训练策略

训练使用 GRPO 风格的策略优化,采用 clipped surrogate objective 配合 KL 散度正则项,防止策略在自博弈环境中因奖励信号的动态变化而发生过大偏移。具体配置:学习率 \(1 \times 10^{-6}\),KL 系数 \(\beta = 0.01\)。采样时温度为 1.0,top-p 为 1.0(long-CoT 模型降至 0.8 以提高单测生成的稳定性)。每步生成 16 个代码和 16 个单测 rollout,构成 \(16 \times 16 = 256\) 次交叉执行。7B 和 14B 模型训练 350 步,4B long-CoT 模型仅需 50 步即可收敛。训练在 8 块 A100 GPU 上进行。

训练数据仅使用 CodeContests 中难度 ≤2 的 4.5K 编程问题,这是一个非常小的训练集。值得注意的是,仅用 4.5K 个问题就能带来跨 benchmark 的显著提升,体现了框架的数据效率。这也和无监督方式直接相关——不需要为每道题准备正确代码解,只需要题目本身和少量 ground-truth 单测。

在训练过程中可以观察到稳定的共同进化动态:单测准确度、代码准确度和估计奖励 \(\mu\) 三条曲线均呈持续上升趋势,没有出现自博弈常见的不稳定震荡现象。这得益于 KL 正则化限制了每步更新幅度,以及 ground-truth 单测提供的锚定作用——它们为代码奖励提供了一个外部参照点,防止奖励信号完全进入自我循环。

实验关键数据

主实验

在五个编程基准上评估三个指标:单测准确率(UT)、one-shot 代码准确率(Code)、Best-of-N 准确率(BoN, 16 code × 16 unit test)。这五个 benchmark 涵盖了从基础编程(MBPP)到竞赛级别(CodeContests, CodeForces)的不同难度层次,以及无污染评估(LiveBench, LiveCodeBench)的需求。

模型 LiveBench UT/Code/BoN MBPP UT/Code/BoN LiveCodeBench UT/Code/BoN CodeContests UT/Code/BoN CodeForces UT/Code/BoN
Qwen2.5-14B-Instruct 27.8/36.4/51.7 72.8/76.3/83.2 35.7/33.5/45.1 43.8/25.6/33.4 20.7/7.3/12.5
Qwen2.5-14B-Coder 39.0/42.2/53.1 75.1/72.6/84.9 41.6/38.2/47.7 37.3/23.3/32.0 22.1/7.8/13.5
ReasonFlux-14B 73.3/47.5/60.2 91.6/78.5/88.2 81.4/40.5/50.5 86.0/32.1/44.4 82.3/12.1/25.9
Qwen2.5-7B-Instruct 26.5/31.1/35.9 35.8/66.3/79.4 28.6/26.9/32.6 26.7/21.2/25.8 18.9/5.4/8.9
Qwen2.5-7B-Coder 19.3/35.0/42.9 41.3/68.0/79.6 20.6/29.8/34.8 12.9/22.8/23.8 7.2/6.7/9.1
ReasonFlux-7B 54.8/37.1/51.6 79.4/70.2/84.6 57.7/31.2/42.7 62.6/25.9/34.1 45.6/8.2/16.1
Qwen3-4B (Long-CoT) 36.8/72.5/78.1 76.5/88.4/90.1 50.9/74.5/80.0 43.6/53.0/58.3 54.1/28.8/38.5
ReasonFlux-4B 84.6/74.6/82.0 83.3/89.5/91.1 86.8/74.9/80.6 72.2/54.6/59.9 65.8/30.9/40.2

消融实验

在 Qwen2.5-14B-Instruct 上进行消融(100 步训练),评估不同优化策略和奖励设计。消融维度覆盖了三个关键设计选择:是否同时优化单测生成器、用 SFT 还是 RL、以及奖励函数的设计。

配置 UT准确率 Code准确率 BoN准确率 说明
CURE (完整) 73.3 47.5 60.2 完整框架,理论推导奖励
仅优化 Coder 27.8 43.2 54.8 不优化单测生成,UT 完全未改善
SFT 替代 RL 65.1 45.8 57.3 有监督微调而非强化学习
简单奖励(\(p_u\) 估计) 68.5 46.1 56.9 只用是否通过正确代码作为奖励

使用简单奖励时 \(p_{01}\)\(p_{00}\) 分别达到 42.2% 和 14.7%(远高于 CURE 的 36.5% 和 9.1%),说明简单奖励确实导致了对错误率的失控。

关键发现

  • 单测准确率的巨幅提升是 CURE 最突出的成就:ReasonFlux-14B 在 LiveBench 上将单测准确率从 27.8% 提升至 73.3%(+45.5%),在 CodeForces 上从 20.7% 提升至 82.3%(+61.6%)。这种数量级的提升远超代码生成本身的提升幅度,说明单测生成是一个巨大的未开发空间。
  • BoN 性能稳健提升说明单测质量的实际价值:平均 BoN 准确率提升 9.0%,这比仅提升 one-shot 代码准确率(5.3%)价值更大,因为 BoN 是实际部署中最常用的策略。
  • 仅优化 Coder 不改善单测质量:消融证明共同进化的必要性——单独训练 coder 的 UT 准确率不变,说明代码能力和测试能力虽然相关但不自动互迁移。
  • SFT 不如 RL:SFT 只利用正样本而忽略负样本中的信息,RL 通过对比正负样本的奖励差异学到了更强的区分能力。
  • 可作为无标签 RL 的奖励模型:用 ReasonFlux-4B 生成的单测代替 ground-truth 单测来做 RL 训练 Qwen2.5-14B,性能提升与使用真实标签的 RL 相当,这意味着可以实现真正的无标注自我改进循环。
  • Long-CoT 模型的效率提升显著:响应长度缩短至 64.8% 但准确率反而提高,说明 long-CoT 模型在单测生成任务上存在大量冗余推理,长度引导奖励成功剪除了这些冗余。
  • 跨模型增强能力:将 ReasonFlux-4B 作为单测生成器搭配 GPT-4o-mini 做 coder,BoN 平均提升 5.5%;搭配 GPT-4.1-mini 提升 1.8%,同时大幅降低 API 成本(因为避免了用昂贵的大模型生成单测)。具体来说,用 GPT-4o-mini + ReasonFlux-4B 的组合性能比 GPT-4o 单独 one-shot 还高 7.0%,但成本更低。
  • 在多种 Agentic Coding Pipeline 上均有效:不仅在简单的 BoN 上有效,在 MPSC(多视角自一致性评估)、AlphaCodium(迭代测试+修正)和 S*(调试+成对判别)三种更复杂的 pipeline 上,ReasonFlux-14B 平均比 base model 提升 8.1%。在 Agentic Unit Test Generation(基于执行结果迭代修正单测)任务上更是提升 25.1%,说明训练带来的改善在迭代场景中会被放大。

亮点与洞察

  • 理论驱动的奖励设计极为精巧:不像大多数 RL for code 工作只使用启发式奖励,CURE 从奖励精度的概率分析出发推导出了 \(\mu\) 这个优化目标,再从 \(\mu\) 推导出具体的个体奖励公式。这整条理论链条既保证了奖励的数学合理性,又提供了对收敛速度的定量保证(Hoeffding 界),这在 RL+代码领域非常少见。
  • "错误代码是宝贝"的洞察非常深刻:传统思路认为训练单测需要正确代码做监督,CURE 反过来利用错误代码——错误代码暴露了典型的失败模式,让单测学会识别这些模式。这种"从错误中学习"的思路可以迁移到很多领域:比如用错误翻译训练翻译质量评估器,用失败的规划训练规划验证器。
  • 长度引导奖励变换是一个通用的效率优化 trick:关键思想是对正奖励样本引入"越短越好"的偏好,对负奖励样本统一给最大惩罚。这个设计保持了奖励的正负分离(不会翻转好坏样本),同时引入了长度效率的优化方向。这个 trick 可以直接应用于任何希望缩短 long-CoT 模型生成长度的场景。
  • 共享模型做两个角色的简洁性:不需要训练两个独立模型,一个 LLM 同时学会写代码和写测试,在共享表示上实现知识互通。这体现了"编程理解是一种统一的能力"这一假设的合理性。理解一道题的逻辑结构既帮助你写出正确的解,也帮助你想到有效的测试用例——一个善于思考边界情况的模型同时在两个方向上都更强。
  • 实验设计的工程哲学值得学习:作者通过精心的 prompt 设计将 base model 的格式错误率降到极低(代码 0.08%,单测 9%),然后在此基础上展示 CURE 的提升显著超过格式错误率的可能影响。这种"先排除混杂因素再展示核心效果"的实验哲学在很多工作中被忽视,导致读者无法区分方法真正的贡献和工程细节的贡献。

局限与展望

  • 对 ground-truth 单测的隐性依赖:虽然声称"无需 ground-truth 代码",但框架仍然需要每个训练任务配备少量 ground-truth 单元测试来判断代码正误(通过 \(\mathcal{I}_{s_j}\))。如果这些 ground-truth 单测本身有误或覆盖不全,整个奖励链条都会受到影响。完全无标注的版本是否可行(例如用代码间的一致性来代替 ground-truth 判断)值得探索。
  • 初始模型能力门槛:共同进化的成功依赖于初始模型已经具备一定的代码生成和测试生成能力。如果 base model 太弱(比如 1B 参数),产出的代码和测试都质量极低,正反馈循环可能无法启动。论文没有探究能力下界在哪里。
  • 推理成本仍然较高:每个任务需要生成 16 个代码 + 16 个单测,然后交叉执行 256 次,这在部署时对计算资源的要求不低。虽然比用大模型做奖励模型便宜,但对于大规模应用仍是瓶颈。
  • 评估任务类型有限:所有实验都在竞赛编程/算法题风格的 benchmark 上进行(stdio 格式),对于真实世界的软件开发场景(如 Web 开发、数据处理、系统编程),泛化效果未知。这些场景中"单元测试"的形态和正确性标准与算法题差距很大。实际上,论文为了统一格式甚至将 MBPP 和部分 LiveBench 的函数式 I/O 转换为了 stdio 格式,这进一步说明当前框架对任务形式有较强的假设。
  • 训练数据仅 4.5K 问题:虽然数据效率高是优点,但论文没有探讨增加训练数据量是否能带来进一步提升。考虑到 RL 训练不需要标注解,扩大问题集应该相对容易。另外,也没有分析训练问题的难度分布对最终效果的影响——只使用了难度 ≤2 的问题,是否加入更难的问题会有帮助?
  • 消融不够完整:没有消融 KL 系数 \(\beta\) 的影响,没有分析不同迭代步数(350 步 vs 更多/更少)的效果曲线,没有探讨 \(n\)(代码候选数)和 \(m\)(单测候选数)的最优配比。此外,7B 和 14B 用 Instruct 模型做 base 而非 Base 模型,没有解释为什么——Instruct 模型已经经过 SFT 和 RLHF,在它上面再做 RL 可能会有不同的动态。

相关工作与启发

  • vs O1-Coder: O1-Coder 用 ground-truth 代码生成单测然后做 SFT,严重依赖标注数据。CURE 完全不需要 ground-truth 代码,通过自博弈 RL 实现无监督训练,flexibility 和 scalability 远超 O1-Coder。CURE 的劣势在于需要少量 ground-truth 单测,而 O1-Coder 只需要代码。
  • vs UTGEN: UTGEN 混合使用 ground-truth 代码产出的正确单测和扰动代码产出的错误单测,本质上是一种数据增强。CURE 的奖励设计从理论上更加严谨,不依赖于人为的数据构造,且奖励信号随训练动态更新。
  • vs CodeT / AlphaCodium / S: 这些方法主要在推理阶段利用生成的单测做筛选或调试,但不训练单测生成器本身。CURE 在训练阶段优化单测生成器,产出的更高质量单测可以在推理阶段直接插入这些 pipeline 并带来额外提升(实验中 S pipeline 提升 8.1%)。
  • vs DeepSeekMath/GRPO: GRPO 是 CURE 使用的底层 RL 算法,而 CURE 的创新在于奖励设计和双角色共演化框架。两者是正交的:任何更好的 RL 算法都可以直接替换 CURE 中的 GRPO。事实上论文也明确表示不打算和 RL 算法本身竞争,而是提供一个可以接入任何 RL 算法的框架。
  • vs 传统单测生成方法(EvoSuite/Randoop): 传统方法基于软件分析(搜索、符号执行、随机测试),虽然在结构化测试方面有优势,但无法处理自然语言描述的任务。LLM 的单测生成弥补了这一gap,而 CURE 进一步提供了训练这类 LLM 生成器的无监督方法。
  • 启发:这种"两个模块互为师生"的自博弈范式,可以迁移到其他需要生成-验证配对的任务:比如数学定理自动证明(证明生成器 vs 证明验证器)、代码翻译(翻译器 vs 等价性检验器)、安全对齐(生成器 vs 安全分类器)。特别值得注意的是"用错误样本训练验证器"这一思路的普适性——任何生成任务中生成出的失败样本都可能成为训练验证/过滤模块的宝贵数据源,这与对比学习中"好的负样本比正样本更重要"的哲学一脉相承。

评分

  • 新颖性: ⭐⭐⭐⭐ 自博弈共演化的思路在代码领域还是较新的,理论推导的奖励设计有独特贡献,但自博弈和 RL for code 各自都不算全新
  • 实验充分度: ⭐⭐⭐⭐ 五个 benchmark、三种模型规模、多种下游应用(BoN/MPSC/AlphaCodium/S*)、跨模型验证(GPT 系列)、reward model 实验,但消融可以更细致
  • 写作质量: ⭐⭐⭐⭐⭐ 论文结构清晰,理论推导严谨且直觉解释到位,实验组织合理,从动机到方法到应用形成完整叙事
  • 价值: ⭐⭐⭐⭐ 框架实用性强(已开源模型权重),无标注训练的 scalability 有长期价值,但实际影响力取决于在真实软件工程场景的泛化性

相关论文