☰
一张A100、8小时,从零训练一个会“循环思考”的小模型
2026/10/7 19:33:47 网站建设 项目流程

一张 A100、8 小时,从零训练一个会“循环思考”的小模型

最近我一直在琢磨一个事:大模型推理时能不能像人一样,先试着想一步,发现不对再退回去重来,而不是一条道走到黑。网上讨论“测试时计算”和“推理时扩展”的内容不少,但大多停留在用现成API、加大采样的层面,真正讲清楚怎么让一个小模型获得这种能力的训练教程,少之又少。

所以我干脆自己动手,用一张A100,花了大概一个工作日的训练时间(8小时),从零搓了一个会“循环思考”的小模型出来。这里的“循环思考”,我给它下的定义很具体:模型在生成回答时,可以产出多轮内部思考状态,每轮都基于上一轮的结论做修正或者推进,而且允许中间出现“我觉得上一步可能有问题”这样的自我纠偏信号。目标不是做通用助手,而是把这个“绕圈检查再前进”的能力训进一个小参数量的模型里。

这篇文就是完整复盘。包含我为什么选小模型而不是硬上大模型、训练数据是怎么构造出来的、LoRA+强化学习的训练管线怎么搭、8小时的时间预算怎么分配到每个环节,以及踩过的那些坑。全程没有分布式、没有多卡,GAA100单卡完成,代码层级的东西我也尽量拆开讲,保证有动手能力的读者能照着复现。

先说清楚这个项目的适用人群。如果你是做NLP算法、LLM推理优化、或者对“让模型学会自己纠错”这个方向感兴趣的工程向选手,这篇能给你一套从数据到训练到评估的完整闭环。如果你只是好奇“循环思考”这个能力是怎么被训出来的,也能看懂大部分内容,因为我尽量把每一步的“为什么”都解释透,不会甩一堆基础概念就让你自己猜。

1. 项目定位与方案选型:为什么是“小模型+单卡+短周期”

1.1 “循环思考”到底是一个什么能力

在展开训练细节之前,我需要把“循环思考”这个词先钉死,不然往下聊全是分歧。

常见的模型推理是“一次前向出答案”:输入问题,模型走一遍Transformer的层,最后一个token输出预测,完事。即便模型内部有隐藏状态的逐步计算,但对用户来说是不可见的、一次性的,模型也没有机会在生成过程中因为它们的内在“草稿”而回头修改输出。

我想做的“循环思考”,有点类似于给模型一个“内部草稿本”。每生成一个新节点(比如一小段推理文本),就把它作为输入的一部分重新编码,让模型在下一轮前向时能看到自己上一轮写了什么。这样模型就具备了“先写一个中间结论,再拿这个中间结论继续推导”的能力。更进一步,在数据里刻意加入“我上一轮推导里有个逻辑漏洞,重新看一下”这类标记,让模型学会在草稿本里做自我修正。

关键点在于:这个能力不是靠改Transformer架构实现的,而是靠改训练数据的“格式”和训练目标,让模型在自回归生成时自然地学到“我可以输出多轮思考,并且它们之间是依赖和修正关系”。所以基座模型不用动,数据才是灵魂。

1.2 为什么选择小参数模型而不是直接上大模型

这个项目从一开始就有一个硬性约束:单张A100,8小时以内。如果你直接上7B或者13B模型,光是加载FP16权重就吃掉14G到26G显存,再叠加LoRA优化器状态、梯度、激活值,想要在8小时内训出有效结果不是不可能,但容错率极低,而且计算密度根本不够用。我第一次用7B模型试跑,光是数据预处理和数据加载器的瓶颈就把GPU利用率压在60%左右,看着都肉疼。

所以我选择了0.5B到1.5B这个区间的小模型。具体基座用的是Qwen2.5-1.5B-Instruct(真实的开源模型)。原因其实很朴素:

  • 参数量小,单卡推理和训练的前向/反向计算开销低,8小时能跑的步数比7B模型多将近10倍;
  • 循环思考这个能力的关键在于“数据教它怎么绕圈”,而不是“模型容量必须多大”。只要任务本身复杂度不高,1.5B模型完全能装下这个行为模式;
  • LoRA在这个规模下效果非常稳定,微调成本低,迭代速度快,适合我这种需要反复调数据的人。

