手把手分享一些这些年我折腾大模型训练时总结出来的SFT与RL细节。很多人第一次接触大模型微调,都是从跑通一个脚本开始的,跑通简单,跑出稳定效果才是真正的门槛。SFT(监督微调)和RL(强化学习)是大模型从“会说话”走向“会做事”的两个关键阶段,也是训练调优中最容易出问题的两段。这篇我尽量不用抽象公式,而是用我自己实际调模型时踩过的坑、试过的参数、看过的曲线来讲,希望给准备入门或正在调优的你一些能直接上手的参考。
1. 大模型SFT与RL到底在做什么:先别急着调参
1.1 一句话说清SFT和RL的分工
SFT全称是监督微调,本质是在预训练好的基座模型上,用一批“问题-答案”文本对做正常的语言建模训练。模型看到的是完整文本,学习的是“当前这个位置后面最可能出现的词是什么”。你把“1+1等于几”和“2”放在一起给模型看,多喂几遍,它就学会了这个映射关系。
RL是强化学习,逻辑和SFT完全不一样。RL不直接给模型“标准答案”,而是让模型自己生成一段回答,再根据这段回答的质量打一个分数(奖励),模型根据分数去调整自己生成内容的策略。如果说SFT是跟着教练模仿动作,RL就是让你自己上路开车,开得好不好由考官打分。
很多人以为两者是同一个东西,或者以为SFT之后接一个RL脚本就能自然变好,其实它们的训练目标完全不同,参数调整逻辑也不同。不理解这点,后面调参就会全靠玄学。
1.2 完整的训练链路里,它们各自解决什么问题
大模型从预训练到落地,通常要经过预训练、SFT、RL三个阶段。预训练阶段给模型注入了海量的语言知识和世界常识,但基座模型只会续写文本,不会好好回答问题。你问它一句“如何评价一部电影”,它可能给你写一段像散文又像剧本的东西,而不是正常回答。
SFT的角色就是“规范格式和语气”。它让模型学会听指令、按照规定的对话模板回话,并输出人类习惯的表述结构。但SFT也有局限,最典型的是:模型会过度贴合训练数据里的表达方式,遇到没见过但类似的问题时,容易一本正经地胡说八道,而且特别容易重复。
RL在这个位置接手,目标是让模型在生成式这种多答案空间中,学会权衡什么是更好的回复。比如礼貌程度、信息密度、是否拒绝不合理请求,这些很难用逐字逐句的交叉熵来定义,RL则可以通过奖励信号来优化。简单说,SFT是教它“怎么张嘴”,RL是教它“怎么说更合适”。
1.3 训练目标与损失函数的本质区别
SFT的损失通常采用交叉熵,而且只计算答案部分。模型每生成一个token,都和标准答案的那个token比一次,算一个分类损失。这个损失非常直接,梯度稳定,这也是SFT训练起来相对平和的原因。
RL优化的目标是期望奖励最大化。模型采样一整段内容,整段内容获得一个分数,但中间每个token到底为该分数贡献了多少,并不确定。所以需要引入策略梯度、优势估计、KL散度这些机制,把整段奖励拆解回每个token上。
这两种目标差异导致了一个常见现象:SFT阶段loss降到非常低,不代表RL一定效果好。模型可能过于自信,输出固定模板;RL阶段reward升高,也不代表内容变好,可能只是学会了“说长句子刷分”。所以调参时不要只盯一个指标,要结合生成样本一起看。
2. SFT阶段:数据、损失和超参,三个容易被忽视的细节
2.1 指令数据的构造模板与mask策略
做SFT第一步是把原始对话构造成模型能稳定学习的模板。比如把用户输入包在一个特殊分隔符里,把助手回答包在另一个分隔符里。模板写起来简单,但坑特别多。
最常见的问题是mask没有做对。训练时你要把prompt部分的token设置为不计算损失,只让答案部分参与梯度更新。如果对整个文本序列都计算损失,模型会学着去生成用户说的话,比如自己造出一个“用户问”,看起来也能跑通,但会导致推理时前言不搭后语。实现方式一般是在label里把prompt位置设为-100,或者在注意力掩码上做处理。
这里提醒一句:模板里特殊分隔符前后是否有空格、有没有换行,训练和推理时都要完全一致。曾经有人训练模板里是(用户:xxx\n助手:xxx),推理时把冒号改成全角,效果就掉了不少。这不是模型问题,是token切分变了。
另一个容易被忽略的是多轮对话。多轮数据中,每一轮助手回复都应该参与loss计算,用户新一轮输入不参与。你不能只对最后一轮答案做监督,否则模型学不会“记住前文”。
2.2 数据数量与质量的取舍
SFT到底要多少条数据?我看过很多项目,以为数据越多越好,恨不能堆上百万条。从我实际对比经验来看,在基座模型已经很强的前提下,SFT数据质量的影响远大于数量。一个项目用8k条精心筛选、去重、分布均衡的数据,效果比用80k条从网上扒来的低质重复数据好很多。
原因在于基座模型本身已经掌握大量语言和世界知识,SFT数据给模型的主要是“交互方式和格式示范”。它不需要从这些数据里重新学习知识,只需要知道“当用户这样问时,我应该怎么组织回答”。
那什么算高质量SFT数据?第一,答案内容准确,不培养幻觉;第二,格式统一,不出现一段对话里三种回答风格;第三,指令多样,覆盖简单问答、复杂推理、拒答场景;第四,重复率低,相似的指令最好聚类后只保留代表性样本。简单任务和困难任务的比例也要控制好,我一般会让困难任务占六成以上,并且至少留5%到10%的对抗样本,让模型学会承认不知道或拒绝不合理请求。
2.3 学习率、batch size和epoch怎么设
SFT阶段有几个起步参数可以参考。全参微调时学习率通常设在1e-5到5e-6之间,低秩适配方法(也就是大家常说的LoRA)可以放大到1e-4左右。迭代轮数不要设太多,我习惯先跑3轮,数据量少且干净时1轮也够。超过5轮还往上涨,很容易让模型把训练集原话背出来。
batch size和显存、稳定性相关。实际训练中一个step的batch一般是32到64条样本,显存不够就加梯度累积。但要明白,梯度累积只是把优化器更新次数减少,并不改变数据分布,所以学习率反而可能需要略微调大。
warmup建议设置5%左右,让模型先从较小的学习率慢慢热身,避免初始几步就把参数推飞。不同规模模型、不同框架下最佳参数会有差异,但上面这组值适合作为第一版跑通的基线。把基线跑通之后,每改一个参数就对比一组实验,不要同时动两个变量,否则出了问题根本定位不到原因。
3. RL阶段:选择合适的对齐方案,比堆理论重要
3.1 三类常见RL方案的适用场景
RL阶段现在有多种可选方案,每个方案都有自己的适用场景。
经典策略梯度方法(比如PPO)需要额外训练一个奖励模型,再用策略模型、参考模型、价值模型等多个组件一起做优化。它的优势是上限高,能够处理复杂的对话质量、安全性等主观评价。坏处是工程复杂度高,超参敏感,训练过程中时不时就出现价值模型不稳定或者KL散度爆炸。适合团队有足够算力和工程能力的场景。
DPO思路不同,它省略了显式奖励模型,直接用偏好对(好回答/坏回答)去对比学习。实现简单,很多开源工具都能跑,适合你手里已经有成对偏好数据的场景。不过DPO对偏好数据质量很敏感,如果“好”和“坏”本身区分度不高,模型很容易学歪。
GRPO是较新的简化,它去掉了价值模型,用同一个prompt采样多个回答,直接从组内相对好坏计算优势。这个方案在数学、代码等能用规则判断对错的任务里特别实用,稳定性和显存开销都比经典方法友好。我做的很多客观题场景,首选就是GRPO加规则奖励。
不要因为某个方案名气大就一定选它。任务有客观标准,优先规则奖励加轻量策略优化;任务主观,再考虑训练奖励模型做偏好优化。
3.2 奖励信号:从规则reward到学习式reward
RL最关键的不是训练算法,而是奖励信号。奖励给得不对,算法再先进也会学偏。
如果任务有客观答案,强烈建议使用规则奖励。比如数学题,可以定义格式分和答案分:只要模型按照“步骤+最终结论”的格式输出,给0.5分;最终答案正确,再给0.5分;全部正确得1分;中间过程写一半给0.3分。规则奖励透明、稳定、可复现,也不会自己变异。
如果任务没有客观答案,比如闲聊质量、情感温度、内容安全,就要训练一个奖励模型。奖励模型的本质是一个二分类偏好模型,给它一对回答,它预测哪个更好,训练完成后对单条回答输出一个分数。这个分数会作为RL的信号。
奖励模型的问题是它也会被“刷”。模型很快能发现哪些表达方式容易获得高分,然后所有回答都朝那个方向偏移。常见刷法包括:无意义的长句、过度礼貌但空洞、堆叠高频词。因此在训练奖励模型时需要定期计算它与人工评分的一致性,并且放到RL环境中做抽检。
3.3 KL散度、参考模型和温度采样:防止模型跑飞
很多人第一次跑RL,把策略模型扔到环境里,发现训练没几步,输出就开始胡言乱语。这是因为只做了奖励最大化,忽略了约束。
标准做法是保留一个参考模型,通常就是SFT后的模型,在每次更新时比较当前策略模型和参考模型对同一段输出给出的概率分布。如果两者差异太大,就在损失里加一个惩罚项,这个惩罚就是KL散度。
KL系数是RL里最需要盯的参数,一般初始值在0.01到0.1之间。系数太小,模型会肆无忌惮去钻奖励的空子;系数太大,模型几乎不敢偏离参考模型,RL等于白做。我通常先设置0.05,训练几百步后观察KL曲线再调整。
采样温度同样会影响探索。可以在RL采样阶段设置0.7到1.0的温度,让同一个prompt生成更多元化的回答,增加策略探索空间。但训练更新时不是简单地直接“生成得更随机”,采样多样性也会受到top_p和重复惩罚的影响,这些参数要和KL系数一起看,不能孤立调。
3.4 探索参数、批次与采样
在基于采样的强化学习变体里,每个prompt采样多少个回答是一个关键参数,通常用G表示。比如G=4表示一个问题生成4条候选回答,然后根据组内相对情况计算奖励优势。G太小,优势估计噪声大;G太大,时间和显存开销成倍增加。常见选择是4、8、16,我一般会从8开始。
mini-batch不宜太大也不宜太小。一个更新批次里同时包含多个prompt,通常8到32个合适。太大容易让策略更新滞后,太小则梯度不稳定。
经典策略优化还有几个默认参数:裁剪范围通常用0.2,控制单次更新的幅度;折扣因子常取0.99,优势估计参数取0.95;策略模型在RL阶段的学习率建议降到1e-6到5e-6。这些值看起来很小,但强化学习本身就要求参数更新克制,跳太大很容易直接让模型退化。
4. 训练稳定性:loss不降、困惑重复、reward飙升怎么查
4.1 六个关键监控指标
很多朋友训练时只盯一个总loss,这是最容易让人误判的做法。RL阶段尤其不能只看一个量,因为奖励和KL是拉扯的,总loss下降不能说明效果好。
我习惯至少监控六个维度的曲线:策略损失、KL散度、奖励均值、生成响应平均长度、梯度范数、token预测熵。策略损失突然变大,常见原因是单次更新步长过大;KL散度持续上涨,说明模型在偏离参考模型,需要调大KL系数或者减小学习率;奖励均值下降但KL也下降,可能是惩罚项压过了奖励信号,这时要检查是不是KL系数设大了。
平均响应长度是一个很隐蔽的指标。如果它一路攀升,模型很可能学会了用冗长废话刷分。token预测熵如果太低,说明输出模式固化,需要增加采样随机性或补充数据的多样性。
4.2 常见故障速查表
这里整理一份我自己反复用的问题排查表,可以保存下来对照:
| 现象 | 常见原因 | 调整方向 |
|---|---|---|
| SFT loss不降 | 学习率太低或数据噪声太大 | 适当调高学习率,清洗明显错误数据 |
| SFT loss降但输出重复 | 数据重复多或epoch过大 | 数据去重,减少训练轮数 |
| RL阶段KL发散 | KL系数太小或策略学习率过大 | 调大KL系数,降低学习率 |
| reward上升但回答质量变差 | 模型在刷奖励 | 增加KL惩罚或长度惩罚,人工抽检生成 |
| 模型开始输出无意义符号 | 生成长度失控或训练不稳定 | 检查奖励设计,限制最大生成长度 |
| 显存频繁溢出 | 序列太长或全参微调开销大 | 开梯度检查点,用低秩适配或量化训练 |
排查问题时有一个原则:先看生成文本是不是符合基本格式。如果格式都乱了,先修训练稳定性和解码参数,不要急着调reward函数。
4.3 显存不够时的降本主线
训练大模型最现实的问题就是显存。我的建议是不要一上来就追求全参微调,先用低秩适配加量化方式把流程跑通。这种方式的显存占用可能只有全参的几分之一,效果在大多数任务上已经足够好。
如果低秩适配效果不够,可以逐步加秩,或者把模型最后几层改为全参训练。实践中这个方案往往能在不大幅增加显存的情况下显著提升效果。
另外两个必备操作:开启梯度检查点,用少量计算换显存;开启混合精度训练,能用半精度的地方就不要用全精度。长序列数据也要处理,不要把所有样本都塞到最大长度,合理的截断更利于训练。这些都是降本主线上的常规操作,虽然听起来不酷,但非常实用。
5. 从0到1的调参参考方案:一个小模型案例
5.1 第一步:SFT基线怎么跑
用一个“数学问答助手”的模拟项目作例子。基座选择7B规模的开源模型,准备5000条高中数学题,每条包含题目、分步解题过程和最终答案。按95比5切分训练集和验证集,模板统一成“用户提问+助手分步骤回答”。
SFT建议参数:学习率1e-5,训练4轮,有效batch 32(显存不够则用8加4步梯度累积),最大长度1024,warmup 5%。跑完在验证集上抽20题人工检查,不要只看loss。注意看模型是否真的按步骤推理,还是只记住了训练集里的表面话。
如果验证集loss很低但推理质量差,大概率是数据里“过程不完整”或“过度重复相似题”,优先补充过程详细的题目,而不是增加epoch。
5.2 第二步:选择轻量的RL对齐路线
数学题有明确标准答案,所以奖励可以采用规则方式:格式包含“步骤”和“结论”两部分得0.5分,最终答案与参考答案一致再得0.5分。这样模型就知道,既要过程规范,也要结果正确。
我选择组内采样对比的策略优化方式,每个问题采样8个回答,策略模型学习率设为1e-6,KL系数初始0.05,参考模型是SFT后的模型且全程不更新。训练800步后观察KL曲线和“格式通过率”。如果KL涨得很快,先调回学习率或加大KL系数,不要急着加训练步数。
这个流程跑起来后会发现,模型开始为了一道题写出更完整的推导,这正是奖励信号在起作用。但你要时刻警惕:它是否开始用大量不相关的铺垫来换取格式分。
5.3 第三步:评估与bad case复盘,把调优变成循环
训练结束后,不要只汇报reward均值上升了多少。准备一个30到50条样本的测试集,把生成结果逐条看。数学任务重点看两点:最终答案正确率、解题步骤是否清晰。把错误case分成三类:格式问题、推理错误、莫名冗余。
格式问题回去修训练模板或解码参数;推理错误看是不是训练数据里缺少类似题型;莫名冗余大概率是被格式奖励带偏了,考虑调整奖励函数或增加长度惩罚。把这些问题反馈到数据和奖励设计里,重新训练一轮。
一套稳定的调优流程应该是循环而不是单向。每次只改动一个因素,对比前后效果,才能知道到底是数据起作用还是参数起作用。我自己踩过最大的坑就是一次性改了好几个变量,最后训练好了却不知道是因为哪一步做对了,导致无法复制经验。
就我个人的操作体验来看,大模型SFT与RL的调优其实并不神秘,只是容易被“跑通脚本”和“看总loss”骗过去。真正有效的做法是:先把数据模板和mask做好,把RL的奖励定义清楚,再盯住关键指标和真实生成样本来迭代。最后分享一个小技巧:跑RL之前,一定要拿SFT模型对每个prompt多采样几次看看分布。如果SFT出来的回答已经千篇一律,先回去补数据多样性,别急着上RL,否则强化学习只会把重复模式当成高分模板,越学越窄。