从 PPO 到 GRPO:丢掉 critic 换来了什么
PPO 的四个模型对单卡是灾难。GRPO 用「组内比较」替掉 critic,这正是 5090 能玩 RL 的原因。
学完这节你能做到
- 说出 PPO 显存里的四个模型各干什么,以及 GRPO 去掉了哪个
- 手算一组 reward 的 GRPO 优势值
- 解释 clip 和 KL 两个约束各在防什么,以及去掉它们会怎样
PPO 的四件套
标准 RLHF 里跑 PPO,显存里同时住着四个模型:
| 模型 | 干什么 | 要训吗 | 显存代价 |
|---|---|---|---|
| actor 策略 | 就是你要训的模型 | ✅ | 权重 + 梯度 + 优化器 |
| critic 价值网络 | 预测「这条回答大概能拿多少分」 | ✅ | 权重 + 梯度 + 优化器 |
| reference 参考 | 训练前的模型副本,算 KL 用 | ❌ | 只有权重 |
| reward model | 给回答打分 | ❌ | 只有权重 |
RLVR 场景下 reward model 可以去掉(用规则判分)。剩下三个里,critic 是最贵的那个:它通常和 actor 同尺寸,而且要训,所以带全套梯度和优化器状态。
按 0.6B 全参算:
actor 权重 1.1 + 梯度 1.1 + 优化器 6.7 = 8.9 GiB
critic 权重 1.1 + 梯度 1.1 + 优化器 6.7 = 8.9 GiB ← 就为了估一个基线
reference 权重 1.1 = 1.1 GiB
critic 几乎让训练侧的显存翻倍。 在 32GB 单卡上,这一份开销直接决定了你能不能跑起来。
critic 是在解决什么问题
要理解 GRPO 为什么能砍掉它,先搞清它存在的理由。
上一节我们得到了朴素的策略梯度:
梯度 ≈ Σ reward × ∇log P(这条回答)
问题:reward 的绝对值没有参照意义。
假设一批 8 条回答的 reward 是 [10, 10, 10, 10, 10, 10, 10, 11]。按上面这个式子,所有 8 条都会被大幅提高概率(因为 reward 都是正数),只是最后一条稍微多一点。
但实际上你想要的是:只提高最后那一条,压低前面七条。 差别不在绝对分数,而在「比同伴好还是差」。
所以要减掉一个基线:
梯度 ≈ Σ (reward − baseline) × ∇log P(这条回答)
└────────┬───────┘
advantage 优势
减掉基线不改变梯度的期望方向(数学上叫「无偏」),但能大幅降低方差。这是 RL 里最重要的一个技巧。
问题变成了:baseline 从哪来?
- PPO 的答案:学一个模型来预测它。这就是 critic。准确,但贵。
- GRPO 的答案:同一个 prompt 多采几条,用这几条的平均分当基线。不用多花一分显存。
GRPO 的核心一招
对每个 prompt,采 G 条回答(比如 G = 8)
r₁ r₂ r₃ … r_G ← 每条的 reward
↓
mean = 这 G 条的均值 ← 这就是基线
std = 这 G 条的标准差
↓
Aᵢ = (rᵢ − mean) / (std + ε)
就这样。基线是同组同伴的平均分。
这个式子有两个漂亮的性质:
- 零均值:所有优势加起来恒等于 0。也就是说,每一批更新里「被提升的概率」和「被压低的概率」自动平衡,不会整体漂移。
- 尺度无关:除以 std 之后,无论你的 reward 是 0/1 还是 0/100,优势的量级都差不多。换 reward 函数时不用重调学习率——这个便利被严重低估了。
GRPO = Group Relative Policy Optimization。「组内相对」正是这个意思:一条回答的好坏不由绝对分数决定,而由它在同组里的相对位置决定。
出处是 DeepSeekMath 那篇论文,后来被 R1 用出来才广为人知。
亲手拨一遍
下面的演示直接实现了上面那个公式(和你在 L2 会写的代码是同一套逻辑)。建议按顺序试这几组:
- 「有对有错」:最理想的情况。注意优势有正有负,且总和为 0。
- 「只有一条对」:那一条会拿到很大的正优势,其余七条都是小的负优势。这就是稀疏成功的处理方式。
- 「全对」和「全错」:std = 0,整组优势归零。这一批采样完全白费——记住这个现象,它是 GRPO 最主要的效率损失。
- 「带格式分」:加了连续的格式分之后,即使正确性全错,分数也不再相同,std 不为 0,这一批就还有信息量。
- 拨最下面的 ratio 滑块,看 clip 什么时候开始生效。
最理想的一组:优势有正有负,梯度方向明确
- 未截断项 ratio × A
- 1.291
- 截断后 clip(ratio) = 1.00
- 1.291
- 取 min 之后的目标
- 1.291
两道护栏:clip 和 KL
有了优势就能算梯度了,但直接更新会出两种事故。GRPO(继承自 PPO)用两道护栏挡住:
clip:防止单步跑太远
采样是用旧参数做的,更新是改新参数。改一点点还好,改多了旧样本就不能代表新模型了——这叫 off-policy 偏差。
用概率比来度量「改了多少」:
ratio = P_new(这条回答) / P_old(这条回答)
= exp( logp_new − logp_old )
- 刚开始更新时
ratio = 1(参数还没变) - 同一批数据反复更新几次后,ratio 会偏离 1
clip 的做法是给它加个信任区间:
目标 = min( ratio × A , clamp(ratio, 1−ε, 1+ε) × A )
min 这一下是关键。效果是不对称的:
| 情形 | 行为 |
|---|---|
| A > 0(好回答)且 ratio 已经很大 | 被截住 → 「这条已经提够了,别再提」 |
| A < 0(坏回答)且 ratio 已经很小 | 被截住 → 「这条已经压够了,别再压」 |
| ratio 在区间内 | 正常传梯度 |
一旦被截断,这个 token 在这一步的梯度就是 0。训练日志里的 clip_ratio 就是统计有多少比例的 token 被截了。
健康的训练里通常在几个百分点。
太高(比如超过 20%)说明单步走得太猛 —— 降学习率,或者减少「同一批数据重复更新的次数」。 一直是 0 也未必是好事:说明更新极其保守,可能学习率太小。
KL:防止把说人话的能力训丢
clip 管的是「单步别跳太远」,但一万步小碎步照样能走到天边。模型可能为了刷分变成一个只会输出特定模式的怪物。
所以再加一项:和训练开始前那个模型(参考模型)的 KL 距离作为惩罚。
loss = −(策略目标) + β × KL(当前模型 ‖ 参考模型)
β 就是各框架里的 kl_loss_coef / beta。
实践中怎么设:
| β | 效果 | 什么时候用 |
|---|---|---|
| 0 | 完全不约束 | 可验证奖励 + 短训练。很多现代配方就是 0 |
| 0.001 ~ 0.01 | 轻度约束 | 默认起点 |
| > 0.05 | 强约束 | 模型明显跑飞时救火 |
slime 的入门配方里就有这样一组参数:--use-kl-loss 打开,但 --kl-loss-coef 0.00。
看着矛盾,其实很聪明:KL 不进损失函数,但作为监控指标记下来。 这样你能看到模型漂了多远,又不让它约束训练。
代价是:省掉 KL 项的同时如果也省掉参考模型,就少了一份权重的显存(0.6B 上约 1.1 GiB)。但那样你就看不到 KL 曲线了 —— L2 会讨论这个取舍。
GRPO 的代价
天下没有免费的午餐。砍掉 critic 换来了两个代价:
一、采样量变大
PPO 里一个 prompt 采 1 条就能训(critic 提供基线)。GRPO 必须采 G 条。
G = 8 意味着采样成本是 8 倍。而采样本来就占墙钟时间的 60%~80%——GRPO 省的是显存,花的是时间。
单卡场景下这笔交易是划算的:显存是硬墙(不够就是跑不了),时间只是等得久。
二、整组归零
上面演示里试过的那个现象:一组全对或全错时 std = 0,整组优势归零,这次 rollout 白跑。
这在实践中很常见:
- 训练后期简单题全对 → 白跑
- 题目太难全错 → 白跑
业界的对策:
| 办法 | 做法 | 出处 |
|---|---|---|
| 动态采样 | 多采一些,过滤掉 std=0 的组,补到目标 batch | DAPO |
| 连续 reward | 加格式分等,让分数不再只有 0/1 | 通用 |
| 难度筛选 | 提前剔掉全队都做不出或都做得出的题 | 通用 |
L4 调参那一节会回到这里。
GRPO 相比 PPO 省掉的是哪个模型?
一组 8 条回答的 reward 全是 1.0(全都答对了)。这一批对训练的贡献是?
关于 clip 和 KL 这两道护栏,下面哪些说法正确?(多选)
这节课的落点
- PPO 四件套:actor、critic、reference、reward model;critic 几乎让训练显存翻倍
- 基线的作用是降方差,减掉它不改变梯度的期望方向
- GRPO 用同组均值当基线:
A = (r − mean) / (std + ε) - 两个性质:优势总和恒为 0;除以 std 后换 reward 不用重调学习率
- clip 防单步跑飞(被截 → 梯度 0),KL 防长期漂移(β 常设 0 但仍记录)
- 代价一:采样量 × G,省显存花时间
- 代价二:整组全对或全错时 std=0,这批白跑