这里也解释一下LoRA的作用。LoRA就是在原始权重旁边挂两个小矩阵(低秩矩阵),训练时只更新这两个小矩阵,原始权重冻结。这样可训练参数量通常只有基座参数的0.1%到1%。好处是显存占用低、训练速度快、不容易灾难性遗忘。坏处是它不改变模型的“底层能力”,只能学到数据中高频出现的模式。这也是为什么我后面在数据构造上花了大量心思——LoRA更像给模型加了一层“行为滤镜”,滤镜画什么,完全取决于训练数据长什么样。

1.3 8小时时间预算的分配逻辑

先说结论:8小时不是拍脑袋定的,而是我把“数据质量、训练收敛、效果验证”三者做了权衡之后倒推出来的预算。

单张A100(80G显存)按BF16算力大概在312 TFLOPS左右(FP16/BF16稠密算力)。实际训练中因为attention计算、激活函数、访存瓶颈,能发挥到40%-60%就算不错了,也就是实际有效算力大概在120-180 TFLOPS。1.5B模型+LoRA跑一个global step(比如batch size 16,序列长度512)大约需要4-6秒。8小时大概能跑5000-7000步。

这个步数对LoRA来说够不够?以我的经验,如果是通用指令跟随数据,2000步就能看到明显效果。但我这里的“循环思考”数据的结构复杂度远高于普通指令对,模型需要更多步来适应“先思考再修正”的格式。所以我把目标设定在5000-8000步区间,兼顾训练时间和收敛质量。

在时间分配上,我给自己定的框架是:

  • 数据构造与清洗:2小时(这是最容易被低估的部分);
  • 基座模型选型与Tokenization验证:0.5小时;
  • 训练框架搭建(SFT阶段):0.5小时;
  • 第一阶段训练(SFT教格式):2小时;
  • 第二阶段训练(类似GRPO的偏好优化):2小时;
  • 评估与问题定位:1小时。

这个时间表排得相当紧。实际操作下来数据构造超时了,我从后面几个阶段各挤了一点时间回来。估算时间合不合理不重要,重要的是你得知道每个环节“合理的耗时区间”是多少,才不会卡在某个地方干瞪眼。

2. 训练数据构造:这是整个项目真正的胜负手

2.1 为什么说数据决定了“会不会循环思考”

模型本身只是一台模式匹配机器。你给它看什么样的输入输出,它就学会什么样的行为。想让模型学会“循环思考”,唯一的路径是让海量的、高质量的“循环思考样例”在训练时反复出现在它的上下文里,让它把这套行为模式刻进LoRA权重里。

如果数据里都是“问题-直接答案”,模型只会学会直接给答案。如果在数据里加入大量“问题-多轮草稿-修正-结论”,模型的生成分布就会被拉向那种格式。这个逻辑说起来简单,但执行起来很容易翻车。数据太少,模型学不会;数据太格式化和单一,模型学会了绕圈但不会真正推理;数据里有错误样例但修正得不好,模型学到的是“乱修正”。

所以我在构造数据时遵循了三个原则,后面每一步操作都围绕这三个原则展开:

  • 真实推理优先,不要编造伪思考。数据的中间步骤必须真的是从“解题过程”中提取出来的,而不是事后编一段“符合思考格式”的废话;
  • 错误修正必须存在且正确。如果只教模型“我错了”,但修正后的答案也是错的,模型学不到修正能力,反而学会胡说;
  • 多轮循环要有终止条件。模型得学会“什么时候不要再绕了”,不然推理链会无限拉长,落到实际部署里就是死循环。

2.2 用合成数据生成“思考轨迹”的核心方法

这部分的实操很关键。我得坦诚地说:项目早期我尝试过手工标注思考轨迹,效果好是好,但根本扛不住数据量需求。手工写500条就累得不行,而我的目标数据量是1万条以上。所以我把重心转到了合成数据上。

具体流程是这样的:

第一步,准备一批“问题种子”。这些问题不能太简单,不然模型不需要思考就能答对,造出来的轨迹也没有信息量。我用了网上公开的数学推理和逻辑推理数据集(GSM8K的子集、一些离散数学的题目)作为种子池,但不用原始答案,只用原始问题。

