第 3 周 · Day 2:为什么 SFT 不够,RLHF 与奖励模型

Transformers and Large Language Models · Week 3 · Day 2。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

个人整理的学习笔记,非官方讲义;课程来源与整理说明见课程介绍。配套示例未逐一运行验证。

本篇目录

时长 7 小时:§1 SFT 的天花板(1h)· §2 奖励模型(2.5h)· §3 RLHF 的全貌与过度优化(1.5h)· §A–§C(2h)

对应正课 Lecture 5 的前半部分:偏好微调、RLHF 概览、奖励建模。学习计划点名要会的:为什么 SFT 不够;RLHF 三阶段每一步的输入输出;Bradley-Terry 损失为什么是这个形式;KL 惩罚去掉会怎样。

配套代码 w3day2_checks.py(纯 numpy):Bradley-Terry 从成对比较恢复分数、标注噪声给准确率设的天花板、奖励模型学到标注者的偏见、best-of-n 的过度优化、KL 约束最优策略随 β 的变化

§B 参考实现(code/ 目录):w3d2_reward_model.py(在 mini-GPT 上训一个玩具奖励模型,供 Day 3 的 PPO 使用;在 Qwen2.5-0.5B-Instruct 上用 UltraFeedback 训练真实的奖励模型,并检查长度偏见)。

今天的目标:能说清 SFT 缺了什么;能从”潜在效用 + 噪声”推出 Bradley-Terry 模型和它的损失;能解释奖励模型 70% 的准确率意味着什么;能画出 RLHF 的完整数据流;能说清过度优化是怎么发生的,以及 KL 惩罚为什么能缓解它。


§0 这一周在做什么

第 2 周结束时,你用 SFT 把一个续写器变成了助手(Week 2 Day 6 §1)。这一周回答两个问题:

  1. 怎样让助手的回答”更好”,而不只是”像训练数据”? 这是 Lecture 5:偏好微调。今天讲奖励模型和 RLHF 的全貌,Day 3 讲强化学习(PPO),Day 4 讲绕过强化学习的 DPO。
  2. 怎样让模型”更会想”? 这是 Lecture 6:推理模型。Day 5 讲可验证奖励的强化学习(RLVR)与 GRPO,Day 6 讲测试时缩放。

两讲共用一套数学:在 KL 约束下最大化期望奖励。今天 §3.3 引入它,Day 3 用 RL 求近似解,Day 4 推出它的闭式解,Day 5 换成可自动验证的奖励。

今天要用到的旧结论:交叉熵与最大似然(Week 0 Day 1);逻辑回归与 sigmoid 的梯度(Week 0 Day 1 §10.1 的 log-sum-exp);SFT、chat template 与 loss 掩码(Week 2 Day 6 §1);温度与采样(Week 2 Day 3 §2);自洽性投票(Week 2 Day 3 §5.3)。


§1 为什么 SFT 不够

1.1 SFT 只能模仿

SFT 的损失是 −log⁡πθ(y∗∣x)-\log\pi_\theta(y^*\mid x):让模型模仿示范回答 y∗y^*。它有三个根本局限:

  • 没有负信号。SFT 只告诉模型”这样写”,从不告诉它”不要这样写”。模型有害的、编造的、啰嗦的输出,在 SFT 里没有任何惩罚,除非恰好和示范冲突。
  • 表达不了”A 比 B 好”。很多时候没有唯一正确的回答,只有更好和更差。两个都说得过去的回答,SFT 没法表达偏好哪一个。
  • 示范的上限就是示范者。模仿学习最多学到和示范一样好。而且模型训练时只见过”完美的前文”,推理时却要在自己生成的前文上继续,一步错就可能步步错(和 Week 0 Day 4 §4.3 的 exposure bias 是同一个问题)。

1.2 比较比写容易

关键观察:让人写一个好回答很难、很贵;让人比较两个回答哪个更好,容易得多,也更一致。这是”生成-验证差距”的一个实例:很多问题上判断比创造容易。

于是换一种数据:给定提示 xx,模型生成几个回答,标注者给出排序或两两比较。这种数据里既有”好”也有”不好”,还有它们之间的相对关系。

