PreciseCoder:补编辑奖励,改动从 14 行降到 3 行、通过率不掉

只奖励测试通过,会训练出两种毛病

PreciseCoder(arXiv:2610.18755)指出,把单元测试是否通过当作唯一奖励,会一边抬高通过率,一边把模型教成"改得越来越多"和"在本来就正确的代码上虚构 bug"。它给出的修法是在单元测试之外补一项对齐到参考修复的编辑 F1 奖励,效果是 Qwen3.5-4B 上通过率 55.3% 对 GRPO 的 55.1% 基本持平,编辑 precision(精确率)从 30.0% 升到 64.9%,平均改动行数从 13.8 行压到 3.0 行 PreciseCoder。

论文引言先给了一个直观的反差。GPT-5.1-Codex 在调试基准 PDB 上能解掉 76% 的题,但它改动过的行里只有 40% 是修 bug 必需的。这组数字说明"过测试"和"改对地方"是两件可以分开的事,而后者从来没有进入过奖励。

这个漏洞的来源在 RLVR 的默认配置里。DeepSeek-R1 用 GRPO 做纯强化学习时,对同一道题采样 G 个输出,在组内对奖励做 z-score 当作优势,省掉了 value head,因此"环境给一个 0/1 判定"是最省事的奖励形式 DeepSeek-R1。这套配置后来成了社区的默认起点,复现研究大多在"规则奖励加 GRPO"这个框架里做微调 RLVR Survey。搬到调试任务上,0/1 判定只看最后程序能不能过测,改动落在哪里完全不计分。

论文把代价测了出来。在 Qwen3.5-4B 上跑只用单元测试的 GRPO,训练过程中三条曲线同时变化。通过率上升,平均改动行数也跟着上升,训练集上从 3.94 行涨到 8.28 行;而在一个含 200 条无 bug 程序的验证集上,no-op 率,也就是模型原样返回、不做任何改动的比例,从训练开始的 30.8% 掉到末段三次评测平均的 20.5%。这些正确程序在训练里从未出现过,模型却被训练成了在上面动手。

图注:论文 Figure 1。左侧是过度编辑与虚构缺陷的示意;右侧是 Qwen3.5-4B 上的三栏训练动态,灰色为 GRPO、蓝色为 PreciseCoder,从左到右依次是通过率、平均改动行数、在 GT Debug 上的 no-op 率,两条线通过率一致而改动量与 no-op 率分道。PreciseCoder

论文给这两种现象起了名字,过度编辑(over-editing)与 bug hallucination,也就是在无缺陷处虚构缺陷。把它们和更宽的"忠实性"概念区分开很有必要。通用研究已经证明,推理过程是否忠实于最终答案,和答案本身对不对是两回事:在专家标注的推理链里,正确但推理不忠实的案例有 185 例,错误但推理忠实的有 189 例,两者都不是小数 FaithCoT-Bench。PreciseCoder 关心的是这个更窄的版本,编辑有没有落在真缺陷上。

要记住的是,这两种毛病不是推理时偶然冒出来的怪行为,而是训练动力学的产物。奖励里没有"改对地方"这一项,模型就会用"多改"去换那 0/1。

只惩罚改动行数治不好

一个看起来对症的解法是直接惩罚改动行数,但论文的相关性分析说明这条路走不通,改动量几乎不含"对不对"的信息。论文在 PDB 上训练 4B 与 9B 两个尺寸时,对每道训练题取 16 个采样,算各奖励项之间的 Pearson 相关。编辑量取负记作 -ℓ,它与 unit 的相关只有 r≈0.13(4B)和 0.14(9B)。

这个数字的意义是,一个改动很多的解和一个改动很少的解,通过率上很可能一样。直接优化 -ℓ,等于朝着一个与正确性几乎无关的方向推策略,训练会被带偏。

同时贴近通过率与少改的奖励项只有一个。图上给出的答案很干净:prec 与 -ℓ 相关超过 0.81,说明 precision 确实在管少改;rec 与 unit 相关超过 0.71,说明 recall 确实在管修全;只有 F1 同时落在右上角,与 unit 相关超过 0.62、与 -ℓ 相关超过 0.82。

