09-大模型后训练的对齐算法:PPO、DPO与GRPO

4 阅读6分钟

开篇:为什么需要对齐?

经过SFT(监督微调)后,模型学会了遵循指令,但还不够"好"。

示例

用户:"如何学习编程?"

SFT模型回答

学习编程可以通过看书、看视频、做项目来学习。

问题:正确但太简略,缺乏具体指导。

理想回答

学习编程的系统路径:

1. 选择第一门语言(推荐Python或JavaScript)
2. 学习资源:
   - CS50(哈佛公开课)
   - 《Python编程:从入门到实践》
   - LeetCode(刷题平台)
3. 学习路径:
   - 1-2月:语法基础
   - 3-4月:数据结构和算法
   - 5-6月:实战项目
4. 关键建议:每天坚持写代码,从小项目开始

需要具体哪方面的详细指导?

SFT的问题:只学会"模仿"训练数据,不知道什么是"好答案"。

解决方案:通过人类反馈让模型学习"好坏"标准,这就是对齐(Alignment)

三种主流对齐算法:概念对比

目前有三种主流的对齐算法:PPO、DPO、GRPO

核心思路对比

类比:学习写作

想象你要学习写作文,老师有三种教学方法:

方法1:PPO(奖励指导法)

步骤1:你写多篇作文
步骤2:老师训练一个"评分机器人"(奖励模型),学会给作文打分
步骤3:你不断写作文,评分机器人打分,你根据分数高低调整写作策略

特点:有明确的"评分标准"(奖励模型)

方法2:DPO(对比优化法)

步骤1:你写两篇作文AB
步骤2:老师直接告诉你"AB好"
步骤3:你学习增大写出A的概率,减小写出B的概率

特点:跳过"评分机器人",直接学习偏好

方法3:GRPO(分组奖励法)

步骤1:你一次写N篇作文(如4篇)
步骤2:老师对这N篇排序,给出相对得分
步骤3:你学习多写高分作文,少写低分作文

特点:一次评估多个输出,更稳定

技术对比表

特性PPODPOGRPO
是否需要奖励模型✅ 需要单独训练❌ 不需要❌ 不需要
训练阶段3阶段2阶段2阶段
算法类型强化学习监督学习强化学习(简化版)
需要的模型数4个(策略、参考、奖励、value)2个(策略、参考)2个(策略、参考)
训练稳定性较差
内存需求(7B)~70GB~35GB~40GB
效果很好很好很好
代表模型ChatGPT早期、ClaudeLlama 3、MistralLlama 3.1、DeepSeek

发展时间线

2017 ──→ PPO算法提出(OpenAI)
         通用强化学习算法

2022 ──→ RLHF-PPO应用于ChatGPT
         首次大规模应用于大模型对齐

2023 ──→ DPO算法提出(Stanford)
         简化RLHF,去掉奖励模型

2024 ──→ GRPO算法流行(Meta、DeepSeek)
         结合PPO和DPO的优点

直观理解:数据流对比

PPO的流程

用户输入 → 生成回答 → 奖励模型打分 → PPO更新策略 → 改进回答质量
          ↑___________________________________________________|
          (循环:生成-打分-优化)

DPO的流程

(输入, 好回答, 差回答) → 直接优化:增大P(好),减小P(差) → 改进回答质量
                        (一步到位,不需要奖励模型)

GRPO的流程

用户输入 → 生成N个回答 → 对N个回答排序打分 → 策略梯度更新 → 改进回答质量
          ↑__________________________________________________|
          (批量生成-批量评分-优化)

优缺点速览

PPO(传统RLHF方法)

  • ✅ 理论完整,效果最稳定(经过大规模验证)
  • ✅ 可以观察奖励信号,便于调试
  • ❌ 复杂度高,需要4个模型
  • ❌ 训练不稳定,需要仔细调参
  • ❌ 内存需求大

DPO(最简化方法)

  • ✅ 最简单,只需2个模型
  • ✅ 训练稳定,类似监督学习
  • ✅ 内存需求最小
  • ❌ 缺乏明确的奖励信号,难以调试
  • ❌ 依赖高质量偏好数据