1.3 从”像人写的”到”人喜欢的”

目标变了:SFT 优化”像示范”,偏好微调优化”人更喜欢”。怎么把一堆两两比较变成可以优化的目标?有两条路:

  1. 先学一个奖励模型,把偏好压缩成一个打分函数 r(x,y)r(x,y),再用强化学习最大化它。这是 RLHF(今天 §2–§3,Day 3)。
  2. 直接用偏好数据训练策略,跳过奖励模型和强化学习。这是 DPO(Day 4)。它之所以可能,是因为第 1 条路的最优解有闭式形式。

InstructGPT(Ouyang et al. 2022)是第 1 条路的标杆:经过 RLHF 的 1.3B 模型,其输出比 175B 的 GPT-3 更受人类标注者偏好。对齐带来的提升超过了一百多倍的参数量。


§2 奖励模型

2.1 偏好数据

一条偏好数据是 (x, yw, yl)(x,\ y_w,\ y_l):提示 xx,被选中的回答 ywy_w(winner),被拒绝的回答 yly_l(loser)。

InstructGPT 的做法:对每个提示采样 K=4K=4 到 99 个回答,标注者给出完整排序,于是一个提示产生 (K2)\binom K2 个比较对(K=4K=4 时 6 对,K=9K=9 时 36 对)。一个细节:同一提示的所有比较对放进同一个 batch 元素里一起算,而不是打散成独立样本。否则每个回答在一个 epoch 里被看 K−1K-1 次,奖励模型很快过拟合。

现代的公开数据集:Anthropic HH-RLHF(有用 / 无害两类)、UltraFeedback(由 GPT-4 按细则打分再二值化成偏好对,参考代码 --hf 用它)、HelpSteer 等。

2.2 Bradley-Terry:从”偏好”到”分数”

动机:我们想要一个标量打分函数 r(x,y)r(x,y),但数据只有两两比较。需要一个模型把两者联系起来。

直觉:设每个回答有一个潜在的”效用”,标注者比较时感受到的是效用加上一点随机噪声,选感受更高的那个。

推导:设标注者感受到的效用是 ui=ri+ϵiu_i=r_i+\epsilon_i,噪声 ϵi\epsilon_i 独立同分布于标准 Gumbel 分布。两个独立 Gumbel 之差服从 logistic 分布,于是

P(yi≻yj)=P(ϵj−ϵi<ri−rj)=σ(ri−rj)=erieri+erj.P(y_i\succ y_j)=P(\epsilon_j-\epsilon_i<r_i-r_j)=\sigma(r_i-r_j)=\frac{e^{r_i}}{e^{r_i}+e^{r_j}}.

这就是 Bradley-Terry 模型(1952)。换成正态噪声会得到 probit 模型(Φ\Phi 代替 σ\sigma),形状几乎一样;选 logistic 是因为梯度简洁。

损失:对偏好数据做最大似然,

LRM=−E(x,yw,yl)[log⁡σ(rϕ(x,yw)−rϕ(x,yl))].\mathcal L_{RM}=-\mathbb E_{(x,y_w,y_l)}\Big[\log\sigma\big(r_\phi(x,y_w)-r_\phi(x,y_l)\big)\Big].

它就是一个逻辑回归:输入是”两个回答的分数差”,标签是”第一个更好”。梯度

∂L∂rw=−σ(rl−rw),∂L∂rl=+σ(rl−rw):\frac{\partial\mathcal L}{\partial r_w}=-\sigma(r_l-r_w),\qquad\frac{\partial\mathcal L}{\partial r_l}=+\sigma(r_l-r_w):

奖励模型排错得越厉害(rl≫rwr_l\gg r_w),梯度越大;已经排对了的对,梯度趋于 0。 这个”按错误程度加权”的形状,在 Day 4 的 DPO 里会再出现一次。

w3day2_checks.py 第 1 组:20 个回答,按 BT 模型随机生成比较,再用最大似然恢复分数:

比较次数恢复的分数与真值的相关系数均方根误差
2000.87630.831
20000.94850.185
200000.99610.051

