小模型越修越错?盲抽重试反超Self-Repair,少耗5.5倍Token

Try Again, Don't Look Back: Blind Resampling Outperforms Self-Repair in Small Code Models

论文原文 ↗ 论文发布 解读发布 解读:AI前沿分享

小模型越修越错?盲抽重试反超Self-Repair,少耗5.5倍Token 论文图示

在当前的智能体(Agent)与自动化代码生成设计中,“自我修复”(Self-Repair)几乎被视作一项不可动摇的标准动作:当模型生成的程序未能通过单元测试时,系统抓取测试报错和异常堆栈,将失败的代码连同诊断反馈一股脑喂回给上下文,要求模型“反思并修正”。从早期的 Reflexion、Self-Refine 到各类软件工程 Agent,这种闭环修复逻辑被广泛采纳,且大部分研究论文都报告了可观的准确率提升。

ArXiv URL:https://arxiv.org/abs/2607.26117v1

然而,一项最新的严谨控制实验揭开了一个被行业长期忽视的评估盲区:以往所有证明“自我修复有效”的实验,绝大多数都在拿“多轮修复”与“单次生成”(Single-Shot)进行不公平对比。这种对比将“额外重试带来的采样红利”与“错误反馈带来的真实信息价值”混为一谈。只要模型在随机采样模式下多试几次,成功率本身就会随着抽样次数天然上升,这并不代表反馈真的起到了正向修正的作用。

为了彻底剥离这一混杂变量,独立研究者设计了一套带安慰剂对照(Placebo-Controlled)的消融实验,在 MBPP+ 和 HumanEval+ 基准上对 1.5B、3B 和 7B 规模的模型展开了端到端评估。实验得出了一个颠覆常规认知的结论:在中小参数代码模型中,向模型展示其刚才写错的代码,不仅没有帮助,反而会显著损害后续生成的准确率;直接把上下文清空、当做什么都没发生一样进行“盲抽重试”(Blind Resampling),在性能和成本上全面碾压传统的自我修复管线。

拆解反馈包:严格的安慰剂实验设计

以往的自我修复研究通常只设置两组对比:要么是不重试的单次贪婪生成,要么是附带完整执行报错的修复循环。这导致研究者无法分辨:模型变好究竟是因为拿到了测试输出、还是因为知道自己错了、抑或仅仅是因为获得了第二次答题机会。

这项研究将一次重试过程中可能包含的要素逐层拆解,设计了四种预算严格对齐的重试条件:

  1. 盲抽重试(Blind Resampling):当第一次尝试失败后,丢弃所有失败历史,仅凭最初的原始 Prompt,以相同的采样温度(0.8)重新生成一份代码。模型完全不知道上一版写了什么,也不知道自己刚才犯了错。

  2. 失败安慰剂(Placebo):在 Prompt 中原样附上模型刚才写出的失败代码,但只追加一句完全没有任何诊断信息的通知,仅告知“该代码未通过测试”,不提供任何错误详情。

  3. 真实执行反馈(Execution Feedback):工业界和学术界最标准的 Self-Repair 做法。附上失败代码,并精准附带测试套件运行后拦截到的真实输出、断言失败信息或异常回溯。

  4. 语言自反思(Reflection):在真实执行反馈的基础上,强行要求模型在给出新代码之前,先用自然语言陈述并推理“为什么上一个方案会失败、应该如何调整逻辑”。

这四种条件构成了层层递进的因果链条。从盲抽到安慰剂,唯一的变化是引入了“模型自己生成的错误代码”;从安慰剂到执行反馈,唯一的变化是引入了“具体的测试报错信息”;而引入自反思,则是为了检验额外的自然语言推理能否弥补自我修复的缺陷。

为了杜绝随机误差并确保统计效力,实验在 MBPP+(378道题)上展开,每次生成循环最多允许 8 次重试($k \le 8$)。每一次重试都采用嵌套评估(Nested Evaluation),只要当前候选代码通过差分测试就立即早停退出,这保证了不同策略之间的比较不仅严丝合缝地共享相同的起始点,还能够精准追踪每一轮迭代所耗费的 Token 开销。所有假设检验均经过 McNemar 精确检验与 Holm 多重假设校正。

颠覆直觉的发现:自我修复反成负资产

实验数据展现出令人意外的倒挂趋势。在 1.5B 和 3B 规模的模型上,“盲抽重试”无论在解决率($\mathrm{pass}@1$)还是在 Token 消耗上,都处于绝对的主导地位,呈现出对传统自我修复方案的严格帕累托优势。

