概述
本文档对大语言模型(LLM)后训练中使用的三种核心强化学习算法进行系统性对比:
| 算法 | 全称 | 提出方 | 核心创新 |
|---|---|---|---|
| PPO | Proximal Policy Optimization | OpenAI, 2017 | 裁剪代理目标、GAE优势估计 |
| GRPO | Group Relative Policy Optimization | DeepSeek, 2024 | 消除价值网络、组相对优势估计 |
| DAPO | Decoupled Clip and Dynamic sAmpling Policy Optimization | ByteDance, 2025 | 大规模RL训练的四大关键技术 |
1. 数学形式化
1.1 PPO(近端策略优化)
PPO引入裁剪代理目标函数以实现稳定的策略更新。
核心目标函数(裁剪代理)
其中:
- 是重要性采样比
- 是使用GAE估计的优势函数
- 通常取0.2
裁剪函数
LLM场景下的PPO
KL正则化奖励
在LLM训练中,通常在奖励信号中加入每token的KL惩罚以防止策略偏移:
1.2 GRPO(组相对策略优化)
GRPO通过组相对优势估计消除了价值网络(critic model)。
核心思想
GRPO不为每个提示训练一个价值函数,而是对同一prompt采样多个响应,利用它们的奖励计算基线。
组相对优势
对于同一prompt的一组 个输出 :
GRPO目标函数
PPO与GRPO的关键差异
| 方面 | PPO | GRPO |
|---|---|---|
| 基线 | 学习得到的价值函数 | 组统计量 |
| 内存 | 4个模型(策略、价值、参考、奖励) | 3个模型(无价值网络) |
| 优势 | 每token GAE | 组相对,同一响应的所有token相同 |
| KL惩罚 | 在奖励信号中 | 在损失函数中 |
1.3 DAPO(解耦裁剪与动态采样策略优化)
DAPO在GRPO基础上增加了四项关键技术用于大规模LLM强化学习训练。
DAPO的四项关键技术
1. Clip-Higher(非对称裁剪)
解决熵崩溃问题,解耦上下裁剪边界:
- :保持较小以防止崩溃
- :提高以允许探索
2. Dynamic Sampling(动态采样)
过滤掉总是正确或总是错误的prompt:
3. Token-Level损失
从样本级变为token级损失聚合:
| GRPO | DAPO |
|---|---|
| $\frac{1}{G}\sum_{i=1}^{G}\frac{1}{ | o_i |
| 每样本等权重 | 每token等权重 |
4. Overlong Reward Shaping
对截断响应添加软惩罚:
5. 无KL惩罚
DAPO完全移除了KL散度项。
2. 关键差异对比
2.1 架构对比
| 特性 | PPO | GRPO | DAPO |
|---|---|---|---|
| 价值网络 | 必须 | 不需要 | 不需要 |
| 价值函数 | 通过GAE学习 | 组均值/标准差 | 组均值/标准差 |
| 裁剪 | 对称 | 对称 | 非对称 |
| KL惩罚 | 在奖励信号中 | 在损失函数中 | 移除 |
| 内存需求 | 最高(4模型) | 较低(3模型) | 较低(3模型) |
| 熵控制 | 熵奖励项 | KL惩罚 | Clip-Higher |
2.2 训练动态
| 方面 | PPO | GRPO | DAPO |
|---|---|---|---|
| 稳定性 | 最高 | 中等 | 高 |
| 样本效率 | 良好 | 良好 | 更好(动态采样) |
| 探索 | 熵奖励 | KL惩罚 | Clip-Higher |
| 训练速度 | 最慢 | 快 | 快 |
| 超参数敏感性 | 高 | 中等 | 中等 |
3. 实验结果与基准测试
3.1 AIME 2024结果
| 模型 | 算法 | 分数 |
|---|---|---|
| DeepSeek-R1-Zero-Qwen-32B | GRPO | 47% |
| Qwen2.5-32B + DAPO | DAPO | 50% |
| Qwen2.5-32B Base | - | ~15.6% |
3.2 arXiv:2512.07611研究发现
- RL在所有基准上提升基线模型
- 组大小重要:增加组大小带来更稳定的训练和更高的准确率
- KL惩罚是非单调的:最优值因任务而异
- 动态采样并非总是有帮助:最佳结果在禁用DS时取得
4. 算法选择指南
4.1 使用PPO当:
✅ 需要最大稳定性和经过验证的性能
✅ 计算资源不是限制因素(需要4个模型副本)
✅ 从事通用RLHF任务(非专门推理)
✅ 生产系统需要经过实战检验的算法
4.2 使用GRPO当:
✅ 训练推理模型(DeepSeek R1/Zero方法)
✅ 内存受限(不需要价值网络)
✅ 偏好更简单的实现
✅ 推理/STEM任务有可验证奖励
4.3 使用DAPO当:
✅ 大规模RL训练(500亿+参数模型)
✅ 处理熵崩溃问题
✅ 长链思维场景
✅ 需要最先进的推理性能
5. 算法选择决策树
┌─────────────────────────────────────┐
│ LLM后训练目标 │
└─────────────────┬───────────────────┘
│
▼
┌─────────────────┐
│ 推理/STEM任务? │
└────────┬────────┘
│
┌────────┴────────┐
▼ ▼
是 否
│ │
▼ ▼
┌─────────┐ ┌──────────┐
│内存受限?│ │通用RLHF?│
└────┬────┘ └────┬─────┘
│ │
┌────┴────┐ ┌────┴────┐
│ │ │ │
是 否 是 否
│ │ │ │
▼ ▼ ▼ ▼
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│DAPO │ │DAPO │ │ PPO │ │ GRPO│
└─────┘ └─────┘ └─────┘ └─────┘
(首选) (DS (稳定, (简单,
有帮助) 验证过) 通用)
6. 代码实现
6.1 GRPO伪代码
// GRPO核心实现
double grpo_loss(Policy& policy, Policy& ref_policy,
const vector<Prompt>& prompts, int group_size) {
double total_loss = 0.0;
for (const auto& prompt : prompts) {
// 采样一组响应
vector<Response> responses;
for (int i = 0; i < group_size; i++) {
responses.push_back(policy.sample(prompt));
}
// 计算奖励
vector<double> rewards;
for (const auto& response : responses) {
rewards.push_back(reward_model.evaluate(prompt, response));
}
// 组相对优势
double mean_reward = mean(rewards);
double std_reward = stddev(rewards) + 1e-8;
vector<double> advantages;
for (double r : rewards) {
advantages.push_back((r - mean_reward) / std_reward);
}
// 策略损失
for (int i = 0; i < group_size; i++) {
double ratio = policy.log_prob(responses[i]) /
old_policy.log_prob(responses[i]);
double clipped = clamp(ratio, 1 - epsilon, 1 + epsilon);
double loss = -min(ratio * advantages[i],
clipped * advantages[i]);
total_loss += loss;
}
// KL惩罚(在损失函数中,不在奖励中)
total_loss += beta * kl_divergence(policy, ref_policy);
}
return total_loss / prompts.size();
}6.2 DAPO关键实现细节
// DAPO核心实现
double dapo_loss(Policy& policy, const vector<Prompt>& prompts,
int group_size) {
double total_loss = 0.0;
int total_tokens = 0;
for (const auto& prompt : prompts) {
// 动态采样过滤
if (!dynamic_sampling_filter(prompt, group_size)) continue;
vector<Response> responses;
vector<double> rewards;
for (int i = 0; i < group_size; i++) {
auto response = policy.sample(prompt);
responses.push_back(response);
rewards.push_back(reward_model.evaluate(prompt, response));
}
// 组相对优势
vector<double> advantages = normalize_advantages(rewards);
for (int i = 0; i < group_size; i++) {
const auto& response = responses[i];
double advantage = advantages[i];
for (const auto& token : response.tokens) {
double ratio = policy.log_prob(token) /
old_policy.log_prob(token);
// 非对称裁剪:epsilon_low < epsilon_high
double clipped = clamp(ratio,
1 - epsilon_low, // 通常0.1或0.2
1 + epsilon_high); // 通常0.3或更高
double token_loss = -min(ratio * advantage,
clipped * advantage);
total_loss += token_loss;
total_tokens++;
}
}
}
// 按总token数归一化,不是按组大小
return total_loss / total_tokens;
}
bool dynamic_sampling_filter(const Prompt& prompt, int group_size) {
auto responses = policy.sample_group(prompt, group_size);
int correct_count = 0;
for (const auto& r : responses) {
if (reward_model.is_correct(prompt, r)) correct_count++;
}
// 保留混合结果:不全对,不全错
return 0 < correct_count && correct_count < group_size;
}7. 总结对比表
| 标准 | PPO | GRPO | DAPO |
|---|---|---|---|
| 内存 | ⭐ (高) | ⭐⭐⭐ | ⭐⭐⭐ |
| 稳定性 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 速度 | ⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| 推理性能 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 实现复杂度 | 高 | 中等 | 中等 |
| 生产就绪 | ✅ 是 | ✅ 是 | ✅ 是 |
| 工业验证 | ✅✅✅ | ✅✅ | ✅ (新兴) |