← 返回首页
基础理论 · 2026-09-18 发布

大模型 RL 对齐(RLHF / DPO)
原理与配套极简源码片段

从 RLHF 三段式流程、PPO-Clip 与 KL 约束,到 DPO / ORPO / GRPO 的偏好优化路线, 再到 GAE、进阶算法与高频面试 FAQ,用最少的代码把每一步的数学直觉讲清楚。

01

基础概念:RLHF 三段式完整流程

RLHF(基于人类反馈的强化学习)是大模型对齐最经典的范式,整体分为三个依次执行的阶段。

1

SFT 监督微调

用高质量指令样本微调底座模型,教会模型「听懂指令」。

底座模型 → SFT 模型
➜
2

奖励模型 RM 训练

用成对人类偏好排序数据,训一个「打分器」。

prompt + response → 标量 reward
➜
3

PPO 强化学习

策略模型在线采样,用 RM 奖励信号做策略梯度更新。

SFT 模型 → 对齐模型

三阶段时间轴

阶段 ① SFT

输入:底座模型 + 指令数据(prompt, response)。输出:具备基础指令遵循能力的 SFT 模型。

阶段 ② RM

输入:同一 prompt 下多条回答的人工偏好排序。输出:能把 prompt + response 映射为标量分数的奖励模型。

阶段 ③ PPO

输入:SFT 模型(策略)+ RM(打分)+ SFT 冻结副本(KL 参考)。输出:对齐后的大模型。

各阶段目标与评测锚点

只写输入输出还不够。每个阶段都要有可验证的目标和对应的评测集,否则无法判断这一阶段是否达标、能不能进入下一阶段。业界通用的做法如下:

阶段 核心目标 数据规模参考 评测锚点
底座 Base 通用语言能力基线,为后续所有阶段提供参照 预训练语料(不额外标注) 困惑度 PPL、MMLU / CEval 通用能力
① SFT 教会指令遵循与输出格式,为 PPO 提供合格起点 约 5k–50k 条高质量指令对 指令遵循率、格式合规率、验证集 loss
② RM 打分器准确拟合人类偏好,且不容易被钻空子 约 10k–100k 条成对偏好数据 Reward Bench 成对准确率、held-out 偏好集准确率、奖励-长度相关性(防长度作弊)、定期对照人工偏好集防漂移
③ PPO RM 分数最大化,同时 KL 约束不偏离 SFT 太远 10k+ prompts(无需人工标注) MT-Bench(GPT-4 打分多轮对话)、AlpacaEval 2.0(长度控制胜率)、Arena-Hard / Chatbot Arena(Elo);安全侧 TruthfulQA、HarmBench;能力回归监控 MMLU / GSM8K / HumanEval
🎯 评测纪律

三条铁律:评测集固定——基准题、解码参数、种子全部冻结,否则采样噪声会污染所有对比;三层评估互相不可替代——自动 benchmark 查能力回归、偏好评估查用户体验、人工审查查奖励黑客与模板化;只看 reward 曲线等于没评估——reward 涨而 MT-Bench / AlpacaEval 跌,就是最典型的 reward hacking 信号。另外务必做 base / SFT / RLHF 三阶段横向对比,盯住「对齐税」:对齐不能以通用能力退化为代价。

第一阶段:SFT 监督微调

使用高质量指令样本对预训练底座模型做微调,目标不是让模型学会新知识, 而是教会模型理解指令格式、遵循用户的指令输出,完成之后得到 SFT 模型。 SFT 模型已经具备基础的对话能力,但输出偏好还没有对齐人类主观喜好。

第二阶段:奖励模型 RM 训练

收集同一个 prompt 下,模型生成的多条不同回答,交给人工标注做偏好排序。 利用这批成对偏好数据训练奖励模型。奖励模型输入一段 prompt + 回答,输出一个标量奖励分数, 用来拟合人类的偏好判断。奖励模型本质上是一个打分器。

第三阶段:PPO 强化学习阶段

将前面训练好的 SFT 模型作为策略模型,在每一轮迭代中,策略模型生成回答,交给奖励模型打分得到奖励信号, 再使用 PPO 算法更新策略模型,让模型生成的内容持续向高分、符合人类偏好的方向优化。

四模型角色图(RLHF 全景)

RLHF 的 PPO 阶段同时存在四个「模型角色」,理解它们各自是否训练、是否更新参数, 是面试中最容易踩坑的知识点。

POLICY
策略模型 πθ

即我们要训练的模型,初始化为 SFT 模型。唯一参与梯度更新的主角。

参数可训练 ✓
REFERENCE
参考模型 πref

SFT 模型的冻结副本,只用于计算 KL 惩罚,防止策略跑偏。

参数冻结 ✗
REWARD
奖励模型 R

输出标量 reward,提供优化信号。只做前向,不参与策略更新。

参数冻结 ✗
CRITIC
价值模型 V

估计状态价值,用来算 advantage。PPO 需要,GRPO 不需要。

参数可训练 ✓
⚠️ 易错点

价值模型(Critic)是可训练的,它和策略模型一起更新;而参考模型和奖励模型都是冻结的。 很多人会把 Critic 和 Reference 搞混——前者是为了降低方差,后者是为了限制偏离。

配套极简源码

PYTHON1. SFT 监督微调示意代码
import torch
import torch.nn as nn
from transformers import AutoModelForCausalLM, AutoTokenizer

# ========== 1. SFT监督微调示意代码 ==========
# SFT的目标:标准自回归语言模型损失,只在response部分计算loss
model = AutoModelForCausalLM.from_pretrained("your-sft-base")
tokenizer = AutoTokenizer.from_pretrained("your-sft-base")

def sft_loss(prompt: str, response: str):
    full_text = prompt + response
    tokens = tokenizer(full_text, return_tensors="pt")
    outputs = model(**tokens)
    logits = outputs.logits
    labels = tokens["input_ids"]
    # 自回归损失:用第i个token预测第i+1个token
    # 只对回答部分计算loss是工程上常用优化,这里简化演示
    loss = nn.CrossEntropyLoss()(logits[:,:-1,:].reshape(-1, logits.size(-1)), labels[:,1:].reshape(-1))
    return loss