GRPO(新兴方法)

  • ✅ 不需要单独的奖励模型
  • ✅ 比PPO简单,比DPO稳定
  • ✅ 批量采样,方差更小
  • ✅ 内存需求适中
  • ⚠️ 相对较新,实践经验较少

什么时候用哪种?

场景推荐算法原因
首次尝试对齐DPO简单易上手,效果好
追求极致性能PPO理论最完整,可精细调节
资源受限DPO内存需求最小
快速迭代GRPO 或 DPO训练快,稳定
学术研究PPO理论基础扎实
工业应用GRPO 或 DPO工程友好,成本可控

现在我们逐个深入讲解这三种算法。


第一部分:PPO(Proximal Policy Optimization)

PPO的完整训练流程

PPO是最传统的RLHF方法,分为3个阶段

阶段1:SFT(监督微调)
  ↓
预训练模型 + 指令数据 → SFT模型(会遵循指令)

阶段2:训练奖励模型
  ↓
SFT模型生成多个回答 + 人类标注偏好 → 奖励模型(会给回答打分)

阶段3:PPO优化
  ↓
奖励模型指导 + PPO算法 → 对齐模型(生成高质量回答)

阶段1:SFT

这一阶段我们在第08章已经讲过,简要回顾:

目标:让模型学会基本的指令遵循

训练

minθE(x,y)DSFT[logPθ(yx)]\min_\theta \mathbb{E}_{(x,y) \sim D_{\text{SFT}}} \left[ -\log P_\theta(y|x) \right]

输出:SFT模型 πSFT\pi^{\text{SFT}}

阶段2:训练奖励模型(Reward Model)

什么是奖励模型?

奖励模型:一个评分器,给定输入和输出,打一个质量分数

r(x,y)=分数Rr(x, y) = \text{分数} \in \mathbb{R}

例子

输入输出奖励分数
"解释机器学习""机器学习是让计算机从数据中学习规律的技术..."8.5
"解释机器学习""机器学习很重要"2.1

模型架构

基于SFT模型改造:

SFT模型:
Transformer → 最后隐藏状态 → LM Head → 下一个Token概率

奖励模型:
Transformer → 最后隐藏状态 → Value Head → 单个分数
            (冻结)                   (新增,可训练)

代码

class RewardModel(nn.Module):
    def __init__(self, sft_model):
        super().__init__()
        self.transformer = sft_model.transformer  # 复用SFT
        self.value_head = nn.Linear(hidden_size, 1)  # 新增打分层

    def forward(self, input_ids):
        hidden = self.transformer(input_ids).last_hidden_state
        last_token_hidden = hidden[:, -1, :]  # 取最后一个token
        score = self.value_head(last_token_hidden)  # 输出单个分数
        return score

收集偏好数据

为什么用偏好而不是绝对打分?

  • ❌ 打分:不同人标准不一致(你的8分 ≠ 我的8分)
  • ✅ 偏好:人类更擅长比较(A比B好)

流程

步骤1:对每个输入,用SFT模型生成4个不同的回答
步骤2:人类标注者对4个回答排序(从最好到最差)
步骤3:排序转换为成对偏好

示例:
  输入:"推荐一本编程书"
  回答1"《Python编程:从入门到实践》详细且实用" ⭐⭐⭐⭐
  回答2"《学习Python》不错" ⭐⭐⭐
  回答3"有很多书,自己搜" ⭐
  回答4"我不知道" ⭐

  排序:1 > 2 > 3 > 4

  转换为偏好对:
    (回答1, 回答2): 1更好
    (回答1, 回答3): 1更好
    (回答1, 回答4): 1更好
    (回答2, 回答3): 2更好
    (回答2, 回答4): 2更好
    (回答3, 回答4): 3更好

数据格式

{
  "prompt": "推荐一本编程书",
  "chosen": "《Python编程:从入门到实践》详细且实用",
  "rejected": "有很多书,自己搜"
}