第二步,用一个现成的通用大模型(我用的是Qwen2.5-72B-Instruct,主要是它的数学和推理能力在现阶段比较稳)对每道题做“思考采样”。但是这里有个关键细节:不能直接让它输出最终答案,而是要诱导它输出“中间草稿和修正信号”。怎么诱导?我在请求里明确要求模型在给出最终答案前,先写出两步或三步的中间推理状态,并且在推导到一半时,主动检查一次上一步的结论,如果发现问题就标记“[发现错误]”,然后重新推导。

第三步,对所有模型生成的思考轨迹做一轮“质量过滤”。这一步极其重要,因为就算72B也会生成一些逻辑断链、修正后依然错误、或者单纯把题目条件复述一遍当作思考的轨迹。我的过滤规则很直接:

  • 如果最终答案与正常推理得到的结果不一致,扔掉(哪怕中间过程看起来很合理);
  • 如果轨迹里完全没有“[发现错误]”标记或“重新推导”信号,扔掉;
  • 如果最终答案正确但中间推理跳跃太大、缺少至少一个“承接上一步结论”的句子,扔掉。

经过这三步,我从大约15000条原始采样里拿到了8000多条合格轨迹。这个筛选比例看起来不高,但其实很正常,你要是在做类似的任务,不要指望模型一次生成的轨迹直接能用,过滤本身就是数据构造的一部分。

2.3 循环思考数据的正样本与负样本设计

这个项目的训练不能只用“正确轨迹”作为正样本,否则模型只会学会“照着正确格式输出”,不会在犯错时意识到自己错了并修正。

所以我引入了负样本和修正样本的概念,把训练样本分成三类:

第一类是标准正样本。完整的多轮思考链,每一步都正确推进,最终答案合理。这类样本教会模型“循环思考的基本格式”。

第二类是有瑕疵但修正成功的样本。我在轨迹的某一步故意干扰一个数字或者逻辑方向,然后让模型生成“发现问题-修正”的后续内容。这类样本教会模型“我发现上一步不对”的能力。

第三类是“放弃绕圈”的样本。对于某些简单题目,我要求模型在两步思考内直接给出答案,不要为了循环而循环。这类样本防止模型过度谨慎,避免输出过长的思考链。

这三类样本的数量比例,我最后定在6:3:1。这个比例不是拍脑袋的,而是经过小规模试验验证的:正样本比例太低,模型格式学习不稳定;修正样本比例太高,模型变得过度怀疑,明明对了也要自我否定;放弃样本比例太低,模型在简单题上也会绕三圈。6:3:1在1.5B模型上效果最稳。

2.4 训练数据格式设计:让“循环思考”被模型结构接受

数据格式这一块,直接决定了训练能不能跑通。我一开始天真地以为模型只要看到多轮文本就能学会,结果训练出来模型只是在重复输出格式,根本没有“基于上一步结论推进”的能力。问题就出在输入输出的组织方式上。

Qwen2.5这类模型用的是ChatML格式,也就是<|im_start|>user<|im_end|>、<|im_start|>assistant<|im_end|>这种结构。但如果我把整个“思考轨迹”放在assistant消息里,模型会把整段思考当成一次生成目标,中间没有任何强制依赖信号。它可能学会“输出多轮”,但学不会“每一轮都读取上一轮的内容”。

解决思路是使用“隐式推理轨迹展开”。我把模型输入设计成一步步拼接的格式:第一轮先给一个部分思考句,让模型续写;然后我把模型上一步生成的content作为下一轮输入的一部分,再让模型续写下一步。因为我做的是自回归生成训练,所以这其实不需要特殊的模型结构改动,只需要在构造函数时把每一轮的文本拼接成一条长序列,并计算好每个token的掩码(mask),让模型只在“该轮新增的部分”上计算损失。

这里给出一个格式模板,是我在实验中最常用的:

<|im_start|>system You are a cautious assistant. Think step by step and revise if necessary.<|im_end|> <|im_start|>user [问题]:一个正方形的面积是49,求它的边长。<|im_end|> <|im_start|>assistant [思考]:设边长为x,则x的平方等于49,所以x可能是7或者-7。<|im_end|> <|im_start|>assistant [检查]:边长不能是负数,所以排除-7,最终x等于7。答案是7。<|im_end|>