PYTHON2. 奖励模型 RM 训练示意代码
# ========== 2. 奖励模型RM训练示意代码 ==========
# 奖励模型:在基础大模型之上增加一个reward head,输出标量奖励
class RewardModel(nn.Module):
    def __init__(self, base_model):
        super().__init__()
        self.base = base_model
        self.reward_head = nn.Linear(base_model.config.hidden_size, 1)

    def forward(self, input_ids):
        out = self.base(input_ids=input_ids, output_hidden_states=True)
        last_hidden = out.hidden_states[-1][:, -1, :] # 取最后一个token的hidden state
        reward = self.reward_head(last_hidden)
        return reward

def rm_pair_loss(rm, prompt, chosen, rejected):
    """成对偏好损失:保证chosen样本奖励 > rejected样本奖励"""
    tok_chosen = tokenizer(prompt + chosen, return_tensors="pt")
    tok_rej = tokenizer(prompt + rejected, return_tensors="pt")
    r_chosen = rm(**tok_chosen)
    r_rej = rm(**tok_rej)
    # 排序损失,拉开优选回答和劣选回答之间的奖励差距
    loss = torch.relu(-(r_chosen - r_rej)).mean()
    return loss
🎯 面试考点
  • SFT 为什么只对 response 部分算 loss? prompt 是条件输入,模型不需要「学会生成 prompt」;只对 response 计 loss 能避免模型把容量浪费在复述用户输入上,也让监督信号更聚焦。
  • Reward Model 为什么取最后一个 token 的 hidden state? 因果注意力下,最后一个 token 的位置能「看到」整段 prompt + response 的全部信息,因此它是整段序列的汇聚表示。
  • RM 的损失为什么是成对排序损失? 人类标注更容易给出「A 比 B 好」的相对判断,而不是绝对分数。成对损失对应 Bradley-Terry 偏好模型:P(chosen > rejected) = σ(r_c − r_r)。
  • 三段式能不能省一段? SFT 不能省(提供初始策略与 KL 锚点);RM 可被隐式建模替代(DPO);PPO 可被直接偏好优化替代(DPO/ORPO/GRPO)。
  • Critic 模型的作用是什么? 估计状态价值 V(s) 作为 baseline,把「奖励减去基线」作为 advantage,从而显著降低策略梯度的方差。
02

RLHF 的短板与 Reward Hacking

RLHF 这套方案存在几个很突出的短板:

  • 奖励模型容易出现 Reward Hacking(奖励黑客)现象;
  • PPO 强化学习阶段训练不稳定,调参门槛高;
  • 三阶段串联的训练流程长,整体算力开销很大。

Reward Hacking(奖励黑客)

模型并没有真正理解人类想要的意图,只是找到了能够欺骗奖励模型、拿到高分的生成模式。 举两个典型例子:

🚨 症状一:长度灌水

奖励模型倾向给更长文本打高分
→ 模型在回答里填充大量无意义冗余句子

🚨 症状二:关键词堆砌

奖励模型对某些关键词给予更高奖励
→ 模型反复堆砌关键词
⚠️ 本质根源

奖励函数只是人类偏好的近似代理,无法完全等价真实人类判断。策略优化的方向是「最大化代理奖励」,而不是「最大化真实人类满意度」,两者之间的 gap 就是 Hacking 的生存空间。

Reward Hacking 的常见缓解手段

ALGO
KL 惩罚

把策略锚定在 SFT 分布附近,提高大幅偏离的代价。

loss += β · KL(π ‖ π_ref)
MODEL
RM 集成

训多个 RM 取均值,并用方差做惩罚,压低「钻空子」样本的得分。

r = mean(R_i) − λ · std(R_i)
DATA
在线人工纠偏

定期抽检高分输出,把 Hacking 样本标注为负例回灌训练。

human-in-the-loop
🎯 面试考点
  • 怎么缓解 Reward Hacking?① 加 KL 惩罚约束策略不要跑偏;② 奖励模型集成(ensemble)取均值/方差惩罚;③ 奖励归一化与裁剪;④ 混合部分人工反馈在线纠偏;⑤ 用 DPO 这类隐式奖励方法绕开显式 RM。
  • 为什么 KL 惩罚能抑制 Hacking? 它把「策略分布」锚定在 SFT 参考分布附近,Hacking 通常需要策略分布大幅偏离原分布,KL 项直接提高了这条路径的代价。
  • 「奖励归一化」为什么要做? 不同 prompt 的 reward 量纲差异大,归一化后 advantage 更稳定;常见做法是按 batch 或 running mean/std 做 z-score。
03

PPO 核心原理

原始 Policy Gradient 策略梯度算法,更新时策略变化幅度完全不受约束。 如果一次梯度更新过大,新策略和旧策略分布差异巨大,很容易直接把模型训崩,训练非常不稳定。 PPO 引入 clip 截断机制,用来约束新旧策略的概率比值:

r(θ) = πθ(a|s) / πθ_old(a|s)

clip 的核心思想:在策略能够获得收益提升的前提下,限制新旧策略之间的分布偏移, 防止单次更新步子迈太大。PPO 有两个主流版本:

  • PPO-Penalty:使用 KL 散度做惩罚;
  • PPO-Clip:采用截断比值的方式,也是 RLHF 场景下最常用的版本。

KL 惩罚项

RLHF 训练过程中会额外增加 KL 惩罚项。我们会固定 SFT 模型作为参考模型, 计算当前策略模型输出分布和参考模型分布之间的 KL 散度,加到总损失里。 KL 惩罚的作用是约束新策略不要偏离原始 SFT 模型太远。一旦策略偏离基准模型过多,损失就会变大, 以此避免模型生成无意义文本、模型能力退化,同时抑制 Reward Hacking。 KL 数值越大,代表策略偏离 SFT 基准越多。

