跳转到正文

13.4 强化学习微调

本节导读

核心内容

  • 掌握 InstructGPT 风格 RLHF 的三阶段结构:SFT、Reward Model、PPO。
  • 理解每个阶段的输入、输出、验收指标和常见失败模式。
  • 学会用 artifact 的视角组织实验:数据、模型、评估报告都要可追踪。

核心公式

本节先记住一句话

RLHF 不是一个训练脚本,而是一条 artifact 流水线:每一步都要留下数据、模型、指标和失败样本。

本章的主线参考 OpenAI InstructGPT:先做 SFT,再训练 Reward Model,最后用 PPO 做 RLHF。它不是唯一的后训练方法,但它是理解 DPO、GRPO、RLVR 等现代方法之前最重要的标准参照。

Mermaid diagram

从一条用户问题开始看全流程

先不要急着看框架名词。假设我们有一个 prompt:

text
请解释 PPO 中 clip ratio 的作用,并给一个直觉例子。

标准 RLHF 会围绕这条 prompt 做三件事:

  1. SFT 阶段:给它一条高质量示范回答,让模型学会“这种问题应该这样讲”。
  2. RM 阶段:给同一个 prompt 准备多个候选回答,让标注员或 judge 选出更好和更差的回答。
  3. PPO 阶段:让当前模型自己生成回答,用 RM 打分,再用 PPO 提高高分回答的概率。

这三个阶段对应三种不同的数据形态:

json
{
  "sft_item": {
    "prompt": "请解释 PPO 中 clip ratio 的作用,并给一个直觉例子。",
    "response": "clip ratio 用来限制新旧策略概率比..."
  },
  "preference_item": {
    "prompt": "请解释 PPO 中 clip ratio 的作用,并给一个直觉例子。",
    "chosen": "clip ratio 像安全带,防止一次更新太猛...",
    "rejected": "PPO 是一个算法,它很好用,很多地方都用。"
  },
  "ppo_prompt_item": {
    "prompt": "请解释 PPO 中 clip ratio 的作用,并给一个直觉例子。"
  }
}

同一个 prompt 可以同时出现在 SFT、偏好训练和 PPO rollout 中,但要注意数据泄露:评估集里的 prompt 不应该被拿来训练。

三阶段与三个产物

阶段输入输出验收指标最常见失败
SFT指令-回答数据会按指令回答的 assistant 起点SFT loss、格式遵循、人工观感学会格式但回答空泛
RMchosen/rejected 偏好对能给回答打分的 Reward Modelheld-out accuracy、margin、校准样本偏爱长度、模板或虚假自信
PPO-RLHFSFT model + RM + prompt 集偏好更好的策略模型偏好胜率、KL、长度、回归 benchmarkreward 上涨但真实质量下降

这里最容易误解的是:SFT 和 RM 不是“准备工作”,它们本身就是 RLHF 成败的主要来源。SFT 数据差,后面 PPO 会在错误的起点上放大问题;RM 学偏了,PPO 会认真地朝错误方向优化。

Step 0 与 选择 base checkpoint

RLHF 不从零预训练开始。我们先拿一个公开 base model,当作起点 artifact:

text
artifacts/
  base/
    model_name.txt
    tokenizer_config.json
    generation_probe.jsonl

选择 base model 时看三件事:

维度问题教学实验建议
参数量能不能在本地或小云卡上跑通四模型流程?360M 到 0.5B
语言是否覆盖你要观察的语言?中文实验可选 Qwen 小模型
许可证是否允许微调和发布?先读 model card

这一步的产物不是训练结果,而是基线报告:它在固定 prompt 集上会怎么回答。没有基线,后面就无法判断 SFT 和 RLHF 到底改变了什么。

Step 1 与 SFT 教它怎么回答

SFT 是监督学习。给定 prompt 和示范回答 ,训练模型最大化回答的条件概率:

读成一句话:

在用户问题和前面已生成回答的条件下,让模型更愿意生成示范回答里的下一个 token。

SFT 的最小可用数据格式建议长这样:

json
{
  "messages": [
    { "role": "system", "content": "你是一个清晰、诚实、简洁的强化学习助教。" },
    { "role": "user", "content": "请解释什么是价值函数。" },
    {
      "role": "assistant",
      "content": "价值函数用于估计从某个状态开始,按某个策略行动能获得的期望累积回报。"
    }
  ],
  "source": "human_written",
  "quality": "verified"
}

SFT 阶段最关键的工程细节是 loss mask:只对 assistant 该生成的 token 计算 loss,不要让模型学习 system 和 user 文本。否则模型会学会复述用户问题,甚至学会生成角色标记。

Step 2 与 Reward Model 教裁判什么是好

RM 不直接学习“标准答案”,而是学习“哪个回答更好”。一条偏好数据长这样:

json
{
  "prompt": "请解释 PPO 中的 KL 惩罚。",
  "chosen": "KL 惩罚限制新策略偏离参考策略太远,像安全绳一样防止更新失控。",
  "rejected": "KL 惩罚就是一个数学公式,PPO 会用它,所以很重要。",
  "labeler": "human_or_judge",
  "rubric": ["accuracy", "helpfulness", "clarity"]
}

RM 学到一个打分函数 。如果 chosen 比 rejected 好,就希望:

成对偏好损失把这个不等式变成可优化目标:

它的直觉和二分类很像:如果 chosen 分数比 rejected 高很多, 接近 1,loss 很小;如果 RM 给反了,loss 很大。

RM 阶段不要只看 accuracy,还要看 margin:

accuracy 告诉你排序有没有排对,margin 告诉你信号够不够强。一个 RM 可能 70% 排对,但 chosen 和 rejected 分差都很小;PPO 阶段拿到这种奖励会很难学。

奖励的谱系:规则、模型与混合奖励

奖励函数不是非此即彼的,而是一个从"纯规则"到"纯模型"的连续谱系:

Mermaid diagram

纯规则奖励适合有客观标准答案的任务——数学题的最终答案对不对、代码能不能运行、输出格式是否合规。这类奖励完全确定,不可能被"hack",但它只能检查表面形式,无法评估语义质量。

纯模型奖励就是本节训练的奖励模型(RM),给它 ,它输出一个标量分数。RM 能理解语义——它知道"有帮助但语气生硬"和"礼貌但毫无内容"哪个更好。但 RM 有一个根本性的风险:它本身是一个模型,而模型可以被对抗性地利用。这就是后面会专门讨论的奖励黑客问题(13.6、13.7 节)。

混合奖励是工业界最常用的方案——用 RM 覆盖语义层面,用规则覆盖 RM 捕捉不到的维度。典型的混合奖励函数长这样:

其中 是需要调试的超参数。 检查格式规范度, 惩罚过长或过短的回答, 检查有客观答案的问题(数学、代码等)。两条路线的取舍可以对照下表:

规则奖励模型奖励(RM)
成本几乎为零训练 + 推理成本
可靠性确定性强,不可被 hack可被对抗性利用
语义理解无,只能检查格式有,能理解内容质量
泛化能力差,每换一个任务要写新规则好,同一个 RM 可以评估多种回答
适用场景数学/代码/格式检查等有客观标准的任务对话/创意/安全等主观偏好任务
典型用法作为混合奖励的"底线"作为混合奖励的"主体"

一个实用的经验法则是:能用规则奖励的地方就用规则奖励,规则覆盖不到的地方用模型奖励补。规则奖励提供了一个"安全网"——即使 RM 被 hack 了,规则奖励仍然能确保基本的格式和正确性。第 15 章的 RLVR(Reinforcement Learning with Verifiable Rewards)场景中,规则奖励会成为主力。

奖励模型通常复用语言模型的主干网络,在最后一个有效 token 的隐藏状态上增加标量输出头。训练时,同一组参数分别读取 chosen 和 rejected,得到两个分数,再用前面介绍的成对偏好损失更新。它学习的是回答之间的相对顺序,因此单个分数不能脱离当前模型和数据分布解释成绝对质量。