在 Qwen2.5-Coder-1.5B 上,只要在上下文里附上模型自己刚才写出的失败代码,模型的解决率就会直接下跌 6.1 个百分点($p=0.006$)。换句话说,让小模型看自己写错的代码,不仅没有带来启发,反而直接把模型带偏了。

更具讽刺意味的是,被无数论文和 Agent 架构奉为核心驱动力的“真实执行反馈”,在剥离了盲抽基线后,几乎没有任何统计学意义上的额外增量。在 1.5B 级别下,“给详细执行报错”与“只给一句没有任何信息的安慰剂通知”,两者的最终通过率完全相同;在 3B 级别下,执行反馈相比安慰剂仅有极微弱的浮动,完全无法解释自我修复在传统评估中表现出的虚假繁荣。过去人们以为模型读懂了 Traceback 和报错日志,实际上它可能只是因为多了一次采样机会而碰巧猜对了答案。

在计算成本方面,差距被进一步拉大。由于盲抽重试的单次提示词极短,且不需要在上下文里反复堆叠失败代码、报错日志和冗长的反思推理,它消耗的输出 Token 比反思条件少了整整 2.5 倍到 5.5 倍。并且,由于盲抽重试在早期轮次命中正确解的概率更高,系统得以更早地触发成功早停,整体吞吐效率远超任何基于上下文记忆的修复循环。

直到模型规模扩大到 7B,局势才勉强发生变化。在 7B 参数下,盲抽重试与附带执行反馈的自我修复在统计学上打成了平手;加入了语言自反思的修复策略虽然在数值上高出了 0.8 个百分点,但在严格统计检验下并不显著,而且为此付出的代价是消耗多达 2.5 倍的 Token。在端侧或中小模型部署场景下,盲抽重试始终牢牢占据着帕累托最优边界。

为什么会越修越错?“锚定效应”的实证度量

为什么给模型看它刚才犯下的错误,反而会导致性能剧烈衰退?研究指出,核心机制在于大语言模型内部严重的“锚定效应”(Anchoring)。

当一个已经失败的程序被塞进上下文中作为后续补全的前置条件时,自注意力机制会天然地将生成空间强行约束在以该程序为中心的局部邻域内。模型在解码时会倾向于复用原有的变量命名、控制流和算法架构,仅仅做微小的局域语法修补,甚至反复修改无关紧要的边界条件。然而,很多时候代码之所以未能通过测试,是因为最初选取的算法思路或解题方向从根源上就是错误的。

为了直接证明锚定效应的存在,研究定义了一项衡量指标:连续两次尝试之间的代码文本相似度(采用最长公共子序列比例计算),并统计了连续重试中生成“近乎完全一致的代码”的比例。

数据清晰地暴露了这种思维固化:

盲抽重试的优势恰恰在于“斩断锚点”。由于完全脱离了上一次糟糕尝试的引力场,每次重新采样都能以全新的随机路径自由探索解空间的不同区域,从完全不同的算法逻辑切入。这种跳出局部最优的能力,在解空间广阔的编程任务中,价值远远超过了在错误路径上缝缝补补。

这也解释了为什么语言自反思(Reflection)在所有接触自身历史的条件中表现相对最好。文本相似度分析显示,强迫模型用文字剖析失败原因,能够使代码间的相似度下降 0.04 到 0.12,近乎重复的代码生成率降低了大约三分之一。换言之,反思的本质作用并非赋予了模型高超的推理能力,而是在上下文里强行插入一段与旧代码不同的 Token 序列,稀释了注意力权重,从而在一定程度上“撬动了沉重的锚”。但这种撬动是极其昂贵且低效的,其最终达到的效果,甚至不如直接清空上下文来得彻底。

排除干扰:上下文变长还是量化缺陷?

在得出上述结论后,必须面对两个极其严苛的技术质疑:第一,小模型性能变差,真的是因为“看到了自己写的错误”,还是单纯因为“上下文变长了导致小模型注意力稀散”?第二,实验采用的是 4-bit 量化(Q4_K_M)模型,这种惩罚会不会只是量化损伤了上下文学习能力导致的实验假象?

针对第一个问题,研究设计了跨任务经验检索对照实验。系统通过 BM25 从模型自身过去成功解决的其他编程任务中检索出两道最相关的例题代码,在重试时拼接到 Prompt 前部。此时,Prompt 增加的 Token 数量与自我修复场景完全一致,甚至同样包含了完整的 Python 实现。

实验结果给出了干净的阴性结果:检索并拼接其他任务的成功代码,对当前任务的通过率没有任何统计学层面的影响,性能波动被严格限制在 $\pm 3.5$ 个百分点以内。这强有力地证实:上下文单纯变长并不会直接摧毁模型的推理能力,自我修复带来的断崖式下跌,完全是由“将自身失败代码作为条件输入”引发的锚定效应所致。