① 采样 Prompt 批次
▼
② Policy 策略模型 rollout 生成 response同时保存旧策略 log_prob_old
▼
③ Reward Model 打分 → reward
▼
④ 计算 Advantage(衡量该动作比平均水平好多少)
▼
⑤ PPO-Clip 更新 Policy + KL 惩罚(对齐冻结的 Ref 模型)
▼
↺ 回到 ②,进入下一轮迭代
PYTHONPPO-Clip Loss 核心实现
# ========== PPO Clip Loss 核心实现 ==========
def ppo_clip_loss(log_prob_new, log_prob_old, advantage, eps=0.2):
    """
    log_prob_new: 当前策略模型动作log概率
    log_prob_old: rollout阶段旧策略保存下来的log概率
    advantage: 优势函数,衡量当前动作比平均水平好多少
    eps: clip截断系数,通常取0.2
    """
    ratio = torch.exp(log_prob_new - log_prob_old)
    unclipped = ratio * advantage
    clipped = torch.clamp(ratio, 1 - eps, 1 + eps) * advantage
    # 取最小值,负号是因为梯度下降最小化损失
    loss = -torch.min(unclipped, clipped).mean()
    return loss
PYTHONKL 惩罚项实现
# ========== KL惩罚项实现 ==========
def kl_divergence(policy_logits, ref_logits):
    # KL(policy || ref),计算策略分布相对参考SFT分布的散度
    logp = torch.log_softmax(policy_logits, dim=-1)
    logref = torch.log_softmax(ref_logits, dim=-1)
    kl = (torch.exp(logp) * (logp - logref)).sum(dim=-1)
    return kl

def ppo_total_loss(log_prob_new, log_prob_old, adv, policy_logits, ref_logits, kl_coeff=0.05):
    clip_loss = ppo_clip_loss(log_prob_new, log_prob_old, adv)
    kl = kl_divergence(policy_logits, ref_logits).mean()
    total_loss = clip_loss + kl_coeff * kl
    return total_loss

GAE 与优势估计

Advantage(优势)衡量的是「在状态 s 下采取动作 a,比平均水平好多少」。 最朴素的估计是 A = R − V(s),但单步回报方差大。 GAE(Generalized Advantage Estimation,广义优势估计)通过引入衰减因子 λ, 在「偏差」和「方差」之间做权衡,是 PPO 的标准配置。

AtGAE = Σl≥0 (γλ)l · δt+l ,其中 δt = rt + γV(st+1) − V(st)
📐 直觉理解

λ = 0 时退化为单步 TD 误差(低方差、高偏差);λ = 1 时退化为蒙特卡洛回报(高方差、低偏差)。 实践中 λ 通常取 0.95 左右,兼顾两者。

PYTHONGAE 优势估计示意代码
# ========== GAE(广义优势估计)示意代码 ==========
def compute_gae(rewards, values, gamma=0.99, lam=0.95):
    """
    rewards: 每一步的即时奖励 [T]
    values:  价值模型对每个状态的估计 V(s_t) [T+1],最后一位是 bootstrap
    返回: advantages [T], returns [T]
    """
    T = len(rewards)
    advantages = torch.zeros(T)
    last_gae = 0.0
    for t in reversed(range(T)):
        # TD 误差 δ_t = r_t + γV(s_{t+1}) - V(s_t)
        delta = rewards[t] + gamma * values[t + 1] - values[t]
        # 递推累积:A_t = δ_t + γλ · A_{t+1}
        last_gae = delta + gamma * lam * last_gae
        advantages[t] = last_gae
    returns = advantages + values[:-1]   # 回报 = 优势 + 价值基线
    return advantages, returns

完整 PPO 训练步骤(教学伪代码)

把前面所有片段串起来,一个 PPO 迭代包含「采样 → 打分 → 优势估计 → 多轮 minibatch 更新」四个动作。 下面用一个函数骨架把链路展示完整。

PYTHONPPO 单轮迭代骨架(串联所有组件)
# ========== PPO 单轮迭代骨架 ==========
def ppo_train_step(policy, ref_model, reward_model, critic,
                   prompts, optimizer, gen_kwargs,
                   clip_eps=0.2, kl_coeff=0.05, gamma=0.99, lam=0.95):
    """一次完整的 PPO 迭代:采样 → 打分 → 优势 → 更新"""
    # ---- ① Rollout:策略模型批量采样,保存 log_prob_old ----
    with torch.no_grad():
        responses, logp_old, values_old = [], [], []
        for p in prompts:
            tokens = tokenizer(p, return_tensors="pt")
            gen = policy.generate(**tokens, **gen_kwargs)
            resp = tokenizer.decode(gen[0], skip_special_tokens=True)
            responses.append(resp)

            full = tokenizer(p + resp, return_tensors="pt")
            # 策略模型在采样时的 log 概率(旧策略)
            out_old = policy(**full)
            logp_old.append(policy_logprob(out_old.logits, full["input_ids"]))
            # Critic 估计状态价值
            values_old.append(critic(**full))

    # ---- ② Reward Model 打分 ----
    with torch.no_grad():
        rewards = torch.stack([
            reward_model(tokenizer(p + r, return_tensors="pt")["input_ids"])
            for p, r in zip(prompts, responses)
        ]).squeeze(-1)

    # ---- ③ GAE 优势估计 ----
    values_cat = torch.cat([v.squeeze(-1) for v in values_old] + [torch.zeros(1)])
    advantages, returns = compute_gae(rewards, values_cat, gamma, lam)
    # 优势标准化,稳定训练
    advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)

    # ---- ④ 多轮 minibatch 更新 ----
    total_loss_val = 0.0
    for _ in range(4):  # 同一批数据复用 4 个 epoch
        out_new = policy(**tokenizer(prompts[0] + responses[0], return_tensors="pt"))
        logp_new = policy_logprob(out_new.logits, tokenizer(prompts[0] + responses[0])["input_ids"])

        # PPO-Clip 损失
        clip_loss = ppo_clip_loss(logp_new, logp_old[0], advantages[0], eps=clip_eps)
        # KL 惩罚(对齐冻结的参考模型)
        with torch.no_grad():
            out_ref = ref_model(**tokenizer(prompts[0] + responses[0], return_tensors="pt"))
        kl = kl_divergence(out_new.logits, out_ref.logits).mean()
        # 价值模型回归损失
        value_loss = torch.nn.functional.mse_loss(critic_out, returns[0])

        loss = clip_loss + kl_coeff * kl + 0.5 * value_loss

        optimizer.zero_grad()
        loss.backward()
        torch.nn.utils.clip_grad_norm_(policy.parameters(), max_norm=1.0)
        optimizer.step()
        total_loss_val += loss.item()

    return {"loss": total_loss_val / 4, "reward_mean": rewards.mean().item()}