在构造训练序列时,我把第一条[思考]和第二条[检查]都作为生成目标,但掩码只保留assistant部分,system和user部分不参与loss计算。这样模型在生成第二条内容时,它能看到自己刚才输出的第一条[思考],从而学会“把上一步结论作为新输入继续处理”。

如果你准备复现,这部分是必须吃透的核心。光给模型看这些格式它不会做,你必须在构造数据时就让loss“感知”到上一步内容的存在。具体就是要在tokenization阶段,把完整对话拼接成一个序列,然后设置一个label_mask,让assistant内容参与loss,其他内容不参与。很多刚入门的人就是在这一步漏了,导致模型训练出来只会背格式。

3. 训练环境与管线搭建:SFT + 类似GRPO的偏好优化

3.1 为什么需要两个训练阶段,而不是一步到位

在做这个项目之前,我试过只用SFT(监督微调)一步训练,效果非常感人——模型确实学会了输出“思考-检查-修正”的样板文,但推理质量几乎没提升。原因很简单:SFT教的是“说什么”,但没教“知道什么时候该修改”。

这正是我引入第二阶段的原因。第二阶段的目标是让模型学会“选择正确的思考路径”,而不是在思考的路上随意漂移。实现方式上,我参考了最近很火的GRPO(Group Relative Policy Optimization)思路。GRPO的核心是通过一组候选输出(比如同一个问题生成4-8条思考轨迹),用规则奖励函数打分,然后让模型去偏好奖励高的那条轨迹。

这里我想重点说一下SFTTrainerWrapper和GRPOTrainerWrapper这两个工具。它们其实是近年兴起的一层轻量封装,把传统HF的Trainer做了一层适配,让你可以方便地把多个模型的采样、奖励计算、强化学习更新逻辑挂进去。如果你的复现环境支持,我建议直接用它们,省去大量样板代码。不过封装也有个坏处,就是你很难看到内部细节,出了问题排查起来费劲。所以我的建议是:第一步先用裸HF Trainer跑通SFT,第二段再引入GRPO框架。没有第一阶段的稳定输出,直接上强化学习大概率会让模型吐出一堆非法格式的文本,奖励信号几乎是噪声。

3.2 A100单卡环境搭建与显存预算分配

A100 80G听起来很土豪,但实际上如果你不规划显存,照样不够用。这个项目需要的显存包括四块:

  • 模型权重:1.5B参数,BF16大概占3G;
  • LoRA适配器参数:我用了rank=32,占大约0.2G;
  • 优化器状态:常规AdamW的momentum和variance,大概占参数量的8倍,这里约1.6G;
  • 激活值与中间状态:取决于batch size和序列长度,这是最大的开销,也是最容易炸显存的地方。

我在SFT阶段用的关键配置如下:

  • LoRA rank = 32,alpha = 64,dropout = 0.05,作用在所有的attention层和MLP层上;
  • 序列长度 = 1024;
  • 全局batch size = 16(4梯度累积 × 4微批);
  • 学习率 = 2e-4,线性调度,前10%步数做warmup;
  • 优化器 = AdamW,权重衰减0.01;
  • 显存峰值大约在42-48G之间,A100稳稳hold住。

第二阶段GRPO我稍微调低了batch size,因为需要对同一个问题做组内采样,显存开销更大。组大小设为4,即每个问题同时采4条轨迹。微批大小降到1,梯度累积设为8,这样总batch size还是4个问题×4条轨迹=16条。虽然训练步数变少,但每条样本的“信息量”更高,因为带上了奖励信号。

3.3 训练细节:mask策略、损失函数、采样温度

训练细节决定了你调出来的模型是“能看”还是“能用”。我在第一阶段的SFT中,损失函数用的就是标准的交叉熵,但加了针对“循环思考标记”的token加权。具体做法是:对[检查]、[发现错误]这类关键语义token,损失权重乘以1.5。这样模型在生成这些token时会更加谨慎和准确。别小看这个改动,我发现不加权重时,模型确实也有一定概率学会修正,但输出质量波动很大;加了权重后,模型的修正路径稳定很多。