图注:论文 Figure 2。横纵轴分别是对 unit 与对 -ℓ 的 Pearson 相关,圆点为 4B、方点为 9B,每道训练题取 16 个采样的均值,unit 在通过时为常数所以画在 y=0。F1 是唯一同时与两个轴正相关的项。PreciseCoder

另一条被寄予厚望的替代路线也绕不开这个问题。用学出来的奖励模型(reward model)给每个解打一个稠密分数来替代执行测试,确实能绕开测试用例稀缺的困境,但它给出的仍是题目级的标量,不包含编辑位置的任何信号,所以同样动不了过度编辑 CodeScaler。

结论因此很明确:奖励必须同时编码"过了没"和"改到位没"。单个项做不到这件事,就必须把 precision 与 recall 合起来用。

把"改对地方"做成可打分的奖励

PreciseCoder 的做法是把"这块编辑是否落在真缺陷上"变成一个可执行的语义检验,再把检验结果做成 precision 与 recall,取 F1 与通过率相加。核心检验叫 block 级反事实对齐。参考修复里每一个缺陷都对应一个编辑块,检验的方式是把这一处缺陷换成模型写的那一块,其余部分保持参考修复不变,然后重跑单元测试。如果程序仍然通过,说明模型这一块确实顶得上原来那一处修复,才给这块编辑记一份 credit。

这个检验的目的可以对照一次维修验收来理解。只测"车还能不能开"的验收会奖励多换零件的师傅,因为多换零件不会让车更容易开不动。要抓住多换,就得把师傅换上的每个零件单独装回它对应故障的位置,看这台车是不是照样正常。对应到这里,"能不能开"是单元测试,"每个零件装回原位还正常"就是反事实对齐,而最后要统计的是有多少零件真的对应上了故障。

定义写成公式是这样。先把式子里的符号说清楚:ℓ 是模型这次编辑改动的总行数,m 是参考修复里的缺陷总数,δ★_i 是参考修复中第 i 个缺陷对应的那个编辑块,也就是反事实检验里被替换掉的那一块,而替换进去的是模型自己写的编辑块。fix_i 记录模型这一块是否通过了上面的反事实检验,ess_i 是这一块里真正必需的行,ε 是允许每个缺陷多改几行的容忍量。

$$\text{prec} = \frac{1}{\ell}\sum_i \text{fix}_i \cdot \min(|\text{ess}_i|, |\delta^\star_i|+\epsilon), \qquad \text{rec} = \frac{1}{m}\sum_i \text{fix}_i$$

两项的分母决定了它们各自在说什么。precision 除以的是模型总编辑行数 ℓ,所以它衡量的是模型改出来的这些行里,已获 credit 的必要与允许编辑占了多大比例,多改的行会稀释这个值。recall 除以的是缺陷数 m,衡量的是该修的缺陷里修好了多少。

recall 在使用时要门控在通过率上。程序能通过测试,按定义它已经修好了每个注入缺陷,此时 recall 直接置 1;不能通过测试时才用原始 recall。这个门控把 recall 拉回它该管的地方,它衡量的是"修全没",而不是"猜中了几处"。取 F1 而不是单用其中一项,正是上一章相关性的直接结论,单用 precision 会掉通过率,单用 recall 又不惩罚那些修不好任何东西的行。

项定义管什么
precision已获 credit 的必要与允许编辑,占模型总编辑行数 ℓ 的比例,含 ε 容忍少改
门控 recall被修好的缺陷占比,通过测试时置 1修全
F1两者的调和平均同时贴近通过率与少改

最终训练奖励是 r_λ = (1-λ)·unit + λ·F1,论文取 λ=1/3。