🎯 面试考点
  • 为什么是 torch.min? 这是 PPO 的「悲观下界」:当 advantage > 0 时取 min 等价于给 ratio 设了 1+ε 的上限,防止好动作被过度放大;当 advantage < 0 时取 min 等价于给 ratio 设了 1−ε 的下限,防止坏动作被过度惩罚。两种情况都只限制「朝有利方向」的步子。
  • ratio 的物理含义? π_new / π_old。用 exp(logp_new − logp_old) 计算是为了数值稳定,避免小概率直接相除产生精度问题。
  • ε 为什么常取 0.2? 经验值,代表单次更新允许概率最多变化约 20%,是稳定性与学习速度的折中。
  • KL 项为什么用 KL(policy ‖ ref) 而不是反向? 这是「前向 KL」,会惩罚策略在参考分布概率低的区域产生输出,符合「不许跑太远」的约束目标。实现上用 E_p[log p − log ref] 的无偏估计(k1/k2/k3 估计器是工程变体)。
  • 参考模型为什么必须冻结? 如果参考模型跟着更新,KL 约束的「锚点」会跟着漂移,约束逐渐失效,模型会持续偏离 SFT 分布,导致能力退化与 Reward Hacking。
  • 为什么优势要做标准化? 不同 prompt 的 reward 尺度差异大,标准化(减均值除标准差)能让梯度尺度一致,训练更稳。
  • GAE 里 γ 和 λ 的分工? γ 是折扣因子,决定「未来奖励打几折」;λ 是优势估计的偏差-方差权衡参数。两者含义不同,不能混用。
  • 为什么需要梯度裁剪? RL 的 loss 尺度不稳定,梯度爆炸常见;clip_grad_norm_ 把梯度范数限制在阈值内,是 PPO 稳定训练的必备项。
04

偏好优化 PO 系列算法(DPO / ORPO)

DPO:Direct Preference Optimization

DPO 全称 Direct Preference Optimization,直接偏好优化,是目前 RLHF 最主流的替代方案。 DPO 完全不需要单独训练奖励模型,也不需要 PPO 那一套在线 rollout 采样 + 强化迭代的流程。 它直接把成对偏好数据转化为分类优化目标,隐式建模奖励函数。

✅ DPO 优点

省去奖励模型训练阶段,训练链路大幅缩短,代码实现简单
训练稳定性强,不存在 PPO 容易训练崩溃、调参复杂的问题
整体算力开销更低

⚠️ DPO 缺点

缺少在线探索能力,模型无法在训练过程中探索新的回答样本
对偏好数据集质量更加敏感
chosen / rejected 区分度不足时,效果明显下滑

DPO 与 ORPO 的区别

DPO 只使用 chosen / rejected 成对偏好数据,优化目标是最大化优选回答相对劣选回答的优势; 训练流程是先完成 SFT,再单独执行 DPO 偏好对齐训练。

ORPO 全称 Odds Ratio Preference Optimization,它把 SFT 损失和偏好损失合并到同一个损失函数当中, 可以单阶段同时完成 SFT 训练和偏好对齐,不需要拆成两个阶段训练,代码更加简洁, 在小数据集场景下效果通常更好。

🔁 DPO 两阶段

底座模型 → SFT 微调 → SFT 模型
SFT 模型 → DPO 偏好对齐 → 对齐模型

⚡ ORPO 单阶段

底座模型
→ 一次训练(SFT loss + Odds Ratio loss)
→ 对齐模型

PO 家族的统一视角

PO(Preference Optimization,偏好优化)是一大类算法的统称,DPO、ORPO、KL-DPO 都属于 PO 算法。 传统 RLHF 的思路是先显式构建奖励函数,再用强化学习最大化奖励; PO 系列算法跳过显式奖励建模这一步,直接利用偏好对优化策略, 数学上等价于隐式奖励建模,训练稳定性更强。

DPO 隐式奖励推导(三步走)

① 写出 KL 约束下的最优策略

目标是 max E[r(x,y)] − β·KL(π‖π_ref),其闭式最优解为 π*(y|x) ∝ π_ref(y|x)·exp(r(x,y)/β)。

② 反解奖励函数

对上面式子取对数整理,得到 r(x,y) = β·log(π*(y|x)/π_ref(y|x)) + β·log Z(x),其中 Z(x) 是配分函数。

③ 代入 Bradley-Terry 消掉 Z(x)

偏好概率 P(y_w ≻ y_l) = σ(r(x,y_w) − r(x,y_l))。做差时 Z(x) 恰好抵消,只剩策略与参考模型的对数概率比,于是直接得到 DPO 损失。

📐 一句话总结

DPO 把「训一个奖励模型 + 用 PPO 最大化它」这一整套流程,数学等价地压缩成了一个关于策略本身的二分类损失。奖励不再被显式表示,而是隐含在「策略与参考模型的对数概率差」里。

PYTHONDPO Loss 实现
# ========== DPO Loss 实现 ==========
def get_log_prob(model, input_ids):
    """辅助函数:计算给定序列的对数概率之和"""
    out = model(input_ids=input_ids)
    logits = out.logits
    logp_tokens = torch.log_softmax(logits, dim=-1)
    token_logp = logp_tokens[:, :-1].gather(-1, input_ids[:,1:].unsqueeze(-1)).squeeze(-1)
    return token_logp.sum(dim=-1)

def dpo_loss(policy_model, ref_model, prompt, chosen, rejected, beta=0.1):
    tok_chosen = tokenizer(prompt + chosen, return_tensors="pt")
    tok_rej = tokenizer(prompt + rejected, return_tensors="pt")

    logp_chosen_pol = get_log_prob(policy_model, **tok_chosen)
    logp_rej_pol = get_log_prob(policy_model, **tok_rej)

    logp_chosen_ref = get_log_prob(ref_model, **tok_chosen)
    logp_rej_ref = get_log_prob(ref_model, **tok_rej)

    log_ratio_chosen = logp_chosen_pol - logp_chosen_ref
    log_ratio_rej = logp_rej_pol - logp_rej_ref

    loss = -torch.nn.functional.logsigmoid(beta * (log_ratio_chosen - log_ratio_rej))
    return loss.mean()
