基础概念:RLHF 三段式完整流程
RLHF(基于人类反馈的强化学习)是大模型对齐最经典的范式,整体分为三个依次执行的阶段。
SFT 监督微调
用高质量指令样本微调底座模型,教会模型「听懂指令」。
奖励模型 RM 训练
用成对人类偏好排序数据,训一个「打分器」。
PPO 强化学习
策略模型在线采样,用 RM 奖励信号做策略梯度更新。
三阶段时间轴
阶段 ① 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 阶段同时存在四个「模型角色」,理解它们各自是否训练、是否更新参数, 是面试中最容易踩坑的知识点。
策略模型 πθ
即我们要训练的模型,初始化为 SFT 模型。唯一参与梯度更新的主角。
参考模型 πref
SFT 模型的冻结副本,只用于计算 KL 惩罚,防止策略跑偏。
奖励模型 R
输出标量 reward,提供优化信号。只做前向,不参与策略更新。
价值模型 V
估计状态价值,用来算 advantage。PPO 需要,GRPO 不需要。
价值模型(Critic)是可训练的,它和策略模型一起更新;而参考模型和奖励模型都是冻结的。 很多人会把 Critic 和 Reference 搞混——前者是为了降低方差,后者是为了限制偏离。
配套极简源码
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
# ========== 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,从而显著降低策略梯度的方差。
RLHF 的短板与 Reward Hacking
RLHF 这套方案存在几个很突出的短板:
- 奖励模型容易出现 Reward Hacking(奖励黑客)现象;
- PPO 强化学习阶段训练不稳定,调参门槛高;
- 三阶段串联的训练流程长,整体算力开销很大。
Reward Hacking(奖励黑客)
模型并没有真正理解人类想要的意图,只是找到了能够欺骗奖励模型、拿到高分的生成模式。 举两个典型例子:
🚨 症状一:长度灌水
→ 模型在回答里填充大量无意义冗余句子
🚨 症状二:关键词堆砌
→ 模型反复堆砌关键词
奖励函数只是人类偏好的近似代理,无法完全等价真实人类判断。策略优化的方向是「最大化代理奖励」,而不是「最大化真实人类满意度」,两者之间的 gap 就是 Hacking 的生存空间。
Reward Hacking 的常见缓解手段
KL 惩罚
把策略锚定在 SFT 分布附近,提高大幅偏离的代价。
RM 集成
训多个 RM 取均值,并用方差做惩罚,压低「钻空子」样本的得分。
在线人工纠偏
定期抽检高分输出,把 Hacking 样本标注为负例回灌训练。
- 怎么缓解 Reward Hacking?① 加 KL 惩罚约束策略不要跑偏;② 奖励模型集成(ensemble)取均值/方差惩罚;③ 奖励归一化与裁剪;④ 混合部分人工反馈在线纠偏;⑤ 用 DPO 这类隐式奖励方法绕开显式 RM。
- 为什么 KL 惩罚能抑制 Hacking? 它把「策略分布」锚定在 SFT 参考分布附近,Hacking 通常需要策略分布大幅偏离原分布,KL 项直接提高了这条路径的代价。
- 「奖励归一化」为什么要做? 不同 prompt 的 reward 量纲差异大,归一化后 advantage 更稳定;常见做法是按 batch 或 running mean/std 做 z-score。
PPO 核心原理
原始 Policy Gradient 策略梯度算法,更新时策略变化幅度完全不受约束。 如果一次梯度更新过大,新策略和旧策略分布差异巨大,很容易直接把模型训崩,训练非常不稳定。 PPO 引入 clip 截断机制,用来约束新旧策略的概率比值:
clip 的核心思想:在策略能够获得收益提升的前提下,限制新旧策略之间的分布偏移, 防止单次更新步子迈太大。PPO 有两个主流版本:
- PPO-Penalty:使用 KL 散度做惩罚;
- PPO-Clip:采用截断比值的方式,也是 RLHF 场景下最常用的版本。
KL 惩罚项
RLHF 训练过程中会额外增加 KL 惩罚项。我们会固定 SFT 模型作为参考模型, 计算当前策略模型输出分布和参考模型分布之间的 KL 散度,加到总损失里。 KL 惩罚的作用是约束新策略不要偏离原始 SFT 模型太远。一旦策略偏离基准模型过多,损失就会变大, 以此避免模型生成无意义文本、模型能力退化,同时抑制 Reward Hacking。 KL 数值越大,代表策略偏离 SFT 基准越多。
# ========== 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
# ========== 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 的标准配置。
λ = 0 时退化为单步 TD 误差(低方差、高偏差);λ = 1 时退化为蒙特卡洛回报(高方差、低偏差)。 实践中 λ 通常取 0.95 左右,兼顾两者。
# ========== 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 更新」四个动作。 下面用一个函数骨架把链路展示完整。
# ========== 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 稳定训练的必备项。
偏好优化 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 模型 → 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 最大化它」这一整套流程,数学等价地压缩成了一个关于策略本身的二分类损失。奖励不再被显式表示,而是隐含在「策略与参考模型的对数概率差」里。
# ========== 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()
# ========== 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 差异小或存在噪声标注,梯度信号会被稀释甚至反向。
Rollout 与工程逻辑
Rollout 是 PPO 这类在线强化学习训练中的核心环节。在每一轮 PPO 迭代的 rollout 阶段, 策略模型批量接收 prompt,采样生成回答,将生成的回答送入奖励模型打分得到 reward 奖励信号, 之后再回传信号用于 PPO 策略更新。
Rollout 阶段的性能瓶颈通常集中在 token 解码生成速度、prefill 阶段、 解码阶段的吞吐能力、batch 调度策略,还有 KV 缓存的内存占用。
在线 Rollout vs 离线偏好数据集
🟠 在线 Rollout(PPO 特有)
交给 RM 或者人工打分
模型拥有探索新输出空间的能力
代价:算力成本很高
🟢 离线偏好数据集(DPO 常用)
整个训练过程不会再生成新样本
没有在线采样环节
代价:无探索能力,依赖数据质量
PPO 训练的时候策略模型和参考模型会分开设置。参考模型固定为 SFT 模型,全程不参与梯度更新, 它唯一的作用就是用来计算 KL 惩罚,限制策略更新幅度。如果参考模型跟着一起更新, KL 约束就会失效,模型很容易发生能力退化,出现 Reward Hacking。
# ========== 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)
数据工程与超参速查
对齐训练的效果,一半靠算法,一半靠数据和超参。下面把常见配置整理成速查卡片。
偏好数据构造
同一 prompt 采样 4~16 条回答,人工排序或模型辅助标注,保证 chosen/rejected 差异明显。
KL 系数 β / kl_coeff
RLHF 常用 0.01~0.1;DPO 的 beta 常用 0.1~0.5。太大保守,太小易 Hacking。
学习率与 batch
PPO 策略模型学习率通常 1e-6 ~ 5e-6,远小于 SFT;batch 要足够大以降低梯度方差。
| 超参 | 典型范围 | 调大后果 | 调小后果 |
|---|---|---|---|
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 才能稳定推进。
GRPO 原理
GRPO 全称 Group Relative Policy Optimization,由深度求索提出, 多用于数学、代码这类推理任务的大模型强化学习对齐。
GRPO 不需要奖励模型。针对同一个 prompt,模型采样 N 个不同回答, 然后依靠组内回答的相对好坏来计算奖励,使用组内归一化替代单独 RM 打分。 这个算法可以大幅削减奖励模型带来的训练成本,非常适合答案可以自动判定对错的场景, 比如数学题、代码执行判题。
# ========== 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 需要什么
价值模型 Critic ✓
参考模型 Ref ✓
策略模型 Policy ✓
GRPO 只需要什么
参考模型 Ref ✓
策略模型 Policy ✓
省掉 Critic,显存大幅下降
- GRPO 和 PPO 最大的区别? 去掉了 Critic / Value Model。PPO 需要额外训一个价值网络来估计 baseline,GRPO 用「同一 prompt 下多个样本的组内均值」当 baseline,省掉了与策略模型同量级的价值网络,显存和算力大幅下降。
- 组内归一化为什么合理? 同一 prompt 的多个回答共享同一个条件,组内相对好坏就是最自然的优势估计,且天然消掉了 prompt 难度带来的方差。
- GRPO 的适用边界? 必须存在可靠的自动判分信号(数学答案比对、代码单测)。开放式对话类任务没有规则验证器,仍需 RM 或人工。
- 为什么 GRPO 特别适合推理任务? 推理任务奖励通常是稀疏的 0/1,组内归一化把稀疏奖励转成了有区分度的相对优势,梯度信号更稳定。
- 组内方差为 0 会怎样? 如果 N 个回答全对或全错,归一化后优势全为 0,该 prompt 不产生梯度。工程上通常跳过这类样本或做过滤。
RL 对齐评估体系
RL 对齐的评估分为自动指标与人工指标两大类。
自动 模型基础能力评测集
- MATH — 数学推理
- GSM8K — 小学数学应用题
- AIME — 竞赛级数学
- HumanEval — 代码生成
用来检验模型推理、代码能力有没有在对齐过程中下降。
自动 自动偏好评估
- 奖励模型打分 — 用 RM 给输出打分
- MT-Bench — 多轮对话评测
快速、低成本,但容易被同一个 RM 的偏好盲区误导。
人工 人工成对偏好对比
- 人工打分、A/B 成对比较,用来检验模型真实人类偏好对齐效果
- 成本高、周期长,但它是唯一能真正发现 Reward Hacking 的手段
训练过程需要持续监控的辅助指标
用来提前发现 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 指什么? 对齐训练后模型在通用能力(推理、知识问答)上出现的性能回退。它是偏好优化与能力保持之间的权衡结果。
PPO 训练不收敛排查思路
当 PPO 训练不收敛时,可以按照从数据到超参,再到模型、采样、训练配置的顺序逐层排查。
数据层面
检查偏好样本质量,重点看 chosen 和 rejected 样本之间区分度是否充足。如果两者差别很小,奖励信号没有有效梯度,模型很难学习。
超参层面
检查 clip 系数、学习率、KL 惩罚权重。KL 权重设置太高,模型会被约束住,学不到新偏好;KL 权重太低,约束失效,容易出现 Reward Hacking。
奖励模型层面
检查 RM 是否过拟合,RM 打分是否具备良好区分度。如果奖励模型本身就训坏了,奖励信号是噪声,PPO 自然无法收敛。
采样策略
rollout 阶段的采样温度、top-p 设置。采样探索不足,模型很难跳出原有分布;探索过度,生成样本质量差,引入大量噪声奖励。
训练稳定性配置
batch 大小、梯度裁剪等设置。梯度爆炸、batch 过小都会带来训练震荡。
- 排查顺序为什么这么定? 遵循「信号源 → 约束 → 估计器 → 探索 → 优化器」的因果链:如果奖励信号本身是噪声,后面所有调参都是徒劳。
- reward 涨但人工评测变差,说明什么? 典型的 Reward Hacking。优先怀疑 RM 过拟合或 KL 权重过低。
- reward 完全不涨,先看哪两个数? ① RM 在验证集上的排序准确率;② rollout 样本的 reward 方差(方差≈0 说明信号无区分度)。
- loss 出现 NaN 通常是什么原因? 学习率过大、梯度爆炸、KL 项数值溢出或 log 概率计算精度不足。优先检查梯度裁剪与 log_softmax 的实现。
进阶算法一览
除了 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 变体。
高频面试 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。
术语速查表
把全篇出现的关键术语集中列在这里,方便面试前快速扫一遍。
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。
附录:算法对比速查表
| 维度 | 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 = 把奖励来源换成程序验证器。