只能确定到一个常数:所有分数同时加 3.7,所有 P(i≻j)P(i\succ j) 的变化是 2×10−162\times10^{-16}。损失只看同一个提示下两个回答的分数差,所以不同提示之间奖励的绝对值没有意义。RL 之前通常把奖励归一化(减均值、除以标准差),或者减去一个参考回答的奖励。

2.3 结构:语言模型 + 标量头

奖励模型通常就是一个语言模型(常从 SFT 模型初始化),把输出词表的 lm_head 换成一个输出标量的线性层,取最后一个 token 的隐状态打分:

rϕ(x,y)=w⊤hL(最后一个 token of [x;y])+b.r_\phi(x,y)=w^\top h_L\big(\text{最后一个 token of }[x;y]\big)+b.

为什么是最后一个 token:因果注意力下只有最后一个位置看到了完整的提示和回答(Week 1 Day 1 §3.1)。

为什么从 SFT 模型初始化:奖励模型要”读懂”回答才能打分,这需要语言理解能力,而且它要评价的正是 SFT 模型会产出的那类文本。InstructGPT 用了一个 6B 的奖励模型去训练 175B 的策略,并报告更大的奖励模型训练不稳定。

2.4 准确率的天花板

奖励模型在留出的偏好数据上的准确率通常只有 65%–75%。这是不是说明它很差?

不一定。标注本身就不一致。 InstructGPT 报告的标注者之间一致率约 73%。第 2 组:设真实奖励差 Δ∼N(0,2.352)\Delta\sim\mathcal N(0,2.35^2)、标注者以 σ(Δ)\sigma(\Delta) 的概率选对,这时两名标注者的一致率正好是 72.6%,而任何模型预测标注的准确率上限(贝叶斯最优)是 E[max⁡(σ(Δ),1−σ(Δ))]=0.801\mathbb E[\max(\sigma(\Delta),1-\sigma(\Delta))]=0.801:

训练对数留出集准确率上限与真实奖励的排序一致
1000.7860.8010.922
10000.7940.8010.951
100000.7990.8010.986

意义:准确率碰到上限之后就不再上升,但奖励模型和”真实偏好”的吻合程度(最后一列)还在变好。而这一列在实践中是测不到的,我们只能看到前一列。所以奖励模型的好坏,最终要靠”用它训练出来的策略好不好”来检验。RewardBench 这类基准用更干净、有明确答案的比较对来测,结果比在噪声数据上测的准确率更有参考价值。

2.5 奖励模型会学到标注者的偏见

奖励模型拟合的是**“标注者会选哪个”,不是”哪个真的更好”**。第 3 组:让标注者除了看质量,还有一点偏爱更长的回答(偏好系数 0.5):

标注者的长度偏好奖励模型学到的权重(质量 / 长度)比值
01.49 / −0.01−0.01
0.51.50 / 0.760.50

它把标注者的偏好比例原样学了过去。 真实的标注者偏爱更长、更自信、格式更花哨、更认同自己观点的回答(sycophancy,谄媚),奖励模型就会奖励这些特征。如果只收集长度相同的回答对,长度这一项就没有信号,学到的权重是 0.00。控制偏见要从数据入手:长度匹配、清晰的标注指南、多名标注者。

其他常见做法(知道即可):

  • Llama 2 训练了两个奖励模型(有用性、安全性),并在损失里加了一个与”偏好强度”相关的间隔项:−log⁡σ(rw−rl−m)-\log\sigma(r_w-r_l-m),标注者说”明显更好”时 mm 更大。
  • K 个回答的完整排序可以直接用 Plackett-Luce 模型(BT 的多元推广)建模,而不是拆成两两比较。

§3 RLHF 的全貌

3.1 三个阶段的数据流

学习计划的自测题:“画出 RLHF 的完整数据流,标出每一步训练哪个模型、用什么数据。”

阶段 1  SFT
  数据:示范 (x, y*)                   ← 标注者写
  训练:预训练模型 → π_SFT              损失 −log π(y*|x),只在回答上算(Week 2 Day 6 §1.3)