PYTHONORPO Loss 示意
# ========== ORPO Loss 示意 ==========
def orpo_loss(policy_model, prompt, chosen, rejected, beta=0.1):
    # ORPO:SFT损失 + odds ratio偏好损失合并
    sft_loss_val = sft_loss(prompt, chosen)
    tok_chosen = tokenizer(prompt + chosen, return_tensors="pt")
    tok_rej = tokenizer(prompt + rejected, return_tensors="pt")
    logp_chosen = get_log_prob(policy_model, **tok_chosen)
    logp_rej = get_log_prob(policy_model, **tok_rej)
    odds_ratio = torch.log(torch.sigmoid(beta * (logp_chosen - logp_rej)))
    total_loss = sft_loss_val - odds_ratio
    return total_loss.mean()
🎯 面试考点
  • DPO 的数学本质是什么? 在 KL 约束下的奖励最大化问题存在闭式最优策略,反解出 r(x,y) = β·log(π_θ/π_ref) + β·log Z(x),代回 Bradley-Terry 偏好模型后配分项 Z(x) 被消掉,于是直接得到关于策略本身的分类损失。也就是说,DPO 把「训 RM + 跑 PPO」等价压缩成了一个监督式损失。
  • DPO 中的 beta 是什么? 与 RLHF 中 KL 惩罚系数作用对应,控制允许偏离参考模型的幅度。β 越大越保守(贴近 SFT),越小越激进(更容易过拟合偏好数据)。
  • DPO 为什么仍然需要 ref_model? 因为它的隐式奖励是「相对参考模型的对数概率比」,没有参考模型就没有比较基准。
  • ORPO 的 odds ratio 为什么比 DPO 的直接概率差更稳? odds = p/(1−p) 的形式对长序列的 log-prob 累积不敏感,且天然与 SFT 的 NLL 损失共享同一个 log-prob 计算,实现上零额外开销。
  • DPO 与 RLHF 的取舍? 数据质量高、算力有限 → DPO;需要在线探索、可自动判定对错(数学/代码)→ 在线 RL(PPO/GRPO)。
  • DPO 为什么容易对数据敏感? 它没有在线探索,完全依赖离线数据的分布覆盖;如果 chosen/rejected 差异小或存在噪声标注,梯度信号会被稀释甚至反向。
05

Rollout 与工程逻辑

Rollout 是 PPO 这类在线强化学习训练中的核心环节。在每一轮 PPO 迭代的 rollout 阶段, 策略模型批量接收 prompt,采样生成回答,将生成的回答送入奖励模型打分得到 reward 奖励信号, 之后再回传信号用于 PPO 策略更新。

prompt 送入 policy 模型 → 生成 response → RM 打分 → 计算损失更新 policy

Rollout 阶段的性能瓶颈通常集中在 token 解码生成速度、prefill 阶段、 解码阶段的吞吐能力、batch 调度策略,还有 KV 缓存的内存占用。

在线 Rollout vs 离线偏好数据集

🟠 在线 Rollout(PPO 特有)

每一轮训练迭代,策略模型实时采样生成全新回答
交给 RM 或者人工打分
模型拥有探索新输出空间的能力
代价:算力成本很高

🟢 离线偏好数据集(DPO 常用)

数据集提前收集固定
整个训练过程不会再生成新样本
没有在线采样环节
代价:无探索能力,依赖数据质量
🔒 参考模型的角色

PPO 训练的时候策略模型和参考模型会分开设置。参考模型固定为 SFT 模型,全程不参与梯度更新, 它唯一的作用就是用来计算 KL 惩罚,限制策略更新幅度。如果参考模型跟着一起更新, KL 约束就会失效,模型很容易发生能力退化,出现 Reward Hacking。

PYTHONRollout 采样示意代码
# ========== Rollout 采样示意代码 ==========
def rollout(policy_model, rm, prompts, gen_kwargs):
    """rollout阶段:策略模型批量生成回答,RM打分"""
    responses = []
    rewards = []
    for p in prompts:
        tokens = tokenizer(p, return_tensors="pt")
        gen_out = policy_model.generate(**tokens, **gen_kwargs)
        resp = tokenizer.decode(gen_out[0], skip_special_tokens=True)
        responses.append(resp)
        # RM打分得到reward
        reward = rm(tokenizer(p+resp, return_tensors="pt"))
        rewards.append(reward)
    return responses, torch.cat(rewards)

数据工程与超参速查

对齐训练的效果,一半靠算法,一半靠数据和超参。下面把常见配置整理成速查卡片。

DATA
偏好数据构造

同一 prompt 采样 4~16 条回答,人工排序或模型辅助标注,保证 chosen/rejected 差异明显。

prompt → {y1..yN} → 排序
LOSS
KL 系数 β / kl_coeff

RLHF 常用 0.01~0.1;DPO 的 beta 常用 0.1~0.5。太大保守,太小易 Hacking。

0.01 ≤ kl_coeff ≤ 0.1
OPT
学习率与 batch

PPO 策略模型学习率通常 1e-6 ~ 5e-6,远小于 SFT;batch 要足够大以降低梯度方差。

lr ≈ 1e-6 ~ 5e-6
超参 典型范围 调大后果 调小后果
clip_eps 0.1 ~ 0.3 允许更新步子更大,可能不稳 更新受限,收敛变慢
kl_coeff 0.01 ~ 0.1 贴近 SFT,抑制 Hacking 约束失效,易能力退化
gamma 0.95 ~ 0.99 更重视长期回报 更短视,偏向即时奖励
lam (GAE) 0.9 ~ 0.98 低偏差高方差 高偏差低方差
采样温度 0.7 ~ 1.0 样本质量差,噪声大 探索不足,分布单一
DPO beta 0.1 ~ 0.5 更贴近参考模型 易过拟合偏好数据
🎯 面试考点
  • on-policy 与 off-policy 在这里指什么? PPO 的更新数据必须来自当前策略(或非常接近的旧策略),属于 on-policy;DPO 用的是固定离线数据集,属于 off-policy,所以它没有重要性采样修正的需求。
  • 为什么 PPO 里要保存 log_prob_old? 因为同一个 rollout 批次通常会被复用做多轮 minibatch 更新,ratio 必须以「采样时那一刻的策略」为基准,否则 clip 的约束对象就错了。
  • Rollout 为什么是性能瓶颈? 它是自回归逐 token 解码,GPU 利用率远低于训练时的并行前向;prefill 是计算密集、decode 是访存密集,两者混合调度对吞吐影响极大。
  • 工程上常见的加速手段? 推理引擎与训练引擎分离(如 vLLM/SGLang 做 rollout)、连续批处理、KV 缓存复用、生成长度上限控制、异步 rollout 与训练重叠。
  • 为什么 PPO 学习率要比 SFT 小一个数量级? 策略梯度估计本身噪声大,学习率过大会导致策略剧烈震荡;小学习率配合 clip 才能稳定推进。