训练奖励模型

使用Bradley-Terry模型

P(ywylx)=11+e(r(x,yw)r(x,yl))P(y_w \succ y_l | x) = \frac{1}{1 + e^{-(r(x,y_w) - r(x,y_l))}}

含义:人类选择 ywy_w 的概率 = sigmoid(两者分数差)

损失函数

LRM=E[logσ(r(x,yw)r(x,yl))]\mathcal{L}_{\text{RM}} = -\mathbb{E} \left[ \log \sigma(r(x, y_w) - r(x, y_l)) \right]

目标:让好回答的分数 > 差回答的分数

训练代码

# 计算好回答的分数
score_chosen = reward_model(prompt + chosen_response)

# 计算差回答的分数
score_rejected = reward_model(prompt + rejected_response)

# 损失:希望 score_chosen > score_rejected
loss = -F.logsigmoid(score_chosen - score_rejected).mean()

loss.backward()
optimizer.step()

验证奖励模型

# 测试
r_good = reward_model("如何学编程", "详细系统的回答...")
r_bad = reward_model("如何学编程", "自己搜")

print(f"好回答分数: {r_good:.2f}")  # 7.3
print(f"差回答分数: {r_bad:.2f}")   # 1.8
print(f"差值: {r_good - r_bad:.2f}") # 5.5

阶段3:PPO强化学习优化

有了奖励模型,现在用PPO优化策略模型。

强化学习基础概念

  • 智能体(Agent):语言模型
  • 环境(Environment):用户输入 + 奖励模型
  • 动作(Action):生成下一个Token
  • 奖励(Reward):奖励模型给的分数

交互流程

1. 环境给状态(用户输入)
2. 智能体生成回答(一个token一个token生成)
3. 生成完整回答后,奖励模型打分
4. 智能体根据分数更新策略(学习生成高分回答)

PPO的目标函数

目标:最大化奖励

maxθEx,yπθ[r(x,y)]\max_\theta \mathbb{E}_{x, y \sim \pi_\theta} [ r(x, y) ]

问题:直接最大化会导致模型"钻空子"(奖励黑客)

解决:加入KL惩罚,防止偏离SFT模型太远

maxθEx,yπθ[r(x,y)βKL(πθπSFT)]\max_\theta \mathbb{E}_{x, y \sim \pi_\theta} \left[ r(x, y) - \beta \cdot \text{KL}(\pi_\theta \| \pi_{\text{SFT}}) \right]

其中:

  • r(x,y)r(x, y):奖励模型打分(鼓励高质量)
  • KL(πθπSFT)\text{KL}(\pi_\theta \| \pi_{\text{SFT}}):与SFT模型的差异(防止偏离)
  • β\beta:平衡系数(通常0.01-0.1)

直观理解

