ch11 · 对齐(Alignment)¶
SFT 之后的模型已经能 "按格式回答",但回答的好坏还没人教过它。 "好"是个主观概念 —— 同一个问题两个回答,A 比 B 好在哪?这种偏好信号 SFT 学不到。
对齐(alignment) 就是把 "人类评委的偏好" 塞进模型的过程。
本章以理论 + 公式推导为主,echo 项目 M5 对齐阶段会回来把 DPO 落地。
学习目标¶
- 能讲清 RLHF 三段式各自的作用与产物
- 能解释 PPO 在 LLM 对齐中为什么难训(奖励 hacking、高方差、显存开销)
- 能写出 DPO loss 公式,并解释 β、参考模型 π_ref 的角色
- 知道在不同数据形态(成对偏好 / 单点打分 / 二元接受)下选哪种对齐算法
前置依赖¶
- ch10(SFT、loss mask、LoRA);ch09 的 CLM loss
- 对 "概率模型对一段序列的对数似然"
log P(y|x) = Σ log P(y_t|x, y_<t)要熟
1. 为什么需要对齐¶
1.1 SFT 的天花板¶
SFT 数据形如 (prompt, response),是一种 "模仿学习" —— 告诉模型这样答,但不告诉模型为什么这样比那样好。
实际场景里同一个 prompt 可以有许多合理回答:
Q: "怎么学 Python?"
A1: 推荐《Python Crash Course》。 ← 简洁
A2: 学 Python 没有标准答案,要看你目标... ← 详细
A3: 先 print('hello world') ← 偷懒
哪个最好?看场景、看用户、看心情。SFT 数据里每条 prompt 通常只配一个 response,模型学到的只是 "某种合理回答的样本",不是 "哪种回答更被偏好"。
1.2 偏好信号比 "金标准" 便宜¶
让人写一条理想的 response 很贵(要写、要校);让人在两条候选里选一个更好很便宜(点击一下)。所以对齐数据通常是 成对偏好:
人不需要写答案,只需要在两个候选间选边。OpenAI / Anthropic 都靠这种数据规模化做对齐。
自检¶
- 已经做了 SFT 的模型为什么还可能 "礼貌但不正确" 或 "胡乱讨好"?对齐能解什么、不能解什么?
答案速查
- SFT 学的是 "格式 + 风格",质量上限取决于训练数据中的 response 质量。对齐通过偏好信号让模型在多个候选里选更好的方向,能改善 "哪种回答更好" 的判断。但若偏好数据本身有偏(如标注员偏爱长回答、偏爱讨好语气),模型便会学到这些偏好。对齐不能创造新知识,只能重新加权已有能力
2. RLHF 三段式¶
RLHF —— Reinforcement Learning from Human Feedback,中译「基于人类反馈的强化学习」。Christiano 2017 / OpenAI InstructGPT 2022 把 RLHF 推到主流。结构清晰但工程复杂。
RLHF 把流程拆成三段:
- SFT 打底获得基本对话能力(π_SFT)
- 用人类标注的偏好对比数据基于 π_SFT 训出一个 Reward Model(RM)来量化 "好坏"
- 最后用 RL 算法 PPO(Proximal Policy Optimization,近端策略优化;一种通过限制每步更新幅度来保证训练稳定的强化学习算法)以 RM 打分为奖励信号,驱动 SFT 模型持续改进输出质量。
阶段 1: SFT
数据: (prompt, response)
目标:见 ch10
产出: π_SFT —— 一个"会按格式说话" 的模型
阶段 2: RM(reward model)
数据: (prompt, chosen, rejected)
目标: 好回答得分 > 差回答得分,即 RM(prompt, chosen) > RM(prompt, rejected)
产物:RM —— 能对任意 (prompt, response) 输出标量分数的评分模型
阶段 3: PPO
从 π_SFT 的拷贝(π_ref)出发,以 RM 打分为奖励信号,用 PPO 持续优化
目标: maximize E[RM(p, π_RL(p))] - β · KL(π_RL || π_SFT)
↑ 让 π_RL 的回答拿更高分 ↑ 不让 π_RL 偏离 π_SFT 太远
产出:π_RL —— 经过 RL 优化后的对话模型
其中,对于阶段 2 的主要技术细节有:
- RM 为基于 π_SFT 改造:把 lm_head(输出词表预测分布,见 ch06)换成 scalar head(只输出一个标量分数)
-
loss: -log σ(RM(p, c) - RM(p, r))
σ = sigmoid;两个分数做差过 sigmoid 再取负对数 → pairwise ranking loss
理论来源:Bradley-Terry 模型(经典的成对比较概率模型)
其中,阶段 3 的拆解如下:
E[RM(p, π_RL(p))]: π_RL 生成的回答平均能拿多高的 RM 分π_RL(p)= 当前模型对 prompt 生成的回答E[RM(...)]= 在很多 prompt 上的回答的 RM 得分期望(平均)
β · KL(π_RL || π_SFT):π_RL 偏离 π_SFT 越多,扣分(惩罚)越多- KL 散度 = 衡量 π_RL 和 π_SFT 两个分布的差异程度
- β = 控制惩罚力度的系数
2.1 PPO 的 KL 惩罚¶
KL(Kullback-Leibler divergence,KL 散度)是信息论中衡量两个概率分布差异的经典度量。
在 RLHF 语境下:P = π_RL 的 token 分布,Q = π_SFT 的 token 分布。对每个位置计算这个值,衡量 π_RL 输出偏离 π_SFT 多少。
PPO 优化 reward 时模型会钻 RM 的漏洞(reward hacking)—— RM 从有限数据训出,评分能力有盲区,存在 "RM 打高分但人类觉得差" 的输出模式(如堆砌套话、重复讨好句式)。模型一旦发现这类捷径,没有拉回策略的话会迅速收敛到这种退化输出。
KL 项把策略约束在 π_SFT 附近,飘越远施加惩罚越大,抑制大幅偏离,也间接 "缓解" 了 reward hacking。β 控制约束强度:β 大 → 偏保守;β 小 → 偏激进。
2.2 PPO 的问题¶
PPO 的问题有哪些呢?
| 问题 | 来源 |
|---|---|
| 显存爆炸 | 训练时同时活着 3 个独立模型副本 + 1 个轻量头:π_RL(含 value head,共享 backbone)、π_ref、RM |
| 超参敏感 | β / clip_eps / lr / KL 目标值,调一组花几天 |
| 训练不稳 | RL 信号高方差,loss 曲线像心电图,崩盘频繁 |
| 实现复杂 | rollout、advantage 估计、ratio clip、value loss 全要写对 |
| Reward hacking | 模型钻 RM 漏洞,输出讨好但实质差 |
PPO 内部术语简介::
- π_ref:π_SFT 的冻结副本,专门用来算 KL 散度,训练中不更新
- value head:挂在 base model 上的 scalar 输出头(与 lm_head 并列共享 backbone),用来估计每个状态的期望回报,算 advantage 时当 baseline
- clip_eps:PPO 裁剪阈值,限制单步策略更新幅度,防止一步跨太大崩盘
- rollout:让 π_RL 对一批 prompt 生成回答,收集(prompt, response, reward)作为训练数据
- advantage 估计:reward − value(实际回报 − baseline),衡量 "这个回答比平均水平好多少"
- ratio clip:新旧策略输出同一 token 的概率之比,裁剪到 [1-ε, 1+ε] 范围内,是 PPO 保证稳定的核心机制
"学术界的 PPO 论文很美,工业界的 PPO 训练日志很丑。"
自检¶
- RM 训练用的是分类还是回归?loss 公式直觉是什么?
- 没有 KL 惩罚 PPO 会怎样?把 β 调到 0 试试?
答案速查
-
打分回归 + 排序约束。形式上 RM 输出标量,但 loss 是
-log σ(s_chosen - s_rejected),本质是让 chosen 比 rejected 分数高的二元交叉熵(Bradley-Terry / pairwise logistic)。RM 学的是"哪个更好"的相对排序,不是绝对分数 -
模型会快速学会 reward hacking 模式(重复某些讨好短语 / 特定格式),输出虽然 RM 打分很高但完全失去语言能力。β=0 等价于"无约束最大化 RM",必然崩
3. DPO¶
Rafailov et al. 2023。把 RLHF 三段简化成一段:RM 不要了,PPO 不要了,直接对偏好数据做有监督训练 —— DPO(Direct Preference Optimization)
3.1 关键洞察¶
回忆一下上面 RLHF 优化的目标是:
目标里 r 想把概率推向高 reward 的 y,KL 想把概率拉回 π_ref,二者平衡解就是"以 π_ref 为先验,按 reward 指数加权重新归一化"。
数学上这个优化问题有闭式最优解:
(数科的)直觉:这是带 KL 约束最大化问题的标准结论(与最大熵 / 玻尔兹曼分布同源),完整推导见 DPO 论文附录 A.1。
反过来,如果已知 π*(理论最优模型)和 π_ref 就能反推 reward:
把这个 r 代回 RM 的 Bradley-Terry loss -log σ(r_chosen - r_rejected),const(x) 抵消了(chosen 和 rejected 共享同一 prompt x,const(x) 相减为 0)。然后用当前训练模型 π 替代理论最优 π(训练目标即让 π 逼近 π):
# 式 3
# π = 当前训练中的模型,目标是逼近 π*
L_DPO = -E[ log σ( β · (log π(y_c|x)/π_ref(y_c|x) - log π(y_r|x)/π_ref(y_r|x)) ) ]
└─ chosen 的 logp ratio ──┘ └─ rejected 的 logp ratio ─┘
- β 来自原 RLHF 目标里的 KL 系数 —— 它同时是隐式 KL 约束强度:β 大 → 约束强,π 留在 π_ref 附近;β 小 → 约束弱,π 可以漂很远
- 训练时 minimize L_DPO,让 π 往设想的 π* 逼近
- 不再需要显式 RM 了
3.2 公式直觉¶
DPO loss 想让模型干的事:
- 拉高
log π(chosen|x) - log π_ref(chosen|x)—— 比参考模型更倾向输出 chosen - 压低
log π(rejected|x) - log π_ref(rejected|x)—— 比参考模型更不倾向输出 rejected
整个 loss 形如二元交叉熵,完全监督学习,没有采样、没有 RL 信号、没有 advantage 估计。
DPO 把 RL 问题降级成了普通的监督学习问题,工程上和训 SFT 一样简单,一个常规的 forward + backward 即可。
3.3 必须冻结的 π_ref¶
π_ref 是 "参考点" —— 通常就是 SFT 后的模型。它的作用:
- 防漂移:DPO 隐式包含 KL 约束,π 跑离 π_ref 太远会被惩罚
- 基准:
log π(y) - log π_ref(y)衡量的是 "相对参考模型,π 在 y 上变了多少"
实现上 π_ref 冻结,前向产出 logp 供 loss 计算但不参与反向传播(梯度只流过 π)。代价:训练时显存里要同时放两个模型副本。
LoRA(见 ch10 §4)场景下可省掉 —— base 权重 W 本身就是 SFT 后的状态且冻结不动,关闭 LoRA 旁路走纯 W 即为 π_ref,打开旁路走 W+BA 即为 π,同一份 base 按需切换身份,零额外显存。
3.4 训练示例¶
训练用的样本大致格式:
计算流程中,需要先把 chosen / rejected 各拼成完整对话送进模型,分别算 m=π/π_ref 的 log m(chosen|prompt) 与 log m(rejected|prompt):
# 伪代码
for batch in loader:
# π 与 π_ref 都吃同样的输入
logp_pi_c = sequence_logp(model, batch.prompt + batch.chosen)
logp_pi_r = sequence_logp(model, batch.prompt + batch.rejected)
logp_ref_c = sequence_logp(ref_model, batch.prompt + batch.chosen)
logp_ref_r = sequence_logp(ref_model, batch.prompt + batch.rejected)
pi_ratio_c = logp_pi_c - logp_ref_c # chosen 的 logp 比例
pi_ratio_r = logp_pi_r - logp_ref_r # rejected 的 logp 比例
loss = -F.logsigmoid(beta * (pi_ratio_c - pi_ratio_r)).mean()
sequence_logp(model, ids)做的事:forward 拿 logits →log_softmax沿 vocab 维 → 用gather取出真实 token 位置的 logp → 沿 response 部分sum。结果是单个标量(每条样本一个),代表"模型生成这条 response 的对数概率之和"。完整实现见练习01_dpo_loss.py。prompt 与 chat template 部分不计入(与 SFT 的 loss mask 同源)。
3.5 DPO 优势与代价¶
| 维度 | RLHF (PPO) | DPO |
|---|---|---|
| 阶段数 | 3(SFT + RM + PPO) | 2(SFT + DPO) |
| 训练稳定性 | 心电图 | 平稳,类似常规 SFT |
| 显存 | 4 个模型 | 2 个模型(π + π_ref),LoRA 下 1 个 |
| 超参敏感度 | 高 | 低(主要就 β,0.1–0.5) |
| 数据复用率 | RM 训完丢,PPO 用 RM 在线打分 | 偏好对直接训,离线即可 |
| 理论上限 | RM 是真 reward 的近似,PPO 能逼近 RM 最优 | 直接拟合最优解,但只用了静态数据,无法在线探索 |
实务上 DPO 几乎完全替代了 PPO 在开源界的位置。Llama-3、Qwen、Yi 系列正式版的对齐都是 DPO 或其变体。
自检¶
- DPO 公式里 β 调大调小分别会怎样?
- 为什么 DPO 要冻结 π_ref 而不是动态更新?
答案速查
-
β 是 DPO 隐式 KL 约束的强度系数。
- β 大 → sigmoid 饱和更快(margin 略大就趋零梯度)→ 模型不需要把 margin 推得很大就能让 loss 趋零 → 收敛时 π 更靠近 π_ref(约束强、保守)
- β 小 → sigmoid 平缓,需要 margin 很大才能压低 loss → π 漂得远(约束弱、激进)
典型值 0.1–0.5。注意 01 验证 3 测的是"同一 margin 下"的单步梯度,那是公式里的线性放大;这里讨论的是"训练收敛终点"的偏移,由 sigmoid 饱和速度主导,二者不矛盾
-
π_ref 是隐式 KL 约束的"原点"。如果 π_ref 跟着 π 一起更新,约束失效(π 永远等于 π_ref,
logp - logp_ref始终 0),整个 loss 退化为常数
4. KTO / ORPO 简提¶
DPO 解决了 PPO 的工程难题,但仍依赖成对偏好数据(来自用户真实反馈或标注员)。许多场景这种数据不易得:
- 用户日志通常是 "点赞 / 不点赞"(二元单点信号)
- 真实标注员有时只能说 "这个还行" 或 "这个不行"(不在/难以在两个间选)
后续工作针对不同数据形态扩展:
| 算法 | 数据形态 | 一句话 |
|---|---|---|
| DPO | (prompt, chosen, rejected) | 成对偏好 |
| KTO | (prompt, response, ±1) | 单点二元信号,每条样本独立打 "好/坏" |
| ORPO | (prompt, chosen, rejected) | DPO + SFT 合并成一阶段,无需 π_ref |
| IPO(Identity Preference Optimization,恒等偏好优化) | (prompt, chosen, rejected) | DPO 的 loss 改良,缓解过拟合偏好对 |
| SimPO(Simple Preference Optimization,简化偏好优化) | (prompt, chosen, rejected) | 也无需 π_ref,把 logp 平均(除以 response 长度)当作隐式 reward —— 平均化让模型自己充当"长度归一"的参考点,省掉 π_ref |
以 7B 量级模型的对齐,工程选型决策树如下:
有 (chosen, rejected) 偏好对?
├─ 是 ─── 显存够(>20GB)?
│ ├─ 是 ─── DPO(首选,社区最稳)
│ └─ 否 ─── ORPO 或 SimPO(无 π_ref,省一份显存)
└─ 否 ─── 数据是"赞/踩"二元?
├─ 是 ─── KTO
└─ 否 ─── 回去标偏好对,或回到 SFT 多迭代数据
工业界几乎不再考虑 PPO(除非你是 OpenAI/Anthropic 这种有专门 RL 团队的)。学术界 PPO 仍有研究价值(探索 + 在线学习)。
echo-mini / echo 的对齐路线:先用 DPO 跑通主流程,需要时再切 ORPO / SimPO 省显存。
自检¶
- ORPO 把 SFT 与对齐合并到一个阶段,听上去更省事,为什么 DPO 仍是事实标准?
- KTO 的"二元点赞"数据有什么坑?
答案速查
-
ORPO 把 SFT loss 与偏好 loss 加权混在一起,超参(两个 loss 的权重)需要调,且没有独立 SFT 阶段意味着模型还没学会基本格式就要学偏好,对底座质量要求更高。DPO 流程更分离、更可控、社区实践更多
-
单点信号方差大、噪声大,且"全是点赞"或"全是踩"的数据集有严重 class imbalance;好回答与差回答之间没有显式比较关系,模型只能学"绝对好/绝对差"的边界,远不如成对信号信息量大
5. 练习¶
落到 Playground/ch11-alignment/:
| 脚本 | 内容 |
|---|---|
01_dpo_loss.py |
手写 DPO loss 公式,用玩具 logits 验证:chosen logp 上升、rejected logp 下降;β 影响梯度幅值 |
02_beta_effect.py |
固定一对 (chosen, rejected) 数据,扫不同 β 训若干步,看 loss 与 logp ratio 的演化曲线 |
跑法:
uv run python Playground/ch11-alignment/01_dpo_loss.py
uv run python Playground/ch11-alignment/02_beta_effect.py
不依赖 trl / transformers,纯 PyTorch 玩具数据,跑几秒钟,看清公式行为即可。
思考题¶
- DPO 不显式训 RM,是不是说 reward 模型在 DPO 里就消失了?从 §3.1 的推导看,π 与 r 的关系是什么?
- 假设你的 SFT 数据本身有偏(如全是过度礼貌的回答),DPO 能纠正吗?需要什么样的偏好数据?
- 为什么 LoRA 与 DPO 是天然搭档?(提示:π_ref 的处理)
参考资料¶
- Christiano et al., "Deep Reinforcement Learning from Human Preferences" (2017):RLHF 起源
- Ouyang et al., "Training language models to follow instructions with human feedback" (InstructGPT, 2022):RLHF 三段式范本
- Rafailov et al., "Direct Preference Optimization" (DPO, 2023):DPO 原论文,§4 推导值得细读
- Ethayarajh et al., "KTO: Model Alignment as Prospect Theoretic Optimization" (2024)
- Hong et al., "ORPO: Monolithic Preference Optimization without Reference Model" (2024)
- HuggingFace TRL:https://huggingface.co/docs/trl — DPO/KTO/ORPO 全有官方实现