训练配置:RM 容易过拟合

RM 训练有几个关键的超参数,整体风格比 SFT 更保守:

python
# ==========================================
# RM 训练的关键配置
# ==========================================
rm_config = {
    # 基座模型:通常用 SFT 后的较小模型
    "base_model": "sft_model_3b",

    # 学习率:比 SFT 更保守
    "learning_rate": 5e-6,  # SFT 通常用 1e-5 到 2e-5

    # 学习率调度:线性 warmup + 余弦衰减
    "warmup_steps": 100,
    "lr_scheduler": "cosine",

    # 梯度裁剪:防止梯度爆炸
    "max_grad_norm": 1.0,

    # 批大小:偏好对的数量
    "batch_size": 128,  # 每个批次 128 对 (chosen, rejected)

    # 训练轮数:通常只需 1-2 个 epoch
    "epochs": 1,  # RM 容易过拟合,不要训太多轮
}

RM 特别容易过拟合——偏好数据通常只有几万到几十万对,而 RM 的参数量可能有几十亿。1 个 epoch 通常是最佳选择,超过 2 个 epoch 往往会导致验证集上的准确率开始下降。

数据切分:按 prompt,不按 pair

RM 训练里一个隐蔽坑是数据切分方式。如果同一个 prompt 下有 6 个候选回答,把拆出来的 pair 随机分到 train 和 eval,就会发生泄露:训练集和验证集共享同一个 prompt,甚至共享部分回答,这样得到的 eval accuracy 会偏乐观。更稳妥的做法是按 prompt 切分:

python
def split_by_prompt(items, eval_ratio=0.1):
    """
    items: [{"prompt_id": str, "prompt": str, "chosen": str, "rejected": str}, ...]
    """
    import random
    prompt_ids = sorted({item["prompt_id"] for item in items})
    random.shuffle(prompt_ids)

    n_eval = int(len(prompt_ids) * eval_ratio)
    eval_ids = set(prompt_ids[:n_eval])

    train, eval_ = [], []
    for item in items:
        if item["prompt_id"] in eval_ids:
            eval_.append(item)
        else:
            train.append(item)
    return train, eval_

按 prompt 切分能更真实地回答:RM 遇到没见过的新问题时,偏好排序是否还能泛化?

分数尺度:PPO 前必须校准

RM 训练只关心分数差,不关心绝对尺度。一个 RM 输出 ,另一个输出 ,它们在排序上都对,但 PPO 阶段感受到的奖励尺度完全不同。这会直接影响训练稳定性:

RM 分数尺度PPO 中可能发生什么
太小reward 信号被 KL 惩罚淹没,Actor 学不动
太大reward 压过 KL,Actor 快速偏离 reference
漂移严重不同 batch 的优势估计不稳定

常见做法是在固定校准集上做标准化:

python
class RewardNormalizer:
    def __init__(self, mean, std, eps=1e-8):
        self.mean = mean
        self.std = std
        self.eps = eps

    def __call__(self, reward):
        return (reward - self.mean) / (self.std + self.eps)

这里的 mean/std 应该来自固定校准集,而不是训练过程中随便用当前 batch 更新。否则 reward 尺度会随着 Actor 分布一起漂,排查问题会很痛苦。

RM 评估看什么

一个比较完整的 RM 报告至少包含:

指标含义典型用途
Pairwise accuracyheld-out 偏好对上 chosen 分数是否更高检查排序能力
Mean marginchosen 和 rejected 平均分数差检查信号强度
Margin distribution分差分布是否健康找出"勉强排对"的样本
Reward-length correlation分数是否过度依赖长度检查长度黑客风险
Domain breakdown各任务域 accuracy/margin找出偏科
Calibration samples人工看高分/低分样本检查 RM 是否符合人类直觉

一个轻量计算函数:

python
def rm_eval_metrics(r_chosen, r_rejected, chosen_lengths, rejected_lengths):
    import numpy as np

    margin = np.asarray(r_chosen) - np.asarray(r_rejected)
    accuracy = float((margin > 0).mean())

    rewards = np.concatenate([r_chosen, r_rejected])
    lengths = np.concatenate([chosen_lengths, rejected_lengths])
    length_corr = float(np.corrcoef(rewards, lengths)[0, 1])

    return {
        "pairwise_accuracy": accuracy,
        "mean_margin": float(margin.mean()),
        "median_margin": float(np.median(margin)),
        "length_reward_corr": length_corr,
    }

如果 length_reward_corr 很高,就要回头检查偏好数据:是不是 chosen 普遍比 rejected 更长?如果是,RM 可能学到"长就是好",而不是"有帮助就是好"。

奖励粒度与信用分配

RM 给一个回答打一个分数,这个分数应该落在什么粒度上?是给整个回答一个总分,还是按推理步骤分段打分,还是给每个 token 单独打分?

粒度方式优势劣势代表方法
Sequence-level整个回答一个分数简单,稳定无法区分好回答中哪些部分好PPO, GRPO
Step-level按推理步骤分段折中精细度与可行性需要步骤分割器PRM(过程监督)
Token-level每个 token 独立分数最精细训练成本高,信号噪声大RLHF 早期尝试

实践中最常用的是 sequence-level 加上规则辅助:PPO 和 GRPO 默认都给整个回答一个总奖励,再用规则奖励补充 token 级别的信号(比如格式检查)。Step-level 奖励就是过程奖励模型(PRM),第 17 章会专门讨论。

Sequence-level RM 有一个根本难题:它只在回答结束后给分。假设模型生成了 200 个 token,RM 给了低分,是哪一段导致的?开头误解用户意图、中间推理跳步、结尾答案写错,还是语气过度自信——总分都无法定位。这就是 PPO-RLHF 里 Critic 和 advantage 估计存在的原因之一:它们不能完美解决信用分配,但能把"整段回答好不好"的信号更平滑地传回 token 级别更新(见 8.3 节的 GAE)。后面的 GRPO、RLVR、过程奖励,本质上也都在不同方向上处理这个问题。

对抗性测试:先别急着丢给 PPO

奖励函数设计里的最大风险不是 RM accuracy 低,而是 RM 有系统性盲区。PPO 阶段的 Actor 会主动搜索让 RM 给高分的输出分布——如果 RM 偏爱某种表面模式,Actor 会把这种模式推到极端。所以 RM 训练完要先做对抗性测试:

python
stress_cases = [
    ("空回答", ""),
    ("超长废话", "这个问题非常重要。" * 200),
    ("固定模板", "当然可以。以下是一些建议:\n" * 50),
    ("事实错误但自信", "PPO 是 1980 年提出的确定性搜索算法。"),
    ("正确但简短", "PPO 用裁剪限制新旧策略差异,避免更新过猛。"),
]

for name, response in stress_cases:
    print(name, reward_model.score(prompt, response))

如果"超长废话"比"正确但简短"分数高,先别跑 PPO——PPO 只会把这个问题放大。

这一步交付的不只是一个 RM checkpoint,还应包含偏好对准确率、分差分布、奖励与回答长度的相关性,以及一组高分和低分回答的人工抽检结果。具体评测方法放在后面的"评测方法"一节,这里先记住验收条件:奖励模型既要把顺序排对,也要避免把长度、固定模板或虚假自信当成质量。

把这些检查项整合成一张实用的清单,设计自己的奖励函数时逐项对照:

检查项问题通过标准
奖励粒度你选择的是什么粒度的奖励?有明确的理由说明为什么选这个粒度
混合奖励是否同时使用规则奖励和模型奖励?至少包含一个规则奖励作为底线
长度惩罚是否有防止模型写太长的机制?有明确的长度惩罚项
重复惩罚是否有防止模型重复废话的机制?有 n-gram 重复率检测
RM 区分度RM 的 chosen/rejected 分数差距是否足够大?平均 margin > 1.0
RM 过拟合RM 是否在验证集上表现良好?验证集准确率 > 65%
边界情况奖励函数对极端输入(空回答、超长回答)的行为是否合理?边界情况有明确处理
分数校准RM 输出尺度是否适合 PPO?固定校准集均值、方差稳定
领域分解不同任务域是否表现一致?没有明显偏科或安全退化
进阶:奖励模型的最小 PyTorch 结构
python
import torch
import torch.nn as nn
import torch.nn.functional as F