奖励项:鼓励生成高分回答("追求卓越")
KL惩罚:不要偏离原始模型太远("不忘初心"

PPO算法核心:限制更新幅度

标准策略梯度可能导致更新步长过大,PPO通过**裁剪(clip)**限制每步更新:

LPPO=E[min(rtAt,clip(rt,1ϵ,1+ϵ)At)]\mathcal{L}^{\text{PPO}} = \mathbb{E} \left[ \min \left( r_t \cdot A_t, \text{clip}(r_t, 1-\epsilon, 1+\epsilon) \cdot A_t \right) \right]

其中:

  • rt=πθ(yt)πold(yt)r_t = \frac{\pi_\theta(y_t)}{\pi_{\text{old}}(y_t)}:新旧策略的概率比
  • AtA_t:优势函数(这个动作比平均好多少)
  • ϵ\epsilon:裁剪范围(通常0.2)
  • clip(rt,0.8,1.2)\text{clip}(r_t, 0.8, 1.2):将比率限制在0.8-1.2之间

效果

  • 如果动作好(At>0A_t > 0):增大概率,但最多增大20%
  • 如果动作差(At<0A_t < 0):减小概率,但最多减小20%
  • 防止策略突变

PPO训练流程(简化)

# 初始化
policy = SFT_model.copy()  # 策略模型(训练)
ref = SFT_model.copy()     # 参考模型(冻结)
reward_model = trained_RM  # 奖励模型(冻结)

for epoch in range(num_epochs):
    # 1. 用当前策略生成回答
    prompts = sample_prompts()
    responses = policy.generate(prompts)

    # 2. 计算奖励
    rewards = reward_model(prompts, responses)

    # 3. 计算KL惩罚
    kl = compute_kl(policy, ref, prompts, responses)

    # 4. 总奖励 = 原始奖励 - KL惩罚
    total_rewards = rewards - beta * kl

    # 5. PPO更新(多个小epoch)
    for _ in range(ppo_epochs):
        # 计算优势函数
        advantages = compute_advantages(total_rewards)

        # 计算概率比率
        ratio = policy.prob(responses) / old_policy.prob(responses)

        # PPO损失
        loss = -min(ratio * advantages, clip(ratio) * advantages).mean()

        # 更新
        loss.backward()
        optimizer.step()

PPO的优缺点

优点

  • ✅ 理论完整,经过大规模验证(ChatGPT使用)
  • ✅ 可以观察奖励信号,便于调试和监控
  • ✅ 效果稳定(在足够数据和调参下)

缺点

  • ❌ 训练复杂,需要4个模型(策略、参考、奖励、value)
  • ❌ 内存需求大(7B模型需要~70GB)
  • ❌ 训练不稳定,需要仔细调参
  • ❌ 训练慢(每次迭代需要生成完整回答)

第二部分:DPO(Direct Preference Optimization)

DPO的核心思想

PPO的痛点:太复杂!需要训练奖励模型,还要用强化学习。

DPO的解决方案:能否直接从偏好数据优化,跳过奖励模型和强化学习?

答案:可以!

从复杂到简单

PPO需要

偏好数据 → 训练奖励模型 → 用RM指导 → PPO优化 → 对齐模型
        (阶段2)          (阶段3,复杂)

DPO直接

偏好数据 → 直接优化策略模型 → 对齐模型
        (一步到位!)

理解认同分(Log Probability)

在深入DPO之前,我们需要理解一个核心概念:认同分,即模型对某个输出序列的对数概率。

例子:比较"您好"和"你好"

假设用户输入:"请打个招呼"

模型可能生成两种回答:"您好""你好"

计算过程分解

计算"您好"的认同分:

  1. 拆解Token:将"您好"拆解为

  2. 计算单步概率

    • 输入"请打个招呼",模型预测第一个字是 的概率:P()=0.8P(\text{您}) = 0.8
    • 输入"请打个招呼,您",模型预测下一个字是 的概率:P()=0.9P(\text{好}) = 0.9
  3. 对数累加

    logP(您好)=log(0.8)+log(0.9)(0.22)+(0.10)=0.32\log P(\text{您好}) = \log(0.8) + \log(0.9) \approx (-0.22) + (-0.10) = -0.32

计算"你好"的认同分:

同理,假设计算得到:

logP(你好)=1.5\log P(\text{你好}) = -1.5

结论

  • "您好"的认同分:0.32-0.32(分值越大越好)
  • "你好"的认同分:1.5-1.5(分值更小)
  • 模型认为"您好"是更可能的回答

为什么使用对数?

  • ✅ 避免连乘导致的数值下溢(概率太小)
  • ✅ 连乘变连加,计算更稳定
  • ✅ 便于计算梯度和优化

这个认同分就是DPO算法中的 logπθ(yx)\log \pi_\theta(y|x),表示模型对输出 yy 的"认同程度"。


DPO的数学推导(简化版)

步骤1:RLHF的最优策略

理论上,RLHF的最优策略有闭式解:

π(yx)=1Z(x)πref(yx)exp(1βr(x,y))\pi^*(y|x) = \frac{1}{Z(x)} \pi_{\text{ref}}(y|x) \exp\left(\frac{1}{\beta} r(x,y)\right)

步骤2:反解奖励

r(x,y)=βlogπ(yx)πref(yx)+βlogZ(x)r(x,y) = \beta \log \frac{\pi^*(y|x)}{\pi_{\text{ref}}(y|x)} + \beta \log Z(x)

步骤3:代入偏好模型

偏好概率:

P(ywyl)=σ(r(x,yw)r(x,yl))P(y_w \succ y_l) = \sigma(r(x,y_w) - r(x,y_l))

代入奖励:

P(ywyl)=σ(βlogπθ(ywx)πref(ywx)βlogπθ(ylx)πref(ylx))P(y_w \succ y_l) = \sigma\left( \beta \log \frac{\pi_\theta(y_w|x)}{\pi_{\text{ref}}(y_w|x)} - \beta \log \frac{\pi_\theta(y_l|x)}{\pi_{\text{ref}}(y_l|x)} \right)

注意:logZ(x)\log Z(x) 在差值中抵消了!

DPO的损失函数

LDPO=E[logσ(β(logπθ(ywx)πref(ywx)logπθ(ylx)πref(ylx)))]\mathcal{L}_{\text{DPO}} = -\mathbb{E} \left[ \log \sigma\left( \beta \left( \log \frac{\pi_\theta(y_w|x)}{\pi_{\text{ref}}(y_w|x)} - \log \frac{\pi_\theta(y_l|x)}{\pi_{\text{ref}}(y_l|x)} \right) \right) \right]

简化理解

目标:增大 π(好回答) / π_ref(好回答)
      减小 π(差回答) / π_ref(差回答)

效果:模型学会生成好回答,避免差回答

DPO训练代码

import torch.nn.functional as F

class DPOTrainer:
    def __init__(self, model, ref_model, beta=0.1):
        self.model = model       # 策略模型(训练)
        self.ref_model = ref_model  # 参考模型(冻结)
        self.beta = beta

    def compute_loss(self, batch):
        prompt, chosen, rejected = batch

        # 1. 计算策略模型的log概率
        logp_chosen = self.model.log_prob(prompt, chosen)
        logp_rejected = self.model.log_prob(prompt, rejected)

        # 2. 计算参考模型的log概率(不计算梯度)
        with torch.no_grad():
            ref_logp_chosen = self.ref_model.log_prob(prompt, chosen)
            ref_logp_rejected = self.ref_model.log_prob(prompt, rejected)

        # 3. 计算log比率差
        logit_chosen = logp_chosen - ref_logp_chosen
        logit_rejected = logp_rejected - ref_logp_rejected
        logits = logit_chosen - logit_rejected

        # 4. DPO损失
        loss = -F.logsigmoid(self.beta * logits).mean()

        return loss

# 使用
model = load_model("sft-model")
ref_model = load_model("sft-model")  # 冻结
ref_model.eval()

trainer = DPOTrainer(model, ref_model, beta=0.1)

for batch in dataloader:
    loss = trainer.compute_loss(batch)
    loss.backward()
    optimizer.step()
    optimizer.zero_grad()

DPO的优缺点

优点

  • 简单:不需要奖励模型,不需要强化学习
  • 稳定:训练过程类似监督学习
  • 高效:只需2个模型,内存需求减半
  • 效果好:多项研究显示与PPO相当

缺点

  • ⚠️ 缺乏奖励信号:无法直接观察"分数",难以调试
  • ⚠️ 依赖数据质量:对偏好标注质量要求高
  • ⚠️ 理论假设强:基于最优策略的假设

第三部分:GRPO(Group Relative Policy Optimization)

GRPO的诞生背景

DPO的问题

虽然DPO很简单,但它只能处理成对比较(A vs B)。如果我们有多个回答(A, B, C, D),只能分解成多个对:

排序:A > B > C > D

DPO需要:
  (A vs B), (A vs C), (A vs D)
  (B vs C), (B vs D)
  (C vs D)

这样会丢失信息:没有利用"A, B, C, D同时生成"这个事实。

PPO的问题

PPO每次只生成1个回答,方差大,需要很多样本才能稳定。

GRPO的解决方案

结合两者优点——一次生成多个回答,利用它们之间的相对关系进行优化

GRPO的核心思想

关键创新:在同一个输入下,同时采样多个输出,根据它们的相对质量进行优化。

流程

输入:"解释量子计算"
  ↓
同时生成4个回答:
  y1: 详细专业的回答
  y2: 简单但正确的回答
  y3: 太简略的回答
  y4: 不太准确的回答
  ↓
评估(可以用奖励模型,也可以用规则):
  r1 = 8.5
  r2 = 6.0
  r3 = 3.0
  r4 = 2.5
  ↓
计算每个回答相对于组平均的优势:
  A1 = 8.5 - 5.0 = +3.5  (好,增大概率)
  A2 = 6.0 - 5.0 = +1.0  (还行,略微增大)
  A3 = 3.0 - 5.0 = -2.0  (差,减小概率)
  A4 = 2.5 - 5.0 = -2.5  (更差,减小概率)
  ↓
策略梯度更新

GRPO的数学形式

目标函数

LGRPO=Ex[Ey1,,yKπθ(x)[i=1KAilogπθ(yix)]]\mathcal{L}_{\text{GRPO}} = -\mathbb{E}_{x} \left[ \mathbb{E}_{y_1, \ldots, y_K \sim \pi_\theta(\cdot|x)} \left[ \sum_{i=1}^{K} A_i \log \pi_\theta(y_i|x) \right] \right]

其中:

  • KK:组大小(通常4-8)
  • y1,,yKy_1, \ldots, y_K:同时采样的K个回答
  • AiA_i:第i个回答的优势(相对于组平均)

优势函数计算

方法1:基于奖励模型

ri=RewardModel(x,yi)rˉ=1Ki=1KriAi=rirˉ\begin{aligned} r_i &= \text{RewardModel}(x, y_i) \\ \bar{r} &= \frac{1}{K} \sum_{i=1}^{K} r_i \\ A_i &= r_i - \bar{r} \end{aligned}

方法2:基于排序

如果只有排序信息(1 > 2 > 3 > 4),可以分配分数:

Ai=KrankiKA_i = \frac{K - \text{rank}_i}{K}

例如:

  • 第1名:(41)/4=0.75(4-1)/4 = 0.75
  • 第2名:(42)/4=0.50(4-2)/4 = 0.50
  • 第3名:(43)/4=0.25(4-3)/4 = 0.25
  • 第4名:(44)/4=0.00(4-4)/4 = 0.00

加入KL惩罚

完整版本:

LGRPO=E[i=1KAilogπθ(yix)]+βKL(πθπref)\mathcal{L}_{\text{GRPO}} = -\mathbb{E} \left[ \sum_{i=1}^{K} A_i \log \pi_\theta(y_i|x) \right] + \beta \cdot \text{KL}(\pi_\theta \| \pi_{\text{ref}})

GRPO训练代码

class GRPOTrainer:
    def __init__(self, model, ref_model, reward_model,
                 group_size=4, beta=0.01):
        self.model = model
        self.ref_model = ref_model
        self.reward_model = reward_model
        self.group_size = group_size
        self.beta = beta

    def compute_loss(self, prompts):
        losses = []

        for prompt in prompts:
            # 1. 采样K个回答
            responses = []
            log_probs = []

            for _ in range(self.group_size):
                response, log_prob = self.model.generate_with_logprob(
                    prompt,
                    temperature=0.7
                )
                responses.append(response)
                log_probs.append(log_prob)

            # 2. 计算每个回答的奖励
            rewards = []
            for response in responses:
                r = self.reward_model(prompt, response)
                rewards.append(r)

            # 3. 计算优势(相对于组平均)
            rewards = torch.tensor(rewards)
            mean_reward = rewards.mean()
            advantages = rewards - mean_reward  # A_i = r_i - mean(r)

            # 4. 计算KL惩罚
            kl_penalties = []
            for response in responses:
                with torch.no_grad():
                    ref_log_prob = self.ref_model.log_prob(prompt, response)
                curr_log_prob = self.model.log_prob(prompt, response)
                kl = curr_log_prob - ref_log_prob
                kl_penalties.append(kl)

            kl_penalties = torch.tensor(kl_penalties)

            # 5. GRPO损失
            policy_loss = -(advantages * torch.tensor(log_probs)).sum()
            kl_loss = self.beta * kl_penalties.sum()
            loss = policy_loss + kl_loss

            losses.append(loss)

        return torch.stack(losses).mean()

# 使用
model = load_model("sft-model")
ref_model = load_model("sft-model")
reward_model = load_reward_model()

trainer = GRPOTrainer(
    model=model,
    ref_model=ref_model,
    reward_model=reward_model,
    group_size=4,
    beta=0.01
)

for batch_prompts in dataloader:
    loss = trainer.compute_loss(batch_prompts)
    loss.backward()
    optimizer.step()
    optimizer.zero_grad()

GRPO的优势

1. 降低方差

PPO每次只生成1个样本:

  • 如果这个样本恰好质量很高/很低,会误导优化
  • 需要大量样本才能稳定

GRPO每次生成K个样本:

  • 通过组内比较,消除绝对奖励的偏差
  • 只关注相对质量,更稳定

2. 更好的探索

同时生成多个回答,自然增加多样性:

  • 发现多种可能的好答案
  • 避免模式崩溃(总是生成相似的回答)

3. 灵活性

可以不需要明确的奖励模型:

  • 只需要能排序即可(人类排序、规则排序等)
  • 也可以使用奖励模型(效果更好)

GRPO vs PPO vs DPO

特性PPODPOGRPO
需要奖励模型必须不需要可选
每次采样数1个2个(预先标注)K个(通常4-8)
方差
训练稳定性较差很好
内存需求最大最小中等
适用数据任意需要偏好对任意

GRPO的实际应用

Meta Llama 3.1(2024年7月):

  • 使用GRPO进行后训练
  • group_size = 4-8
  • 在多个benchmark上超越GPT-4

DeepSeek V2(2024年):

  • 使用改进的GRPO变体
  • 结合奖励模型和人类排序
  • 达到开源模型的最佳效果

效果验证

任务:帮助性评估(Helpfulness)

SFT模型:基准
PPO:+12% 胜率
DPO:+10% 胜率
GRPO:+13% 胜率(最好)

三种算法的全面对比

训练流程对比

PPO(3阶段)

1. SFT训练(1-3天)
   ↓
2. 训练奖励模型(1-2天)
   ↓
3. PPO优化(3-7天)

总计:5-12天

DPO(2阶段)

1. SFT训练(1-3天)
   ↓
2. DPO优化(1-3天)

总计:2-6天

GRPO(2阶段)

1. SFT训练(1-3天)
   ↓
2. GRPO优化(2-4天)

总计:3-7天

资源需求对比(7B模型)

资源PPODPOGRPO
GPU数量8-16张A1004-8张A1004-8张A100
显存/GPU80GB40GB48GB
总显存640-1280GB160-320GB192-384GB
训练时间5-12天2-6天3-7天
成本估算$50k-100k$10k-30k$15k-40k

效果对比(实际数据)

Benchmark:Helpful & Harmless

模型Helpful胜率Harmless胜率综合
SFT基线50%50%50%
PPO62%68%65%
DPO60%66%63%
GRPO63%67%65%

观察:三种方法效果接近,GRPO略有优势。

实际选择建议

场景1:首次尝试对齐

  • 推荐:DPO
  • 原因:最简单,上手快,效果好

场景2:追求最佳效果

  • 推荐:GRPO或PPO
  • 原因:效果略优,更灵活

场景3:计算资源受限

  • 推荐:DPO
  • 原因:资源需求最小

场景4:需要可解释性

  • 推荐:PPO
  • 原因:有明确的奖励信号,便于分析

场景5:工业快速迭代

  • 推荐:GRPO
  • 原因:平衡了效果和工程复杂度

实践建议

1. 数据准备

偏好数据质量最重要

✅ 好的偏好对:
  好回答:详细、准确、有用
  差回答:明显更差(简略、错误、无关)

❌ 差的偏好对:
  两个回答质量接近
  标注不一致
  差回答其实也不错

数据量建议

算法最少推荐理想
PPO10K50K100K+
DPO10K30K50K+
GRPO5K20K50K+

2. 超参数调优

DPO

beta = 0.1              # 起点(范围:0.05-0.5)
learning_rate = 5e-7    # 较小的学习率
num_epochs = 1-3        # 不要过拟合

GRPO

group_size = 4          # 起点(范围:4-8)
beta = 0.01             # KL惩罚(范围:0.01-0.1)
learning_rate = 1e-6    # 更小的学习率

PPO

learning_rate = 1e-6
ppo_epochs = 4
clip_range = 0.2
kl_coef = 0.02

3. 训练监控

关键指标

# 1. 奖励趋势(PPO/GRPO)
if using_reward_model:
    print(f"Mean reward: {mean_reward:.2f}")
    print(f"Reward std: {reward_std:.2f}")

# 2. KL散度(所有方法)
print(f"KL divergence: {kl_div:.4f}")
# 期望:0.01-0.1(太大说明偏离太远)

# 3. 准确率(DPO)
print(f"Preference accuracy: {accuracy:.1%}")
# 期望:>60%(随机是50%)

# 4. 生成质量(人工抽查)
sample_outputs = model.generate(test_prompts)
# 每100步抽查一次

4. 常见问题与解决

问题1:KL散度爆炸

症状:KL > 1.0,模型输出变差
原因:学习率太大或beta太小
解决:
  - 降低学习率(减半)
  - 增大beta(×2)

问题2:奖励饱和(PPO/GRPO)

症状:奖励不再增长,但生成质量没提升
原因:过度优化奖励模型的缺陷
解决:
  - 增大KL惩罚
  - 早停
  - 更新奖励模型

问题3:模式崩溃

症状:模型总是生成相似的回答
原因:过度优化单一模式
解决:
  - 增加采样温度
  - 使用top-p采样
  - 增大beta(鼓励多样性)

小结

核心要点

  1. 三种算法对比

    • PPO:传统方法,需要奖励模型,复杂但理论完整
    • DPO:最简化,直接优化偏好,工程友好
    • GRPO:新兴方法,批量采样,平衡效果与复杂度
  2. 核心公式

    PPOmaxE[r(y)]βKL(ππref)\max \mathbb{E}[r(y)] - \beta \cdot \text{KL}(\pi \| \pi_{\text{ref}})

    DPOminE[logσ(βlogπ(yw)πref(yw)βlogπ(yl)πref(yl))]\min -\mathbb{E}\left[\log \sigma\left(\beta \log \frac{\pi(y_w)}{\pi_{\text{ref}}(y_w)} - \beta \log \frac{\pi(y_l)}{\pi_{\text{ref}}(y_l)}\right)\right]

    GRPOminE[i=1KAilogπ(yi)]+βKL\min -\mathbb{E}\left[\sum_{i=1}^K A_i \log \pi(y_i)\right] + \beta \cdot \text{KL}

  3. 选择建议

    • 首次尝试:DPO
    • 追求效果:GRPOPPO
    • 资源受限:DPO
    • 工业应用:GRPO
  4. 关键实践

    • 数据质量 > 数据数量
    • 监控KL散度(防止偏离)
    • 人工评估不可缺少
    • 超参数需要实验调整

发展趋势

2022: PPO主导(ChatGPT时代)2023: DPO兴起(简化浪潮)2024: GRPO流行(效果与工程平衡)未来: 更多创新算法(在线学习、多目标优化...)

RLHF是让大模型从"能回答"到"回答得好"的关键,而PPO、DPO、GRPO是三种各有千秋的实现路径。理解它们的原理和权衡,才能在实践中做出正确的选择!