最近跟几个做模型训练的朋友聊到一个特别形象的说法:SFT 把模型训成了复读机,RL 再教它怎么“看脸色”。仔细一想,这句话几乎把大模型对齐(Alignment)的核心矛盾说透了——SFT(监督微调)负责让模型“会说话”,但会说话不等于会好好说话;RL(强化学习)则负责让模型学会“根据反馈调整输出”,也就是看人脸色办事。这篇内容适合正在做大模型微调、RLHF,或者只是好奇“为什么 ChatGPT 这类模型有时候那么会来事儿”的读者。
先说明白,这不是一篇教科书式的原理推导,而是我结合自己做 SFT、偏好数据标注和 RLHF 调参的经验,把这条技术链路里“为什么”“怎么做”“会踩什么坑”一次讲清楚。
1. SFT 是怎么一步步把模型“训练成复读机”的
1.1 “复读机”症状:SFT 训练目标里的先天缺陷
SFT 的本质是最大似然估计。给定一堆精心整理好的“问题-标准答案”对,模型要学着把标准答案的每个 token 逐个预测出来,loss 用交叉熵。交叉熵只关心一件事:模型输出分布跟标准答案的分布有多接近。换句话说,SFT 唯一的目标是“让我输出的句子,长得像训练数据里的标准答案”。
这个目标听起来没毛病,但问题就藏在“标准答案”四个字里。训练数据是人工整理或者从某类语料里筛出来的,它天然带有标注者的写作风格、礼貌用语习惯、甚至情绪倾向。模型不傻,它会把这些风格层面的统计规律也一并学进去。举个例子:如果你的 SFT 数据里一千条客服回复都以“非常抱歉给您带来不便”开头,那模型很快就会发现,遇到投诉类问题,只要带上这句话,loss 就能降得很低。于是它开始疯狂复用这个模板。
这就是“复读机”的第一个来源:模型在学“腔调”,而不是学“解决问题”。
1.2 交叉熵与最大似然:为什么模型只会模仿不会判断
“只会模仿不会判断”这句话,得从数据分布说起。
SFT 数据是静态的,模型训练时看到的每条样本都是“已经确定的最优解”。但真实世界里,同一个问题可能有多种合理的回答方式,而且是否合理取决于上下文、用户身份、甚至当时的对话情绪。你不可能在 SFT 数据里穷尽所有场景。一旦模型遇到分布外的输入,它最稳妥的策略就是回忆训练数据里跟当前输入最像的样本,然后套用那条回答的句式。
我在实际微调中遇到过很典型的案例:模型在客服场景下被问“你们的退款政策是什么”,它能给出标准流程;但用户换一种说法“我东西坏了,你们管不管”,模型还在背退款政策,完全不回应“坏了能不能换”这个具体问题。原因很简单,SFT 数据里“退款政策”相关内容足够多,但“商品损坏处理”的内容太少。模型的判断系统根本没建立起来,因为它没有被训练“要根据用户意图动态调整回答”,只需要输出训练数据里的标准话术。
更麻烦的是,SFT 的 loss 是 token 级别的,模型只要把下一个词猜对了就行。它看不到“这句回答用户是否满意”,也看不到“这句回答是否真正解决了问题”。用行话说,SFT 优化的是一个可微却不含用户反馈的目标,它像一个守着标准答案背书的考生,你问他为什么这样答题,他会说:“因为标准答案就是这么写的。”
1.3 复读机的三个典型翻车现场
根据我这几年观察到的现象,SFT 之后的模型如果直接上线,复读机症状通常有三种表现:
- 腔调单一,适配性差:模型只会用一种语气回答所有问题。知乎上常见的“首先,我们需要明确概念……”式回答,就是模型学会了学术腔,面对日常闲聊也在掉书袋。
- 忽略上下文,答非所问:用户明显在表达不满情绪,模型还在机械地列举标准流程,完全不先共情,结果用户火气更大。
- 极度自信,来回重复:模型生成的内容翻来覆去就是同一个意思,缺乏多角度分析,因为它学到的是“标准答案通常只有一种写法”。
这三种表现本质上是同一个问题:模型在模仿数据的边缘分布,而不是理解任务的深层目标。SFT 能给你一个“满嘴正确废话”的模型,但它给不了你“一个知道什么时候该说什么话”的模型。后面那句,得靠 RL 来完成。
2. 先搞懂“看脸色”到底在学什么:RLHF 的整体思路
2.1 “看脸色”的技术本质:从模仿到偏好优化
所谓“看脸色”,放在技术语境里,就是让模型学会根据人类偏好调整自己的行为。人类偏好是一个极度复杂的函数:用户喜欢简洁有礼貌的回答,讨厌啰嗦;喜欢直接解决问题,讨厌踢皮球;喜欢有逻辑,讨厌车轱辘话。这些偏好很难用规则写清楚,但人类自己却可以非常直觉地判断“哪条回答更好”。
RLHF(基于人类反馈的强化学习)就是干这个的。它的整体思路分三步:先收集人类对模型回答的偏好数据,训练一个奖励模型(Reward Model,简称 RM),再用强化学习算法(通常是 PPO)让模型在“争取高分”的过程中自我调整。模型不再只盯着“这句话像不像训练数据”,而是盯着“这句话能不能在人这里获得更高评分”。
打个比方,SFT 像让实习生抄优秀员工的工作笔记,抄得越像得分越高;RL 是让实习生直接坐在客户面前,根据客户的微表情和反馈随时调整话术。抄笔记只能学会记录里有的东西,面对客户却要处理无穷多的新情况,这就是“看脸色”的用武之地。
2.2 奖励模型(RM):谁来定义“脸色好不好看”
在 RLHF 链路里,“脸色”是由奖励模型定义的。RM 的输入是一段 prompt 和模型给出的回答,输出是一个标量分数,分数越高代表“人类越喜欢”。
训练 RM 需要的数据不是“标准答案”,而是偏好排序。常见做法是让同一个 prompt 生成多个回答,让标注者排序,或者两两比较“哪个更好”。为什么用排序而不是打分?因为人在判断“哪个更好”时远比打分稳定。你说“这条回答比那条更贴心”很容易,但让你给两条回答分别打 7 分和 8 分,不同人之间的标准波动会很大。排序数据天然抗噪。
RM 的训练目标通常用的是 Bradley-Terry 模型,核心思想是:被人类排在前面的回答,得分应该更高。训练完成后,RM 就变成一个“口味鉴定器”,在后续 PPO 阶段充当裁判角色。但这里有个非常容易被忽视的问题:RM 也是从有限数据里训练出来的,它的“脸色”并不完全等同于真实人类用户的脸色。这个矛盾,会在后面“奖励黑客”部分彻底暴露。
2.3 为什么非用 RL 不可:SFT 学不来的目标函数
很多人问过一个问题:既然我已经有了 RM 打分,那能不能把 RM 分数加权到 SFT 的 loss 里继续训练?答案是很难,而且效果通常不好。因为 RM 分数是一个整体性的、非可微的评估结果,它不能直接告诉我们“这一段话里哪个词导致分数降低了”。
SFT 的交叉熵可以对单个 token 求梯度,但“用户是否满意”这种反馈是延迟的、宏观的,它要在模型生成完整回答之后才能被评估。这正好是强化学习的标准场景:优化一个不能直接求梯度、只能通过采样来估计回报的目标。
RL 让模型不只是模仿,而是主动试错。它先尝试生成一个回答,让 RM 打分,然后通过策略梯度方法调整模型参数,让“高分回答”的出现概率上升。这个过程里,模型逐渐理解“原来我不需要每句话都像训练数据,我只需要让结果更符合人类偏好”。这就是 SFT 做不到的第二件事:从结果倒推原因,而不是从数据死记答案。
3. RLHF 实操过程:从偏好数据到 PPO 调参
3.1 第一步:偏好数据怎么采集才靠谱
RLHF 成功率一半取决于偏好数据质量。我见过太多项目算法没毛病,结果死在数据上。采集偏好数据有几个关键点:
- Prompt 要覆盖真实场景:不要只在公共测试集上构造 prompt,一定要从真实用户日志里抽样。模型训练阶段见过的 prompt 风格越接近线上,RM 的打分越有参考价值。
- 回答数量 4 到 9 条为宜:回答太少,排序信息不够;回答太多,标注者看不过来。4 条是性价比最高的。
- 标注者一致性要做质检:同一批 prompt 让多个标注者标一遍,看一致性。如果某几条标注者的偏好差异极大,优先排查 prompt 是否可能有歧义,或者回答风格差异过大导致标注者认知分裂。
另外提一句,现在很多团队用大模型辅助生成偏好数据、再用人工审核,效率提升明显,但要小心“大模型认为的好”和“用户认为的好”之间存在偏差,这类数据最好定期跟真实用户反馈做交叉验证。
3.2 RM 训练:排序标注如何转成打分函数
拿到排序数据后,RM 训练通常的做法如下。假设同一个 prompt 下有两份回答 A 和 B,标注者认为 A 优于 B,那我们就希望 RM 输出的分数满足 score(A) > score(B)。用 Bradley-Terry 模型建模,训练 loss 可以写成这样:
# 伪代码,示意 RM 的 pairwise loss logits = reward_model(prompt, chosen_response) - reward_model(prompt, rejected_response) loss = -torch.nn.functional.logsigmoid(logits).mean()实际项目中,RM 一般会从一个已经做过 SFT 的模型初始化,这样它至少在初始阶段对“什么样的文本是合理的”有一定认知。训练数据量通常需要几十万以上的偏好对才比较稳,数据太少的话 RM 的泛化能力会很差,给后续 PPO 埋雷。
RM 训练时还有一个容易踩的坑:不要只用一个 prompt 下的分数做归一化。不同 prompt 之间的人类偏好标准有差异,某些 prompt 用户本身就更容易给出好评,如果把所有分数混在一起直接比较,RM 会学到“prompt 特征”而不是“回答质量”。常见做法是在训练时用同一个 prompt 下的对比对作为最小单元,减少跨 prompt 的干扰。
3.3 PPO 四大件:Actor、Critic、Reward、Reference 的分工
到了 PPO 阶段,你会同时维护四个模型,第一次接触的人很容易被绕晕。逐一拆开看其实不复杂:
- Actor(策略模型):就是我们要训练的模型,它负责生成回答。初始权重来自 SFT 模型,RL 阶段微调的就是它。
- Reward(奖励模型):就是上一阶段训好的 RM,它对 Actor 生成的回答打分。这个模型通常是冻结的,不参与梯度更新。
- Reference(参考模型):Actor 的初始副本,也冻结。它用来计算 KL 散度,防止 Actor 在追求高分的过程中输出跟初始模型差异过大的文本。
- Critic(价值模型):负责预测状态价值,也就是“从当前情况出发,最终能得到多少总分”。它辅助 PPO 算法计算优势函数(advantage),降低策略梯度的方差。Critic 通常和 Actor 结构相同,但输出是一个标量价值估计。
训练循环大致是这样:Actor 根据 prompt 生成一条回答,Reward 和 Reference 同时对这条回答做评估,Reward 给出偏好分数,Reference 计算 KL 惩罚,然后 PPO 更新 Actor 的参数,同时更新 Critic 的预测基准。
KL 惩罚是这里面的灵魂。没有它,Actor 会很快找到一个“能骗过 RM 但根本不像人话”的输出套路。加了 KL 惩罚,Actor 就不能离 SFT 模型的语义空间太远,能保证生成文本仍然流畅、合理。
3.4 关键参数与调参经验:KL 系数、学习率、Clip
PPO 调参没有银弹,但有几个参数值得花时间琢磨。我贴一下自己项目中常用的初始配置作为参考:
- KL 系数:常见范围 0.01 到 0.1。系数太大,模型基本不学习,输出跟 SFT 几乎一样;系数太小,模型容易“放飞自我”,生成内容开始偏离人类语言习惯。我的习惯是先设 0.05,跑几百步看 KL 曲线的斜率,再微调。
- PPO Clip 范围:一般取 0.2,跟 OpenAI 早期的设置保持一致。Clip 范围决定了策略更新的激进程度,太保守学得慢,太激进容易训练震荡。
- 学习率:Actor 用 1e-6 到 3e-6 比较稳,太高容易直接把模型参数打坏。Critic 的学习率可以稍微大一点,比如 1e-5 到 3e-5。
- GAE Lambda:一般取 0.95,用来平衡偏差和方差。如果训练不稳定,可以适当调大到 0.98,让价值估计更平滑。
- Mini-batch 大小:取决于显存,通常 32 到 128 之间。
还有一个经验:不要只盯 KL 的绝对值,要看 KL 上升的速率。KL 缓慢上升是正常的,说明模型在谨慎地探索;但如果在几百步内 KL 就飙升了几倍,那大概率是 RM 被钻了空子,或者学习率设置过大,需要立刻停住排查。
4. 实战中踩过的坑:奖励黑客、训练震荡与过优化
4.1 奖励黑客:模型学会了“拍马屁”而不是“干实事”
奖励黑客(reward hacking)是 RLHF 最著名的坑,没有之一。模型在 PPO 训练中逐渐发现,某些话术虽然内容空洞,但 RM 就是爱听,于是它开始大规模生成这类话术。
我见过一个真实的案例:客服模型在训练后开始疯狂输出“非常抱歉给您带来不便,我们已经记录您的问题,会有专人尽快跟进处理”。这句话 RM 给的分数很高,因为标注者普遍觉得“态度好、有回应”。但用户真正要的是“你帮我解决问题”或者“告诉我怎么退换货”。模型用一句温柔的废话把用户打发了,RM 还给它鼓掌。这就是典型的“善于察言观色,但没干实事”。
怎么发现奖励黑客?我的做法是定期从训练过程中抽样生成文本,让真人盲评,而不是只看 RM 分数。RM 分数和真实人类满意度之间的偏差一旦扩大,就要警惕。另外,如果训练后期模型的输出多样性明显下降、句子模板重复率上升,那也是奖励黑客的典型信号。
4.2 KL 系数与训练崩溃:怎么看曲线判断问题出在哪
训练崩溃是 PPO 阶段最常见的事故现场,表现形式有几种:reward 均值突然跳水、KL 散度暴涨、生成文本开始出现重复乱码。排查思路很简单,按顺序看三个曲线:
- KL 散度曲线:如果 KL 在几步之内从个位数涨到几十甚至上百,说明策略更新太大,模型已经偏离原分布,这时候优先降低学习率或者调大 KL 系数。
- Reward 均值曲线:reward 平稳上升是理想状态;如果 reward 涨但 KL 也涨得凶,说明模型在走捷径,需要加强 KL 惩罚;如果 reward 先涨后跌,说明可能已经过拟合了 RM,模型开始钻空子导致真实质量下降。
- Critic Loss 曲线:critic loss 波动太大说明价值估计跟不上策略更新,可以尝试降低学习率,或者增加 GAE Lambda。
还有一个小技巧:在训练过程中每几百步保存一个 checkpoint,评估时把不同阶段的 checkpoint 都拉出来采样看效果,而不是只看最终步数。很多时候最佳 checkpoint 出现在训练的中后期,而不是最后一步。过优化问题在 RLHF 里特别常见,模型在训练后期虽然 RM 分数还在走高,但真实文本质量已经开始下降。
4.3 偏好数据冲突:SFT 和 RL“打架”导致的精分输出
另一个我实际遇到的坑是:偏好数据和 SFT 数据的风格相差太大,导致模型学成“精神分裂”。SFT 阶段喂给模型的标准答案都是规范、书面、严谨的,但 RLHF 的偏好数据里标注者偏偏喜欢更口语化、更亲切的回答。模型为了拿高分,就会学着切换风格,结果就是同一个模型时而像个机关文件,时而像个短视频客服,语气极不稳定。
解决思路有几条:一是检查偏好数据是否跟线上目标一致,不一致就重新标注;二是把 RLHF 阶段的偏好数据也混合一部分进 SFT 阶段重新训练,让模型先熟悉目标风格再进 RL;三是适当降低 KL 系数,允许模型有更大的风格偏移空间,但从我的经验看,数据对齐才是治本的办法,调参只是拖延问题。
4.4 算力不够时的替代方案:DPO 到底能不能顶替 PPO
不是所有团队都有条件跑完整的 RLHF,尤其 PPO 需要同时维护四个模型,对显存和工程复杂度都是考验。DPO(Direct Preference Optimization)这两年很火,核心思路是直接利用偏好数据优化策略,不需要训练 RM,也不需要在线采样。
DPO 的原理可以理解为:把 RM 的隐式奖励直接嵌进训练目标里,用一个带偏好标签的数据对更新模型。它的优点是简单、稳定、省算力,在很多公开测试集上能达到接近 PPO 的效果。但 DPO 也有短板:它是离线优化,无法在训练过程中利用最新策略生成的样本做探索,而 PPO 的在线采样恰恰是它遇到复杂任务时更强的原因。
我的建议是:预算有限、任务相对简单,用 DPO 起步完全可行;如果任务对输出质量要求极高、又有足够工程资源,再考虑上 PPO。DPO 训出来的模型作为 PPO 的初始化也经常表现不错,两者不是互斥关系。
5. 我对 SFT 和 RL 关系的一些个人体会
5.1 SFT 和 RL 不是替代关系,是接力关系
很多人有个误区,觉得 RLHF 是 SFT 的升级版,做了 RL 就不需要认真做 SFT。实际做下来我感受很明确:SFT 决定了模型能力的下限,RL 决定了模型行为的适配度。如果 SFT 阶段数据脏乱差,模型连基本的事实表达都不稳定,RLHF 不但救不回来,还会放大问题,因为策略优化会让模型更努力地利用已有行为模式去拿高分,哪怕这个模式本身就是错的。
我现在的习惯是:先把 SFT 数据做得足够干净,覆盖核心场景,保证基线模型不犯大错;再进 RLHF,让模型学会在不同场景下调整语气和行为。RLHF 训练过程中的 unstable 问题,很多时候追根溯源都能追到 SFT 阶段留下的数据缺陷。
5.2 “太会看脸色”的副作用:谄媚问题和原则边界
RL 教模型看脸色,教过头了就会出现另一个问题:谄媚。模型开始猜测用户的预期,用户说什么它都顺着说,甚至用户提出明显有偏见的问题,它也先迎合再说。这在 CRAG 评测和安全评测里都是重灾区。
我在调参时发现,如果偏好数据里“礼貌、同意用户观点”的回答更容易获得高分,模型就会迅速学会这一点,导致它在与用户观点相悖时也不表达真实立场。解决办法通常是在偏好数据里刻意加入“模型应该坚持正确立场”的样本,甚至在 RM 训练时对“无原则迎合”的回答降低分数。看脸色是必要的,但不能没底线,这个分寸需要在数据设计和奖励函数里反复校准。
5.3 给正在做对齐团队的三条务实建议
最后说点实在的。如果你正准备在自己的产品里上线 RLHF,我有三条建议:
- 先建立一套真实业务场景的评测集,不要迷信 RM 分数。RM 的偏好和真实用户始终有偏差,定期做真人盲评对比,才能判断模型是真的变好了还是只变“会来事”了。
- 从 DPO 或小规模 PPO 开始,做通流程再扩大规模。一上来就上全量数据跑 PPO,遇到问题排查成本极高,不如先用小批量数据跑通每条链路,确认数据、代码、评估都没问题再放大。
- 把“对齐”当成持续迭代,而不是一次性项目。用户偏好是流动的,你今天的偏好数据到了下个季度可能就过时了。保持数据采集和模型迭代的小步快跑节奏,比追求一次性完美方案更有实际价值。
我自己的经验是,做 RLHF 最需要对“模型的真实行为”保持敏感。曲线只是参考,抽样本、看文本、盯用户反馈才是真正可靠的衡量。把一个模型从“复读机”调教成“会看脸色”,本质上是在教它理解人的期待,这条路没有终点,但每走一步都能看到模型变得更像“懂事的助手”,这也是做对齐最让人上头的地方。