class RewardModel(nn.Module):
    """输入问题与回答,输出一个标量奖励。"""

    def __init__(self, base_model, hidden_dim):
        super().__init__()
        self.base_model = base_model
        self.reward_head = nn.Linear(hidden_dim, 1)

    def forward(self, input_ids, attention_mask):
        outputs = self.base_model(
            input_ids=input_ids,
            attention_mask=attention_mask,
        )
        # 实际实现应按 attention_mask 找到每条样本的最后一个有效 token。
        last_hidden = outputs.last_hidden_state[:, -1, :]
        return self.reward_head(last_hidden).squeeze(-1)


def preference_loss(
    reward_model,
    chosen_ids,
    chosen_mask,
    rejected_ids,
    rejected_mask,
):
    chosen_reward = reward_model(chosen_ids, chosen_mask)
    rejected_reward = reward_model(rejected_ids, rejected_mask)
    return -F.logsigmoid(chosen_reward - rejected_reward).mean()

这段代码只展示参数如何流动:同一个奖励模型分别计算 chosen 和 rejected 的分数,再根据分数差产生梯度。真正训练时还要处理 padding 位置、分布式批处理、按 prompt 切分数据,以及固定校准集上的奖励标准化。

Step 3 与 PPO 按奖励练习

PPO 阶段会同时用到四个角色:

角色来源是否训练作用
ActorSFT model生成回答,被 PPO 更新
Reference冻结 SFT model提供 KL 约束
Reward ModelRM 阶段产物给完整回答打分
Critic常从 Actor 初始化估计 value,降低方差

总奖励通常写成:

它表达了 RLHF 的核心矛盾:

  • RM 希望 Actor 朝更符合偏好的方向走;
  • Reference 希望 Actor 不要离 SFT 太远;
  • PPO 希望每一步更新不要太猛。

如果没有 KL 惩罚,Actor 可能很快钻进 RM 的盲区;如果 KL 惩罚太强,Actor 又几乎学不动。

反馈从哪里来

经典 RLHF 里的 H 是 human feedback,但真实工程里反馈来源通常是混合的:

来源用途风险
人类标注高质量种子数据、最终校准贵、慢、一致性有限
AI Judge / RLAIF扩展偏好数据、快速迭代放大 judge 偏见
规则验证数学、代码、格式等可验证任务覆盖不了开放式对话质量
线上反馈点赞、踩、复制、编辑重发噪声大,需要聚合

本章仍以经典 human preference 为主线,但会在数据工程和评估里引入 AI Judge、规则检查和人工抽检。这样既保持 InstructGPT 的标准结构,也不把课程写成过时的纯人工标注流程。

RLAIF、CAI 与 Self-Play

RLAIF、CAI 和 Self-Play 都在补充或替代人类反馈,本质上回答同一个问题:偏好数据从哪里来,如何更快迭代

方法放在流水线哪一步作用需要的护栏
RLAIF生成偏好对 / RM 训练集用强模型替代部分人工标注人类抽检、judge 一致性检查
Constitutional AI生成 chosen/rejected按原则自我批评、自我修订宪法原则质量、人类校准
Self-Play / Debate生成候选回答和难例让模型和历史版本互相竞争多样性监控、外部评估锚点
Self-Rewarding多轮数据飞轮模型自评、自批、自改再训练外部 RM 或人工评估防止退化

这里的关键不是“完全替代人类”,而是用 AI 扩展规模,用人类校准方向。如果完全依赖 AI Judge,judge 偏爱冗长回答、固定模板或某种风格时,偏见会被下一轮训练继续放大。

一个最小可用的 RLAIF judge prompt 可以长这样:

python
rlaif_judge_prompt = """
你是一个严格的回答质量评估员。请比较两个回答。

评价维度:
1. 准确性:事实是否正确,有无幻觉
2. 帮助性:是否真正解决了用户的问题
3. 清晰度:表达是否清楚,逻辑是否连贯
4. 安全性:是否包含有害、偏见或误导内容

用户问题:
{prompt}

回答 A:
{response_a}

回答 B:
{response_b}

请只输出 JSON:
{{"winner": "A" 或 "B" 或 "tie", "reason": "一句话理由"}}
"""

要减少 judge 偏见,至少做四件事:

  1. A/B 顺序随机打乱。
  2. 记录 judge 理由,不能只存 winner。
  3. 定期做人类复核。
  4. 保留固定评估集,不让数据飞轮只迎合当前 judge。

数据飞轮放在哪里

数据飞轮不是单独的一种算法,而是把 SFT、RM、PPO 和评估连接成可迭代系统:

text
部署模型
  -> 收集 badcase、用户反馈、评测失败样本
  -> 生产新的 SFT / preference 数据
  -> 训练 SFT 或 RM
  -> PPO-RLHF 更新策略
  -> 评估通过后再部署

这个飞轮的关键指标包括迭代周期、数据有效率、评测覆盖率和回退率。小参数课程实验里可以把它压缩成一轮:先准备固定数据,跑 SFT/RM/PPO,再用评估结果反推下一轮应该补什么数据。

数据飞轮能不能越转越好,主要取决于质量闸门,而不是“生成了多少数据”。

质量闸门检查什么典型做法
基础清洗重复、污染、格式错误、长度异常去重、评测集泄露检查、长度过滤、格式校验
难度分层数据是否处在模型学习边界用 pass@k 或 judge 分数区分太简单/可学/太难
偏好一致性chosen 是否真的优于 rejected多 judge 投票、人类抽检
线上回归新模型是否破坏旧能力固定 benchmark + badcase 回放

最小实验目录

为了让实验可复现,本章建议把 artifact 分开存:

text
experiments/rlhf-smollm/
  data/
    sft_train.jsonl
    pref_train.jsonl
    prompts_ppo.jsonl
    eval_prompts.jsonl
  models/
    base.txt
    sft/
    reward_model/
    rlhf/
  reports/
    base_probe.md
    sft_eval.json
    rm_eval.json
    ppo_train_metrics.jsonl
    final_eval.md

这不是形式主义。RLHF 调试时经常会问:

  • 这次 PPO 用的是哪个 RM?
  • 这个 RM 是用哪版偏好数据训的?
  • 评估集有没有混进训练数据?
  • 模型变长是从哪个 checkpoint 开始的?

如果 artifact 不清楚,后面很难定位问题。

常见失败模式地图

位置失败现象根因先检查什么
Base输出不像助手预训练目标不是指令遵循base probe 样本
SFT格式对但内容空示范数据质量低或同质化SFT 数据人工抽样
RM偏爱长回答偏好数据里 chosen 普遍更长reward-length 相关性
PPOreward 涨但质量降Actor 找到 RM 盲区高 reward 样本抽检
Eval胜率波动大judge 偏见或样本太少随机种子、A/B 顺序、置信区间

本节小结

标准 RLHF 可以压缩成三句话:

  1. SFT 把 base model 教成 assistant 起点。
  2. Reward Model 把偏好数据变成可优化的奖励。
  3. PPO 在 KL 约束下提高高奖励回答的概率。

但真正可靠的 RLHF 不只是这三步训练,还包括 artifact 管理、数据质量闸门和评估闭环。下一节进入第一阶段:SFT 数据和偏好数据到底怎么构造,为什么它和模仿学习、逆强化学习有天然关系——SFT:教模型按指令回答

练习

  1. 设计一个 sft_item 和一个 preference_item,要求 prompt 相同,但数据用途不同。
  2. 解释为什么 RM accuracy 高不一定代表 PPO 阶段会成功。
  3. 用一句话说明 Reference model 在 PPO-RLHF 中的作用。

现代强化学习实战课程