针对第二个质疑,研究在 1.5B 规模下使用完全无损的半精度浮点(FP16)权重进行了全量复现。由于模型体积只有 3.1 GB,FP16 权重完全驻留在显存中,彻底排除了量化误差与 CPU 内存交换的干扰。

复现结果显示,FP16 下的所有指标与 Q4 量化几乎完全平行:盲抽重试相对于自我修复的优势依然是 $-6.1$ 与 $-6.3$ 个百分点($p=0.0008$),真实执行反馈相比安慰剂依然毫无超额增益(仅差 $+0.016$,不显著),且锚定度指标在两种精度下的差异不超过 0.003。这彻底排除了精度量化对核心结论的干扰。

此外,研究还将这一实验外推到了完全不同技术路线的开源模型家族——DeepSeek-Coder(测试了 1.3B 和 6.7B)。在 DeepSeek-Coder-1.3B 上,盲抽重试同样以超过 11 个百分点的悬殊优势痛击了包含反馈的自我修复方案($p < 10^{-4}$),这表明锚定惩罚不是特定训练集或特定分词器的偶然产物,而是当前自回归架构在特定参数规模下的普适共性。

规模的真相:不是反馈变聪明,而是首发没那么烂

在纵观 Qwen 和 DeepSeek 两大家族跨越 1.3B 到 7B 的六组配置后,一个极具洞察力的统一规律浮出水面:模型因自我修复而遭受的性能惩罚,与模型自身的“基线能力”(单次生成解决率)呈现出近乎绝对的强负相关,皮尔逊相关系数高达 $r = 0.96$。

这一发现重新定义了学界对于“模型规模演进”的理解。许多人直觉地认为,7B 模型之所以不再像 1.5B 那样惧怕自我修复,是因为更大参数的模型学会了如何理解执行反馈中的 Traceback 信息。但实验中的安慰剂对照推翻了这一假说——即便到了 7B 级别,真实执行反馈相较于毫无信息的安慰剂通知,依然没有拉开有说服力的差距。

真正的原因在于:锚定效应的代价,取决于被锚定的对象有多糟糕。

在 1.3B 或 1.5B 级别,模型的单次通过率极低,初次写出的代码往往存在严重的逻辑硬伤或结构性错误。此时让模型锚定在这段垃圾代码上,相当于强迫它在一栋地基已经坍塌的建筑上粉刷墙壁,付出代价极高。而到了 7B 级别,模型的首发代码质量已经大幅提升,许多失败仅仅源于某个变量名打错或微小的边界偏移,此时对已有代码做局域微调的成本大幅降低。换言之,不是 7B 的模型学会了如何利用反馈,而是 7B 模型的初次尝试终于变得“值得被锚定”了。

根据 $r=0.96$ 的线性拟合推演,只有当模型的基线代码能力进一步跨越某个临界点之后,对自身代码的局域修正收益才可能实质性地压倒重新采样的空间探索收益。

重新思考 Code Agent 的重试哲学

这项研究不仅用极其干净的实验设计对长久以来的技术惯性完成了“纠偏”,也为从事本地大模型部署、边缘计算以及端侧 Coding Agent 开发的工程团队带来了非常具体的架构启示。

首先,在参数规模处于 7B 及以下的端侧代码智能体中,应当果断放弃结构复杂的“反思-修复”流水线。默认采用丢弃历史的盲抽重试,不仅能在准确率上取得持平甚至大幅超越的表现,更能将 Token 开销与响应延迟缩减数倍,极大地节约推理算力。

其次,对于任何基于采样的代码生成系统,盲目堆叠重试轮数是极其低效的。轨迹分析表明,模型通过重试能够获得的所有潜在收益中,有接近一半(46%–53%)都在前两次重试($k=2$)之内全部兑现,从第 7 次重试开始,边际收益已经几乎收敛至零。将重试预算卡在 2 次左右,是兼顾吞吐与性能的最优工程分水岭。

最后,这项研究为学术界和工业界在评估 Agent 能力时树立了一根关键的标尺:在评估任何声称具备“自愈”、“自我反思”或“自我改进”特性的框架时,绝对不能仅仅对比不重试的静态基线。必须引入计算成本严格对齐的盲抽重试或安慰剂对照,才能真正验证那些精巧设计的 Prompt 模板,究竟是带来了真实的认知跃迁,还是仅仅把模型锁死在了一处昂贵的思维死胡同里。