阶段 2  奖励模型
  数据:x → π_SFT 采样 K 个回答 → 标注者排序 → 偏好对 (x, y_w, y_l)
  训练:π_SFT 的主干 + 标量头 → r_φ      损失 −log σ(r_w − r_l)(§2.2)

阶段 3  强化学习(PPO,Day 3)
  数据:只有提示 x(不需要任何回答和标注)
  循环:π_θ 采样 y → r_φ(x, y) 打分 → 减去 β·KL(π_θ ‖ π_ref) → 用 PPO 更新 π_θ
  同时在显存里:π_θ(训练)、π_ref = π_SFT(冻结)、r_φ(冻结)、价值模型 V(训练)

几个要点:

  • 阶段 3 的数据是策略自己生成的(on-policy)。这是它和 SFT、奖励模型训练最大的区别,也是它慢、贵、难调的原因。
  • 参考模型 πref\pi_{ref} 通常就是 πSFT\pi_{SFT},冻结不动,用来算 KL。
  • 四个模型同时在显存里,这是 PPO 工程上难的根本原因(Day 3 §4.3)。
  • InstructGPT 还在 RL 的目标里混入了一部分预训练数据的语言模型损失(PPO-ptx),用来减轻”对齐税”:RLHF 之后在一些公开 NLP 基准上的退化(Day 4 §5.2)。
  • 实践中这个循环会迭代:用新策略采样、收集新的偏好、更新奖励模型、再做 RL(Llama 2 做了五轮)。

3.2 过度优化:奖励模型只是一个代理

动机:奖励模型 rϕr_\phi 是真实偏好的一个有噪声的近似。我们对它做优化,优化得越狠,越可能找到”它打高分、但人并不喜欢”的回答。这就是 Goodhart 定律:“当一个度量变成目标,它就不再是好的度量。”

直观的例子:奖励模型学到了”长一点好”(§2.5)。RL 很快发现:写得越长,分越高。最后的模型长篇大论、反复罗列。这就是 reward hacking。

第 4 组用 best-of-nn 演示:每个回答有真实质量 qq 和一个”可钻空子”的特征 ℓ\ell(比如啰嗦程度),奖励模型给的是 q+ℓq+\ell,真实质量是 q−0.3ℓ2q-0.3\ell^2(适度最好,过了就是坏事)。从参考模型采 nn 个回答,挑奖励模型打分最高的那个:

nnKL(上界 log⁡n−n−1n\log n-\frac{n-1}n)代理奖励真实质量
10.000.00−0.29
40.641.460.34
161.842.500.58
643.173.310.65
1283.863.690.68
2564.553.980.63
10245.934.600.54

代理奖励单调上升,真实质量先升后降。 nn 越大,挑出来的越是”在奖励模型的误差方向上走了极端”的回答。

Gao et al. 2022(Scaling Laws for Reward Model Overoptimization)在真实模型上系统地测了这件事:用一个大的”金标准”奖励模型当人类的替身,用小的代理奖励模型做优化。以 d=KL(π∥πref)d=\sqrt{\mathrm{KL}(\pi\|\pi_{ref})} 为横轴,金标准奖励的形状是:best-of-nn 为 d(α−βd)d(\alpha-\beta d),RL 为 d(α−βlog⁡d)d(\alpha-\beta\log d)。两者都是先升后降,而且代理奖励模型越大、数据越多,峰值越高、越靠后。

(表里的 KL 用的是常见公式 log⁡n−(n−1)/n\log n-(n-1)/n,它其实是 best-of-nn 真实 KL 的一个上界,在大部分情况下已经足够准确。)

3.3 KL 约束:不要离参考模型太远

目标:RLHF 实际优化的不是”最大化奖励”,而是

max⁡π Ex, y∼π[rϕ(x,y)]−β KL(π(⋅∣x) ∥ πref(⋅∣x)).\max_\pi\ \mathbb E_{x,\ y\sim\pi}\big[r_\phi(x,y)\big]-\beta\,\mathrm{KL}\big(\pi(\cdot\mid x)\,\|\,\pi_{ref}(\cdot\mid x)\big).

