Skip to content

12. RLHF 总览

经过预训练(Pre-training)和监督微调(SFT),模型已经"学会了语言"和"学会了对话格式"。但它仍然可能:编造事实、输出有害内容、回避真实问题、对模糊指令给出无关回答。RLHF (Reinforcement Learning from Human Feedback) 的目标是用人类偏好信号继续打磨模型,让它的行为真正"对齐"人类期望。

本章是整个 Part 3 的入口。我们将先回答"为什么需要对齐",再系统介绍 InstructGPT 奠定的三阶段范式,最后给出后续章节(PPO/DPO/GRPO/...)共同依赖的两块数学基石:Bradley-Terry 偏好模型KL 约束的 RL 目标


12.1 为什么需要对齐

12.1.1 SFT 不是终点

SFT 用 (instruction, response) 对训练模型,看起来已经能解决"模仿人类回答"的问题。但实际产品中,仅做完 SFT 的模型仍然存在以下问题:

  1. 不诚实 (dishonesty):模型会自信地编造不存在的引文、API、函数;
  2. 不无害 (harmful):在巧妙诱导下输出仇恨言论、危险化学品配方、隐私泄露内容;
  3. 不有用 (unhelpful):对边缘问题过度拒绝("我不能讨论这个"),或答非所问;
  4. 不一致 (inconsistent):相同语义不同表述会得到完全不同的回答;
  5. 数据稀疏 (sparse coverage):人工写的高质量回复有限,难以覆盖长尾分布。

更深层的原因是 SFT 是模仿学习 (imitation learning),它只学"什么是对的",但不学"什么是错的"。模型见过海量"好回答",却几乎没见过"坏回答 + 否定信号"。当输入分布偏离 SFT 数据时,模型很容易回到预训练时学到的"互联网先验"——而互联网先验是包含大量噪声、偏见和错误的。

12.1.2 HHH 标准

Anthropic (Bai et al., 2022) 提出了 HHH 三角

维度含义典型坏案例
Helpful (有用)真正回答用户问题,给出可执行建议答非所问、过度拒绝、敷衍
Harmless (无害)不输出有害、危险、违法内容教人合成毒品、生成歧视言论
Honest (诚实)不编造,不隐瞒不知道,承认局限幻觉 (hallucination)、自信编造引文

三者之间有张力:完全 harmless 容易导致过度拒绝 (over-refusal),从而损害 helpful;过度 helpful 又可能违反 harmless。RLHF 的本质就是让模型在三者间找到人类偏好的平衡点。

12.1.3 强化学习视角

我们可以把语言模型看作一个 马尔可夫决策过程 (MDP)

  • 状态 (state) st:当前已生成的 token 序列 (x,y<t)
  • 动作 (action) at:下一个 token yt
  • 策略 (policy) πθ(at|st):模型给定上文输出下一 token 的分布;
  • 奖励 (reward) r(x,y):完整回答的人类满意度。

SFT 等价于"克隆专家行为",而 RL 直接最大化期望奖励:

maxθExD,yπθ(|x)[r(x,y)]

但人类偏好难以直接写成数学函数,因此 RLHF 把它外包给两个阶段:先用偏好数据训一个奖励模型(学一个 r),再用 RL 优化模型(按 r 提升)。


12.2 InstructGPT 三阶段范式

Ouyang et al. (2022) 在 InstructGPT 论文中确立了沿用至今的 RLHF 标准流水线:

Pretrained LM


┌────────────┐
│ Stage 1    │  人工撰写示范回答
│ SFT        │  (instruction, demonstration)
└─────┬──────┘
      │  π_SFT

┌────────────┐
│ Stage 2    │  人工偏好对比
│ Reward     │  (prompt, y_w, y_l)
│ Modeling   │
└─────┬──────┘
      │  r_φ(x, y)

┌────────────┐
│ Stage 3    │  PPO + KL 惩罚
│ RL Tune    │  最大化 r_φ - β·KL(π_θ‖π_SFT)
└─────┬──────┘
      │  π_RLHF

   最终模型

