VERL & GRPO
强化学习训练核心概念备忘录
专注于 GRPO、相对优势、对数概率与 KL 惩罚
📑 目录 (Table of Contents)
- GRPO 是什么?
- 为什么要组内相对归一化?(G, mu, sigma, A_i)
- 各种 Logprob 的区别 (Rollout / Old / Ref)
- KL 惩罚是如何工作的?
- 为什么响应级奖励存在信用分配问题?
- VERL/GRPO 训练数据流是怎么样的?
- 为什么 G / temperature / top-p 会影响 RL 信号?
- PPO / GRPO 的 clipping 到底在限制什么?
- 一个 RL batch 里到底装了什么?
- 哪些 token 参与 loss,哪些不参与?
- 最终 reward 是怎么拼出来的?
- RLHF / GRPO 为什么容易不稳定?
🧩 深度题库 (Q13–Q67)
- 为什么要 old policy 算 ratio?
- KL 为什么对 ref model?
- Reference model 为什么冻结?
- Reward model 容易被 exploit?
- Verifier vs preference reward
- Rule-based reward 更稳定?
- Reward normalization
- G 太小会不稳定?
- G 太大拖慢吞吐?
- GRPO 不需要 critic?
- PPO 需要 critic,GRPO 不用?
- Token-level advantage 偏差
- 长回答放大训练噪声
- Response length 监控
- Entropy 下降太快
- Temperature 太低学不动
- Top-p 太宽质量变差
- Rollout/train/microbatch 分开
- Gradient accumulation
- Mixed precision / fp8
- Stale rollout
- Async RL 吞吐 vs 稳定
- vLLM / trainer 解耦
- 权重同步频率
- Replay buffer 不能太旧
- On-policy vs off-policy
- Ratio = exp(logp差)
- Logprob 按 token,reward 按序列
- Padding mask 错了毁训练
- EOS 处理影响 reward
- Prompt truncation
- Reward hacking 先变格式
- KL 太小不一定好
- KL 太大 reward 也不差?
- Loss 下降 ≠ 策略更好
- Eval prompt 必须固定
- Pass@k vs 平均 reward
- 多轮对话 RL 更难
- Tool use 更依赖 verifier
- 数学 RL vs 聊天 RL 调参
- Sequence packing 更敏感
- Ref model 显存压力
- Rollout 记录 old_logprobs
- Actor update 次数过多
- Minibatch shuffle
- RL 学习率比 SFT 更小
- Advantage scale 梯度爆炸
- Reward 延迟 credit assignment
- 多样本比较更稳
- Verifier false negative
- Verifier false positive
- Logging / tracing 重要性
- Token / 序列 / 系统指标
- RLHF 问题 = 数据分布问题
1. GRPO 是什么?
GRPO (Group Relative Policy Optimization) 是 PPO 的一种变体,专为大语言模型 (LLMs) RLHF 训练设计。它省去了 Critic (价值网络),通过对同一个 Prompt 采样 G 个不同的响应(Group),在组内对奖励进行归一化来计算优势函数。
深入:GRPO 伪代码与 PPO 对比
PPO 需要训练一个额外的 Critic 模型来估计 V(s),内存开销很大(参数量往往和 Actor 相同,占双倍显存)。GRPO 利用语言模型生成任务的特性,同一个输入生成多个结果,通过横向对比直接得出优势。
# GRPO Core Logic
for prompt in batch:
# 1. Rollout G responses
responses = policy.generate(prompt, num_return_sequences=G)
# 2. Get rewards (e.g. from Reward Model or rule-based)
rewards = reward_fn(prompt, responses) # shape: [G]
# 3. Group Relative Advantage
adv = (rewards - rewards.mean()) / (rewards.std() + 1e-4)
# 4. PPO-style actor update
ratio = torch.exp(logprob - old_logprob)
loss = -torch.min(ratio * adv, torch.clamp(ratio, 1-eps, 1+eps) * adv) + kl_penalty
2. 为什么要组内相对归一化?(G, r_i, A_i)
绝对奖励值(如 10 或 -5)在 RL 中很难直接使用。我们真正关心的是“这个回答比当前模型的平均水平好多少?”。组内归一化提供了一个无偏的局部基准线 (Local Baseline)。
尝试修改奖励值 r_i,观察优势 A_i 的变化:
| Response | Reward (r_i) | Advantage (A_i) |
|---|---|---|
| 1 | +1.12 | |
| 2 | -0.56 | |
| 3 | -0.14 | |
| 4 | -0.56 |
深入:极端情况与方差爆炸
1. 如果所有 r_i 相同,标准差 sigma_G 为 0。为了防止除零,通常会加上 epsilon (如 1e-4)。此时所有 A_i = 0,这意味着模型不需要在这些同等质量的选择中做区分。
2. 即使奖励函数整体发生了偏移(比如从 0~1 变成了 10~11,或同时乘以 10),经过减均值除标准差的归一化后,优势 A_i 完全不变!这极大地增强了训练对抗 Reward Model 不稳定的鲁棒性。
3. 各种 Logprob 的区别 (Rollout / Old / Ref)
在 GRPO/PPO 训练中,我们会维护三个不同状态的策略模型,产生三种不同的 logprob:
- Reference Logprob (参考策略): 永远冻结的模型(如初始的 SFT 模型),用于计算 KL 惩罚,防止模型偏离人类语言分布太远。
- Old Logprob (旧策略/Rollout): 用于在环境中生成文本回答的模型。在一次 PPO 迭代(包含多次 minibatch update)的开始时固定,用于计算 PPO 的 Importance Ratio (新旧概率比率)。
- Current/New Logprob (当前策略): 正在反向传播和梯度下降的活跃模型。
(Frozen)
(Rollout Gen)
(Updating)
深入:代码中的张量计算
# VERL/vLLM Actor 中常见的张量计算 (Pseudo-code)
with torch.no_grad():
ref_logprobs = ref_model(input_ids)
old_logprobs = old_model(input_ids) # 或在 Rollout 时直接记录
# 在 Actor Training 循环中
current_logprobs = current_model(input_ids)
# PPO Ratio (exp(log a) / exp(log b) = exp(log a - log b))
ratio = torch.exp(current_logprobs - old_logprobs)
# KL 惩罚 (基于 Reference Model) 的无偏估计
log_ratio_ref = current_logprobs - ref_logprobs
kl = torch.exp(log_ratio_ref) - log_ratio_ref - 1.0
4. KL 惩罚是如何工作的?
KL 惩罚的目的是把强化学习后的语言模型限制在参考模型(初始 SFT 模型)的分布附近。如果不加限制,模型会变成“Reward Hacker”,学会输出无意义但能骗取高分的规律字符(模式崩塌)。
调节 KL 系数 β (Beta):0.10
当 β 过大时,KL 惩罚压倒环境奖励,模型倾向于什么都不学(退化回 SFT)。当 β 为 0 时,容易发生 Reward Hacking。
深入:Token 级无偏 KL 估算公式
实践中,我们无法计算整个词表的精确 KL 散度,而是使用按 Token 采样的无偏估计 (Unbiased estimators):
1. 原始估计:\( KL \approx d_t \) (可能为负,方差高 / High variance)
2. PPO3 原版近似:\( KL \approx \frac{1}{2} d_t^2 \) (强制非负,但在某些分布下方差极大)
3. 现代标准近似:\( KL \approx \exp(d_t) - d_t - 1 \) (John Schulman 推荐,非负且低方差)
5. 为什么响应级奖励存在信用分配问题?
传统 RLHF 中,奖励模型(RM)或基于规则的验证器只给整个句子一个单一的标量奖励 (Scalar Reward)。这导致了一种“连坐”现象:一句很长的推理回答中,即便 99% 的推理是完美的,只要最后算错了一步导致总奖励为负,那前面所有完美的 Token 也会被同等扣分更新!
Prompt: What is 2 + 2?
“The answer is” 本身是非常完美的生成,不应该被扣分,但在响应级奖励 (Response-level) 下,它们不幸地背了最后的“5”的锅。这被称为 Credit Assignment (信用分配) 问题。
深入:如何解决这个问题?(PRM 与 Token KL)
- PRM (Process Reward Model,过程奖励模型): 为推理过程的每一步(Step-level)单独打分,能够精准定位错误发生的节点。(如 OpenAI 的 Let's Verify Step by Step)。
- Token 级 KL 惩罚 (Token-level KL): 虽然外部奖励(如对错)是全局的,但 KL 惩罚是按 Token 计算并加到每个 Token 的奖励上的。这能在一定程度上为正确的 Token 提供“锚点”,保留细粒度信号。
- GAE (广义优势估计): 如果引入 Critic 模型,它可以评估每个状态(即当前 Token 之前的前缀)的期望价值,从而计算出每个 Token 对应的细粒度时序差分残差 (TD Residuals)。这就是为什么 PPO 虽然需要 Critic,但在信用分配上比只用最终奖励的 REINFORCE 更好。
6. VERL/GRPO 训练数据流是怎么样的?
在现代高吞吐量 RLHF 框架(如 VERL 或 OpenRLHF)中,为了最大化硬件利用率,采样与训练往往被解耦为不同的计算集群 (Workers)。
深入:VLLM 与 Megatron 的分离架构设计
生成长文本序列的自回归过程是典型的 Memory-Bound (显存带宽瓶颈),需要 vLLM 这种具有 PagedAttention 机制的高效推理引擎。而计算梯度是 Compute-Bound (算力瓶颈),适合 3D 并行的 Megatron-LM 或 FSDP。通过解耦,两种任务都能跑在最适合的引擎上。
唯一的代价是:在 Actor 训练完成几次更新后,需要把模型权重 (Weights) 同步回 vLLM 的显存中,这引入了进程间通信的开销。对于大型集群,这部分通常通过 NVLink 或高速 RDMA 网络异步完成 (如 AReaL 的架构)。
7. 为什么 G / temperature / top-p 会影响 RL 信号?
RL 训练并不是只优化 loss,它还依赖 rollout 阶段采样出来的数据分布。因此采样参数本身就会改变训练信号:G 太小,组统计噪声大;temperature 太低,样本太相似;temperature 太高,噪声过大;top-p 太窄会错过好解,太宽则拉高方差。
深入:一个具体数值例子
假设同一个 prompt 上,temperature=0.2 时 4 个答案 reward 近似为 [0.61, 0.60, 0.62, 0.61],那么组内标准差很小,优势几乎都接近 0。模型会收到“这些答案差不多”的信号。若 temperature=1.1 时 reward 变成 [0.95, 0.10, 0.78, -0.20],组区分度更大,但方差也会显著升高。工程上常取 G=4 或 8、temperature≈0.7~1.0,再结合任务类型调参。
8. PPO / GRPO 的 clipping 到底在限制什么?
clipping 不是为了让 loss 好看,而是为了限制单次更新不要把策略推得太远。没有 clipping 时,importance ratio 可能从 1.0 一下跳到 3.0 或 0.1,导致某些 token 的梯度被极端放大,从而训练震荡甚至崩掉。
深入:一个 token 的手算例子
若 advantage=2.0,ratio=1.6,ε=0.2,则未裁剪目标为 1.6×2=3.2;裁剪后上限是 1.2×2=2.4。PPO/GRPO 会选更保守的那个,从而阻止一次更新把该 token 概率拉得过猛。反过来若 advantage 为负,裁剪也会阻止概率被一次性打得太低。
9. 一个 RL batch 里到底装了什么?
很多人第一次看 RLHF 代码会困惑:一个 batch 里不是只有 input_ids,而是还会带上 attention_mask、position_ids、old_logprobs、ref_logprobs、rewards、advantages、response_mask 等一整包经验。因为 actor update 不是普通 supervised fine-tuning,而是在回放 rollout 时对整段轨迹做重新加权。
深入:一个具体 shape 例子
# Example RL batch
input_ids.shape = [8, 512]
attention_mask.shape = [8, 512]
position_ids.shape = [8, 512]
response_mask.shape = [8, 512]
old_logprobs.shape = [8, 128]
ref_logprobs.shape = [8, 128]
rewards.shape = [8]
advantages.shape = [8, 128] # after token broadcast
这里常见的坑是:有些张量按整句存储(如 rewards=[B]),有些按 response token 存储(如 old_logprobs=[B,T_resp])。真正算 loss 前,通常需要把句级信号 broadcast 到 token 级,再乘上 response_mask。
10. 哪些 token 参与 loss,哪些不参与?
在 RLHF / GRPO 中,通常只有 response 部分参与 PPO-style loss。prompt token 只是条件,不应该被当成模型本轮要优化的动作;padding token 当然也不能算。否则 loss 会被无意义位置污染。
深入:response_mask 在代码里怎么用
token_loss = ppo_loss_per_token(current_logprobs, old_logprobs, advantages)
masked_loss = token_loss * response_mask
loss = masked_loss.sum() / response_mask.sum()
这段逻辑看起来简单,但它非常关键:它保证 prompt 区域不会平白给梯度,padding 区域不会污染平均值,长短句也能被合理归一化。
11. 最终 reward 是怎么拼出来的?
实际训练里的 reward 往往不是一个单独分数,而是多个来源的组合:环境正确性奖励、格式奖励、长度惩罚、KL 惩罚、rule-based verifier 奖励,最后拼成 actor update 使用的 final reward。
Logic Match
Thinking Blocks
Verbosity
Mode Collapse
深入 1:代码层面的 Reward 拼装逻辑 (Tensor 视角)
# tensors of shape [batch_size, seq_len]
reward_env = verifier_score(response) # e.g., [8.0, 0.0, 8.0, ...]
reward_fmt = format_bonus(response) # e.g., [1.5, 1.5, 0.0, ...]
# length penalty often applied to final token or distributed
penalty_len = length_penalty(response) # e.g., 0.01 * len(response)
# KL penalty is computed per-token: log(P_theta) - log(P_ref)
kl_divergence = log_probs - ref_log_probs
penalty_kl = beta * kl_divergence # beta is often dynamically adjusted
# Final assembly (often done per-token for PPO, or summed for GRPO)
reward_final = reward_env + reward_fmt - penalty_len - penalty_kl.sum(dim=-1)
注意:KL 惩罚通常是在 token 级别计算并参与 advantage 估计的,而 env reward 和 format reward 通常只在最后一个 token 给定。这就涉及到了 credit assignment 的问题。
深入 2:工程排坑 - 量纲失衡与 Reward Hacking
如果 KL penalty 的系数 beta 设置过大(例如 0.1,且 KL 本身在 10-20 左右),惩罚项可能达到 -2.0。如果 env reward 只有 1.0,模型宁愿什么都不学(保持原始输出),也不愿为了 1.0 的奖励去承受 -2.0 的惩罚。
如果 format reward 给得太高(例如正确性给 +1,格式对给 +5),模型会迅速发现“瞎写一通但格式完美”是通往高分的捷径(Reward Hacking)。
深入 3:为什么需要 Reward Normalization?
假设有一批数据的 env reward 是 [0, 100, 0, 100],均值为 50,方差极大。如果不做归一化,梯度的方差会非常大,导致训练极不稳定。GRPO 通过直接减去 batch 均值并除以标准差来进行优势估计(Advantage Estimation),本质上自带了 Reward Normalization 的效果。
advantages = (rewards - rewards.mean()) / (rewards.std() + 1e-4)
12. RLHF / GRPO 为什么容易不稳定?
RLHF 比普通 SFT 难很多,因为它同时包含了分布漂移、奖励噪声、长序列 credit assignment、KL 约束、采样方差和系统延迟。问题往往不是单点故障,而是多个小偏差叠加后一起爆炸。
深入 1:最常见的排障检查单 (Checklist)
- ✅ 检查 reward 的均值 / 方差是否突然漂移 (极有可能导致 Advantage 爆炸)
- ✅ 检查 ratio 是否大量超出 [1-ε, 1+ε] (意味着 Clipping 机制经常被触发,策略偏离过远)
- ✅ 检查 KL 是否突然飙升 (Reward Hacking) 或长期贴近 0 (不学习)
- ✅ 检查 response_mask / padding mask 是否正确 (最常见的低级 Bug)
- ✅ 检查 old_logprob 是否真来自 rollout 时的旧策略 (异步系统容易用错版本)
- ✅ 检查 reward shaping 各项量纲是否失衡 (例如 KL 惩罚完全掩盖了 Env Reward)
深入 2:数值剖析 - Ratio 爆炸是如何发生的?
PPO/GRPO 的核心公式包含 ratio = exp(log_prob - old_log_prob)。在长序列生成中,如果累积的策略差异过大:
Step 2: log_prob = -2.0 (更新后极其自信的新策略)
Step 3: diff = 8.0
Step 4: ratio = exp(8.0) ≈ 2980.9 💥
如果不做 Clipping(或者 Clipping 的 loss 没有被正确传播),梯度就会被放大 3000 倍,导致下一次更新直接 NaN。这就是为什么 clip_range (通常为 0.2) 如此重要。
深入 3:系统架构挑战 - Asynchronous Latency
在 VERL 等系统中,Rollout (vLLM) 和 Actor Update (Megatron/DeepSpeed) 是分布在不同 GPU 上的。如果 vLLM 生成数据太慢,Actor 已经更新了 3 个 step,此时 vLLM 才把基于 version-1 权重生成的数据发过来给 version-4 的 Actor 训练。
这种极端的 Off-policy 数据会导致 KL 散度极高,PPO 算法会疯狂拒绝这些数据(所有的 ratio 都被 clip),最终表现为:系统在跑,GPU在烧,但 loss 不降,能力不涨。