为什么需要 KL 项:

  1. 奖励模型只在参考模型的分布附近可信。它是在 πSFT\pi_{SFT} 的样本上训练的,离得越远,它的判断越没有依据,误差越容易被利用(§3.2)。
  2. 防止模式坍缩:没有 KL 项,最优策略是把全部概率放在奖励最高的一个回答上,多样性归零。
  3. 保留语言能力:KL 项把策略拴在一个会说人话的模型附近。

这个目标有闭式最优解(Day 4 §1 推导):

π∗(y∣x)=1Z(x) πref(y∣x)exp⁡ ⁣(r(x,y)β).\pi^*(y\mid x)=\frac1{Z(x)}\,\pi_{ref}(y\mid x)\exp\!\Big(\frac{r(x,y)}\beta\Big).

也就是在参考模型上按奖励做指数加权:β\beta 大,几乎不动;β→0\beta\to0,全部概率压到奖励最高的回答上。第 5 组用同一批回答算出每个 β\beta 下的最优策略(这个设定下有解析解:均值平移 a=1/βa=1/\beta,KL=a2\mathrm{KL}=a^2):

β\betaKL(采样 / 解析)代理奖励真实质量(采样 / 解析)
∞0.00 / 0.000.00−0.30 / −0.30
2.00.25 / 0.251.000.12 / 0.12
1.00.99 / 1.001.990.40 / 0.40
0.62.66 / 2.783.250.52 / 0.53
0.45.23 / 6.254.490.35 / 0.33
0.37.42 / 11.115.260.04 / −0.30

β\beta 太大学不到东西,太小就过度优化。 最优点在 KL 约 2.8 附近,和第 4 组 best-of-nn 的最佳点在同一个量级。β\beta 很小时采样估计比解析值低很多:40 万个样本里根本没有那么极端的回答,这也说明了”离参考模型太远的区域,参考模型根本采不到,奖励模型也从没见过”。

回答学习计划的自测题”KL 惩罚如果去掉会发生什么?” 策略会一路朝奖励模型的误差方向走:代理奖励持续上涨,真实质量先升后降(上表 β\beta 很小的几行)。表现为 reward hacking:回答越来越长、反复讨好用户、堆砌格式、重复某些”高分短语”,甚至输出不再通顺;多样性坍缩到少数几种模板。Day 3 的参考代码会在 mini-GPT 上让你亲眼看到它。

3.4 对抗 reward hacking 的工具箱

手段做什么出处
KL 惩罚把策略拴在参考模型附近§3.3
奖励归一化减均值、除标准差,让 β\beta 的尺度有意义§2.2
更大、更多数据的奖励模型推迟过度优化的拐点§3.2
奖励模型集成取多个奖励模型的均值或最小值,一个模型的误差不容易被利用
长度惩罚 / 长度控制直接对付最常见的一种 hacking§2.5
迭代收集数据用新策略的样本重新标注、更新奖励模型,补上盲区§3.1
早停按人工评测或留出的金标准奖励选检查点

§A 奖励模型:API 用法与边界

A.1 用 AutoModelForSequenceClassification 做奖励模型

model = AutoModelForSequenceClassification.from_pretrained(name, num_labels=1, torch_dtype=torch.bfloat16)
model.config.pad_token_id = tok.pad_token_id
r = model(input_ids=ids, attention_mask=att).logits.squeeze(-1)   # (batch,) 每条一个标量
loss = -F.logsigmoid(r_chosen - r_rejected).mean()
  • num_labels=1 让分类头输出一个标量。加载时会提示 score.weight 是新初始化的,这是正常的。
  • 用 LoRA 训练时把 task_type 设成 "SEQ_CLS",peft 会自动把分类头 score 设为可训练。

A.2 取”最后一个 token”的坑

  • HF 的序列分类模型靠 config.pad_token_id 找每行最后一个非 padding 的 token。不设 pad_token_id 时,batch 大于 1 会直接报错。
  • 参考代码的 last_token_index 用 attention_mask.sum(1) − 1,只在右补齐时成立。左补齐时最后一个 token 永远在最后一列。换补齐方向前先确认取的位置对不对。
  • pad token 不要和文本里会出现的 token 相同。如果把 eos 当 pad、而 chat template 里本来就有 eos,“找第一个 pad”的逻辑会取错位置。

