让语言模型学会用代码思考
摘要
本文介绍了 ThinC(Thinking in Code,用代码思考)框架。在该框架中,语言模型在简短的自然语言规划步骤后,仅使用代码块进行推理,在数学基准测试中优于现有的工具集成推理基线。
arXiv:2605.07237v1 公告类型:新论文
摘要:工具集成推理(TIR)已成为语言模型解决数学问题的主导范式,它将自然语言(NL)推理与代码执行相结合。然而,这种交错式设置存在三个主要局限性:代码通常充当事后验证器,中间的自然语言计算容易出错,且自然语言与代码的角色重叠,而非界限分明。我们提出了 ThinC(Thinking in Code)框架,在该框架中,代码本身作为推理主体,而非由自然语言调用的工具。ThinC 轨迹始于简短的自然语言规划步骤,此后所有推理均仅通过代码块及其执行输出之间的连接展开。我们从教师模型中蒸馏出 12,200 条以代码为中心的轨迹,并通过监督微调结合强化学习训练了 ThinC-1.7B 和 ThinC-4B 模型。ThinC-4B 在五个竞赛级数学基准测试中始终优于所有 TIR 基线模型,甚至超越了规模大得多的 Qwen3-235B-A22B-Thinking。进一步分析表明,ThinC 是通过代码进行推理的:其 99.2% 的最终答案均基于解释器输出,且模型能够在没有中间自然语言推理的情况下,从代码执行故障中可靠恢复。我们的代码和模型即将发布。
查看缓存全文
缓存时间: 2026/05/11 06:55
# 让语言模型学会用代码思考 **来源:** https://arxiv.org/html/2605.07237 **Hyeon Hwang** 高丽大学,韩国首尔 [email protected] **Jiwoo Lee** 高丽大学,韩国首尔 [email protected] **Jaewoo Kang** 高丽大学,AIGEN 科学系,韩国首尔 [email protected] ## 摘要 工具集成推理(Tool-Integrated Reasoning, TIR)已成为语言模型解决数学问题的主流范式,它将自然语言(NL)推理与代码执行相结合。然而,这种交错式设置存在三个主要局限:代码往往仅作为事后验证器;中间的自然语言计算容易出错;自然语言与代码扮演的角色重叠,而非界限分明。我们提出了 **ThinC**(Thinking in Code,用代码思考),这是一个让代码本身充当推理者、而非由自然语言调用的工具的框架。一个 ThinC 轨迹始于简短的自然语言规划步骤,此后所有的推理过程仅通过代码块及其执行输出进行衔接。我们从教师模型中蒸馏出 12.2k 条以代码为中心的轨迹,并通过监督微调(SFT)结合强化学习(RL)训练了 ThinC-1.7B 和 ThinC-4B。ThinC-4B 在五个竞赛级数学基准测试中始终优于所有 TIR 基线,甚至超过了规模大得多的 Qwen3-235B-A22B-Thinking。进一步分析表明,ThinC 确实通过代码进行推理:99.2% 的最终答案基于解释器输出,且模型能够从代码执行失败中可靠恢复,无需中间的自然语言推理。我们的代码和模型即将开源。 ## 1 引言 在长思维链(chain of thought)上应用强化学习(RL)的最新进展 [19] 大幅增强了大型语言模型(LLM)的数学推理能力,催生了如 OpenAI o1 [9] 和 DeepSeek-R1 [7] 等强大的自然语言(NL)推理器。尽管取得了这些进展,数学推理对于 NL 推理器而言仍然具有挑战性,特别是在需要精确多步计算的问题上,哪怕是一个算术错误都可能导致整个推理过程失效。 为了使计算更加可靠,先前工作越来越多地将可执行代码纳入推理过程。基于提示的方法如 PAL [5] 和 PoT [2] 生成端到端解决数学问题的 Python 程序,将精确计算委托给代码解释器。这些方法证明了代码在数学计算和符号表达上的可靠性,但仅限于单次程序生成,缺乏与执行结果的迭代交互。 为了结合 NL 推理和代码执行互补的优势,后续工作引入了**工具集成推理(TIR)** [6, 18],其中 NL 负责高层规划,而代码执行精确计算。TIR 在多个回合中交错 NL 推理与代码执行,通过解释器反馈实现迭代 refinement 和中间验证。最近的工作进一步从几个方向扩展了这一范式:ReTool [4] 使用 RL 优化工具使用策略,ASTER [23] 强调推理过程中的密集工具交互,Tool-Star [3] 将 TIR 扩展到跨多个工具的协作推理。 然而,如图 1 所示,TIR 的交错推理范式存在三个反复出现的结构性局限: 1. **代码常作为事后验证器:** 模型通常先在 NL 中完成推导,然后运行代码仅用于确认;代码变成了事后验证器而非推理者,没有贡献新的计算。 2. **中间 NL 计算易出错:** 当模型在 NL 中执行算术或代数步骤时,错误的值可能被硬编码为常量复制到下一个代码块中。解释器无法检测此错误,错误会静默影响最终答案。 3. **角色重叠而非界限分明:** 尽管 NL 推理擅长高层规划,代码可作为精确数学表达和计算的推理者,但交错的 TIR 未能分离这些角色,导致两者在做同样的工作。NL 推理逐步展开算法,承担了代码更擅长的工作,而随后的代码仅仅是在转录 NL 推理。 > **图 1:交错式工具集成推理的三个结构性局限。** > (A) 事后工具验证:模型在 NL 中完成推导,运行代码仅用于确认答案,因此解释器不进行新的计算。 > (B) 不可靠的基于 NL 的计算:NL 算术错误作为硬编码常量静默传播到下一个代码块。 > (C) 推理角色分配不当:NL 推理描述了后续代码重新实现的算法。 为了解决这些局限,我们提出了 **ThinC**(Thinking in Code),这是一个训练框架,其中代码本身充当推理者,而非由 NL 推理驱动的工具。ThinC 推理始于一个简短的 NL 规划步骤以框定策略,此后所有推理均通过仅由执行输出连接的代码块展开。这种结构设计性地解决了上述三个局限:代码执行推导而非验证 NL 结论,每个中间值均由解释器生成因而得到验证,NL 仅限于高层规划,而代码承担所有推理任务。 我们通过三个阶段实现这一范式:通过少样本提示从教师模型蒸馏轨迹以构建 12.2k 条 ThinC-SFT 数据集;通过监督微调(SFT)建立以代码为中心的行为先验;通过带有可验证奖励的强化学习 [16] 加强问题解决能力。我们在两个规模上评估了 ThinC,即基于 Qwen3-1.7B 和 Qwen3-4B-Thinking-2507 [20] 构建的 ThinC-1.7B 和 ThinC-4B,涵盖五个竞赛级数学基准(AIME 2024–2026, HMMT 2025, 和 BeyondAIME [14])。ThinC-1.7B 达到 42.8% 的平均准确率,超过 Qwen3-1.7B 10.6 个百分点。ThinC-4B 达到 **78.1%** 的平均准确率,超过我们评估中的所有 TIR 基线,并在五个基准中的四个上超过了规模大得多的 NL 推理器 Qwen3-235B-A22B-Thinking。 进一步分析表明,ThinC-4B 以真正以代码为中心的方式进行推理,**99.2%** 的最终答案基于解释器输出,而非通过 NL 推理生成。这种行为也使 ThinC 在初始代码执行失败时具有鲁棒性,而交错的 TIR 基线性能急剧下降。 我们的贡献如下: * 我们提出了 **ThinC**,一个训练框架,教导语言模型将代码视为数学问题解决的主要推理者,而非由 NL 推理调用的工具。ThinC 包括轨迹蒸馏、SFT 和带有可验证奖励的 RL。 * 我们提供了包含 12.2k 条以代码为中心轨迹的 **ThinC-SFT** 数据集,以及两个训练好的模型:ThinC-1.7B 和 ThinC-4B。ThinC-4B 在五个竞赛级数学基准上达到 **78.1%** 的平均准确率,优于所有 TIR 基线和规模大得多的 Qwen3-235B-A22B-Thinking。 * 我们提供了全面的分析,表明 ThinC-4B 在推理时以真正以代码为中心的方式运作,并确定了其对早期代码执行失败的鲁棒性是这一结构的具体后果。 ## 2 预备知识 ### 2.1 工具集成推理 TIR 为语言模型增强了一个或多个外部工具,这些工具可以在生成过程中被调用,例如代码解释器、搜索引擎或符号求解器。在 TIR 中解决问题是一个**多轮**过程:模型在生成文本和调用工具之间交替,每次后续行动都基于工具的输出来调整。在本文中,我们关注数学推理设置,其中工具是 Python 解释器 $\mathcal{E}$,每轮包含由模型生成的自然语言**思维块** $t \in \mathcal{T}$、**代码块** $c \in \mathcal{C}$ 以及由解释器确定性产生的**执行输出** $o = \mathcal{E}(c)$,作为不可训练的观测附加到上下文中。 给定一个问题 $q$,标准的交错式 TIR 范式产生如下形式的轨迹: $$ \tau_{\mathrm{TIR}} = (q, \, t_1, c_1, o_1, \, t_2, c_2, o_2, \, \ldots, \, t_N, c_N, o_N, \, a) \quad (1) $$ 其中 $N$ 是轮数,$a$ 是最终答案。所有最近的 TIR 系统 [6, 4, 23] 都遵循这一结构。 ### 2.2 监督微调 监督微调(SFT)通过在演示轨迹上训练下一个 token 预测目标,将预训练 LLM 适配到目标行为。在 TIR 中,演示通常从更强的教师模型中蒸馏,轨迹的选择直接塑造了模型学习产生的工具使用模式 [22, 4, 23]。给定轨迹数据集 $\mathcal{D}_{\mathrm{SFT}}$,SFT 目标为: $$ \mathcal{L}_{\mathrm{SFT}}(\theta) = - \mathbb{E}_{\tau \sim \mathcal{D}_{\mathrm{SFT}}} \sum_{k=1}^{|\tau|} m_k \log \pi_\theta(x_k | x_{<k}) $$ *(注:原文此处公式截断,根据上下文补全标准 SFT 损失函数形式)* --- ## A.1 问题陈述 找出小于或等于 100 的整数中,有多少个等于 $a + b + ab$,其中 $a$ 和 $b$ 是某些不同的正整数。参考答案为 $\boxed{70}$。 ### A.2 阶段 1 — 战略规划 ($t_1$) 单个 `think` 块不包含数值计算。它执行了一次代数重构——因式分解 $a + b + ab = (a+1)(b+1) - 1$——并指定了随后需要用代码解决的搜索问题。 > 我需要找出有多少个整数 $\le 100$ 可以写成 $a + b + ab$,其中 $a$ 和 $b$ 是不同的正整数。 > 让我想想这个表达式:$a + b + ab = (a+1)(b+1) - 1$。 > 所以我需要找出有多少个整数 $n \le 100$ 满足 $n = (a+1)(b+1) - 1$,其中 $a, b$ 是不同的正整数。 > 这意味着 $n + 1 = (a+1)(b+1)$,其中 $a, b \ge 1$ 且 $a \neq b$。 > 令 $m = a+1, n' = b+1$。那么 $m, n' \ge 2, m \neq n'$,且 $m \cdot n' = n+1$。 > 所以我需要计算有多少个 $n \le 100$ 的值可以写成 $m \cdot n' - 1$,其中 $m, n' \ge 2, m \neq n'$,且 $m \cdot n' \le 101$。 > 实际上,让我直接暴力求解。既然 $a$ 和 $b$ 是不同的正整数,让我遍历可能的值并检查哪些和 $\le 100$。 > 让我写代码直接计算这个。 这实现了 ThinC 为 $t_1$ 保留的角色(第 3.1 节):高层战略计划,而非逐步推导。此思维块的三个特性说明了设计意图: 1. **NL 中无算术:** 模型从不手动评估表达式或枚举情况。思维通道纯粹用于问题的符号重构。 2. **搜索空间规范:** 它在编写任何代码之前固定了确切约束($m, n' \ge 2, m \neq n', m \cdot n' \le 101$),为随后的代码块提供了干净的起点。 3. **单次过渡到代码:** 块以一句话(“让我写代码直接计算这个”)结束,从此点开始,NL 不再返回:所有后续推理步骤都在 `code` 块内执行。 ### A.3 阶段 2 — 以代码为中心的推理 滚动的其余部分由五个 `code` 交换组成。每个代码块都基于先前块的执行输出构建,如公式 6 所规定,每个推理步骤——包括自我修正、结构验证和独立重新推导——都在代码中执行。 #### 第 1 轮:初始暴力求解。 模型实现了一个直接枚举,并带有提前 `break`,它预先标记这可能是不正确的。 **Turn 1 [⬇](data:text/plain;base64,IyAoYSsxKShiKzEpIC0gMSAtLSBicnV0ZSBmb3JjZSBvdmVyIGRpc3RpbmN0IChhLGIpCnJlc3VsdHMgPSBzZXQoKQpmb3IgYSBpbiByYW5nZSgxLCAxMDApOgogICAgZm9yIGIgaW4gcmFuZ2UoYSsxLCAxMDApOiAgICAgIyBiID4gYSB0byBrZWVwIHBhaXJzIGRpc3RpbmN0CiAgICAgICAgbiA9IGEgKyBiICsgYSpiCiAgICAgICAgaWYgbiA8PSAxMDA6CiAgICAgICAgICAgIHJlc3VsdHMuYWRkKG4pCiAgICAgICAgZWxpZiBuID4gMTAwOgogICAgICAgICAgICBicmVhayAgICAgICAgICAgICAgICAgIyBwb3RlbnRpYWxseSBwcmVtYXR1cmUgLS0gcmV2aXNpdGVkIG5leHQgdHVybgpwcmludChmIk51bWJlciBvZiBkaXN0aW5jdCB2YWx1ZXM6IHtsZW4ocmVzdWx0cyl9Iik=)** ```python results = set() for a in range(1, 100): for b in range(a+1, 100): # b > a to keep pairs distinct n = a + b + a*b if n <= 100: results.add(n) elif n > 100: break # potentially premature -- revised next turn print(f"Number of distinct values: {len(results)}") ``` **输出:** Number of distinct values: 70 #### 第 2 轮:代码中的自我修正。 基于第 1 轮的输出,模型重新审视其循环边界,并将单调性论证(对于固定的 $a$,$n = a + b(1+a)$ 关于 $b$ 是单调的)直接作为代码注释形式化,然后以修正后的条件重新运行。审计和修复完全在代码行内进行;块之间不需要 NL 推理。 **Turn 2 [⬇](data:text/plain;base64,IyBGb3IgZml4ZWQgYSwgbiA9IGEgKyBiKDErYSkgaXMgbW9ub3RvbmUgaW4gYi4KIyBTbyBgYnJlYWtgIGlzIGp1c3RpZmllZCAtLSBidXQgb25seSBhZnRlciB3ZSBoaXQgbiA+IDEwMCwgbm90IGVhcmxpZXIuCnJlc3VsdHMgPSBzZXQoKQpmb3IgYSBpbiByYW5nZSgxLCAxMDApOgogICAgZm9yIGIgaW4gcmFuZ2UoMSwgMTAwKToKICAgICAgICBpZiBhID09IGI6IGNvbnRpbnVlCiAgICAgICAgbiA9IGEgKyBiICsgYSpiCiAgICAgICAgaWYgbiA+IDEwMDogYnJlYWsKICAgICAgICByZXN1bHRzLmFkZChuKQpwcmludChmIk51bWJlciBvZiBkaXN0aW5jdCB2YWx1ZXM6IHtsZW4ocmVzdWx0cyl9IikKcHJpbnQoIkFsbCB2YWx1ZXMgc29ydGVkOiIsIHNvcnRlZChyZXN1bHRzKSk=)** ```python # For fixed a, n = a + b(1+a) is monotone in b. # So `break` is justified -- but only after we hit n > 100, not earlier. results = set() for a in range(1, 100): for b in range(1, 100): if a == b: continue n = a + b + a*b if n > 100: break results.add(n) print(f"Number of distinct values: {len(results)}") print("All values sorted:", sorted(results)) ``` **输出:** Number of distinct values: 70 All values sorted: [5, 7, 9, 11, 13, 14, 15, 17, 19, 20, 21, 23, 25, 26, 27, 29, 31, 32, 33, 34, 35, 37, 38, 39, 41, 43, 44, 45, 47, 49, 50, 51, 53, 54, 55, 56, 57, 59, 61, 62, 63, 64, 65, 67, 68, 69, 71, 73, 74, 75, 76, 77, 79, 80, 81, 83, 84, 85, 86, 87, 89, 90, 91, 92, 93, 94, 95, 97, 98, 99] #### 第 3 轮:结构性健全性检查。 模型按奇偶性分解结果,以确认值集中没有差一错误(off-by-one error)。 **Turn 3 [⬇](data:text/plain;base64,dmFsdWVzID0gc29ydGVkKHJlc3VsdHMpCm9kZHMgID0gW3YgZm9yIHYgaW4gdmFsdWVzIGlmIHYgJSAyID09IDFdCmV2ZW5zID0gW3YgZm9yIHYgaW4gdmFsdWVzIGlmIHYgJSAyID09IDBdCnByaW50KGYiQ291bnQ6IHtsZW4odmFsdWVzKX0iKQpwcmludChmIk9kZDoge2xlbihvZGRzKX0sIEV2ZW46IHtsZW4oZXZlbnMpfSIp)** ```python values = sorted(results) odds = [v for v in values if v % 2 == 1] evens = [v for v in values if v % 2 == 0] print(f"Count: {len(values)}") print(f"Odd: {len(odds)}, Even: {len(evens)}") ``` **输出:** Count: 70 Odd: 48, Even: 22 #### 第 4 轮:独立重新推导。 *(原文在此处截断,模型正在双重公式中重新实现计数...)*
相似文章
多语言思维,而非更难的思维:教授推理模型代码切换的数据高效框架
本文介绍了一个数据高效的微调框架,用于教授推理模型有效地进行代码切换(混合使用多种语言),证明了战略性的代码切换可以提升低资源语言的推理能力。该工作分析了大型语言模型在不同语言、任务和领域中的代码切换行为,并开发了促进有益代码切换模式的干预措施。
向思维模型教授工具推理:工具集成推理的全流程方案
本文提出了一种全流程方案,用于向思维模型教授工具推理,该方法应用于 Qwen3 模型时,在 AIME 2025 等基准测试上实现了最先进的性能。
脆弱的思考:大型语言模型如何处理思维链扰动
本论文对大型语言模型在思维链推理步骤中处理损坏情况的能力进行了全面的实证评估,在数学推理任务上针对13个模型和5种扰动类型(数学错误、单位转换、盲从、跳过步骤、额外步骤)进行了测试。研究结果揭示了异质性的漏洞模式,对在多阶段推理管道中部署LLM具有重要意义。
LaTER:通过潜在探索与显式验证实现高效的测试时推理
本文介绍了 LaTER,一种两阶段推理范式,它将潜在探索与显式思维链(Chain-of-Thought)验证相结合,从而在保持准确率的同时,降低大型语言模型的标记使用量并提升效率。
语言模型学习什么以及何时学习?隐性课程假设
本文提出隐性课程假设,证明语言模型预训练遵循一个结构化的、组合性的课程,其中能力跨架构一致涌现,并可从内部表示预测。作者通过设计涵盖检索、形态学、共指消解、推理和数学的任务进行验证,发现四个模型族中涌现顺序高度一致(ρ=0.81)。