第二阶段GRPO,我用的是规则奖励函数。我用了一大一小两个分数组合:

  • 格式分(0-1分):思考链路是否包含至少一个“检查”步骤,且最终确实给出了结论。纯格式匹配就给0.3分;
  • 正确性分(0-1分):用程序执行方式验证问题答案(我以数学题为主的另一个好处就是答案可以脚本校验),答对给1分,答错给0分。如果中间过程也全对,额外加0.5分;
  • 修正成功奖励:如果模型在思考链里有“发现错误”并且在后面的输出中纠正了过来,加0.5分。如果发现了错误但最后答案还是错的,则罚-0.5分。

最终奖励 = 0.2 × 格式分 + 0.5 × 正确性分 + 0.3 × 修正奖励。

这个权重是我上手调出来的,不一定对所有场景最优,但它在我的数据集上表现稳定。一个经验:不要一开始就把正确性分权重拉太高,否则模型会迅速坍缩到“只输出结论不输出思考”的分布——因为它发现直接抄答案是拿高奖励最稳的方式。需要用格式分和修正奖励“逼”模型保留思考链。

3.4 第一阶段SFT的实操记录

第一阶段SFT我实际跑了大约2.5小时,一共5500步,3200条训练样本。你可能想问,样本量这么少,模型不会过拟合吗?实测下来,LoRA本身就是最好的正则化器,低秩约束天然限制了模型能记住的细节,所以3200条样本对LoRA来说刚刚好。

训练过程中我重点监控两个指标:loss下降曲线和“思考标记token的准确率”。这里提醒一句:loss不是抓得越狠越好。我在训练到第3000步时,发现loss已经降得很平滑,但检查模型输出时发现它开始疯狂使用[发现错误]标记,几乎每句话都带“我重新检查”,这明显是过拟合到了修正样本上。后来我把模型回退到第2500步的checkpoint,再往后的训练都在这个版本上继续。这个“回退到中间checkpoint”的操作,在后续调优中帮了我大忙。所以如果你也是自己跑训练,建议每隔500步存一个checkpoint,不要只存最后一个,万一过拟合就有后悔药吃。

3.5 第二阶段GRPO的实操记录

GRPO阶段的具体流程是:对每个batch的问题,让当前模型采样4条思考轨迹,用奖励函数打分之后,在组内比较哪个轨迹更好。

模型会怎么调整自己的输出分布呢?简单说就是:如果第2条轨迹奖励最高,那模型会加大输出第2条轨迹风格的概率;如果原来的输出分布跟高奖励轨迹的方向一致,就维持甚至加强;如果不一致,就削弱并转向。

这里有一个很常见的坑:奖励函数是规则函数,但规则函数覆盖不到“模型自我修正是否在逻辑上真有效”。一个典型的虚假修正长这样:

[思考]:面积49,边长是7。 [检查]:但也许边长是负数?不可能。 [结论]:7。

模型虽然触发了“发现错误”并纠正,但它只是在Randomly强行插入一个否定,并没有真正的逻辑推进。为了抑制这种“假修正”,我在奖励函数里加了一项:如果检查步骤前后包含的数值或符号完全一致,且没有新增任何推导信息,则修正奖励不生效。这个规则虽然粗糙,但非常有用,能砍掉超过三成虚假修正分支。

第二阶段实际训练时长约2小时。总训练步数2500步,组大小4,换算成样本轨迹量约等于10000条。训练结束后我对比了SFT版本和GRPO版本的输出质量,发现GRPO版本在“修正有效性”上提升了大概20个百分点,这在查表式奖励下已经很难得了。

4. 循环推理评估:怎么判断它是真会还是背模板

4.1 评估指标设计:不能只看最终答案正确率

如果你只用“最终答案正确率”来评估循环思考模型,会严重误判。因为一个只会背模板、没有任何修正能力的模型,在简单题上也能拿高分。而真正需要关注的是:它在关键时刻有没有真的识别错误并修正。

我给自己定了三个评估维度,分别来拷问模型的三项能力:

  • 任务完成度:最终答案是否正确,占40%权重,这是底线指标;
  • 纠错必要性识别:在人为设置了“上一步结论错误”的场景下,模型有没有正确触发纠错信号,占30%权重,因为这是循环思考的核心价值;
  • 思考效率:完成任务所需的思考轮数。如果模型在简单题上也绕了三圈,说明它无脑循环,占30%权重。