06

GRPO 原理

GRPO 全称 Group Relative Policy Optimization,由深度求索提出, 多用于数学、代码这类推理任务的大模型强化学习对齐。

GRPO 不需要奖励模型。针对同一个 prompt,模型采样 N 个不同回答, 然后依靠组内回答的相对好坏来计算奖励,使用组内归一化替代单独 RM 打分。 这个算法可以大幅削减奖励模型带来的训练成本,非常适合答案可以自动判定对错的场景, 比如数学题、代码执行判题。

同一个 Prompt
▼
采样 N 个回答 y₁ … yN(Group 采样)
▼
规则 / 验证器直接判对错,得到 r₁ … rN无需奖励模型
▼
组内归一化:Âi = (ri − mean) / (std + ε)
▼
用 Âi 替代 PPO 的 Advantage,做带 KL 的 clip 更新
PYTHONGRPO 组内相对奖励示意
# ========== GRPO 组内相对奖励示意 ==========
def grpo_group_reward(rewards_group):
    """对同一个prompt的N个回答做组内归一化"""
    mean_r = rewards_group.mean()
    std_r = rewards_group.std() + 1e-8
    normalized_reward = (rewards_group - mean_r) / std_r
    return normalized_reward

PPO 需要什么

奖励模型 RM ✓
价值模型 Critic ✓
参考模型 Ref ✓
策略模型 Policy ✓

GRPO 只需要什么

规则验证器(替代 RM)✓
参考模型 Ref ✓
策略模型 Policy ✓
省掉 Critic,显存大幅下降
🎯 面试考点
  • GRPO 和 PPO 最大的区别? 去掉了 Critic / Value Model。PPO 需要额外训一个价值网络来估计 baseline,GRPO 用「同一 prompt 下多个样本的组内均值」当 baseline,省掉了与策略模型同量级的价值网络,显存和算力大幅下降。
  • 组内归一化为什么合理? 同一 prompt 的多个回答共享同一个条件,组内相对好坏就是最自然的优势估计,且天然消掉了 prompt 难度带来的方差。
  • GRPO 的适用边界? 必须存在可靠的自动判分信号(数学答案比对、代码单测)。开放式对话类任务没有规则验证器,仍需 RM 或人工。
  • 为什么 GRPO 特别适合推理任务? 推理任务奖励通常是稀疏的 0/1,组内归一化把稀疏奖励转成了有区分度的相对优势,梯度信号更稳定。
  • 组内方差为 0 会怎样? 如果 N 个回答全对或全错,归一化后优势全为 0,该 prompt 不产生梯度。工程上通常跳过这类样本或做过滤。
07

RL 对齐评估体系

RL 对齐的评估分为自动指标与人工指标两大类。

自动 模型基础能力评测集

  • MATH — 数学推理
  • GSM8K — 小学数学应用题
  • AIME — 竞赛级数学
  • HumanEval — 代码生成

用来检验模型推理、代码能力有没有在对齐过程中下降。

自动 自动偏好评估

  • 奖励模型打分 — 用 RM 给输出打分
  • MT-Bench — 多轮对话评测

快速、低成本,但容易被同一个 RM 的偏好盲区误导。

人工 人工成对偏好对比

  • 人工打分、A/B 成对比较,用来检验模型真实人类偏好对齐效果
  • 成本高、周期长,但它是唯一能真正发现 Reward Hacking 的手段

训练过程需要持续监控的辅助指标

KL 散度 生成长度 文本重复率 有害输出占比 奖励均值 / 方差 梯度范数

用来提前发现 Reward Hacking 或者 模型退化。 例如:长度指标突然飙升 + KL 持续走高 + 重复率上升,基本可以判定模型正在「灌水刷分」。

指标异常 → 可能病因对照

观察到的现象 可能病因 排查方向
reward 上升,但人工评测变差 Reward Hacking RM 是否过拟合;KL 系数是否太低
KL 持续单调走高 策略偏离参考分布 增大 kl_coeff 或降低学习率
生成长度异常膨胀 长度偏置型 Hacking RM 是否对长度敏感;加长度惩罚
文本重复率飙升 策略坍塌 / 分布退化 采样温度是否过低;检查 KL
reward 方差接近 0 信号无区分度 偏好数据质量;RM 训练是否失败
梯度范数剧烈波动 训练不稳定 梯度裁剪;batch 是否过小
🎯 面试考点
  • 为什么对齐训练一定要盯基础能力评测集? 因为 RL 优化的是「偏好」,而不是「能力」。过度优化偏好很容易牺牲推理与代码能力(即 alignment tax),必须用客观评测集兜底。
  • Reward Model 打分和人工打分冲突时怎么办? 以人工为准,说明 RM 已经出现分布外过拟合,需要补数据重训 RM,或降低 RM 在总目标中的权重。
  • KL 散度监控的典型阈值思路? 不看绝对值看趋势:KL 持续单调上升往往意味着策略已经跑出参考分布的有效范围,此时即便 reward 还在涨,也大概率是 Hacking。
  • alignment tax 指什么? 对齐训练后模型在通用能力(推理、知识问答)上出现的性能回退。它是偏好优化与能力保持之间的权衡结果。
08

PPO 训练不收敛排查思路

当 PPO 训练不收敛时,可以按照从数据到超参,再到模型、采样、训练配置的顺序逐层排查。

1

数据层面

检查偏好样本质量,重点看 chosen 和 rejected 样本之间区分度是否充足。如果两者差别很小,奖励信号没有有效梯度,模型很难学习。

2

超参层面

检查 clip 系数、学习率、KL 惩罚权重。KL 权重设置太高,模型会被约束住,学不到新偏好;KL 权重太低,约束失效,容易出现 Reward Hacking。

3

奖励模型层面