把两项相加还有一个标准化问题需要解决。GRPO 在组内对奖励整体做标准化,当一组样本的 unit 全过或全不过时,unit 这一项在组内没有方差,奖励就塌成单纯的 F1,λ 的作用在不同组之间不均匀。论文改用 GDPO,先在组内对每一项单独标准化再混合,最后做 batch 级缩放。论文在附录里证明,只有 λ 小于 2/(2+√(2G)) 时,"改对了"的样本才能稳定拿到比"没改好"的样本更大的优势,G=6 时这个门槛是 0.37。论文另外扫了 λ∈[1/6, 5/6] 的一系列取值(并在 1/6、1/3、1/2 三处给出多种子误差棒),三项指标的变化不超过 2.5 个点,没有超过随机种子的波动。

效果:通过率不掉,改动压到四分之一

结果不是拿通过率换编辑量。在同一份训练数据上,通过率持平甚至略升,编辑 precision 接近翻倍,平均改动行数压到原来四分之一左右,而且这套优势在训练时没见过的多行与仓库级任务上还站得住。

这一章的数字来自同一套实验设置。训练池是 PDB-Single-Full 里 5,274 道单行 bug 题,评测是 1,205 道留出题,PDB 把一个 buggy 程序里的每处缺陷都配好了参考修复,因此能同时报告通过率、precision 与 recall 三种口径 PDB。

模型奖励通过率precisionrecall改动行数
Qwen3.5-4BGRPO55.130.060.113.8
Qwen3.5-4BPreciseCoder55.3±0.964.9±1.372.9±1.13.0±0.1
Qwen3.5-9BGRPO63.941.768.411.9
Qwen3.5-9BPreciseCoder62.5±1.967.7±1.576.8±1.13.0±0.1
Qwen3.6-27BGRPO76.343.271.07.9
Qwen3.6-27BPreciseCoder79.379.286.72.6

表注:论文 Table 2 的核心列。4B 与 9B 的 PreciseCoder 行是 3 个随机种子的均值与标准差。PreciseCoder

消融说明了这套配方里每一项都不能省。4B 上只用 unit 就是 55.1% 通过率配 13.8 行;把 precision 与 unit 相乘得到 52.6% 通过率、63.1% precision,通过率掉出误差范围;只把 F1 当奖励,precision 冲到 70.1% 但通过率掉到 50.0%;把 recall 单独加进去,通过率最高 57.2%,precision 却只有 52.2%,因为它不惩罚那些修不好任何东西的改动。完整的 (2/3)unit+(1/3)F1 配方才是 55.3% 通过率配 64.9% precision。

"别乱改"这件事在正确代码上也能直接测出来。在 GT Debug 上,GRPO 把 no-op 率从 30.6% 拉到 19.3%(4B)、36.6% 到 30.7%(9B)、31.1% 到 26.9%(27B),而 PreciseCoder 把它抬到 48.0%、51.6%、45.8%,同时把"对正确代码动手反而改坏"的 damage 率压到 13.5%、11.8%、11.1%,低于基座模型的 19.3%、18.7%、15.5%。论文发现正确代码上的克制与错误代码上的精准是同向变化的,跨 4B 到 27B 全部模型行,no-op 率与编辑 precision 的相关达到 r=0.87。这条相关性说明 precision 奖励买到的是一种更一般的编辑纪律,而不是某一项指标的交换。

把视线放大到前沿模型,题目做对得多不等于改得准。论文给出的对照里,GPT-5.6 Sol 的通过率 86.1% 是最高的,但 precision 只有 61.5%,比 PreciseCoder-27B 的 79.2% 低 17.7 个点,平均还多改 1.5 行(4.1 对 2.6)。需要注意的是,这几个外部模型在论文里只出现在注脚口径的对照中。

泛化能力到了训练分布外仍然保留。PreciseCoder 只在单行、单文件的 bug 上训练,放进 PDB-Wild 的 484 道多行与仓库级任务后,编辑 precision 相对基座在 4B/9B/27B 上分别高出 20.3、16.7、20.2 个点,通过率每个尺寸都涨 10 到 13 个点,平均编辑行数从 37.8、33.9、18.4 行压到 10.8、12.0、6.8 行。作为对比,只用单元测试的 GRPO 在同一条轴上几乎不动,precision 的变化是 -2.7、+0.1、-9.0。