这三个维度合起来,才能回答“模型到底会不会循环思考”而不是“模型有没有输出循环文本”。

4.2 在评测集上展示的真实推理过程

下面放一个我在评测集上随机抽到的实际输出,人工对比判断效果:

问题:一个长方形的长是8,宽是5,面积是多少?

模型的输出(原样保留):

[思考]:面积 = 长 × 宽 = 8 × 5 = 40。 [检查]:面积计算没有单位转换问题,数值没问题。 [结论]:40。

这个回答在“思考效率”上表现优秀,因为一理解就锁定答案,没有无脑绕圈。再看另一条更复杂的:

问题:x + 3 = 7,x等于多少?

模型的输出:

[思考]:x = 7 - 3 = 4。 [检查]:代入x=4,4+3=7,匹配原方程,正确。 [结论]:4。

这个回答好在它做了“代入验证”,不是干巴巴给个答案。这说明模型学会了把检查步骤当成真正的“验证机制”,而不是一个空壳标签。

当然也有翻车情况。比如模型偶尔会在简单问题上过度思考:

[思考]:x = 7 - 3 = 4。 [检查]:但也许x=10?让我重新计算。 [重新思考]:7-3=4,x=4。 [检查]:确认代入正确。 [结论]:4。

这个输出最终答案对了,但思考轮数明显大于合理需求,这就是典型的过度循环。我的GRPO奖励函数虽然做了频率惩罚,但还没完全根治。这个现象也提醒我:循环思考不是越多越好,而是“该省则省,该绕则绕”。

4.3 人类评估与自动化评估的结论

我最终在300道题的测试集上做了自动化评估,另外抽了50条做了人工盲评。自动化评估结果大致是:

  • 最终答案正确率:78%(基线模型只有61%);
  • 纠错信号触发率:在人为引入错误的场景下,有大概30%的题目标记了[检查]或[发现错误],但其中约8%属于“假修正”(修正动作对最终答案没实质影响);
  • 平均思考轮数:1.8轮。

这个结果我算是满意的。对比基线模型61%的正确率,78%已经说明“循环思考”这个行为在小模型上是有收益的,而且不需要在推理阶段额外算力成本(虽然训练阶段成本更高)。

人工盲评的结论也很有意思:评审者普遍反馈“模型输出的思考过程虽然简短,但逻辑上一致性比之前只用SFT训练的版本强很多”,尤其是“代入验证”这类行为,像一个真的在自检的人。

5. 训练踩坑实录:六个最容易翻车的细节

5.1 数据拼接错误导致模型输出乱码

这个坑几乎人人都会踩。如果你在构造多轮对话序列时,拼接顺序出错,比如把第二轮assistant内容放在了第一轮之前,模型会学到完全错乱的生成顺序。训练时loss会莫名降不下去,生成输出就像“前一句和后一句完全对不上”。排查方法很简单:直接打印tokenized之后的输入序列,用分词器把它解码回文本,肉眼看一遍顺序对不对。千万不要直接拿数字tensor去训练。

我在做了这个检查之后才发现,自己第二轮的输入spacing少了换行符,导致模型把“检查”内容紧贴在第一轮结尾的<|im_end|>后面输出,模型生成出来全是拼接乱象。一个换行符引发的血案,排查了一个晚上。

5.2 奖励函数照顾不到语义逻辑

GRPO的奖励模型设计是第二个重灾区。如果你只给“最终答案对不对”打分,模型很快学会一个投机取巧的策略:在思考阶段输出大量看似高深但没有实际意义的内容,因为思考阶段不直接进费效公式。想要避免这种“空思考”现象,必须在奖励里对思考链长度和工作量做惩罚。我最后的做法是,如果思考轮数超过2轮,最终得分乘以0.9;如果思考阶段含有重复的结论复制,乘以0.85。虽然粗暴,但抑制效果明显。

5.3 长序列训练时显存爆炸的处理

A100有80G,但你以为这就稳了?我在GRPO阶段采样4条轨迹时,每条轨迹长度可能达到1024个token,如果还启用了梯度累积和checkpoint,显存峰值可以直接冲到75G以上。解决办法是启用gradient checkpointing(重计算)和混合精度训练(BF16)。注意,BF16 + A100能发挥最好效果,FP16累加精度稍低,训练容易震荡,我实际测下来BF16首选。