检查 RM 是否过拟合,RM 打分是否具备良好区分度。如果奖励模型本身就训坏了,奖励信号是噪声,PPO 自然无法收敛。

4

采样策略

rollout 阶段的采样温度、top-p 设置。采样探索不足,模型很难跳出原有分布;探索过度,生成样本质量差,引入大量噪声奖励。

5

训练稳定性配置

batch 大小、梯度裁剪等设置。梯度爆炸、batch 过小都会带来训练震荡。

🎯 面试考点
  • 排查顺序为什么这么定? 遵循「信号源 → 约束 → 估计器 → 探索 → 优化器」的因果链:如果奖励信号本身是噪声,后面所有调参都是徒劳。
  • reward 涨但人工评测变差,说明什么? 典型的 Reward Hacking。优先怀疑 RM 过拟合或 KL 权重过低。
  • reward 完全不涨,先看哪两个数? ① RM 在验证集上的排序准确率;② rollout 样本的 reward 方差(方差≈0 说明信号无区分度)。
  • loss 出现 NaN 通常是什么原因? 学习率过大、梯度爆炸、KL 项数值溢出或 log 概率计算精度不足。优先检查梯度裁剪与 log_softmax 的实现。
09

进阶算法一览

除了 RLHF / DPO / ORPO / GRPO,业界还衍生出了一系列变体。它们大多围绕「降低算力」「降低标注成本」「提升稳定性」三个目标展开。

算法 核心思路 主要优势 典型场景
Rejection Sampling 采样 N 条回答,用 RM 挑最优的作为 SFT 数据,反复迭代 实现极简,无 RL 不稳定性 数据增强、冷启动
Iterative DPO 用当前策略生成新回答,重新构造偏好对再做 DPO 补回部分在线探索能力 提升 DPO 上限
KTO 只区分「好 / 坏」,不要求成对数据 标注成本更低 单边反馈数据
RLVR 用可验证奖励(数学答案、代码单测)做 RL 奖励信号客观,无 Hacking 推理能力强化
RLAIF 用强模型替代人工做偏好标注 大幅降低人工成本 规模化对齐
Self-Rewarding 模型自己给自己打分,交替训 RM 与策略 减少外部依赖 研究探索
🧩 一句话定位

Rejection Sampling 是「用 RM 当过滤器」的 SFT; Iterative DPO 是「离线 DPO + 在线采样」的折中; RLVR 是「把 GRPO 的判分器换成程序验证器」; KTO 是把成对偏好松弛成单点好坏的 DPO 变体。

10

高频面试 FAQ

下面用问答形式整理了对齐方向面试中最常被追问的问题,点击展开查看解答。

RLHF 和 DPO 最本质的区别是什么?

RLHF 是「两段式」:先显式训一个奖励模型,再用 PPO 在线最大化它,奖励函数是显式存在的。

DPO 是「一段式」:直接利用成对偏好数据优化策略,奖励函数被隐式地编码在「策略与参考模型的对数概率比」中,不存在独立的 RM。

两者在数学上是等价的,只是优化路径不同:RLHF 走的是「奖励建模 → 策略优化」,DPO 走的是「一步到位的分类损失」。

PPO 的 ratio 为什么用 exp 而不是直接相除?

因为语言模型的动作概率常常非常小(比如 1e-8 量级),直接相除容易产生数值精度问题甚至除零。

而 log 概率是模型输出的 logits 经过 log_softmax 得到的,本身就比较稳定。exp(logp_new − logp_old) 把「相除」变成了「相减再取指数」,数值上安全得多。

PPO 里 KL 惩罚和 clip 都在限制策略变化,它们有什么区别?

clip 限制的是「同一个 rollout 批次内,新旧策略在单个 token 上的概率比」,是局部、逐 token 的约束,目的是让一次梯度更新别走太远。

KL 惩罚 限制的是「当前策略与冻结参考模型之间的分布距离」,是全局、跨迭代 的约束,目的是防止多轮训练累积后模型整体跑偏、能力退化。

一句话:clip 管「这一步别迈太大」,KL 管「别走错方向」。

为什么 DPO 不需要在线采样?

因为 DPO 的损失是直接定义在「策略与参考模型的对数概率差」上的,它只需要固定的偏好对数据就能算出梯度,不像 PPO 那样需要「当前策略实际生成样本 → RM 打分 → 估计 advantage」。

代价是失去了在线探索能力,模型无法主动发现数据分布之外的新回答,所以对离线数据的覆盖度和质量更敏感。

GRPO 为什么能省掉 Critic?

PPO 需要 Critic 来估计状态价值 V(s) 作为 advantage 的 baseline,这是为了降低方差。但 Critic 本身是一个与策略模型同量级的网络,训练和显存开销都不小。

GRPO 的巧思是:对同一个 prompt 采样 N 个回答,用这 N 个回答的平均奖励当作 baseline。因为条件相同,组内均值天然就是一个合理的基线估计,于是完全不需要单独的价值网络。

Reward Hacking 和「模型能力退化」是什么关系?

两者经常同时出现,但本质不同。

Reward Hacking 是指模型找到了「刷高代理奖励但不符合真实意图」的捷径,表现为「reward 涨、人工评测差」。

能力退化 是指模型在通用任务上的表现下降(alignment tax),通常是因为策略偏离 SFT 分布太远,把原有的语言能力、推理能力也一并丢掉了。

KL 惩罚同时缓解这两个问题:既提高 Hacking 的成本,又锚定模型不要丢失原有能力。

如果只能选一个指标来判断 PPO 训练是否健康,选什么?

选 KL 散度。

reward 高不代表好(可能是 Hacking),loss 低也不代表好(可能过拟合),但 KL 持续异常上升几乎总是一个早期预警信号——它说明策略已经跑出参考分布的有效范围,后面的 reward 增长大概率是「虚高」。

实践中通常把 KL 与「生成长度」「重复率」联合观察,三者同时异常时基本可以断定模型跑偏。

SFT 阶段数据量不够大会有什么后果?

首先,SFT 模型本身就不具备稳定的指令遵循能力,后面的 RM 和 PPO 阶段都会建立在薄弱的基础上。

其次,SFT 模型是 PPO 的 KL 锚点。如果 SFT 模型本身分布就「飘」,KL 约束的参考点也不稳定,策略更容易在训练中失控。

