QiMeng-PRepair:通过编辑感知奖励优化实现精确代码修复 QiMeng-PRepair: Precise Code Repair via Edit-Aware Reward Optimization
针对LLM代码修复中的「过度编辑」问题,提出编辑感知奖励与自造数据框架,显著提升修复精度与推理速度。
前置知识
Group Relative Policy Optimization (GRPO)
GRPO 是一种在 PPO 框架上演进而来的 on-policy 强化学习算法,核心思想是为同一个 prompt 采样一组候选输出(rollout group),用组内奖励的均值和标准差来归一化每个样本的优势(advantage),从而避免使用独立的价值模型(value model),节省推理成本。
PRepair 的核心 EA-GRPO 是对 GRPO 的扩展,理解 GRPO 的组内相对优势机制是看懂「动态编辑惩罚为何只在组级正确率达到阈值时才触发」的前提。
Levenshtein / 编辑距离 (Edit Distance)
编辑距离衡量将一个字符串(或代码序列)转换为另一个所需的最小插入、删除、替换次数。本文采用行级 Levenshtein 距离,并将其按行数归一化为 Edit Cost DEC(X,Y) = D(X,Y)/|X|,反映代码被修改的比例。
Edit Cost 是 fixp@k 指标和 EA-GRPO 编辑惩罚项的共同基础,理解它如何被归一化和标准化是掌握奖励塑形的关键。
Speculative Decoding / 推测式解码
通过先用一个轻量级 draft 模型(或 n-gram 查找)生成多个候选 token,再由目标模型并行验证,加速推理。在代码编辑场景中,由于 prompt 与输出共享大量相同代码片段(n-gram 命中率高),特别适合使用 Prompt Lookup Decoding 这种无 draft 模型的变体。
EA-GRPO 的最终落地价值依赖于推测式编辑:编辑成本越低,prompt 中的 n-gram 越容易被复用为 draft token,吞吐率显著上升。
pass@k 与代码修复评估
pass@k 衡量模型生成 k 个候选中至少有一个通过全部单元测试的概率,使用无偏估计 1 - C(n-c, k)/C(n, k)。它只关注正确性,无法反映模型是否进行了最小化修改。
理解 pass@k 的局限性——只看对错、不看改多少——是看懂作者为什么要新提出 fixp@k 指标的核心动机。
研究动机
当前基于 LLM 的代码修复方法普遍存在「过度编辑」(over-editing)现象:尽管模型最终能让代码通过测试,但它倾向于对原程序进行大刀阔斧的重写,而不是去理解并最小化地修复原有的 Bug。这种现象在 GRPO 等基于正确性奖励的强化学习训练中尤为严重——作者在 Figure 2 中量化显示,随着训练步数增加,Qwen2.5-Coder 在 Python 与 Verilog 修复任务上的 edit cost 从约 0.35 一路飙升至 0.6 以上,意味着超过一半的代码行被改动。过度编辑带来两个具体危害:一是破坏了对错误行(buggy line)的隐式定位能力,模型实际上是在用暴力搜索「撞」出一个能通过的版本,跨领域泛化能力骤降(例如 7B 模型在 Python 上训练后迁移到 Verilog,GRPO 版本的 fix1@1 从 36.38% 暴跌至 10.88%);二是工程上严重破坏可维护性,开发者 review 一个几乎被推倒重写的 patch 比 review 一行 fix 要费时得多,挫伤了 Copilot、Cursor 等代码助手在 IDE 中的实用性。
本文的目标是本文的目标是让 LLM 学会「在保留下来的正确代码骨架上做最小化但充分的修复」,具体定义为:最大化原始正确逻辑的复用率,最小化对非 Bug 区域的扰动,同时不牺牲通过测试的正确率。围绕这一目标,论文设计了三件事:(1) 提出新的修复精度指标 fixp@k,把「通过测试」与「编辑成本」放在同一框架下联合度量;(2) 构造一个无需人工标注的训练数据生成管线(Self-Breaking),解决「含大量正确逻辑 + 局部 Bug」的稀缺训练样本问题;(3) 设计一种编辑感知的强化学习算法 EA-GRPO,将 edit penalty 嵌入奖励函数,让模型显式学到「少改」的行为。
与已有工作不同的是,已有的 LLM 代码修复工作(Muennighoff 2023、Qwen2.5-Coder、Hui 2024、Yang 2025、Fu 2025 等)几乎都把修复视作纯正确性优化任务,使用 SFT 或仅基于 pass/fail 的 RL 奖励,缺乏对「修改幅度」的显式监督;同时现有 buggy 代码数据集要么来自 LLM 自己生成的错误代码(缺乏真实错误模式,如 RTLFixer)、要么用 LLM 注入 bug 但样本规模太小(如 HLSdebugger 不开源、DebugBench 仅 40 个程序),要么由人工构造但过于狭窄(QuixBugs 仅含非常简单的算法题)。本文的独特切入角度在于:(1) 首次系统量化了 GRPO 训练中的过度编辑现象并证明它会同时损害精度与效率;(2) 把「编辑成本」从评估指标升级为训练目标的一部分,提出 EA-GRPO 这种动态条件触发的奖励塑形;(3) 通过 Self-Breaking + min-max sampling 的自造数据管线,构造了规模过万、bug 模式可控且多样化的高质量训练集。
核心方法
PRepair 的整体思路是把「精确修复」转化为两个独立但可串联的训练阶段。首先是 Self-Breaking:让 LLM 自己担任「bug 注入器」,从一份 golden reference code 出发,通过受控的 prompt 生成多个候选 buggy 版本,再用 min-max 采样挑出彼此编辑距离最大的 k 个,使得训练集既规模大又具备 bug 模式多样性。然后是 Self-Repairing:在自造数据上跑 EA-GRPO 强化学习,每个 prompt 采样 N 个候选修复,用一个同时考虑「通过测试」与「编辑成本」的双段奖励函数驱动模型向「少改且改对」的方向更新。这里的关键技术挑战是何时施加编辑惩罚——太早会打压正确率的提升,太晚则失去意义——作者通过引入 Group Accuracy Threshold α 实现条件触发:当某 prompt 的一组候选已经有足够高的通过率(即「够对」)之后,才在该组内按 edit cost 排序施加连续型惩罚,从而让模型在确保正确的前提下尽量少改。
EA-GRPO 与传统 GRPO 的本质区别在于把奖励函数从「二值是否通过」扩展为「通过 + 编辑成本的连续塑形」。具体而言,奖励 RGi 在样本正确时取 1 - T(G)·β·PGi,其中 T(G) 是一个指示函数:只有当组级正确率 AccG 达到阈值 α 时才等于 1(即施加惩罚),否则等于 0(即只奖励正确性)。PGi 是一个组内归一化的 sigmoid,把每个正确样本的 DEC 减去组内均值再除以标准差后压到 (0,1) 区间。这一设计的核心洞察是:编辑惩罚应当作为「正确率已饱和时的精细化梯度」,而不是从头到尾压制模型的探索欲。本质上,这是把代码修复的目标函数从单目标(正确性)扩展为多目标(正确性 + 编辑成本)的帕累托优化,并通过 α 实现自动的阶段切换。
方法步骤详情
PRepair 的训练流程可拆解为以下步骤。第一步(Self-Breaking):给定一份由 LeetCodeDataset(Python,2,869 题)或 QiMeng-CodeV-R1(Verilog,3,033 题)提供的 problem-description + golden-code 对,用精心设计的 prompt(见 Appendix F)让 LLM 对同一份 golden code 注入 bug,采样 |X| = 32 个候选 buggy 版本,过滤掉仍能通过测试的「假 buggy」样本;第二步(min-max 采样):在候选集中求解子集 X' ⊂ X,|X'| = k,最小化子集内任意两个 buggy 样本间的最大相似度(即最大化 1 - DEC),从而挑出彼此差异最大的 k = 4 个,最终生成 10,242 条 Python 与 11,200 条 Verilog 训练样本。第三步(Self-Repairing 初始化):把问题描述与每个 buggy code 包装为 prompt,要求模型生成修复后代码。第四步(rollout):对每个 prompt,用 vLLM 采样 N = 8 个候选修复 oi,构成组 Gt;运行单元测试得到每个候选的 pass/fail 标记。第五步(奖励计算):计算组平均正确率 AccG,若 AccG ≥ α(默认 0.8)则启用编辑惩罚——对每个通过测试的样本计算 PGi = σ((DEC(Xt, oi) - mean(DEC(X, Gtc))) / std(DEC(X, Gtc))),最终奖励 RGi = 1 - T(Gt)·β·PGi(β = 0.05);不通过则奖励为 0。第六步(策略更新):在 VeRL 框架下用 PPO 风格的剪切目标 J(θ) = E[Σ min(ri,t·AGi, clip(ri,t, 1-ϵ, 1+ϵ)·AGi) - γKL] 更新参数,学习率 1e-6、KL 系数 0.001、8 卡 A100(7B)/L40S(3B)。第七步(推理):训练后的模型既可单独使用,也可与 Prompt Lookup Decoding 结合,通过 n-gram 复用 prompt 中的代码行提速。
技术新颖性
技术新颖性体现在三个层面。指标层面,fixp@k 把 pass@k 与 edit cost 联合起来,公式为 1 - C(n, c)/C(n, k),其中 c 是同时满足「通过测试」和 DEC(X,Y')/DEC(X,Y) ≤ p 两个条件的样本数,p 控制允许的编辑容忍度(1/1.5/2),这是首个面向「精确修复」而非「正确性」的评估标准。算法层面,EA-GRPO 通过动态阈值 T(G) 把编辑惩罚嵌入 GRPO 的组相对优势框架,避免了「在训练早期就用惩罚干扰探索」的问题,同时 PGi 的 sigmoid + 组内标准化让不同难度样本可以共享同一惩罚强度。数据层面,min-max 采样策略把「如何让自造 bug 数据集多样化」从启发式工程变成了一个可优化的组合问题,理论保证是所选子集内的最大成对相似度最小,从而推动模型学习「多种 bug 模式下的最小修复」而非记忆某一类 bug 的解法。此外,作者还给出了 EA-GRPO 与 Speculative Editing 的理论联动:在 N-gram 推测解码下,吞吐率 T ∝ (1 - (1-DEC)^(K+1)) / DEC,即 DEC 越低吞吐率越高,意味着「少改」具有免费的工程红利。
实验结果
Table 1 的主结果包含 8 行模型 × 4 列指标族(pass@k / fixp@k,k = 1/5/10,p = 1/1.5/2),跨 Python 与 Verilog 两种语言。在 Python 上,Qwen2.5-Coder-7B + EA-GRPO 把 fix1@1 从 60.67% 提升至 81.62%,提升 20.95%;fix1.5@1 提升到 82.13%、fix2@1 提升到 89.54%,全部大幅优于 GRPO 版本的 47.44% / 48.20% / 60.88%,也超过 GPT-4 + Prompt 的 62.56% / 63.90% / 72.44% 与 Gemini 2.0-Flash + Prompt 的 63.78% / 64.70% / 71.71%。Qwen2.5-Coder-3B 上同样规律明显,fix1@1 从 33.72% 涨到 67.96%,接近翻倍。Verilog 上提升更夸张:Qwen2.5-Coder-3B 的 fix1@1 从 34.08% 提升到 37.40%,fix1.5@1 从 36.14% 提升到 40.80%;7B 模型 fix1@1 从 36.70% 直接跃升到 68.11%,相对涨幅达 31.41 个百分点,而 GRPO 训练后反而跌到 8.49%,过编辑现象极为严重。pass@k 方面,EA-GRPO 在多数情形下也能保持甚至提升 pass@1(例如 7B 在 Python 上从 86.28% 升到 91.19%,Verilog 上从 57.36% 升到 68.66%),证明「少改」并不以牺牲正确性为代价。Figure 4 展示了跨域泛化能力:7B 模型在 Python 上训练后到 Verilog 上评估,GRPO 的 pass@1 从 57.12% 跌到 48.81%(-8.31%),fix1@1 从 36.38% 暴跌到 10.88%(-26.50%);而 EA-GRPO 在两个指标上均维持稳定,说明模型真的学到了「局部定位」的能力而非死记 Python 模式。Figure 5 与 Table 7 报告了推测解码下的推理效率:Verilog 任务上 EA-GRPO 的吞吐率从 24.34 tokens/s 提升至 28.15 tokens/s(+15.6%),接受率从 0.498 升到 0.601;Python 上从 29.70 升到 31.51 tokens/s,接受率从 0.577 升到 0.677;相反 GRPO 由于加剧过度编辑,吞吐率下降到 15.87 tokens/s(-35%),验证了「低 edit cost → 高 n-gram 命中 → 高吞吐率」的理论。Figure 6 的注意力热力图进一步佐证:EA-GRPO 模型对 buggy 行(如 line 12、14)的注意力显著高于 baseline GRPO,而 GRPO 模型几乎均匀地把注意力铺满整个输入,缺少错误定位能力。
查看结构化数据
| 任务 | 指标 | 本文 | 基线 | 提升 |
|---|---|---|---|---|
| Python 代码修复(HumanEval-Fix) | fix1@1 | Qwen2.5-Coder-7B + EA-GRPO = 81.62% | Qwen2.5-Coder-7B + GRPO = 47.44%(绝对提升 +34.18,相对提升约 72%) | +34.18 个百分点(相对 +72%) |
| Verilog 代码修复(QiMeng-CodeV-R1 自建基准,352 题) | fix1@1 | Qwen2.5-Coder-7B + EA-GRPO = 68.11% | Qwen2.5-Coder-7B + GRPO = 8.49%(GRPO 因过度编辑几乎完全崩溃) | +59.62 个百分点(相对 8 倍以上) |
| Verilog 代码修复 pass@1 | pass@1 | Qwen2.5-Coder-7B + EA-GRPO = 68.66% | Qwen2.5-Coder-7B(base,未训练)= 57.36% | +11.30 个百分点 |
| 推测解码吞吐率(Verilog,Qwen2.5-Coder-7B) | tokens/s | EA-GRPO = 28.15 | Origin (无训练) = 24.34;GRPO = 15.87 | 相对 Origin 提升 +15.6%,相对 GRPO 提升 +77% |
| 跨域迁移:Python 训练 → Verilog 评估 | fix1@1 | EA-GRPO 训练后保持稳定(具体数值见图 4,未训练 36.38%) | GRPO = 10.88% | GRPO 跨域下降 -26.50%,EA-GRPO 无此崩塌 |
局限与改进
作者在 Limitations 与正文多个位置承认了三类局限。第一是超参数敏感:α 与 β 在不同数据集上最优值不同,Table 2 的消融显示 α = 0.8 / β = 0.05 是 Python 7B 上的最佳组合(pass@1 91.19%, fix1@1 81.62%),但 α = 0、β = 0.05 时 pass@1 升到 90.73 但 fix1@1 只 80.27;α = 1.1 即退化为纯正确性奖励时 fix1@1 暴跌到 47.44%;α = 0.5、β = 0.2 时 pass@1 跌至 85.06。作者明确表示将在低算力预算下探索自动调参。第二是粒度选择:fixp@k 基于行级编辑距离,虽然附录 C 通过 token-level 验证了 ranking 一致性,但 token 级别的编辑可能无法完全反映语义变化(如 a = b 改为 a = c 只动 1 个 token)。第三是任务范围:当前仅覆盖函数级修复,未涉及文件级或项目级 Bug,后者对精确修复的需求同样强烈但需要更大上下文窗口与跨文件定位能力。我自己的观察还包括:(1) Self-Breaking 依赖 LLM 自己生成 bug,可能存在 bug 模式分布偏差(例如更容易生成「运算符写错」而非「竞态条件」),对真实工程 Bug 的迁移性需要更多跨数据集验证;(2) EA-GRPO 仅在 Qwen2.5-Coder 系列验证,未在 Code Llama、DeepSeek-Coder 等其他代码模型上复现,泛化性待确认;(3) 论文没有报告对超长上下文(> 2K token)或带注释/docstring 的工业代码的修复表现。
独立分析的弱点
独立分析三个可改进的弱点。弱点一:Self-Breaking 的 bug 多样性受限于「打 bug 模型本身的能力」。如果打 bug 的模型只会生成「改运算符」「改数字」这类浅层错误,模型训练后也只能应付这类浅层 Bug,对真实工程中复杂的逻辑 Bug(如 off-by-one、边界条件缺失、并发问题)泛化能力存疑。改进方向是引入基于 AST 扰动、覆盖引导变异(coverage-guided mutation)或人类 patch 反向工程的混合 bug 注入管线。弱点二:min-max 采样的目标函数是非凸组合优化(minimize max pairwise similarity),k = 4 时可行,但当 |X| 增大、k 增大时计算代价急剧上升,本文未给出可扩展近似算法。改进方向是借鉴 Determinantal Point Process(DPP)或最大边际相关性(MMR)思想,用连续松弛代替组合搜索。弱点三:EA-GRPO 的 Group Accuracy Threshold α 是一个静态超参,但训练过程中不同 prompt 难度不同,「哪些 prompt 已经够对」应当自适应判定——可以引入滑动窗口的 EMA 准确率或 per-prompt 的难度估计器动态调整 α。改进方向是参考课程学习(curriculum learning),从简单 prompt 开始施加强编辑惩罚,逐步扩展到困难 prompt。
未来方向
作者明确提出的方向有两个:(1) 在受限算力下做 EA-GRPO 超参的自动调优,例如用贝叶斯优化或 population-based training 搜索 (α, β) 联合最优;(2) 把 PRepair 扩展到文件级与项目级代码修复,需要解决长上下文建模与跨文件 bug 定位两个新问题。基于论文成果可以延伸的方向还有三个:(1) 把 EA-GRPO 的「动态条件奖励」思想推广到其他代码任务,例如代码重构(refactoring)、测试用例生成、漏洞修复,让模型同时优化功能正确性与改动幅度;(2) 把 fixp@k 指标与代码 review 工作量建模结合,引入「开发者认知成本」作为更上层的优化目标;(3) 与 RLHF 或 DPO 结合,用人类对「哪种修复更易 review」的偏好数据训练 reward model,让 EA-GRPO 的编辑惩罚不再是单一的 sigmoid 标准化,而是学习自人类反馈的非线性偏好函数。
复现评估
可复现性较好但仍有门槛。代码与模型:作者声明在摘要中提供 code 与 models(§ Code Models & Datasets,链接在 arxiv 主页),但当前论文中未给出具体的 GitHub URL,潜在用户需要去 arxiv 页或作者主页(ICT-CAS 团队)查找。数据:Self-Breaking 的输入是公开可获取的 LeetCodeDataset(Xia et al., 2025)和 QiMeng-CodeV-R1(Zhu et al., 2025),输出 buggy 数据集本身未独立开源;HumanEval-Fix 是公开基准;自建的 Verilog 352 题基准未单独发布但可由 QiMeng-CodeV-R1 + 自造 bug 复现。算力:7B 模型 RL 训练需 8 张 A100-80GB SXM GPU,3B 模型需 8 张 L40S-48GB GPU,按 200 步训练推算单次实验约需 24-48 GPU·小时,整体 ablation + 主实验估计需要数千 GPU·小时。复现难度:算法实现层面依赖 VeRL 框架与 vLLM,两者均为开源组件,奖励函数与 EA-GRPO 的 PGi 计算逻辑简单;主要难点在 Self-Breaking 阶段的 prompt 工程与 min-max 采样实现,需要重新调试以保证不同 bug 类型的平衡。超参方面,Table 3 列出了完整的 RL 训练超参(学习率 1e-6、rollout N=8、PPO epoch=1 等),Table 4 给出了推理参数(temperature 0.7、top-p 0.8、top-k 20、max tokens 2048),Table 2 给出了 EA-GRPO 的 α/β 搜索空间,整体可复现性评级为中等偏高。
论文图表
顶部展示了 Python 累加器 buggy 代码(变量 i 初始化为 1 而非 0),中部展示 vanilla LLM 修复结果——把 i 从 1 改成 0 但顺带改写了 sum[0]、cout[0]、sum[i]、cout[i] 等大量本来正确的逻辑,形成 over-editing;底部展示 PRepair 修复结果——只改 i = 0 这一行,其余代码原样保留。
这是论文的核心动机图,用一个最小反例直观说明「过度编辑」是什么、为什么有害、PRepair 又是怎么修的,比任何文字描述都更易传播。
四张子图分别展示 Python 与 Verilog 任务上 Accuracy 和 Edit Cost 随训练步数(50-250)的变化曲线。Python 上 accuracy 从 ~0.5 升到 ~0.7 但 edit cost 从 0.45 升到 0.55+;Verilog 上 accuracy 从 ~0.4 升到 ~0.55 但 edit cost 从 0.4 一路升到 0.65+。
这张图是论文「过度编辑是 GRPO 训练副作用」这一核心论断的关键证据,也是引出 fixp@k 指标与 EA-GRPO 算法的直接铺垫。
柱状图对比 Origin、GRPO、EA-GRPO 三种模型在 N-gram 推测解码下的吞吐率与接受率,附带 Python 与 Verilog 两个语言子图,EA-GRPO 的接受率显著高于另外两者。
这是论文「工程价值」主张的关键证据——少改不只是指标好看,还能免费加速推理部署,对实际 IDE 集成有直接影响。