A.3 数据集

ds = load_dataset("HuggingFaceH4/ultrafeedback_binarized")
ds["train_prefs"][0]["chosen"]     # [{"role": "user", ...}, {"role": "assistant", ...}] 完整对话
  • chosen 和 rejected 都是完整的对话(包含同一个提示),直接用 chat template 渲染即可。
  • 训练前先检查两类回答的平均长度:如果被选中的回答系统性地更长,奖励模型一定会学到长度偏好(§2.5)。参考代码训练完会打印奖励与长度的相关系数。

A.4 trl 的 RewardTrainer

trl 提供了 RewardTrainer,内部就是上面的 BT 损失。和 Week 2 Day 6 §A.4 一样,它的参数名和数据格式要求在不同版本间变过,用之前打印一条处理后的样本,确认 chosen / rejected 各自的 input_ids 和它取分数的位置。


§B 实操

  1. 跑通 w3day2_checks.py,5 组结论都看懂。第 4、5 组把 KL 和真实质量画成曲线,和 Gao et al. 2022 的图对照。
  2. 推导(1h):(a) 从 Gumbel 噪声推出 Bradley-Terry;(b) BT 损失对 rwr_w、rlr_l 的梯度;(c) best-of-nn 的 KL 上界 log⁡n−(n−1)/n\log n-(n-1)/n(提示:best-of-nn 的概率是 n F(y)n−1p(y)n\,F(y)^{n-1}p(y),其中 FF 是分数的累积分布)。
  3. mini-GPT 上的玩具奖励模型(1h):python code/w3d2_reward_model.py --mini。看测试准确率离贝叶斯上限多远,以及它给”重复的正面词”打多少分。这个奖励模型明天 PPO 要用,一定要跑。
  4. 真实的奖励模型(1.5h,GPU 更好):--hf,在 UltraFeedback 的 2000 对上训练,记录测试准确率与”奖励 vs 长度”的相关系数。
  5. 读论文:InstructGPT 的第 3 节(方法)与图 1;Gao et al. 2022 的图 1。

参考实现:code/w3d2_reward_model.py。


§C 自测

动机与直觉层

  1. SFT 缺了哪三样东西?为什么”比较”比”写”更适合收集数据?
  2. 为什么 1.3B 的 InstructGPT 能胜过 175B 的 GPT-3?这说明了什么?
  3. 奖励模型为什么要从 SFT 模型初始化?为什么取最后一个 token?
  4. 奖励模型测试准确率只有 70%,你会得出什么结论?
  5. 什么是过度优化?为什么代理奖励一直涨,真实质量却会下降?
  6. KL 惩罚去掉会发生什么?reward hacking 长什么样?

推导与计算层

  1. 从”效用 + Gumbel 噪声”推出 Bradley-Terry 模型,写出损失和梯度。
  2. 为什么奖励只能确定到一个常数?这对 RL 有什么影响?
  3. 写出 RLHF 的 KL 约束目标和它的最优解形式,解释 β→0\beta\to0 和 β→∞\beta\to\infty 两个极端。
  4. K=7K=7 个回答的完整排序能产生多少个比较对?为什么要把它们放在同一个 batch 元素里?

工程层

  1. 画出 RLHF 三个阶段的数据流:每一步训练哪个模型、用什么数据、哪些模型冻结。
  2. 用 HF 训练奖励模型时,pad_token_id 和补齐方向各有什么坑?
  3. 列出至少四种对抗 reward hacking 的手段。

出关标准

  • 能说清 SFT 的三个局限,以及偏好数据为什么更容易收集
  • 能从 Gumbel 噪声推出 Bradley-Terry 模型,写出损失和梯度
  • 能解释奖励模型准确率的天花板,以及它会学到标注者的偏见
  • 能画出 RLHF 三阶段的数据流
  • 能用 Goodhart 定律解释过度优化,说清 KL 惩罚的三个作用
  • 跑通 --mini,得到 Day 3 要用的奖励模型

疑问清单

← 返回课程目录