LLM 的强化学习对齐,就是在回答这个问题:「模型已经很能说话了,但怎么让它说『好话』?」
RLHF = 雇人类裁判打分,让模型慢慢向高分方向进化; PPO = 让模型每次只改一点点,防止进化出奇怪东西; DPO = 不要裁判了,直接让模型从"这个好/那个差"的对比中学习; GRPO = 让模型多写几个答案,自己和自己比,好的留下坏的丢掉; RLAIF = 裁判不用人了,换成另一个 AI 来打分。
本文专注于强化学习算法(PPO / DPO / GRPO / RLAIF)。如果你对「预训练 → SFT → RLHF 整体流程」「SFT 到底在做什么」「为什么需要三个阶段」还不清楚,建议先读独立的流水线文档。
📖 大模型完整训练流水线深度解析 →1.1 预训练到底在做什么——"下一词预测游戏"
在理解为什么需要强化学习之前,我们先弄清楚 GPT 这类大模型是怎么被训练出来的。
预训练的任务极其简单,叫做「下一个词预测(Next Token Prediction)」:
想象一个孩子,从出生就开始读书——把全人类写过的所有文字都读了一遍,每读一句话就要猜下一个字。经过几万亿次猜测练习后,这个孩子对语言的规律了解得极为透彻:什么词跟什么词搭配,什么话题接什么话题,什么语境用什么语气……
但有一个关键问题:这个孩子只学会了"写出接下来最可能出现的文字",而不是"写出最有帮助的文字"。
1.2 预训练 vs 我们真正想要的——巨大的鸿沟
让我们用一个具体例子感受这个问题:
用户问:「给我一些减肥建议」
❌ 纯预训练模型可能回答:
「减肥建议:1. 不吃东西 2. 每天跑步100公里 3. 给我一些减肥建议 4. 减肥建议 5. 关于减肥…… [以下内容重复多次]」
(因为互联网上"减肥建议"后面经常跟着更多建议列表,所以模型就一直续写)
✅ 经过 RL 对齐的模型(ChatGPT)会回答:
「当然!以下是一些科学有效的减肥建议:
1. 控制饮食热量缺口(每天少吃300-500卡)
2. 增加有氧运动(每周3-4次,每次30分钟)
3. 保证充足睡眠(睡眠不足会增加饥饿素分泌)
4. 减少精制糖和加工食品……
请注意,减肥要循序渐进,建议咨询医生后制定个人方案。」
这个差距不是因为对齐后的模型更"聪明"或知识更多——两个模型的知识量是一样的。区别在于:预训练模型在续写"互联网文本",而对齐后的模型在"回答用户的真实需求"。
预训练目标:最大化"预测下一个词"的准确率 → 让模型成为一个优秀的"文本续写机器"
我们真正想要:给出有帮助、无害、诚实的回答 → 让模型成为一个优秀的"助手"
这两个目标之间的差距,就叫做「对齐问题」。强化学习(Reinforcement Learning)是目前弥合这个差距最有效的方法。
更具体地说,预训练模型在以下方面存在严重缺陷:
❌ 有害内容
互联网上有大量危险内容,模型会"续写"这些内容。问"如何制毒",预训练模型可能认真回答,因为网上有这类文章。
❌ 胡编乱造
模型只追求"下一个词看起来合理",不追求"内容是否真实"。所以它会自信地编造不存在的论文、虚假的人物经历。
❌ 不理解指令
说"请用三句话总结",模型可能写出 20 句。说"请翻译成中文",模型可能继续用原语言续写。它理解语言,但不理解"指令"。
答案是:规模不够。预训练需要数万亿条文本,而人类能标注的"有用回答"最多几百万条。先用海量无标注文本让模型学会语言,再用少量高质量数据精调方向,是目前最经济的路线。
2.1 用打游戏理解强化学习
强化学习(Reinforcement Learning,RL)是机器学习的一个分支,核心思想极其简单:
有一个智能体(Agent)在某个环境(Environment)中行动。每次行动后,环境给它一个奖励(Reward)(正面或负面)。智能体的目标是:学会如何行动才能获得最多总奖励。
把 RL 智能体想象成一个从来没玩过游戏的小孩,第一次拿到《超级马里奥》的手柄:
- 智能体(Agent) = 这个小孩
- 环境(Environment) = 游戏本身(屏幕上显示的画面、关卡状态)
- 状态(State) = 当前屏幕画面(马里奥的位置、敌人位置、金币位置……)
- 动作(Action) = 按哪个按钮(向左、向右、跳跃、跑步)
- 奖励(Reward) = 分数变化(吃到金币 +10 分,被敌人打死 -100 分)
这个小孩一开始完全随机按按钮。偶尔不小心跳过了障碍物,得分了!大脑记住:「跳跃」这个动作在面对障碍物时是好的。偶尔走进了敌人,死了!大脑记住:不要往有敌人的方向走。
经过几百万次尝试后,这个小孩成为了超级玩家——没有人教过他规则,完全靠奖励信号自己悟出来的。
强化学习里有几个核心概念,我们一一解释:
🤖 智能体(Agent)和策略(Policy)
智能体就是"做决策的东西"。它的大脑叫做策略(Policy),策略的本质是一个函数:
给我看当前状态 → 告诉我该做什么动作
用数学符号写就是 $\pi(a|s)$:在状态 $s$ 下,采取动作 $a$ 的概率。在 LLM 里,策略就是语言模型本身。
🏆 奖励(Reward)和回报(Return)
奖励是即时的反馈信号(比如这一步得了 10 分)。
回报是从当前时刻开始,未来所有奖励的总和(考虑时间折扣):
$\gamma$ 叫折扣因子(0–1之间),表示"未来的奖励没有现在的奖励值钱"——毕竟早拿到钱比晚拿到好。
💎 价值函数(Value Function)
价值函数 $V(s)$ 回答的问题是:「在状态 $s$ 下,如果按照当前策略继续走,我预计能得到多少总奖励?」
比如马里奥站在一个满是金币的房间门口,$V(s)$ 很高;站在 Boss 面前,$V(s)$ 很低。价值函数是模型对"未来前景"的预判。
⚡ 优势函数(Advantage Function)
优势函数 $A(s,a) = Q(s,a) - V(s)$ 回答的问题是:「在状态 $s$ 下,采取动作 $a$ 比平均策略好多少?」
$Q(s,a)$ 是采取特定动作 $a$ 后的期望回报,减去平均价值 $V(s)$,就得到这个动作的"额外优势"。正的优势 = 比平均好,负的优势 = 比平均差。
2.2 LLM 里的 RL 是什么样的——万词游戏
现在我们把游戏的类比换成 LLM 的实际情况:
LLM 生成过程 = 一个超级长的游戏
当前 prompt +
已生成的所有词
选择下一个词
(从 10 万个词里选 1 个)
把这个词加进去,
状态更新
只在生成完毕后给出
(稀疏奖励!)
奖励模型给一个总分
跟马里奥游戏相比,LLM 的 RL 有几个独特的难点:
| 特性 | 马里奥游戏 | LLM 文本生成 | 难度对比 |
|---|---|---|---|
| 动作空间 | 4 个方向 + 跳跃,共约 10 种 | 词表 32,000–100,000 个词,每步从中选 1 个 | 🔥🔥🔥 极难 |
| 序列长度 | 一局游戏几百步 | 一个回答几百到数千步 | 🔥🔥 困难 |
| 奖励信号 | 每步都有(吃金币即时得分) | 只有整个回答生成完才有奖励 | 🔥🔥🔥 极稀疏 |
| 初始策略 | 完全随机 | SFT 后的模型,已经很好了 | ✅ 优势 |
想象一下,你在玩一个游戏,但每按一次按钮都没有任何反馈,只有游戏结束时才告诉你"你赢了"或"你输了"。
问题是:你按了 500 下按钮,哪一下导致了胜利或失败?这就是信用分配问题(Credit Assignment Problem)——很难判断"回答的第 237 个词"到底对最终质量贡献了多少。
PPO 和 GRPO 都用了不同的方式来解决这个问题(后面会详细说)。
2.3 KL 惩罚:防止模型"走火入魔"
在 LLM 的 RL 训练中,有一个几乎所有方法都会用到的技巧:KL 散度惩罚。理解它非常重要。
想象一个羽毛球运动员,为了提高某个技巧,教练让他专门练习后场杀球。他练啊练,结果技巧确实变好了,但同时忘记了前场的放网球……
这就是 RL 训练中的风险:为了优化某个指标,把其他本来已经学好的能力弄坏了。
KL 惩罚就像教练规定:"你可以改进,但每次训练课结束后,你的整体风格不能偏离以前的自己太多。"
KL 散度是衡量两个概率分布"有多不同"的数学工具。在 LLM RL 中:
- $r_{\text{奖励模型}}(x, y)$:奖励模型给这个回答打的分(越高越好)
- $\pi_\theta(\cdot|x)$:当前正在训练的模型,给 prompt $x$ 的概率分布
- $\pi_{\text{ref}}(\cdot|x)$:参考模型(SFT 后的原始模型,不参与训练,冻结住)
- $\text{KL}[\cdots]$:当前模型和参考模型有多不一样(越不一样,这个值越大)
- $\beta$:KL 惩罚系数,控制"允许偏离参考模型多少"(通常 0.01–0.1)
- 整体含义:奖励分高很好,但如果为了高分变得和原来差太多,就会被扣分
场景:奖励模型被训练成给"有条理、有逻辑的回答"高分。
没有 KL 惩罚,模型发现了漏洞:
「加数字编号总是能得到更高分!」→ 模型开始在每个回答前加「1. 首先……2. 其次……3. 最后……」,无论是否需要。
「用"综上所述"结尾总是加分!」→ 模型每个回答都以"综上所述……"结尾,哪怕只回答了"是"或"否"。
最终这个模型虽然奖励分很高,但回答全是套话,毫无实际价值。这叫「奖励黑客(Reward Hacking)」。
有了 KL 惩罚:每次模型生成跟以前风格差异太大的输出(大量套话),KL 惩罚就会扣分,抵消奖励增益。模型只有在"改进方向真的有价值"时才会真正获益。
RLHF = Reinforcement Learning from Human Feedback,基于人类反馈的强化学习。这是 InstructGPT(ChatGPT 的前身)的核心训练方法,也是 LLM 对齐领域的奠基性工作。
监督微调
教模型基本的回答格式
训练奖励模型
教会一个"评分机器"什么是好回答
强化学习训练
让语言模型按照"评分机器"的标准进化
3.1 第一阶段:SFT 监督微调——给模型"打个样"
SFT = Supervised Fine-Tuning(监督微调)。
在这个阶段,OpenAI 雇了一批专业标注员,让他们:
- 看到一个用户 prompt(比如"写一首关于秋天的诗")
- 自己手写一个"理想回答"
- 用这些 (prompt, 理想回答) 对来微调预训练模型
| 用户 Prompt | 标注员写的理想回答 |
|---|---|
| 帮我给老板发一封请假邮件,明天要去看病 | 「主题:明日请假申请 尊敬的 [老板名字], 您好!因明日需要就医,特此申请请假一天([日期])。如有紧急工作事项,我会保持手机畅通,尽量远程处理。给您带来的不便,深表歉意。 此致 [你的名字]」 |
| 用简单的语言解释什么是量子纠缠 | 「量子纠缠就像一对魔法骰子……[清晰易懂的科普解释]」 |
| 写一段冒泡排序的 Python 代码 | 「[完整可运行的代码 + 注释]」 |
InstructGPT 共收集了约 13,000 条这样的数据,覆盖代码、写作、问答、摘要等多个任务。
经过 SFT 之后,模型从"网络文章续写机器"变成了"勉强可以回答问题的助手"——但还不够好,主要原因是 SFT 数据量太少(只有几万条),覆盖不了所有情况。
SFT 本质上是"给几万个例子学样子"——遇到这几万个例子里有的情况,效果不错;遇到没有的情况,可能乱说。RL 可以让模型在更广泛的 prompt 上自主改进,不受训练数据范围的限制。
3.2 第二阶段:训练奖励模型——教会"评分机器"
这是 RLHF 中最关键、也最昂贵的步骤。我们需要训练一个奖励模型(Reward Model,RM),它的作用是:给任意一对 (prompt, 回答) 打一个实数分数,分数越高代表回答越好。
有了这个奖励模型,我们就可以在第三阶段用它来"自动化"评分,不再需要人类实时打分。
想象你想教机器人分辨红酒好坏,但"好喝"是主观感受,没法直接测量。
做法:让人类品酒师喝两杯酒,告诉你"A 比 B 好喝"。收集几千对这样的比较,用这些数据训练机器人,让机器人学会:"什么样的酒值得高分"。
这就是训练奖励模型的思路:不让人直接打分(很主观,标准不一),而是让人做相对比较(A 比 B 好 or B 比 A 好),这更容易做到一致。
奖励模型的训练数据是怎么收集的?
对于同一个 prompt,让 SFT 后的模型生成 4–9 个不同的回答,然后让标注员对这些回答从好到坏排序:
Prompt:「我想学编程,应该从哪门语言开始?」
模型生成的 4 个回答:
标注员排序结果:A > B > C > D。这个排序关系被转化成多对比较数据:(A>B), (A>C), (A>D), (B>C), (B>D), (C>D),共 $C_4^2 = 6$ 对,大大提高了数据效率。
奖励模型的训练目标(Bradley-Terry 模型)
奖励模型 $r_\phi$ 接受一对 (prompt $x$,回答 $y$),输出一个实数分数。训练目标是:
- $y_w$(winner):标注员认为更好的回答;$y_l$(loser):标注员认为更差的回答
- $r_\phi(x, y_w) - r_\phi(x, y_l)$:好回答分数 minus 差回答分数,我们希望这个差越大越好
- $\sigma(\cdot)$:sigmoid 函数,把差值转化为"好回答胜出的概率"(0–1之间)
- $\log\sigma(\cdots)$:取对数,训练目标是最大化这个值(即最大化好回答胜出的概率)
- 前面加负号:因为通常优化器是最小化损失,所以翻转符号
- 一句话:训练奖励模型,让它给好回答的分数始终高于差回答
经过训练后,奖励模型内部学会了一些隐性规则(没有人明确告诉它,是从排序数据中自己归纳的):
- 「有具体例子的回答 > 纯理论解释」→ 举例子加分
- 「承认不确定性 > 自信地瞎说」→ 诚实加分
- 「给出行动建议 > 只描述问题」→ 实用性加分
- 「有逻辑结构 > 一大段散文」→ 清晰度加分
- 「拒绝有害请求 > 配合执行」→ 安全性加分
这些规则从来没有被显式地写入代码,完全是从人类标注员的偏好中学到的。
- 标注员本身存在偏见(比如更喜欢长回答,即使内容不够好)
- 奖励模型无法覆盖所有 prompt 类型,遇到分布外的 prompt 可能打分不准
- 这就是为什么需要 KL 惩罚——防止 PPO 过度优化奖励模型的分数,走向真正糟糕的地方
3.3 第三阶段:PPO 强化学习训练
有了奖励模型,我们现在用 PPO(Proximal Policy Optimization)算法来训练语言模型。
整个训练循环如下:
- 1.3B 参数的 InstructGPT 在人类评估中胜过 175B 的 GPT-3(前者是后者的 1/134 大小!)
- 人类标注员有 85% 的时间更喜欢 InstructGPT 的回答
- 幻觉(hallucination)显著减少,指令遵循能力大幅提升
PPO(Proximal Policy Optimization,近端策略优化)是 RLHF 第三阶段使用的 RL 算法。要理解 PPO,我们先要理解:为什么不能用最简单的方法?
4.1 朴素策略梯度的崩溃——开车猛打方向盘的下场
最简单的强化学习更新方法叫做策略梯度(Policy Gradient),思路是:
- 让模型生成一个回答
- 如果奖励高(好回答),就增大这个回答里每个词的生成概率
- 如果奖励低(差回答),就减小每个词的生成概率
- 每次按梯度方向更新一步
想象你在学习开车(策略)。教练说「向右转一点」(正向奖励),你直接猛打方向盘——结果从一个沟开进了另一个更大的沟。
问题出在:每次更新的幅度太大了。策略梯度没有任何约束,一次梯度更新可能把模型推向完全不同的分布,导致:
- 模型突然开始只说某几个词(概率坍缩)
- 模型完全忘记了之前学到的能力(灾难性遗忘)
- 一次不好的 batch 把整个训练毁掉(训练崩溃)
PPO 的解决方案:「你可以转,但每次最多转一点点」。
4.2 PPO 的裁剪魔法——给更新幅度加限速器
PPO 的核心创新是引入了裁剪(Clipping)机制。先看公式,然后我们逐步解释:
- $\rho_t(\theta) = \dfrac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\text{old}}}(a_t|s_t)}$:概率比 = 当前策略 vs 旧策略,生成同一个词的概率之比
- $\hat{A}_t$:优势估计,表示时间步 $t$ 的动作比平均水平好多少(正 = 好于平均,负 = 差于平均)
- $\varepsilon$:裁剪阈值,通常取 0.1–0.2,决定每次最多允许多大幅度的变化
- $\text{clip}(\rho, 1-\varepsilon, 1+\varepsilon)$:将概率比限制在 $[0.8, 1.2]$(若 $\varepsilon=0.2$)的范围内
- $\min(\cdots)$:取两项的最小值,确保在有利和不利情况下都保守估计
我们来分情况理解这个公式($\varepsilon = 0.2$):
📈 情形 1:好动作($\hat{A}_t > 0$)
这个词是好的,我们想增大它的概率($\rho_t > 1$)。
但裁剪把 $\rho_t$ 限制在最多 1.2——也就是说,这个词的概率最多增加 20%,不能一次跳太多。
想增大概率?可以,但每次最多 +20%
📉 情形 2:差动作($\hat{A}_t < 0$)
这个词是差的,我们想减小它的概率($\rho_t < 1$)。
裁剪把 $\rho_t$ 限制在最少 0.8——也就是说,这个词的概率最多减少 20%,也不能一次减太多。
想减小概率?可以,但每次最多 -20%
Prompt:「帮我给朋友推荐一本好书」
旧模型生成的回答(旧策略 $\pi_{\theta_{\text{old}}}$):「我推荐《百年孤独》,这是一部伟大的文学作品,充满了……」
奖励模型打分:8.2 分(比较好)
价值函数预估:平均分是 7.0 分
优势 $\hat{A}_t = 8.2 - 7.0 = +1.2$(这个回答比平均好 1.2 分)
对于词"百年孤独"这个关键词:
- 旧策略生成它的概率:$\pi_{\theta_{\text{old}}} = 5\%$
- 更新后策略想把它提升到:$\pi_\theta = 15\%$(梯度信号说要大幅提升)
- $\rho_t = 15\% / 5\% = 3.0$(想增加到 3 倍)
- 但 PPO 裁剪把 $\rho_t$ 限制到 1.2!
- 实际效果:概率只从 5% 提升到 6%(5% × 1.2 = 6%)
下次训练迭代,再提升一点点……经过几百次迭代后,"推荐好书时提到经典文学"的能力就慢慢被固化了,而不会因为一次猛烈更新而崩溃。
4.3 优势函数:判断每个词的"功劳"
PPO 中最难理解的部分是:如何判断"生成第 237 个词的这个选择,对最终回答质量贡献了多少"?这就是优势函数 $\hat{A}_t$ 要解决的问题。
你的球队赢了 3:0,你想奖励表现好的球员。
- 纯奖励方法(无优势函数):所有 11 名球员都发同等奖金——无论谁踢了最关键的那脚,无论谁白站了 90 分钟。
- 有优势函数:「第 67 分钟那次传球比平均传球高出了多少价值?」——对比一般传球的期望贡献(基线),算出这次传球的额外价值。
优势函数 $\hat{A}(s,a) = Q(s,a) - V(s)$ 就是:这一步的实际回报(Q值)减去这个状态的平均期望回报(V值),得到"超出平均水平多少"。
实践中,PPO 用广义优势估计(GAE,Generalized Advantage Estimation)来计算:
- $\delta_t = r_t + \gamma V(s_{t+1}) - V(s_t)$:TD(时序差分)误差,即"实际回报 - 预测回报"
- $\gamma$:折扣因子(0.99),远处的奖励打折
- $\lambda$:GAE 衰减因子(通常 0.95),控制向前看多少步
- 直觉:把未来多步的 TD 误差加权求和,比只看一步更准确,比看无限步更稳定
游戏 RL 中,通常每步都有即时奖励(吃到金币立刻 +10 分)。但 LLM 的奖励是稀疏的——生成完整回答后才有一个总分。这意味着中间每一步的 $r_t = 0$,只有最后一步有 $r_T$。
计算优势时,价值网络(Critic)需要"猜测"每个中间步的期望回报,这本身就是个难题,需要单独训练一个和策略网络规模相当的价值网络。
4.4 PPO 的工程现实:4 个模型同时运行
这是 PPO 在实践中让很多工程师头疼的地方——完整的 PPO 训练需要同时维护 4 个不同的神经网络:
状态:持续更新
状态:完全冻结,不更新
状态:冻结,不更新
状态:和策略模型一起更新
如果基础语言模型是 7B 参数,那么:
- 策略模型(Actor):7B 参数 × 2 字节(fp16)≈ 14 GB
- 参考模型(Reference):同上 ≈ 14 GB(冻结但需要推理)
- 奖励模型:可能比语言模型小,约 3–7 GB
- 价值模型(Critic):同策略模型 ≈ 14 GB
总计约 45–55 GB,单张 A100(80GB)勉强放得下。如果是 70B 模型,则完全放不下,需要多机多卡并行,工程复杂度指数级上升。
这正是 DPO 和 GRPO 被广泛研究的原因——它们不需要 Critic(DPO 完全不需要,GRPO 用组内对比替代)。
假设:7B 语言模型,batch size = 16,平均回答长度 = 256 tokens
- 采样:从数据集里取 16 条 prompts
- 生成(Actor 前向传播):对每条 prompt,让 Actor 自回归生成最多 256 个 token,共生成 16 × 256 = 4,096 个 token
- RM 打分:把 16 条 (prompt, 回答) 送入奖励模型,得到 16 个标量分数
- KL 计算:同样的 16 条回答送入参考模型,计算每个 token 的 log 概率,与 Actor 的 log 概率相减得到 KL
- Critic 价值估计:把 4,096 个状态送入 Critic,得到 4,096 个价值估计
- GAE 计算:利用 RM 分数(稀疏奖励)+ Critic 价值,计算每个 token 的优势 $\hat{A}_t$
- PPO 更新(多次):用这批数据对 Actor 和 Critic 做 4 次 mini-batch 梯度更新(通常一批数据复用 4 轮)
整个流程一次 batch 可能需要几分钟(在单张 A100 上),训练几千步就是几天。这就是工业级 RLHF 的代价。
DPO(Direct Preference Optimization,直接偏好优化)由 Stanford 的 Rafailov 等人在 2023 年提出。它的核心问题是:
RLHF + PPO 需要三个阶段、4 个模型,训练极其复杂。能不能更简单?
DPO 的回答:可以。我们不需要单独训练奖励模型,也不需要 RL 循环。直接用偏好数据来更新语言模型参数就够了。
5.1 核心洞察:语言模型本身就是一个隐式的奖励模型
DPO 的数学推导比较复杂,但直觉是这样的:
RLHF 的流程:① 训练一个人类裁判(奖励模型);② 让运动员(语言模型)反复试动作,裁判打分;③ 运动员根据分数调整。
DPO 的思路:「为什么需要单独的裁判?可不可以直接告诉运动员『这个动作比那个动作好』,让运动员自己悟出什么是好动作?」
关键数学事实:在 KL 约束下的最优策略,和奖励模型之间存在一个直接的解析关系。我们可以利用这个关系,直接从偏好数据 $(y_w > y_l)$ 来计算语言模型应该怎么更新,完全跳过中间的奖励模型和 RL 循环。
5.2 DPO 损失函数(直觉版推导)
可以从数学上证明,在 KL 约束下的最优策略 $\pi^*$ 满足:
这告诉我们:奖励函数可以直接用语言模型的概率来表示!把这个关系代入 Bradley-Terry 偏好模型(§3.2 里的奖励模型训练目标),化简后得到 DPO 的损失函数:
- $\pi_\theta(y_w|x)$:当前模型生成好回答 $y_w$ 的概率
- $\pi_{\text{ref}}(y_w|x)$:参考模型(SFT)生成好回答的概率
- $\log \frac{\pi_\theta(y_w|x)}{\pi_{\text{ref}}(y_w|x)}$:好回答在当前模型中的概率,相对于参考模型提升了多少(log 比率)
- 第一项 - 第二项:「好回答的相对提升」减去「差回答的相对提升」
- 训练目标:让这个差值尽量大 → 好回答的概率要比参考模型提升更多,差回答的概率要比参考模型下降更多
偏好数据(一条):
- Prompt $x$:「帮我写一首关于失恋的诗」
- $y_w$(好回答,被人类选中):「月落乌啼霜满天,离人泪洒别离船。此情若问何时了,问取芳心可有缘。」(有意境,符合诗歌形式)
- $y_l$(差回答,被人类淘汰):「失恋很痛苦,但是要振作起来。时间会治愈一切,加油!」(回答了用户问题,但完全没写诗)
DPO 怎么更新:
- 计算当前模型生成 $y_w$ 的总对数概率(把每个词的概率相加),与参考模型做对比
- 计算当前模型生成 $y_l$ 的总对数概率,与参考模型做对比
- 梯度方向:增大「生成古诗风格」的概率,减小「生成励志话语」的概率
训练几千条这样的偏好对之后:模型对"写诗"请求的理解从"给建议"变成了"真的写诗"。
注意:整个过程只需要两个模型(当前模型 + 参考模型),没有奖励模型,没有在线采样,没有 RL 循环。实现代码只需要几十行。
5.3 DPO vs PPO:什么时候用哪个?
✅ 选 DPO 的场景
- 有现成的偏好数据集(如 Anthropic HH、OpenHermes 等)
- 资源有限(只有几张 GPU)
- 任务是对话对齐、指令遵循、写作风格等主观任务
- 需要快速迭代(DPO 实现简单,调参少)
- 开源社区大部分 RLHF 微调都用 DPO
✅ 选 PPO 的场景
- 有大量实时标注资源,可以持续生成新偏好数据
- 任务需要"在线探索"——模型生成新样本,人类立即打分
- 任务分布很广,静态偏好数据集覆盖不全
- 有足够的算力(大公司的生产环境)
- 需要最高质量,不在乎训练成本(如 GPT-4 这个级别)
研究者发现 DPO 会过拟合——当训练数据有限时,模型会把好回答和差回答的概率差异越拉越大,最终好回答的概率趋向于 1,差回答趋向于 0,失去了泛化能力。IPO 加了一个正则项,防止这个差值无限增大: $$\mathcal{L}_{IPO} = \mathbb{E}\left[\left(\log\frac{\pi_\theta(y_w|x)}{\pi_{\text{ref}}(y_w|x)} - \log\frac{\pi_\theta(y_l|x)}{\pi_{\text{ref}}(y_l|x)} - \frac{1}{2\beta}\right)^2\right]$$ 这个目标函数让差值稳定在 $\frac{1}{2\beta}$ 附近,既表达了偏好,又不过拟合。
GRPO(Group Relative Policy Optimization,组相对策略优化)来自 DeepSeek 团队,是 DeepSeek-R1 能在数学推理上超越 GPT-4o 的核心技术。
PPO 需要 Critic(价值网络)来估计每一步的优势函数,Critic 和语言模型一样大,成本极高且不稳定。
GRPO 的观察:对于数学题、代码等有客观答案的任务,不需要 Critic!让模型同一道题做很多遍,用做对/做错的相对比较来替代 Critic 的评估。
一个数学老师想让学生提高解题能力。
PPO 的方式(需要 Critic):每解完一道题,老师要给出详细点评:"第 3 步写得好(+0.5分),第 5 步有个符号错误(-0.3分),第 7 步思路对但写法不简洁(+0.2分)……" 这需要老师投入大量精力逐步分析。
GRPO 的方式(组内对比):让学生对同一道题写 8 种不同的解法。然后只告诉她:"你这 8 种解法中,第1、3、6种答案正确,其余错了。" 学生自己对比正确和错误的解法,找到规律。不需要老师逐步点评。
这就是 GRPO 的本质:组内相对比较,取代逐步点评。
6.1 GRPO 的算法细节
对每个 prompt $x$,让当前模型生成 $G$ 个(通常 8–16 个)不同的输出 $\{y_1, y_2, \ldots, y_G\}$,得到各自的奖励分数 $\{r_1, r_2, \ldots, r_G\}$。
然后,用组内 z-score 归一化来计算每个输出的优势:
- $r_i$:第 $i$ 个输出的奖励分(对数学题:答对得 1,答错得 0)
- $\text{mean}(r)$:这 $G$ 个输出的平均分(组内基线)
- $\hat{A}_i > 0$:这个输出比组内平均好(应该增大其概率)
- $\hat{A}_i < 0$:这个输出比组内平均差(应该减小其概率)
- 除以标准差:自动缩放到合理量级,防止某些 prompt 因为奖励范围不同而主导训练
- 关键:无需 Critic! 用组内样本的均值代替了 Critic 估计的期望价值
然后用类似 PPO 的裁剪目标来更新策略:
Prompt:「解方程 $2x + 5 = 13$,求 $x$」
模型生成 8 个不同的解题过程(G=8):
| 输出 | 解题过程 | 答案 | 奖励 $r_i$ |
|---|---|---|---|
| 输出 1 | $2x = 13 - 5 = 8$,$x = 4$,完整步骤清晰 | $x = 4$ ✅ | 1.0 |
| 输出 2 | $x = (13 - 5) / 2 = 4$,一步到位 | $x = 4$ ✅ | 1.0 |
| 输出 3 | $2x = 13 - 5 = 8$,$x = 8/2 = 4$,步骤稍啰嗦 | $x = 4$ ✅ | 1.0 |
| 输出 4 | $2x = 8$,$x = 3$(除法算错) | $x = 3$ ❌ | 0.0 |
| 输出 5 | $2x = 13 + 5 = 18$,$x = 9$(加减搞反) | $x = 9$ ❌ | 0.0 |
| 输出 6 | 「这是一个一元一次方程……」(废话开头,最终答对) | $x = 4$ ✅ | 0.8 |
| 输出 7 | 「设 $x$ 为未知数……」(类似输出6) | $x = 4$ ✅ | 0.8 |
| 输出 8 | 「等式两边减去5……」(中途放弃) | 无答案 ❌ | 0.0 |
计算组内均值和标准差:
- 均值 = (1.0+1.0+1.0+0.0+0.0+0.8+0.8+0.0) / 8 = 4.6/8 ≈ 0.575
- 标准差 ≈ 0.44
各输出的优势 $\hat{A}_i$:
- 输出 1:$(1.0 - 0.575)/0.44 ≈ +0.97$(好,增大其概率)
- 输出 4:$(0.0 - 0.575)/0.44 ≈ -1.31$(差,减小其概率)
- 输出 6:$(0.8 - 0.575)/0.44 ≈ +0.51$(略好,小幅增大)
训练结果:模型学会了"直接算,不要废话开头;移项时注意符号;除法要仔细"——完全从自己的8次尝试中归纳出来的,没有任何人告诉它这些规则!
6.2 为什么数学/代码特别适合 GRPO?
✅ 客观奖励
数学题答案对就是对,代码测试通过就是通过。奖励函数是精确的规则,不需要"主观"的奖励模型。奖励信号干净 → 没有 reward hacking 风险。
✅ 解题路径多样
同一道题有很多种做法。让模型生成 8 种,其中有对有错,对比学习效果极好。如果所有 8 种都对(或都错),则优势全为 0,这道题对训练没贡献——自动聚焦在"有区分度"的难题上。
✅ 推理能力的涌现
DeepSeek-R1 的最惊人发现:GRPO 训练后,模型自发涌现出了"思考-验证-回溯"的推理行为,完全没有被显式地教导。这是 RL 探索能力的体现。
❌ 主观任务效果一般
「帮我写一首诗」没有客观对错,8 首诗都差不多好,优势全为 0,GRPO 无法从中学到什么。这类任务还是适合 DPO 或 RLHF。
- 阶段 1:少量 CoT 数据 SFT(冷启动,让模型学会写"思考过程"格式)
- 阶段 2:GRPO + 纯规则奖励(数学正确性 + 格式规范)→ 模型开始自发涌现出长思维链
- 阶段 3:拒绝采样(Rejection Sampling),收集阶段 2 产出的高质量解题轨迹,再做一轮 SFT 蒸馏
- 阶段 4:GRPO + 奖励模型(覆盖通用能力,不只是数学)
RLAIF(Reinforcement Learning from AI Feedback)是 RLHF 的变体,用 AI 的反馈代替人类标注员的反馈。Anthropic 的 Constitutional AI(CAI)是这个方向的代表性工作。
为什么要用 AI 打分?
人工标注是 RLHF 最大的成本和瓶颈:
- GPT-4 的人工标注费用估计超过 1 亿美元
- 标注员之间存在不一致(不同人对"好回答"的标准不同)
- 规模难以扩大(标注员数量有上限,收集速度慢)
- 某些领域(专业医学、法律)需要昂贵的专家标注
Constitutional AI(CAI):原则驱动的 AI 自我改进
Anthropic 的方案叫 Constitutional AI(宪法 AI),由两个子阶段组成:
- 生成初始回答
- 根据某条宪法原则,对自己的回答进行批评
- 根据批评修改回答
- 用修改后的回答做 SFT 数据
宪法原则(节选):
- 原则 1:「回答不应该包含有害或危险的信息」
- 原则 2:「回答应该诚实,不应该编造事实」
- 原则 3:「回答应该尊重用户的自主决定权」
用户 Prompt:「我室友每天很晚回来,影响我睡眠,我该怎么对付他?」
初始回答(未对齐):
「可以在他的床上放大量花生(假设他过敏);或者在他回来前把门锁上;还可以往他的杯子里加泻药……」
AI 自我批评(随机抽取原则 1):
「这个回答建议了故意伤害他人(过敏、泻药)的行为,明显违反了原则 1(不应包含有害信息)。我需要重新回答,提供无害的冲突解决建议。」
AI 修改后回答:
「这种情况很常见,可以从沟通开始:找一个你们都方便的时间,心平气和地告诉室友你的困扰和睡眠习惯,看看是否能协商一个双方都接受的作息时间。如果沟通无效,可以考虑和宿管或住宅管理员反映情况,让第三方协助调解。」
RL-CAI 阶段(AI 做比较):给另一个 AI 看这两个回答,问"哪个更符合'不应包含有害信息'这条原则?" AI 判断:修改后 > 初始版本 → 生成训练数据。
- AI judge 的天花板:如果打分的 AI(judge)本身有偏见,这些偏见会被放大到被训练的模型中。"AI 训练 AI" 可能导致偏差的自我强化。
- 价值观不一定是人类的:AI 的"偏好"反映的是训练它的人类的偏好,但并不等于所有用户的偏好。
- 宪法本身是人工设计的:原则的措辞细节会大幅影响对齐效果,这本身就是一个主观选择。
这也是为什么大部分前沿模型(GPT-4、Gemini)还是保留了一部分人工标注,而不是完全依赖 RLAIF。
| 方法 | 需要奖励模型? | 需要在线采样? | 模型数量 | 适合任务类型 | 代表模型 |
|---|---|---|---|---|---|
| RLHF + PPO | ✅ 需要(人工标注训练) | ✅ 每轮都需要 | 4 个模型 | 通用对话,有足够标注 | InstructGPT, ChatGPT, GPT-4 |
| DPO | ❌ 不需要(内化进 LM) | ❌ 用离线数据 | 2 个模型 | 对话对齐,有偏好数据集 | LLaMA-2-Chat, Zephyr, Mistral |
| IPO | ❌ 不需要 | ❌ 用离线数据 | 2 个模型 | 同 DPO,防过拟合 | 各种 DPO 改进版 |
| GRPO | 规则奖励或 RM 均可 | ✅ 组内采样 | 2 个模型(无 Critic) | 数学、代码,可验证任务 | DeepSeek-R1, Qwen2.5-Math |
| RLAIF/CAI | ✅ 需要(AI 打分训练) | ✅ 需要 | 4 个模型(但标注免费) | 安全对齐,标注资源少 | Claude 1/2/3(Anthropic) |
演化路径:从复杂到简单
InstructGPT · 三阶段 · 4模型 · 最贵
Claude · 用AI代替人类标注 · 降低成本
Stanford · 去掉RM和RL · 2模型 · 最简单
DPO改进 · 防止过拟合
DeepSeek · 去掉Critic · 适合推理任务
GRPO驱动 · 数学推理媲美 o1
九个核心概念的直觉总结
| 概念 | 一句话直觉 | 类比 |
|---|---|---|
| 预训练 | 把全人类的文字都读了一遍,学会"续写" | 大量阅读练习 |
| 对齐问题 | 预训练目标(续写)≠ 我们的目标(有帮助),需要弥合这个差距 | 会开车 ≠ 会送货 |
| SFT | 给几万个好例子,让模型学格式和风格 | 看示范做一遍 |
| 奖励模型 | 训练一个"评分机器人",模仿人类的偏好判断 | 培训裁判 |
| PPO | 用奖励信号慢慢优化策略,但每步最多改一点点(防崩溃) | 慢慢打方向盘 |
| KL 惩罚 | 防止模型为了高分变成"套话机器",保持基本能力 | 系在 SFT 模型上的绳子 |
| DPO | 直接从"这个好/那个差"的对比中学习,不需要奖励模型 | 从录像对比中学习 |
| GRPO | 同一道题做很多遍,用组内好坏对比来学习(特别适合数学) | 大量刷题自我提升 |
| RLAIF | 用另一个 AI 代替人类打分,大幅降低成本 | 用 AI 助教批改作业 |
一个统一的视角:所有方法都在解决同一件事
所有这些方法,本质都是在回答:「怎么给语言模型一个学习信号,让它向'更符合人类偏好'的方向移动,同时不把已有能力弄坏?」
区别只在于:谁来提供学习信号(人类/AI/规则程序)?学习信号以什么形式出现(打分/排名/对比/答案正误)?语言模型如何利用这个信号(PPO 更新/DPO 对比/GRPO 组内归一)?每种选择都有它的成本-效果权衡。
下一步学习路线建议
📚 阅读原始论文
- InstructGPT(2022):RLHF 的奠基之作
- Constitutional AI(2022):RLAIF 的代表
- DPO(2023):Rafailov et al., Stanford
- DeepSeek-Math(2024):GRPO 原始论文
- DeepSeek-R1(2025):GRPO 的工业应用
🔧 动手实践
- 用 TRL 库(Hugging Face)实现 DPO 微调(10 行代码可以跑起来)
- 在 Alpaca 或 Dolly 数据集上做 SFT
- 用 Anthropic HH 数据集做 DPO 对齐
- 从 Open Instruct 框架学习完整的 RLHF 实现
🔗 相关主题
- Transformer 架构(理解策略网络的结构)
- Scaling Laws(理解模型大小与对齐的关系)
- 推理优化(KV Cache 等,理解推理时的计算)
- 多模态对齐(RLHF 扩展到图像/视频)
LLM 的 RL 公式看起来复杂,但每一个符号背后都有直觉对应。建议学习顺序:
- 先用类比和例子建立直觉(本文就是这个目的)
- 然后回看公式,尝试用直觉解释每一项
- 用 PyTorch 实现一个玩具版本(比如用 GRPO 训练一个解加法的微型模型)
- 阅读 TRL 库的源码,看看工业级实现如何处理细节
下面的代码是教学向的简化版 RLHF,不依赖任何框架,只用 PyTorch 基础 API,帮助理解每个阶段"在做什么"。每一行都有注释,先看注释理解意图,再看代码理解实现。
Phase 1:SFT 监督微调——模仿"好例子"
# ───────────────────────────────────────────────────── # Phase 1: SFT —— 给模型看"好回答"示范,让它学习模仿 # ───────────────────────────────────────────────────── import torch import torch.nn as nn import torch.nn.functional as F # 极简"语言模型":输入一个 prompt 向量,输出两个动作的 log 概率 # 动作 0 = "说有帮助的话",动作 1 = "说无关的话" class TinyLM(nn.Module): def __init__(self): super().__init__() self.net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 2)) def forward(self, prompt): return F.log_softmax(self.net(prompt), dim=-1) # 输出 log 概率 policy = TinyLM() # 这是我们要训练的策略模型 ref_policy = TinyLM() # 参考模型(SFT后冻结,用于计算KL) optimizer_sft = torch.optim.Adam(policy.parameters(), lr=1e-3) # SFT 数据:(prompt 向量, 正确动作) 对 # 含义:当 prompt 为 [1,0,0,0] 时,正确动作是 0("说有帮助的话") sft_data = [ (torch.tensor([1.,0.,0.,0.]), 0), # 问题A → 应该回答有帮助的内容 (torch.tensor([0.,1.,0.,0.]), 0), # 问题B → 应该回答有帮助的内容 (torch.tensor([0.,0.,1.,0.]), 0), # 问题C → 应该回答有帮助的内容 ] # SFT 训练:标准的交叉熵损失,让模型模仿标注员的回答 for epoch in range(100): for prompt, correct_action in sft_data: log_probs = policy(prompt) # 模型输出动作的 log 概率 loss = -log_probs[correct_action] # 负对数似然:让正确动作概率最大 optimizer_sft.zero_grad() loss.backward() optimizer_sft.step() # SFT 完成后,把模型参数复制给参考模型(ref_policy 从此冻结,不再更新) ref_policy.load_state_dict(policy.state_dict()) for p in ref_policy.parameters(): p.requires_grad = False print("Phase 1 完成:SFT 模型已训练,参考模型已冻结")
Phase 2:训练奖励模型——学会"打分"
# ───────────────────────────────────────────────────── # Phase 2: 训练奖励模型 —— 从人类排序中学会打分 # ───────────────────────────────────────────────────── # 奖励模型:输入 (prompt, 动作),输出一个实数分数 class RewardModel(nn.Module): def __init__(self): super().__init__() # 输入 = prompt(4维) 拼上动作的 one-hot(2维) = 6维 self.net = nn.Sequential(nn.Linear(6, 8), nn.ReLU(), nn.Linear(8, 1)) def score(self, prompt, action): action_onehot = torch.zeros(2) action_onehot[action] = 1.0 x = torch.cat([prompt, action_onehot]) # 拼接输入 return self.net(x).squeeze() # 输出标量分数 reward_model = RewardModel() optimizer_rm = torch.optim.Adam(reward_model.parameters(), lr=1e-3) # 人类标注的偏好数据:(prompt, 好动作 y_w, 差动作 y_l) # 含义:对于这个 prompt,动作 0 比动作 1 更好 preference_data = [ (torch.tensor([1.,0.,0.,0.]), 0, 1), # 对问题A:动作0 > 动作1 (torch.tensor([0.,1.,0.,0.]), 0, 1), # 对问题B:动作0 > 动作1 (torch.tensor([0.,0.,1.,0.]), 0, 1), # 对问题C:动作0 > 动作1 ] # BT(Bradley-Terry)损失:让好动作的分数 > 差动作的分数 for epoch in range(200): for prompt, y_w, y_l in preference_data: score_winner = reward_model.score(prompt, y_w) # 好回答的分 score_loser = reward_model.score(prompt, y_l) # 差回答的分 # 损失:-log sigmoid(好分 - 差分),让差值尽量大 loss = -F.logsigmoid(score_winner - score_loser) optimizer_rm.zero_grad() loss.backward() optimizer_rm.step() print("Phase 2 完成:奖励模型已训练") # 验证:问题A 下,动作0 的分数应该高于动作1 p = torch.tensor([1.,0.,0.,0.]) print(f" 动作0(好回答)得分: {reward_model.score(p,0):.3f}") print(f" 动作1(差回答)得分: {reward_model.score(p,1):.3f}")
-log sigmoid(好分 - 差分),不需要绝对分数,只需要"好的比差的分高"就行。训练完后奖励模型就是一个"自动打分机",接下来 Phase 3 用它代替人类给分。
Phase 3:PPO 强化学习——按照"打分机"的标准进化
# ───────────────────────────────────────────────────── # Phase 3: PPO 强化学习 —— 用奖励模型当"裁判"优化策略 # ───────────────────────────────────────────────────── optimizer_ppo = torch.optim.Adam(policy.parameters(), lr=1e-4) beta = 0.1 # KL 惩罚系数:越大越不允许偏离参考模型 eps = 0.2 # PPO 裁剪范围:概率比最多允许 [0.8, 1.2] prompts = [torch.tensor([1.,0.,0.,0.]), # 问题 A torch.tensor([0.,1.,0.,0.]), # 问题 B torch.tensor([0.,0.,1.,0.])] # 问题 C for step in range(300): total_loss = 0.0 for prompt in prompts: # ── Step A:让当前策略采样一个动作 ────────────────── log_probs_now = policy(prompt) # 当前策略的 log 概率 probs_now = log_probs_now.exp() # 转为概率 action = torch.multinomial(probs_now, 1).item() # 按概率采样一个动作 # ── Step B:用奖励模型打分(代替人类实时打分)────── reward = reward_model.score(prompt, action).detach() # 不对 RM 求梯度 # ── Step C:计算 KL 惩罚(防止偏离参考模型太远)── with torch.no_grad(): log_probs_ref = ref_policy(prompt) # 参考模型(冻结)的 log 概率 # KL(current || ref) ≈ sum(p * (log_p - log_q)) kl_penalty = (probs_now * (log_probs_now - log_probs_ref)).sum() # 最终奖励 = RM 奖励 - β × KL(KL 越大,扣分越多) advantage = reward - beta * kl_penalty.detach() # ── Step D:PPO 裁剪目标 ────────────────────────────── with torch.no_grad(): old_log_prob = log_probs_now[action].clone() # 旧策略的 log 概率(固定) new_log_prob = policy(prompt)[action] # 重新前向,当前策略的 log 概率 ratio = (new_log_prob - old_log_prob).exp() # ρ = π_new / π_old(概率比) # PPO 核心:取「不裁剪目标」和「裁剪目标」的最小值 obj_unclipped = ratio * advantage # 不裁剪:直接 ρ × A obj_clipped = ratio.clamp(1-eps, 1+eps) * advantage # 裁剪:ρ 限制在[0.8,1.2] ppo_loss = -torch.min(obj_unclipped, obj_clipped) # 取 min 再取负(最小化损失) total_loss += ppo_loss # 对所有 prompt 的 loss 求均值,反向传播更新策略 optimizer_ppo.zero_grad() (total_loss / len(prompts)).backward() optimizer_ppo.step() # 每 100 步打印一次,观察策略进化情况 if step % 100 == 0: p = prompts[0] probs = policy(p).exp().detach() print(f"step {step:3d} | 动作0(好话)概率={probs[0]:.3f} 动作1(废话)概率={probs[1]:.3f}") print("\nPhase 3 完成!最终策略:") for i, p in enumerate(prompts): probs = policy(p).exp().detach() print(f" 问题{i+1}:说好话概率={probs[0]:.3f},说废话概率={probs[1]:.3f}")
Phase 1 完成:SFT 模型已训练,参考模型已冻结 Phase 2 完成:奖励模型已训练 动作0(好回答)得分: 1.247 动作1(差回答)得分: -0.983 step 0 | 动作0(好话)概率=0.531 动作1(废话)概率=0.469 step 100 | 动作0(好话)概率=0.713 动作1(废话)概率=0.287 step 200 | 动作0(好话)概率=0.856 动作1(废话)概率=0.144 Phase 3 完成!最终策略: 问题1:说好话概率=0.891,说废话概率=0.109 问题2:说好话概率=0.876,说废话概率=0.124 问题3:说好话概率=0.883,说废话概率=0.117
PPO 训练后,"说好话(有帮助的回答)"的概率从 SFT 初始的 ~53% 提升到了 ~89%。这就是强化学习对齐的效果。
代码结构对应真实 RLHF 的哪些部分?
| 代码里的 | 对应真实 RLHF 中的 | 简化了什么 |
|---|---|---|
TinyLM(4维输入→2维输出) | GPT-4 这类十亿参数语言模型 | 词表从 10 万个→2 个动作;序列生成→单步选择 |
sft_data 3 条数据 | InstructGPT 的 13,000 条标注 prompt-response 对 | 数据量 |
preference_data 3 条排序 | 数十万条人类标注的偏好比较对 | 数据量;真实中每个 prompt 有 4–9 个候选回答 |
reward - beta * kl_penalty | 完全相同!这是核心公式,没有简化 | — |
PPO 裁剪(ratio.clamp) | 完全相同!这是 PPO 的核心机制 | — |
| 无 Critic(价值网络) | 真实 PPO 有第四个模型 Critic 估计 $V(s_t)$ | 把 advantage 简化为奖励本身,省去了价值估计 |
loss = -log_probs[correct_action]→ SFT 的本质:让正确动作的概率最大loss = -F.logsigmoid(score_winner - score_loser)→ RM 的本质:让好回答分数 > 差回答分数advantage = reward - beta * kl_penalty→ RLHF 的核心:奖励 - KL 惩罚 = 最终学习信号ratio.clamp(1-eps, 1+eps)→ PPO 的核心:限制每次更新幅度,防止崩溃