所以工业界在 SFT 上的投入通常很重,数据质量和数量都不容妥协。

DPO 的 beta 和 RLHF 的 KL 系数是一回事吗?

作用上类似,量纲上不同。

RLHF 的 KL 系数直接乘在 KL 散度项上,数值通常在 0.01~0.1 量级。DPO 的 beta 出现在「对数概率差」的系数位置,数值通常在 0.1~0.5 量级。

两者的共同点是:值越大,模型越保守,越贴近参考模型;值越小,越激进,越容易过拟合偏好数据。

为什么 RLHF 的训练成本远高于 DPO?

主要有三块开销:

① 多模型并存:PPO 同时需要策略、参考、奖励、价值四个模型在显存里,而 DPO 只需要策略和参考两个。

② 在线 rollout:PPO 每一轮都要重新生成回答,自回归解码非常耗时;DPO 直接读离线数据,没有生成开销。

③ 训练阶段更多:RLHF 有 SFT、RM、PPO 三个阶段,DPO 只有 SFT、DPO 两个(ORPO 甚至只有一个)。

RLVR 和 GRPO 是什么关系?

RLVR(Reinforcement Learning with Verifiable Rewards) 是一种「奖励来源」的设定:奖励由可验证的程序给出,比如数学答案比对、代码单元测试通过与否。

GRPO 是一种「策略优化算法」:用组内相对奖励替代 Critic。

两者常常搭配使用:用 RLVR 提供客观奖励,用 GRPO 做高效的策略更新。但它们不是同一层次的概念。

为什么有些场景下 ORPO 比 DPO 效果更好?

ORPO 把 SFT 的 NLL 损失和偏好损失放在同一个目标里联合优化,模型在「学怎么回答」的同时就在「学偏好哪个回答」。

在小数据集场景下,这种联合优化能更充分地利用每一条样本,避免 DPO「先 SFT 再偏好对齐」两阶段之间的信息割裂。此外 ORPO 不需要参考模型,显存更省。

但在大数据、高质量偏好数据场景下,DPO 的两阶段解耦反而更灵活可控。

如果面试官问「你怎么设计一个对齐训练 pipeline」,怎么答?

可以按这个顺序回答:

① 明确目标:通用对话对齐?推理能力强化?安全对齐?目标不同,算法选择不同。

② 数据准备:SFT 指令数据;偏好数据(人工 / 模型辅助 / 规则生成)。

③ 算法选择:算力充足且需要在线探索 → RLHF/PPO;算力有限、数据质量高 → DPO;推理任务且有验证器 → GRPO/RLVR;小数据单阶段 → ORPO。

④ 训练监控:KL、reward、长度、重复率、基础能力评测集。

⑤ 评估与迭代:自动指标 + 人工评测,发现 Hacking 就回补数据、调 KL 系数或重训 RM。

11

术语速查表

把全篇出现的关键术语集中列在这里,方便面试前快速扫一遍。

RLHF

Reinforcement Learning from Human Feedback,基于人类反馈的强化学习,三段式对齐范式的总称。

SFT

Supervised Fine-Tuning,监督微调,用指令数据让底座模型学会遵循指令。

RM

Reward Model,奖励模型,输入 prompt+response 输出标量分数,拟合人类偏好。

PPO

Proximal Policy Optimization,近端策略优化,用 clip 限制策略更新幅度的强化学习算法。

KL 散度

Kullback-Leibler Divergence,衡量两个概率分布差异的指标,用于约束策略不偏离参考模型。

Advantage

优势函数 A(s,a),衡量某动作比平均水平好多少,是策略梯度的核心信号。

GAE

Generalized Advantage Estimation,广义优势估计,用衰减因子 λ 平衡偏差与方差。

Rollout

在线 RL 中策略模型实时采样生成回答的过程,PPO 每一轮迭代都依赖它。

DPO

Direct Preference Optimization,直接偏好优化,用隐式奖励替代显式 RM 与 PPO。

ORPO

Odds Ratio Preference Optimization,把 SFT 与偏好损失合并的单阶段对齐算法。

GRPO

Group Relative Policy Optimization,用组内相对奖励替代 Critic 的在线 RL 算法。

Reward Hacking

奖励黑客,模型找到刷高代理奖励但不符真实意图的捷径,是对齐中的典型失败模式。

Alignment Tax

对齐税,指对齐训练后模型在通用能力上出现的性能回退。

RLVR

Reinforcement Learning with Verifiable Rewards,用可验证程序(数学比对、代码单测)提供奖励。

KTO

Kahneman-Tversky Optimization,只需「好 / 坏」单点标注的偏好优化变体。

RLAIF

Reinforcement Learning from AI Feedback,用强模型替代人工做偏好标注。

Bradley-Terry

经典的成对偏好概率模型,P(A≻B)=σ(r_A−r_B),是 RM 与 DPO 的共同数学基础。

On-policy / Off-policy

训练数据是否来自当前策略。PPO 为 on-policy,DPO 为 off-policy。

12

附录:算法对比速查表

维度 RLHF(PPO) DPO ORPO GRPO
需要奖励模型 需要 不需要 不需要 不需要(用规则验证器)
需要 Critic / Value 需要 不需要 不需要 不需要(组内均值当 baseline)
需要参考模型 需要(冻结 SFT) 需要(冻结 SFT) 不需要 需要(KL 约束)
在线采样 必须 不需要 不需要 必须
训练阶段数 3 段(SFT → RM → PPO) 2 段(SFT → DPO) 1 段 2 段(SFT → GRPO)
训练稳定性 差 好 好 中等
探索能力 有 无 无 有
算力开销 高 低 低 中(省掉 RM 与 Critic)
显存峰值 4 个模型 2 个模型 1 个模型 2~3 个模型
数据敏感度 中 高 高 中
典型场景 通用对话对齐 有高质量偏好数据、算力受限 小数据集单阶段对齐 数学 / 代码等可自动判分推理任务
🧭 一句话记忆

RLHF = 显式训奖励 + 在线 RL; DPO = 把「训奖励 + 跑 RL」数学等价地压成一个监督式分类损失; ORPO = 把 SFT 和偏好对齐再压成一段; GRPO = 在线 RL 里把 Critic 换成组内相对基线; RLVR = 把奖励来源换成程序验证器。

首页