12.2.1 各阶段数据规模

InstructGPT 论文公布的数据量:

阶段数据类型规模
SFT(instruction, response) 对13K
Reward ModelK-wise comparison (K=4~9)33K
PPOunlabeled prompts31K

关键细节:

  • 三阶段使用不同的 prompt 集合,但都来自相同的真实用户分布;
  • 标注员有 40 人专业团队,长期培训;
  • 奖励模型只用了 6B 参数(policy 是 175B),但效果足够好。

12.2.2 关键经验性发现

  1. 小模型 + RLHF > 大模型纯预训练:1.3B InstructGPT 击败 175B GPT-3,人工偏好胜率 85%
  2. 泛化性强:仅用英文偏好数据训练,模型在中文、其他语言上的偏好对齐也得到提升;
  3. 真实性提升:TruthfulQA 上 truthfulness 从 27% 提升到 55%;
  4. 存在 alignment tax:在部分公共 NLP benchmark (SQuAD, DROP, HellaSwag) 上掉了 5-10 分。InstructGPT 用 PPO-ptx (混入预训练数据) 缓解此问题;
  5. 泛化到分布外指令:模型对未见过的指令格式也能合理响应,说明 RLHF 学到的是"人类偏好的一般规律"而非记忆。

12.3 偏好数据收集

12.3.1 标注协议

最常见的格式是 pairwise comparison

D={(x(i),yw(i),yl(i))}i=1N

其中 yw 是 chosen(更优),yl 是 rejected(较差)。给标注员的协议示例:

你会看到一个用户提示和两个候选回答 A、B。请选出"更好"的那个,依据:

  1. 相关性:是否真正回答了问题?
  2. 正确性:事实是否准确?
  3. 帮助性:是否提供了用户需要的信息或行动?
  4. 无害性:是否包含有害内容? 若无法判断,标记 "tie"。

12.3.2 K-wise 排序

InstructGPT 让标注员对 K{4,9} 个回答进行整体排序,再展开成 (K2) 对 pairwise:

rank(y1,y2,,yK)(K2) 对偏好对

这样做的好处:

  • 效率高:标注员看一次 prompt 可生成多对训练数据;
  • 方差小:同一标注员的相对判断比绝对判断更稳定;
  • 避免循环偏好:整体排序天然满足传递性。

LLaMA-2 进一步引入 margin(程度) 标注:让标注员标记胜方"显著好"、"较好"、"略好"还是"几乎相同",并把 margin 作为奖励差的下界写进损失。

12.3.3 标注质量控制

人类标注是 RLHF 流水线最贵也最容易翻车的环节,常见质量控制手段:

  1. Inter-annotator agreement (IAA):让多个标注员独立标同一对,计算一致率(典型 70-85%);
  2. 黄金标注 (gold set):由资深 PI 预先标好的"标准答案",定期混入考核标注员;
  3. rubric 培训:给出详细评分细则、示例、边界案例;
  4. calibration session:定期开会讨论分歧样本,统一理解;
  5. 数据清洗:剔除一致性极低的标注员、相同 (prompt, y_w, y_l) 出现矛盾标注的样本。

12.3.4 偏好数据的隐性偏差

研究 (Singhal et al., 2023; Chen et al., 2024) 发现真实偏好数据中存在多种偏差:

  • 长度偏差:标注员倾向认为"更长 = 更好"(实际未必);
  • 格式偏差:带 bullet point、bold、emoji 的回答更受欢迎;
  • 风格偏差:自信、礼貌、结构化的回答得分高;
  • 位置偏差:界面上"左边的"或"先看到的"略占优势。

后续算法(如 SimPO 的长度归一化)和评估(如 AlpacaEval 2 的 LC win rate)都尝试缓解这些偏差。


12.4 Bradley-Terry 偏好模型

12.4.1 模型形式

Bradley-Terry 模型 (Bradley & Terry, 1952) 是 RLHF 的数学骨架。它假设每个候选 y 在 prompt x 下存在一个潜在效用 (utility) r(x,y)R,则:

P(yiyjx)=exp(r(x,yi))exp(r(x,yi))+exp(r(x,yj))=σ(r(x,yi)r(x,yj))

其中 σ(z)=1/(1+ez) 是 Sigmoid 函数。

核心性质

  1. 偏好概率仅取决于效用差 rirj
  2. 加常数不变性:r=r+c 不改变所有偏好概率,因此 r 只在差值意义下唯一;
  3. 传递性:BT 模型自动满足 Luce's choice axiom(独立无关选项性质 IIA);
  4. 范围:P(0,1),永远不会是确定性 0 或 1。

12.4.2 从 Luce 选择公理推导

Luce (1959) 的 choice axiom 认为,从集合 S 中选出 y 的概率可写成:

P(yS)=w(y)ySw(y)

其中 w(y)>0y 的"绝对吸引力"。当 |S|=2 时:

P(yi{yi,yj})=w(yi)w(yi)+w(yj)

w(y)=exp(r(x,y)),立刻得到 BT 形式。这把"偏好"嵌入到经济学的 Random Utility Theory 框架下:人类选择 = max(效用 + 噪声),噪声若服从 Gumbel 分布则恰好导出 softmax,等价 BT。

12.4.3 与 Elo 评分的联系

Elo 评分系统(国际象棋、Chatbot Arena 用)等价于 BT 模型的特例。Elo 假设玩家 A 击败 B 的概率为:

PA=11+10(RBRA)/400

R/400ln10 看作"以 ln10/400 为单位的 BT 效用",立刻看出 Elo = BT。Chatbot Arena 直接用这套框架对模型排序。

12.4.4 何时 BT 假设失效

BT 不是万能的。常见反例:

  • 循环偏好:A>B>C>A("剪刀石头布"),BT 模型无法表示;
  • 多维偏好:标注员同时考虑多个维度(helpful、harmless),单标量 r 不足;这促使 ArmoRM 等多目标 RM;
  • 个体异质:不同人偏好不同,单一 r 是聚合后的结果,可能模糊重要信号。

后续算法(IPO, Nash-MD, multi-objective RM)尝试突破 BT 假设。


12.5 奖励模型 (Reward Model)

12.5.1 架构

最常见的做法:拿 SFT 后的模型,替换 LM head 为 scalar head

python
# 伪代码:基于 LLaMA 的奖励模型
class RewardModel(nn.Module):
    def __init__(self, base_model):
        super().__init__()
        self.transformer = base_model.model  # 共享 backbone
        hidden_dim = base_model.config.hidden_size
        # 替换 LM head:vocab_size → 1
        self.score_head = nn.Linear(hidden_dim, 1, bias=False)

    def forward(self, input_ids, attention_mask):
        # input_ids: [B, T] = (prompt + response) tokens
        h = self.transformer(input_ids, attention_mask).last_hidden_state
        # h: [B, T, D]

        # 取最后一个非 padding token 的 hidden state
        last_idx = attention_mask.sum(dim=1) - 1   # [B]
        last_h = h[torch.arange(h.size(0)), last_idx]   # [B, D]

        # 标量打分
        scores = self.score_head(last_h).squeeze(-1)    # [B]
        return scores

设计要点:

  • 取最后 token 的 hidden state:因为 causal attention 让最后位置编码了整个序列;也有实现取 [CLS] 或所有 token 平均;
  • 标量输出rϕ(x,y)R,可正可负;
  • 共享 backbone:通常和 policy 同源(同一 SFT 模型),但参数独立训练。

12.5.2 损失函数

在 BT 假设下,对 (x,yw,yl)最大似然估计

LRM(ϕ)=E(x,yw,yl)D[logσ(rϕ(x,yw)rϕ(x,yl))]

直观理解:让模型给 chosen 打分高于 rejected 的概率最大化。

K-wise 损失展开

如果数据是 K 个回答的整体排序,展开为 (K2) 对:

LRM(K)=1(K2)(i,j):ranki<rankjlogσ(rϕ(x,yi)rϕ(x,yj))

InstructGPT 同时把 K 个回答的 forward 共享 prompt 部分以节省计算。

