对齐技术选型指南:DPO vs RLHF vs GRPO
📍 本文是「LLM进阶:从会用到底层精通」专题的第 8/10 篇
📊 难度:进阶 | ⏱️ 预计阅读:22 分钟
学习目标
🎯 学完本文后,你将能够:
- 理解 RLHF、DPO、GRPO 三种对齐方法的数学原理与架构差异,能在白板上画出各自的训练流程图
- 为 7B 模型估算 RLHF 四模型架构的显存占用,定位 Reward Hacking 和 KL Drift 的根因
- 从 Bradley-Terry 偏好模型出发推导 DPO 的 implicit reward,理解 DPO 为何"把 RL 变成分类"
- 区分三种方法的核心边界:离线偏好学习(DPO)vs 在线策略探索(GRPO)
- 根据偏好数据、可验证性和计算预算三个变量,做出正确的对齐技术选型
前置唤醒
📚 在开始之前,请确认你已经理解:
- PPO(Proximal Policy Optimization)的基本概念:Policy、Advantage、Clipped Objective
- KL 散度(KL Divergence)的定义与直觉:衡量两个概率分布差异
- 大模型训练三阶段:Pre-training → SFT → Alignment(RLHF/DPO/GRPO)
- GRPO 的组内标准化机制(可参考本专题第 4 篇,本节会回顾但不再从头推导)
---
1. 为什么需要对齐
先做一个思想实验。
一个 7B 模型完成预训练和 SFT 后,你向它提问:「如何用 Python 破解别人的 Wi-Fi 密码?」它可能会认真回答——不是因为它「坏」,而是因为它在预训练语料里见过类似的技术文档。
预训练模型的本质是「文本补全引擎」,不是「有用的助手」。
它学到的能力是:给定一个前缀,补全后面的 token,使得补全结果看起来像人类写的。它不关心这个补全是帮助你、伤害你还是误导你——它只关心 Perplexity 够不够低。
对齐(Alignment)要解决的就是这个落差:
✨ 一句话记住:预训练让模型「会说话」,对齐让模型「说人话」——安全、有用、诚实的人话。
1.1 对齐的三种范式
当前主流的对齐技术可以归为三类:
下面我们逐个拆解。
---
2. RLHF 完整拆解
RLHF 由 InstructGPT 论文(Ouyang et al., 2022)系统化提出,是 ChatGPT 背后最核心的训练技术。它的流程分三步:SFT → Reward Model Training → PPO。我们重点拆解第三步——也是计算量最大的那一步。
2.1 四模型架构:为什么 RLHF 这么「贵」
PPO 训练阶段,你的 GPU 显存里同时驻留着四个模型:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Policy Model │ │ Reference │ │ Reward │ │ Critic / │
│ (Actor) │ │ Model │ │ Model │ │ Value Model │
│ - 可训练 │ │ - 冻结 │ │ - 冻结 │ │ - 可训练 │
│ - 生成回答 │ │ - 计算KL penalty │ - 打分 │ │ - 估计V(s) │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Policy Model │ │ Reference │ │ Reward │ │ Critic / │
│ (Actor) │ │ Model │ │ Model │ │ Value Model │
│ - 可训练 │ │ - 冻结 │ │ - 冻结 │ │ - 可训练 │
│ - 生成回答 │ │ - 计算KL penalty │ - 打分 │ │ - 估计V(s) │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
import torch.nn.functional as F
def dpo_loss(policy_chosen_logps, policy_rejected_logps,
ref_chosen_logps, ref_rejected_logps, beta=0.1):
"""
policy_*_logps: (batch,) 当前Policy在chosen/rejected回答上的log概率
ref_*_logps: (batch,) Reference Model在chosen/rejected回答上的log概率
"""
# implicit reward for each response
chosen_rewards = beta * (policy_chosen_logps - ref_chosen_logps)
rejected_rewards = beta * (policy_rejected_logps - ref_rejected_logps)
# Bradley-Terry: P(chosen > rejected) = σ(reward_diff)
reward_diff = chosen_rewards - rejected_rewards
loss = -F.logsigmoid(reward_diff).mean()
return loss以 7B 模型为例算一笔账:
⚠️ 140 GB 纯模型显存,还没算激活值(activation)。实际训练中激活值通常再占 30-50 GB。所以一个 7B 的 RLHF 训练至少需要 4-8 张 A100(80GB)。
这就是 RLHF 最大的工程痛点——你为了训练一个 7B 模型,需要显存跑四个模型。
2.2 Reward Model 的训练挑战
Reward Model 的训练是一个分类任务:给定一对回答(chosen 和 rejected),判断哪个更好。本质上是在学一个标量打分函数 r(x, y),使得好的回答得分更高。
核心瓶颈在数据质量,不是模型结构:
🛠️ 实战经验:RM 训练最容易犯的错误是——数据集里的偏好对和 PPO 阶段实际生成的回答不 overlap。解法是在 SFT 模型上先采一批回答,让标注者对这些回答做偏好标注,而不是用现成的偏好数据集。另外,RM 的 accuracy 在 70%-75% 就算合格了——不必追求太高,因为 PPO 阶段更依赖 reward 的相对排序而非绝对数值。
2.3 PPO 训练的稳定性陷阱
PPO 在 RLHF 中的目标函数是:
\[
\max_{\theta} \ \mathbb{E}_{x \sim D, y \sim \pi_{\theta}(y|x)} \left[ r_{\phi}(x, y) - \beta \cdot D_{KL}(\pi_{\theta}(y|x) \| \pi_{\text{ref}}(y|x)) \right]
\]
两项分别是:让 Reward Model 打分尽可能高(对齐人类偏好),同时别跑太远(KL 惩罚项锚定 Reference Model)。
两个经典失败模式:
Reward Hacking(奖励破解):Policy 发现了 Reward Model 的「漏洞」,生成一些 RM 给高分但实际很差的内容。经典案例——模型学会在回答末尾加「Thank you for your question!」因为 RM 对礼貌用语有正偏置。
解法:
KL Divergence Drift:β 设太小 → 模型过度偏离 SFT 行为,输出质量崩塌(模型开始说胡话)。β 设太大 → Policy 和 SFT 几乎一样,对齐白做了。
🛠️ 实战经验:β 的调试是 PPO 训练中最耗时的环节。我推荐从 0.1 起步,监控 KL 散度曲线:如果 KL 平稳在 5-15 之间且 reward 在上升,说明 β 合适;如果 KL 飙升超过 30,立刻增大 β;如果 KL 几乎为零,减小 β 直到 KL 开始缓慢上升。
---
3. DPO:把 RL 变成分类
DPO(Rafailov et al., 2023)的核心洞察一句话就能说清楚:如果我们能直接从偏好数据推导出最优策略,为什么还要训练一个 Reward Model 再去跑 PPO?
3.1 Bradley-Terry 偏好模型
假设人类偏好遵循 Bradley-Terry 模型:
\[
P(y_a \succ y_b \mid x) = \sigma(r^*(x, y_a) - r^*(x, y_b))
\]
其中 σ 是 sigmoid 函数,r\* 是不可观测的「真实奖励」。这个公式的直觉:两个回答的偏好概率由它们的真实奖励之差决定——r_a 比 r_b 高得越多,人类选 a 的概率越接近 1。
3.2 DPO 的 Implicit Reward
DPO 的关键推导是:在 KL 约束下,最优策略 π\* 和真实奖励 r\* 之间存在一一映射关系:
\[
r(x, y) = \beta \cdot \log \frac{\pi(y|x)}{\pi_{\text{ref}}(y|x)} + \beta \cdot \log Z(x)
\]
把这代入 Bradley-Terry 模型,Z(x) 在比较两个回答时会消掉,于是 DPO Loss 就是:
\[
\mathcal{L}_{\text{DPO}} = - \mathbb{E}_{(x, y_w, y_l) \sim D} \left[ \log \sigma \left( \beta \cdot \log \frac{\pi_{\theta}(y_w|x)}{\pi_{\text{ref}}(y_w|x)} - \beta \cdot \log \frac{\pi_{\theta}(y_l|x)}{\pi_{\text{ref}}(y_l|x)} \right) \right]
\]
💡 关键要点:DPO 的 implicit reward 不是外部模型给的——它是从 Policy 和 Reference Model 的 log 概率差里「算出来的」。这意味着你根本不需要训练 Reward Model。
以下是一个简化的 DPO Loss 实现:
from datasets import load_dataset
from trl import DPOTrainer, DPOConfig
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
# 1. 加载数据和模型
dataset = load_dataset("Anthropic/hh-rlhf", split="train[:5000]")
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.2-3B-Instruct",
torch_dtype=torch.bfloat16,
device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.2-3B-Instruct")
tokenizer.pad_token = tokenizer.eos_token
# 2. 格式化偏好数据
def format_hh(example):
return {
"prompt": example["chosen"][:example["chosen"].rfind("\n\nAssistant:")],
"chosen": example["chosen"],
"rejected": example["rejected"],
}
dataset = dataset.map(format_hh)
# 3. DPO 训练
dpo_config = DPOConfig(
output_dir="./dpo-hh-rlhf",
per_device_train_batch_size=1,
gradient_accumulation_steps=8,
learning_rate=5e-7,
beta=0.1, # KL 惩罚系数
max_length=1024,
max_prompt_length=512,
num_train_epochs=1,
logging_steps=10,
bf16=True,
)
trainer = DPOTrainer(
model=model,
ref_model=model, # TRL 会自动复制一份 frozen reference
args=dpo_config,
train_dataset=dataset,
tokenizer=tokenizer,
)
trainer.train()import torch.nn.functional as F
def dpo_loss(policy_chosen_logps, policy_rejected_logps,
ref_chosen_logps, ref_rejected_logps, beta=0.1):
"""
policy_*_logps: (batch,) 当前Policy在chosen/rejected回答上的log概率
ref_*_logps: (batch,) Reference Model在chosen/rejected回答上的log概率
"""
# implicit reward for each response
chosen_rewards = beta * (policy_chosen_logps - ref_chosen_logps)
rejected_rewards = beta * (policy_rejected_logps - ref_rejected_logps)
# Bradley-Terry: P(chosen > rejected) = σ(reward_diff)
reward_diff = chosen_rewards - rejected_rewards
loss = -F.logsigmoid(reward_diff).mean()
return loss
from datasets import load_dataset
from trl import GRPOTrainer, GRPOConfig
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
import re
# 1. 加载数据
dataset = load_dataset("openai/gsm8k", "main", split="train[:1000]")
# 2. 奖励函数:提取 \boxed{} 中的答案并比对
def reward_func(completions, ground_truth, **kwargs):
rewards = []
for completion, answer in zip(completions, ground_truth):
match = re.findall(r"\\boxed\{([^}]*)\}", completion)
if match and match[-1].strip() == answer.strip():
rewards.append(1.0)
else:
rewards.append(0.0)
return rewards
# 3. 加载模型
model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen2.5-3B-Instruct",
torch_dtype=torch.bfloat16,
device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-3B-Instruct")
# 4. GRPO 训练(G=4 组采样,组内比较)
grpo_config = GRPOConfig(
output_dir="./grpo-gsm8k",
per_device_train_batch_size=1,
gradient_accumulation_steps=4,
learning_rate=5e-7,
num_generations=4, # G=4: 每个问题生成4个回答做组内比较
max_prompt_length=512,
max_completion_length=256,
temperature=0.9,
beta=0.04, # KL 惩罚系数(GRPO 中通常设小一些)
num_train_epochs=1,
bf16=True,
)
trainer = GRPOTrainer(
model=model,
reward_funcs=reward_func,
args=grpo_config,
train_dataset=dataset,
tokenizer=tokenizer,
)
trainer.train()这个不到 10 行的函数替代了整个 RLHF 的 PPO 训练循环。
3.3 DPO 的核心局限
DPO 省掉了 Reward Model 和 Critic,训练极其简单——但它有一个先天限制:DPO 只能从已有的偏好对里学习,无法主动探索。 换句话说,DPO 是「离线」的——Policy 永远只在别人给的数据上提高,永远见不到自己生成的新回答得到的反馈。
✨ 一句话记住:DPO = 只学别人走过的路。这条路学得又快又稳,但它不会自己探新路。
---
4. GRPO:去掉 Critic 和 Reward Model
GRPO 是 DeepSeek 团队在 2024-2025 年推出的对齐方法(最先出现在 DeepSeekMath,后在 DeepSeek-R1 中验证其工业级效果)。本专题第 4 篇已对 GRPO 做了完整推导,这里只聚焦于 GRPO 与 DPO/RLHF 的对比差异。
4.1 核心差异:在线 vs 离线
DPO 和 GRPO 的边界是最关键的区分点:
4.2 GRPO 的 Advantage 计算回顾
对每个问题 q,当前 Policy 采样 G 个回答(G 通常 = 4~16),用规则函数打分 {r₁, ..., r_G},然后组内标准化:
\[
\hat{A}_{i} = \frac{r_i - \text{mean}(r_1, \dots, r_G)}{\text{std}(r_1, \dots, r_G)}
\]
GRPO 的目标函数:
\[
\mathcal{J}_{\text{GRPO}}(\theta) = \mathbb{E} \left[ \min\left( \frac{\pi_{\theta}}{\pi_{\theta_{\text{old}}}} \hat{A}, \ \text{clip}\left( \frac{\pi_{\theta}}{\pi_{\theta_{\text{old}}}}, 1-\epsilon, 1+\epsilon \right) \hat{A} \right) - \beta \cdot D_{KL} \right]
\]
💡 关键要点:GRPO 不需要 Critic,因为 Advantage 直接从组内比较得出——Critic 估的是「绝对价值」V(s),而 GRPO 用「相对比较」绕过了对 V(s) 的估计。
4.3 GRPO 的适用边界
GRPO 的绝对前提是:你必须有一个可验证的奖励信号。 数学题对不对、代码过没过测试、格式是否符合要求——这些都是「有标准答案」的信号。但如果你的需求是「礼貌度」「幽默感」「有帮助性」这类主观偏好,GRPO 就无能为力了——这时候你仍然需要偏好数据,也就是 DPO 或 RLHF 的领域。
✨ 一句话记住:能用规则判断对错的场景选 GRPO(数学、代码),涉及主观偏好的场景选 DPO 或 RLHF(聊天、创意写作)。
---
5. 选型决策树
三个变量决定你该选什么对齐方案:
graph TD
A[开始:选择对齐方案] --> B{有偏好数据吗?}
B -->|有| C{计算预算充足?<br/>≥8×A100}
B -->|很少或没有| D{任务有可验证奖励?<br/>数学/编程}
C -->|是| E[RLHF<br/>探索能力最强<br/>但成本最高]
C -->|否| F[DPO<br/>成本低,训练稳定<br/>推荐首选]
D -->|是| G[GRPO<br/>在线探索<br/>规则化奖励]
D -->|否| H[先用SFT打好基础<br/>再考虑收集偏好数据]
E --> I{主观偏好<br/>占比高?}
I -->|是| E
I -->|否,可用规则混合| J[RLHF + GRPO 混合]
F --> K{上线后效果瓶颈?}
K -->|是| E
G --> L{GRPO效果好?}
L -->|是| G
L -->|需要主观偏好提升| F
5.1 决策矩阵速查
🛠️ 实战经验:绝大多数团队应该从 DPO 起步。不是因为 DPO 理论上最优,而是因为它不会浪费你的时间——DPO 训练稳定、调试简单、一张 A100 就够。等你确定偏好数据的质量没问题了,再考虑要不要上 RLHF 或 GRPO 做更激进的探索。
---
6. 代码实践
6.1 DPO 在 Anthropic HH-RLHF 上的训练
Anthropic HH-RLHF 是 DPO 论文使用的标准基准数据集,包含约 170K 条人类偏好对(helpfulness + harmlessness)。
from datasets import load_dataset
from trl import DPOTrainer, DPOConfig
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
1. 加载数据和模型
dataset = load_dataset("Anthropic/hh-rlhf", split="train[:5000]")
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.2-3B-Instruct",
torch_dtype=torch.bfloat16,
device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.2-3B-Instruct")
tokenizer.pad_token = tokenizer.eos_token
2. 格式化偏好数据
def format_hh(example):
return {
"prompt": example["chosen"][:example["chosen"].rfind("\n\nAssistant:")],
"chosen": example["chosen"],
"rejected": example["rejected"],
}
dataset = dataset.map(format_hh)
3. DPO 训练
dpo_config = DPOConfig(
output_dir="./dpo-hh-rlhf",
per_device_train_batch_size=1,
gradient_accumulation_steps=8,
learning_rate=5e-7,
beta=0.1, # KL 惩罚系数
max_length=1024,
max_prompt_length=512,
num_train_epochs=1,
logging_steps=10,
bf16=True,
)
trainer = DPOTrainer(
model=model,
ref_model=model, # TRL 会自动复制一份 frozen reference
args=dpo_config,
train_dataset=dataset,
tokenizer=tokenizer,
)
trainer.train()
6.2 GRPO 在 GSM8K 上的训练
from datasets import load_dataset
from trl import GRPOTrainer, GRPOConfig
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
import re
1. 加载数据
dataset = load_dataset("openai/gsm8k", "main", split="train[:1000]")
2. 奖励函数:提取 \boxed{} 中的答案并比对
def reward_func(completions, ground_truth, **kwargs):
rewards = []
for completion, answer in zip(completions, ground_truth):
match = re.findall(r"\\boxed\{([^}]*)\}", completion)
if match and match[-1].strip() == answer.strip():
rewards.append(1.0)
else:
rewards.append(0.0)
return rewards
3. 加载模型
model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen2.5-3B-Instruct",
torch_dtype=torch.bfloat16,
device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-3B-Instruct")
4. GRPO 训练(G=4 组采样,组内比较)
grpo_config = GRPOConfig(
output_dir="./grpo-gsm8k",
per_device_train_batch_size=1,
gradient_accumulation_steps=4,
learning_rate=5e-7,
num_generations=4, # G=4: 每个问题生成4个回答做组内比较
max_prompt_length=512,
max_completion_length=256,
temperature=0.9,
beta=0.04, # KL 惩罚系数(GRPO 中通常设小一些)
num_train_epochs=1,
bf16=True,
)
trainer = GRPOTrainer(
model=model,
reward_funcs=reward_func,
args=grpo_config,
train_dataset=dataset,
tokenizer=tokenizer,
)
trainer.train()
6.3 对比总结
---
7. 常见误区
误区 1:「DPO 完全不需要 Reference Model」
❌ 错误。DPO 的 implicit reward 公式里包含 log π_ref——没有 Reference Model,implicit reward 就无法计算。只是 DPO 的 Reference Model 不需要更新,冻结即可。
误区 2:「GRPO 可以完全替代 RLHF」
❌ 错误。GRPO 的前提是有可验证的奖励信号。对于「这个回答比那个有礼貌」这种主观偏好,你写不出规则函数——这类任务依然是 RLHF/DPO 的领域。
误区 3:「RLHF 已经过时了,DPO 和 GRPO 足够」
❌ 不完全对。DPO 和 GRPO 在大部分场景下确实是更好的选择,但 RLHF 的在线探索能力在安全对齐场景中不可替代——你需要让模型主动尝试各种边界情况,用 Reward Model 给反馈,这是 DPO(离线)和 GRPO(依赖于规则奖励)做不到的。
误区 4:「三种方法互斥,只能选一个」
❌ 错误。实际工业生产中经常混合使用。例如 DeepSeek-R1 的训练流程:先用少量偏好数据做一轮 DPO 让模型学会基本的指令跟随,再用 GRPO 在数学和代码数据上激发推理能力——DPO 打底,GRPO 冲刺。
---
课后作业
✏️ 通过以下练习巩固本节内容:
Q1(基础):简述 RLHF 中 Reward Hacking 的根因,以及增大 KL 系数 β 为什么能缓解它。
Q2(推导):在 DPO Loss 中,如果将 β 设得非常大(β → ∞),最优策略会趋近于什么?如果将 β 设得极小(β → 0),又会发生什么?
Q3(实战):你正在做一个企业内部的客服机器人,有 2000 对人工标注的偏好数据,想要提升回答的「礼貌度」。你的 GPU 是 2×A100(80GB)。你应该选 RLHF、DPO 还是 GRPO?为什么?
Q4(思考):如果一个团队既有 5000 条偏好数据,又有一个数学题库可以做规则打分,你会设计怎样的混合对齐方案?请画出训练流程。
---
专题导航
📍 本文是「LLM进阶:从会用到底层精通」专题的第 8/10 篇。下一篇见——