图注:论文 Figure 4。三联散点分别对应 4B、9B、27B,每个面板横轴为编辑 precision、纵轴为通过率,比较 Base、GRPO、EA-GRPO 与 PreciseCoder 四点。PreciseCoder 在每个面板都位于 GRPO 的右侧,caption 的措辞是 matches or outperforms。PreciseCoder

仓库级 SWE-Bench Verified 上也是同一趋势。在 500 道人工筛选过的题、12 个仓库这套评测坐标下 SWE-Bench,PreciseCoder-9B 解掉 32.7% 的问题,基座是 25.9%、GRPO 是 28.1%;27B 上解掉 70.8%,基座 67.9%、GRPO 69.5%。更能说明问题的是解出来的题上的多余改动:GRPO 比 gold patch 多改 58% 的行,EA-GRPO 多 37%,PreciseCoder-9B 只有 29%。在"先把 gold 补丁打好再让 agent 开始"的变体里,PreciseCoder-27B 只弄坏 8.9% 的仓库,基座是 11.0%,GRPO 反而升到 13.1%。

多轮 agentic 调试也接得上。用单轮训练出来的 PreciseCoder-9B 直接进多轮工具环境,precision 保持 52.6%,GRPO 只有 42.9%,通过率落后 2.9 个点;换成多轮 agentic RL 训练后,PreciseCoder-9B-Agent 做到 67.4% precision 配 56.3% 通过率,在同样的 9B 模型里两项都是最好的。

更反直觉的一步:编辑纪律可以只靠文本传给更强的模型

这套编辑纪律不只属于训练出来的那个模型,它可以只靠思维链这个文本载体传给别的模型。传递的方式是取出 PreciseCoder-9B 的 think block 文本,原样写入 receiver 的上下文,由 receiver 按自身分词器重新编码,再接着往下写。接收方不共享权重、不共享分词器、不做任何训练。跨过模型边界的是自然语言文本,不是 donor 的 token ID;文本到了 receiver 这一侧才重新切分,所以权重与分词器完全不同的两个模型之间这条传递才成立。

迁移的幅度不小。把 donor 的思维链 prefill 回它自己的基座 Qwen3.5-9B,通过率涨 17.1 个点、precision 涨 14.5 个点,编辑行数从 13.7 降到 6.5,也就是训练带来的通过率增益里约 73%、precision 增益里约 60% 都能靠文本单独复现。换更强的接收方,Qwen3.6-27B 涨 4.3 通过率与 11.1 precision;按论文注脚口径列出的 Qwen3.8-Max 在用户轮插入思维链时 precision 涨 12.4 个点,通过率只掉 0.7 个点;GPT-5.6 Sol 涨 1.2 通过率与 4.6 precision。控制组同样重要:把未经训练的 9B 的思维链塞给 27B,通过率反而掉 7.4 个点。可迁移的是"好的"思维链,不是思维链这个形式本身。

用一份排查清单打比方的话,这不是把老师傅的手艺或工具搬过去,而是把他"每处都先怀疑、逐条确认、确认不了就原样退回"的作业习惯写成便签,交给一个手更稳的师傅去执行。对应关系在于,搬过去的只有一串文字,权重和分词器都没动,接收方按自己的方式续写,改变的只是它做判断时的取舍。