LLaMA-2 的 margin 损失

加入显式 margin m(rating),由标注员的"程度"标签决定(例如 significantly better → 1.0,slightly better → 0.25):

LRMmargin=logσ(rwrlm(rating))

强迫"显著更好"的样本必须有更大奖励差,提升 RM 的判别力。

12.5.3 训练实现要点

python
# 训练一个 step 的伪代码
def train_step(model, batch):
    chosen = batch["chosen_input_ids"]    # [B, T]
    rejected = batch["rejected_input_ids"] # [B, T]

    # 拼接成一个大 batch,节省 GPU 通信
    stacked = torch.cat([chosen, rejected], dim=0)  # [2B, T]
    scores = model(stacked)  # [2B]

    chosen_scores, rejected_scores = scores.chunk(2, dim=0)

    # BT 损失
    loss = -F.logsigmoid(chosen_scores - rejected_scores).mean()

    # (可选)加 margin
    # margin = batch["margin"]
    # loss = -F.logsigmoid(chosen_scores - rejected_scores - margin).mean()

    # 监控指标
    accuracy = (chosen_scores > rejected_scores).float().mean()

    return loss, {"acc": accuracy, "reward_gap": (chosen_scores - rejected_scores).mean()}

关键超参

超参典型值备注
Learning rate5e-6 ~ 1e-5比 SFT 小 5-10×
Batch size32 ~ 128 (preference pairs)小 batch 容易过拟合
Epochs1 (LLaMA-2)、2-3 (InstructGPT)多了过拟合
Weight decay0.001 ~ 0.01防过拟合
序列长度≤ 4K tokens超长样本截断或丢弃
模型大小与 actor 同级或稍小LLaMA-2 用 70B RM

训练后归一化

训练完后,按惯例对训练集做奖励均值归零

python
# 把奖励均值减到 0
mean_reward = compute_mean_reward(model, train_data)
model.score_head.bias = nn.Parameter(torch.tensor(-mean_reward))

这样做不改变 BT 偏好(差值不变),但让 PPO 阶段方差更小。

12.5.4 RM 评估

训练完后通常用以下指标评估:

  1. Pairwise accuracy:在 held-out 偏好对上,r(yw)>r(yl) 的比例。SOTA RM 在 RewardBench 上 acc ≈ 80-90%;
  2. Reward gap distributionr(yw)r(yl) 的均值和分布。健康的 RM 应有清晰右偏分布;
  3. 校准 (calibration):把 sigmoid 输出与真实胜率对比,是否一致;
  4. OOD 表现:在分布外(不同领域、不同长度)的偏好对上是否仍准。

12.6 KL 约束的 RL 阶段目标

12.6.1 目标函数

RLHF 第三阶段的目标:

maxπθExD,yπθ(|x)[rϕ(x,y)]βEx[KL(πθ(|x)πref(|x))]

含义:在最大化 RM 打分的同时,约束策略不要离参考策略 πref(一般是 πSFT)太远。

12.6.2 KL 散度展开到 token 级

KL 散度对完整序列:

KL(πθπref)=Eyπθ[logπθ(y|x)πref(y|x)]

由于自回归分解 π(y|x)=tπ(yt|x,y<t),可以展开为 token 级:

logπθ(y|x)πref(y|x)=t=1|y|logπθ(yt|x,y<t)πref(yt|x,y<t)

把 KL 惩罚摊到每个 token 的 reward 上:

rttotal(x,y)={βlogπθ(yt|x,y<t)πref(yt|x,y<t),t<|y|rϕ(x,y)βlogπθ(yt|x,y<t)πref(yt|x,y<t),t=|y|

也就是说:

  • 每个 token 都有一个 KL 惩罚项;
  • 只有最后一个 token(response 结束)拿到 RM 给的稀疏奖励 rϕ
  • 这种 reward 设计是 PPO 在 LLM 上的标准做法(InstructGPT、LLaMA-2、TRL 默认)。

12.6.3 KL 系数 β 的影响

β 是 RLHF 最重要的超参之一:

β行为
太小 (e.g. 0.001)几乎无约束,模型快速 reward hacking,输出退化(重复、刷模板)
适中 (0.05 ~ 0.2)既能优化 RM 又保留 SFT 流畅性,最佳实践区间
太大 (>1.0)过度约束,模型基本不动,等同于 SFT

InstructGPT 提出 adaptive KL controller:维护一个目标 KL KL,根据实际 KL 自适应调整 β

et=KLtKLKL,βt+1=βt(1+Kpclip(et,0.2,0.2))

类似 PID 控制器,让训练过程稳定锁定在目标 KL 附近。

12.6.4 为什么 KL 约束这么重要

直接最大化 rϕ 而不约束策略,会触发 Goodhart 定律

When a measure becomes a target, it ceases to be a good measure.

具体表现:策略会利用 RM 的缺陷找到那些 RM 打分高、但实际质量很低的输出。例如:

  • 重复某些 RM 训练数据中常见的"赞美短语"("Sure, here is...");
  • 输出特殊格式(多余的 markdown、emoji);
  • 长度爆炸(更长 ≈ 看起来更详细 ≈ RM 给分更高);
  • 出现训练分布外的怪异 token 序列。

KL 约束把策略强行拉回 SFT 附近,间接保证了

  1. 输出仍然流畅自然(因为 SFT 流畅);
  2. 不远离 RM 训练分布(RM 在 SFT 附近最准);
  3. 优化路径平稳(trust region 思想)。

12.6.5 KL 与 reward hacking 的定量关系

Gao et al. (2023) "Scaling Laws for Reward Model Overoptimization" 用合成的"gold reward"(视为真实人类偏好)系统研究了过度优化:

Rgold(π)Rgold(πref)dDKL(ππref)cDKL(ππref)
  • 第一项 (dKL):优化获益,与 KL 平方根成正比;
  • 第二项 (cKL):过度优化惩罚,与 KL 线性增长。

两者相加是倒 U 形:KL 太小则没优化空间,KL 太大则代理(RM)和真实目标偏离。RM 越大,c 越小(更准),可优化空间越大。

gold reward
     │     ╱╲
     │    ╱  ╲
     │   ╱    ╲
     │  ╱      ╲
     │ ╱        ╲___
     │╱
     └─────────────────→ KL(π‖π_ref)

        最优 KL

实际训练中需要:

  • 早停:监控 gold metric(如人评、Arena),在 KL 还没爆炸前停;
  • 限 KL:用 adaptive controller;
  • RM 越好越激进:更大、更新的 RM 允许更大 KL。

12.7 RLHF 的挑战与反思

12.7.1 Reward Hacking

如上所述,是 RLHF 最棘手的问题。常见诱因和缓解:

诱因缓解手段
RM 是 imperfect proxyRM ensemble、WARM (weighted average)、RM 持续更新
RL 推 actor 到 RM OODKL 约束、reference refresh、保守优化 (TRPO/PPO)
数据偏差被 RM 学到长度归一化、数据去偏、多样化标注
评估不准监控多个 metric、用 gold human eval、Arena

12.7.2 标注成本

高质量偏好数据非常昂贵:

  • 单条偏好对约 $0.5 ~ $2(普通众包) / $5+(专业标注);
  • 100K 条偏好数据 ≈ $50K - $200K;
  • 标注员培训、考核、质检还有额外成本。

替代方案:

  • RLAIF (RL from AI Feedback):用更强的 LLM 做标注(Bai et al., 2022);
  • Constitutional AI:用一组 principles + 自批评生成偏好;
  • Self-Rewarding LM:模型自己当 judge(Yuan et al., 2024);
  • 基于规则的奖励(DeepSeek-R1):在数学、代码等任务上完全用规则替代 RM。

12.7.3 分布偏移

SFT 数据分布PPO 部署分布。具体:

  • SFT 见过的 prompt 是标注员写的;
  • PPO rollout 是模型自己生成的回答(在线分布);
  • 真实用户分布又是另一回事。

这导致 RM 在 PPO 后期"看不懂"actor 输出,给出错误打分。Iterative RLHF 通过周期性重收集偏好数据缓解。

12.7.4 多轮、长上下文、工具使用

经典 RLHF 假设:

  • 单轮对话;
  • 短响应;
  • 单一标量奖励。

现代 LLM 需求:

  • 多轮对话(RM 需考虑全历史);
  • 长 reasoning chain(PRM、过程奖励);
  • 工具调用(每次 tool call 都该有信号)。

GRPO + rule-based reward + PRM 的组合(DeepSeek-R1)是当前应对这些复杂场景的主流方案。


12.8 与后续章节的衔接

章节内容与本章关系
Ch.13 PPO详解策略梯度、GAE、4 模型架构、稳定性技巧实现 §12.6 的目标
Ch.14 DPO把 §12.6 闭式求解,跳过 RM 与 RL 阶段不再需 RM,直接从偏好数据优化
Ch.15 GRPO用 group baseline 替代 critic简化 PPO,仍最大化 RM/规则奖励
Ch.16 其他KTO/ORPO/SimPO/RLOO/ReMax对 §12.6 不同维度的简化或扩展
Ch.17 RM & 评估RM 架构变体、PRM、评估基准、对齐税深入 §12.5 与 §12.7

记住一个核心问题:所有对齐算法都在尝试用不同方式优化"人类偏好",差别在于:

  • 是否需要显式 RM(PPO/GRPO 需要,DPO/SimPO 不需要);
  • 是否需要在线采样(PPO/GRPO 需要,DPO 不需要);
  • 是否需要 reference model(多数需要,ORPO/SimPO 不需要);
  • 数据形式(pairwise/binary/group)。

理解 §12.4 的 BT 模型和 §12.6 的 KL-约束目标,是看懂后续章节所有数学推导的基础。


12.9 实战:训练一个奖励模型

下面给出完整的 RM 训练代码框架,配套示例见 code/05_reward_model.py

python
import torch
import torch.nn as nn
import torch.nn.functional as F
from transformers import AutoModelForCausalLM, AutoTokenizer
from torch.utils.data import Dataset, DataLoader

class RewardModel(nn.Module):
    """基于因果语言模型的奖励模型"""
    def __init__(self, model_name_or_path):
        super().__init__()
        base = AutoModelForCausalLM.from_pretrained(model_name_or_path)
        self.transformer = base.model
        hidden_dim = base.config.hidden_size
        self.score = nn.Linear(hidden_dim, 1, bias=False)

    def forward(self, input_ids, attention_mask):
        outputs = self.transformer(
            input_ids=input_ids,
            attention_mask=attention_mask,
            output_hidden_states=False,
        )
        hidden = outputs.last_hidden_state    # [B, T, D]

        # 取每条序列最后一个非 padding token
        seq_len = attention_mask.sum(dim=1) - 1   # [B]
        batch_idx = torch.arange(hidden.size(0), device=hidden.device)
        last_h = hidden[batch_idx, seq_len]       # [B, D]

        return self.score(last_h).squeeze(-1)     # [B]


class PreferenceDataset(Dataset):
    """偏好数据集:每条样本含 prompt + chosen + rejected"""
    def __init__(self, jsonl_path, tokenizer, max_len=2048):
        self.data = [json.loads(line) for line in open(jsonl_path)]
        self.tokenizer = tokenizer
        self.max_len = max_len

    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        ex = self.data[idx]
        chosen_text = ex["prompt"] + ex["chosen"]
        rejected_text = ex["prompt"] + ex["rejected"]

        chosen = self.tokenizer(chosen_text, truncation=True,
                                max_length=self.max_len, return_tensors="pt")
        rejected = self.tokenizer(rejected_text, truncation=True,
                                  max_length=self.max_len, return_tensors="pt")
        return {
            "chosen_input_ids": chosen.input_ids[0],
            "chosen_attention_mask": chosen.attention_mask[0],
            "rejected_input_ids": rejected.input_ids[0],
            "rejected_attention_mask": rejected.attention_mask[0],
        }


def collate_fn(batch, pad_id):
    """左侧 padding,方便取 last token"""
    def pad(seqs):
        max_len = max(s.size(0) for s in seqs)
        out = torch.full((len(seqs), max_len), pad_id, dtype=torch.long)
        mask = torch.zeros(len(seqs), max_len, dtype=torch.long)
        for i, s in enumerate(seqs):
            out[i, :s.size(0)] = s
            mask[i, :s.size(0)] = 1
        return out, mask

    c_ids, c_mask = pad([b["chosen_input_ids"] for b in batch])
    r_ids, r_mask = pad([b["rejected_input_ids"] for b in batch])
    return {
        "chosen_input_ids": c_ids,
        "chosen_attention_mask": c_mask,
        "rejected_input_ids": r_ids,
        "rejected_attention_mask": r_mask,
    }


def train_rm(model, loader, optimizer, device, num_epochs=1, log_every=10):
    model.train()
    step = 0
    for epoch in range(num_epochs):
        for batch in loader:
            batch = {k: v.to(device) for k, v in batch.items()}

            # 拼接 chosen 和 rejected,一次 forward
            input_ids = torch.cat(
                [batch["chosen_input_ids"], batch["rejected_input_ids"]], dim=0)
            mask = torch.cat(
                [batch["chosen_attention_mask"], batch["rejected_attention_mask"]], dim=0)

            scores = model(input_ids, mask)    # [2B]
            chosen_scores, rejected_scores = scores.chunk(2, dim=0)

            # BT 损失
            loss = -F.logsigmoid(chosen_scores - rejected_scores).mean()
            acc = (chosen_scores > rejected_scores).float().mean()

            optimizer.zero_grad()
            loss.backward()
            torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
            optimizer.step()

            step += 1
            if step % log_every == 0:
                gap = (chosen_scores - rejected_scores).mean()
                print(f"step={step} loss={loss.item():.4f} "
                      f"acc={acc.item():.3f} gap={gap.item():.3f}")

更完整的实现见 code/05_reward_model.py,包括分布式训练、混合精度、checkpoint、归一化等。


本章小结

  • 为什么需要 RLHF:SFT 是模仿学习,无法学到"什么是错的",RLHF 通过偏好信号补足这一短板,目标是 HHH(helpful、harmless、honest);
  • 三阶段范式:SFT → RM → PPO,由 InstructGPT 奠定,至今仍是主流;
  • Bradley-Terry 模型P(ywyl)=σ(rwrl) 是所有偏好建模的数学基石,源自 Luce 选择公理,等价于 Gumbel 噪声下的 Random Utility 模型;
  • 奖励模型:LLM backbone + scalar head,用 BT 负对数似然训练;
  • KL 约束的 RL 目标maxE[rϕ]βKL(ππref) 是后续所有算法(PPO、DPO、GRPO)的共同起点;
  • Reward Hacking:是 RLHF 最大的工程挑战,需要 KL 约束、RM ensemble、early stopping 等多重防御;
  • 替代方案:当人类标注昂贵时,可用 RLAIF、Constitutional AI、规则奖励(如 DeepSeek-R1)。

思考题

  1. 为什么 InstructGPT 使用比 actor 小得多的 RM (6B vs 175B),而 LLaMA-2 选择与 actor 同规模的 70B RM? 从 reward hacking 与 scaling laws 角度分析两种选择的 trade-off。

  2. 推导:证明在 BT 模型下,给所有 reward 加同一常数 c(即 r(x,y)=r(x,y)+c)不会改变任何偏好概率。这意味着 RM 训练后做归一化(让训练集 reward 均值为 0)不影响偏好排序,但可以降低 PPO 阶段的方差,请解释为什么方差会降低。

  3. 设计题:假设你要为一个法律咨询 LLM 做 RLHF,但发现标注员对"什么是好答案"一致率只有 50%(接近随机)。你会如何调整数据收集流程和损失函数?提示:考虑 cDPO 中 label noise 的处理思路。

基于 MIT 协议发布