另外一个小技巧:把PE(位置编码)部分换成RoPE的缓存离线模式,可以省一点显存,不至于为了省几百M去砍batch size。

5.4 模型学会了“为了检查而检查”

这是我这个项目里最头疼的问题,也是“循环思考”训练最难平衡的地方。加入纠错样本的比例太高,模型会学会每句话都自我怀疑一遍。不只是结果上多绕圈,而是它会在简单任务里也输出很多“矛盾-否定-再肯定”的废话。处理方式有两个方向:一是降低负样本比例(从4:4:2调整为6:3:1),二是像上文那样在奖励函数里加处罚。两者配合,效果才好。

5.5 checkpoint策略不当导致前功尽弃

我一直强调每隔500步存一个checkpoint。为什么?因为强化学习阶段训练很容易出现“奖励飙升后迅速崩溃”的悬崖。模型在某一步开始输出策略大改,导致生成格式不合规,奖励掉到谷底。如果没有中间checkpoint,你就得回退到SFT阶段重跑,心态直接崩。我实际训练时踩了两次悬崖,都是靠回退到几千步之前的checkpoint救回来的。

如果你准备复现,建议至少保存以下checkpoint:SFT阶段每1000步存一次;GRPO阶段每500步存一次。存文件不大(几G一个),但能救命。

5.6 评估指标和训练目标错位

最后一个容易忽略的问题:你的训练目标是“最大化奖励”,但你真正关心的是“人类觉得它会不会思考”。这两者不一定一致。所以我在训练过程中每500步做一次在线人工抽测,而不是等全部训练结束才评估。抽测方法是让模型回答10道评测集题目,我看输出轨迹里有没有真正的逻辑推进、有没有虚假修正。这比任何自动化指标都快。我有一次GRPO训练导致奖励一直在涨,但实际上模型已经塌缩到“大量输出’我错了’然后什么都不改”,只靠自动指标完全发现不了。

6. 复盘总结与实操建议

写到这里,这个项目的核心环节基本都讲完了。最后聊点我个人的体会和下一步可以怎么扩展。

以我几天来反复调参的经验,这种“循环思考”能力,真正难的不是模型架构和训练框架,而是你对“思考”的定义和数据结构的设计。我希望你特别记住这一点:模型的循环能力是你用数据一点点“灌”出来的,不是它在训练里灵光一现自己长出来的。你给的轨迹质量怎样,它的思考模式就怎样。你给的都是单步就答对的题,它就懒得思考;你给大量“按部就班推导-代入验证-修正”的样本,它就学会这套流程。

关于未来扩展,我目前测试了两个方向都挺有潜力:

一是把这个“循环思考”能力跟工具调用(tool use)结合。让小模型不只修改自己的文本推理,还能在思考链中主动决定“这一步需要调用工具验证”,比如程序执行或者查表,然后基于工具结果继续推理。我试过简单原型,效果比纯文本修正更强。

二是把检查信号从文本token改成“特殊控制token”。比如专门分配一个小量的嵌入向量表示“检查上一轮”,而不是每次都让模型生成“检查”这两个字。这样思考过程更紧凑,模型不会陷入用自然语言写检查步骤导致的长输出现象。这个改法对推理延迟也更友好。

如果你只是想快速复现一个baseline,我建议你按照这个顺序走:先用Qwen2.5-0.5B做数据管线验证(速度快、踩坑便宜),数据验证没问题了再换1.5B正式训练。千万不要一上来就用大模型做大工程,除非你时间多到用不完。

最后放一个送分建议:把数据构造脚本、训练脚本、评估脚本全部模块化,数据和学生分开存,训练任务每次改参数都单独建一个实验目录。这个习惯救了我太多次,尤其当你同时在对比不同数据配比、不同奖励权重的时候,没有实验目录管理,你根本分不清哪个版本是最优的。

这个项目的模型权重我暂时还不会开源,因为里面用了不少从公开题库抽出来的题目,授权边界我还需要再确认一下。但训练代码和数据构造逻辑我可以整理好发出来,如果你真的照着做了一遍,欢迎来跟我对一下评测指标,看看你的“循环思考”模型在纠错触发率上能不能干过我这版。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询