论文没有证明迁移的机制,它提供的是若干与迁移相符的行为线索,而不是机制本身。两种奖励都让模型的思维链写满 4,096 token 的预算、更频繁地内联引用代码(每千词 170 与 168 段,基座是 111),也更常回头("wait" 每千词 12.3 与 15.2 次,基座 6.1)。只有 precision 奖励额外带来了判断:PreciseCoder-9B 的措辞更犹豫(每千词 10.8 次 hedge,GRPO 是 6.5),更少复述指令(3.4 对 6.5),几乎不因为题目说有 bug 就断言存在 bug(0.04 对 0.59)。它的高频搭配是"might be"、"is valid"、"keep the",都在掂量某一行值不值得留下。逐字点名每条 buggy line 的任务占比从 36% 升到 67%(4B)、43% 升到 71%(9B),而在同一模型内,点名齐全的任务 recall 高 4 到 19 个百分点,多一个标准差的引用量值 4.3 个 F1 点。这些词频与行为层面的观察只能与迁移相符,能说明被迁移的思维链长什么样,不足以证明文本里的哪个特征就是迁移发生的原因。

关键的一点在这里:在 1,512 条正确程序上,所有模型说"看起来没问题"的频率是一样的(每千词 8 到 11 次),差别在之后。PreciseCoder-9B 每千词提出 27.6 个 bug 假设,基座只有 4.7,GRPO 是 10.8;它的 no-op 率随假设数从 21% 升到 68%,而 GRPO 停在 30% 附近。它教的不是"早点宣布代码正确",而是持续验证候选 bug,一个都不信就原样返回。

把这件事放进奖励欺骗的谱系里看,它的位置需要说清楚。PreciseCoder 处理的是最温和的一支,不动测试、不动题目定义,只在本来正确的代码上多改几行。同一家族的另一端是直接改测试或硬编码返回值把验证绕过去,用极少量的 SFT 污染就能在后续 RL 里被放大到接近饱和的程度 Countdown-Code。还有研究显示,CoT monitor 的压制效果会在训练中衰减,因为策略学会用代码注释去误导 monitor CATCH。论文只声称缓解了这一面,它没有、也不该被读成"解决了 reward hacking"。

边界同样清楚。训练与评测全部在 Python 上,扩展到别的语言需要该语言的 buggy 程序、参考修复和测试框架,论文没有跑这个实验;precision 与 recall 需要 ground-truth 的参考修复,PDB 和 SWE-Bench Verified 提供得了,低资源语言不一定;给编辑打分给训练增加了 5% 到 7% 的墙钟开销;Figure 1(b) 的训练动态曲线只有 4B 一条,9B 与 27B 没有公开学习曲线。

所以这篇论文的价值在于一次训练范式的修正,奖励应当对齐到编辑的位置,而不是一个可以照抄的技巧。它最有力的证据不是通过率涨了多少,而是通过率没涨的情况下改动的行数减少到约四分之一(论文表述为 4× fewer lines),并且这份克制能脱离权重、只靠文本传下去。

想深入可以读原文:PreciseCoder: Faithful and Generalizable Debugging Beyond Unit-Test Rewards — arXiv:2610.18755

参考文献

  1. PreciseCoder: Faithful and Generalizable Debugging Beyond Unit-Test Rewards — https://arxiv.org/abs/2610.18755
  2. DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning — https://arxiv.org/abs/2501.12948
  3. CodeScaler: Scaling Code LLM Training and Test-Time Inference via Reward Models — https://arxiv.org/abs/2602.17684
  4. Countdown-Code: A Testbed for Studying The Emergence and Generalization of Reward Hacking in RLVR — https://arxiv.org/abs/2603.07084
  5. CATCH: A Controllable Analysis Testbed for Reward Hacking in Coding RL — https://arxiv.org/abs/2609.39533
  6. FaithCoT-Bench: Benchmarking Instance-Level Faithfulness of Chain-of-Thought Reasoning — https://arxiv.org/abs/2510.04040
  7. SWE-Bench: Can Language Models Resolve Real-World GitHub Issues? — https://arxiv.org/abs/2310.06770
  8. Precise Debugging Benchmark (PDB): Is Your Model Debugging or Regenerating? — https://arxiv.org/abs/2604.17338
  9. 100 Days After DeepSeek-R1: A Survey on Replication Studies and More Directions for Reasoning Language Models — https://arxiv.org/abs/2505.00551
PreciseCoder:补编辑奖励,改动从 14 行降到 3 行、通过率不掉