杨宁华,杨总整理 · github.com/yangninghua

VERL & GRPO

强化学习训练核心概念备忘录

专注于 GRPO、相对优势、对数概率与 KL 惩罚

📑 目录 (Table of Contents)

  1. GRPO 是什么?
  2. 为什么要组内相对归一化?(G, mu, sigma, A_i)
  3. 各种 Logprob 的区别 (Rollout / Old / Ref)
  4. KL 惩罚是如何工作的?
  5. 为什么响应级奖励存在信用分配问题?
  6. VERL/GRPO 训练数据流是怎么样的?
  7. 为什么 G / temperature / top-p 会影响 RL 信号?
  8. PPO / GRPO 的 clipping 到底在限制什么?
  9. 一个 RL batch 里到底装了什么?
  10. 哪些 token 参与 loss,哪些不参与?
  11. 最终 reward 是怎么拼出来的?
  12. RLHF / GRPO 为什么容易不稳定?

🧩 深度题库 (Q13–Q67)

  1. 为什么要 old policy 算 ratio?
  2. KL 为什么对 ref model?
  3. Reference model 为什么冻结?
  4. Reward model 容易被 exploit?
  5. Verifier vs preference reward
  6. Rule-based reward 更稳定?
  7. Reward normalization
  8. G 太小会不稳定?
  9. G 太大拖慢吞吐?
  10. GRPO 不需要 critic?
  11. PPO 需要 critic,GRPO 不用?
  12. Token-level advantage 偏差
  13. 长回答放大训练噪声
  14. Response length 监控
  15. Entropy 下降太快
  16. Temperature 太低学不动
  17. Top-p 太宽质量变差
  18. Rollout/train/microbatch 分开
  19. Gradient accumulation
  20. Mixed precision / fp8
  21. Stale rollout
  22. Async RL 吞吐 vs 稳定
  23. vLLM / trainer 解耦
  24. 权重同步频率
  25. Replay buffer 不能太旧
  26. On-policy vs off-policy
  27. Ratio = exp(logp差)
  28. Logprob 按 token,reward 按序列
  29. Padding mask 错了毁训练
  30. EOS 处理影响 reward
  31. Prompt truncation
  32. Reward hacking 先变格式
  33. KL 太小不一定好
  34. KL 太大 reward 也不差?
  35. Loss 下降 ≠ 策略更好
  36. Eval prompt 必须固定
  37. Pass@k vs 平均 reward
  38. 多轮对话 RL 更难
  39. Tool use 更依赖 verifier
  40. 数学 RL vs 聊天 RL 调参
  41. Sequence packing 更敏感
  42. Ref model 显存压力
  43. Rollout 记录 old_logprobs
  44. Actor update 次数过多
  45. Minibatch shuffle
  46. RL 学习率比 SFT 更小
  47. Advantage scale 梯度爆炸
  48. Reward 延迟 credit assignment
  49. 多样本比较更稳
  50. Verifier false negative
  51. Verifier false positive
  52. Logging / tracing 重要性
  53. Token / 序列 / 系统指标
  54. RLHF 问题 = 数据分布问题

1. GRPO 是什么?

GRPO (Group Relative Policy Optimization) 是 PPO 的一种变体,专为大语言模型 (LLMs) RLHF 训练设计。它省去了 Critic (价值网络),通过对同一个 Prompt 采样 G 个不同的响应(Group),在组内对奖励进行归一化来计算优势函数。

Prompt (Query)
↓ 采样 G=3 个响应 ↓
Res 1 (r=0.8)
Res 2 (r=0.2)
Res 3 (r=0.5)
↓ 组内奖励归一化为优势 A_i ↓
A_1 = +1.22
A_2 = -1.22
A_3 = 0.00
深入: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)。

\[ A_i = \frac{r_i - \mu_G}{\sigma_G}, \quad \mu_G = \frac{1}{G}\sum_{j=1}^G r_j, \quad \sigma_G = \sqrt{\frac{1}{G}\sum_{j=1}^G (r_j - \mu_G)^2 + \epsilon} \]

尝试修改奖励值 r_i,观察优势 A_i 的变化:

Response Reward (r_i) Advantage (A_i)
1+1.12
2-0.56
3-0.14
4-0.56
μ_G = 5.25,   σ_G = 1.64
深入:极端情况与方差爆炸

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:

  1. Reference Logprob (参考策略): 永远冻结的模型(如初始的 SFT 模型),用于计算 KL 惩罚,防止模型偏离人类语言分布太远。
  2. Old Logprob (旧策略/Rollout): 用于在环境中生成文本回答的模型。在一次 PPO 迭代(包含多次 minibatch update)的开始时固定,用于计算 PPO 的 Importance Ratio (新旧概率比率)。
  3. Current/New Logprob (当前策略): 正在反向传播和梯度下降的活跃模型。
Ref Model
(Frozen)
Old Policy
(Rollout Gen)
New Policy
(Updating)
KL penalty and PPO ratio relationships KL Penalty (KL Div) PPO Ratio
深入:代码中的张量计算
# 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”,学会输出无意义但能骗取高分的规律字符(模式崩塌)。

\[ r_{\text{final}} = r_{\text{env}} - \beta \cdot D_{\text{KL}}(\pi_{\text{current}} \| \pi_{\text{ref}}) \]

调节 KL 系数 β (Beta):0.10

Env Reward (r)
+10.0
-
KL Div
20.0
=
Final Reward
+8.0

当 β 过大时,KL 惩罚压倒环境奖励,模型倾向于什么都不学(退化回 SFT)。当 β 为 0 时,容易发生 Reward Hacking。

深入:Token 级无偏 KL 估算公式

实践中,我们无法计算整个词表的精确 KL 散度,而是使用按 Token 采样的无偏估计 (Unbiased estimators):

令 \( d_t = \log \pi_{\text{new}}(a_t|s_t) - \log \pi_{\text{ref}}(a_t|s_t) \)

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?

-1.0The
-1.0answer
-1.0is
-1.05
-1.0.

“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)。

1. Rollout Workers (vLLM)
Generate(x) -> y, logp_old
Send Prompts + Responses
2a. Reward Model
r_i = RM(x, y)
2b. Ref Policy
logp_ref = Ref(x, y)
Batch Experience (Replay Buffer)
3. Actor Trainer (Megatron/FSDP)
A_i = (r_i - μ) / σKL_penLoss & Backprop
Sync Weights back to vLLM
深入: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 太窄会错过好解,太宽则拉高方差。

G4
temp0.8
top-p0.95
探索度
0.55
组区分度
0.62
训练方差
0.38
深入:一个具体数值例子

假设同一个 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 的梯度被极端放大,从而训练震荡甚至崩掉。

ratio1.00
ε0.20
1.00
未裁剪目标
1.00
裁剪后目标
inside
当前状态
深入:一个 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 时对整段轨迹做重新加权。

input_ids
[B, T]
attention_mask
[B, T]
old_logprobs
[B, T_resp]
ref_logprobs
[B, T_resp]
advantages
[B, T_resp] or [B]
response_mask
[B, T]
深入:一个具体 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 会被无意义位置污染。

User
prompt
asks
prompt
2+2?
prompt
The
loss on
answer
loss on
is
loss on
4
loss on
<pad>
padding
深入: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。

Env Reward
+8.0
Correctness &
Logic Match
+
Format Bonus
+1.5
XML Tags &
Thinking Blocks
Length Penalty
0.7
Prevents
Verbosity
KL Penalty
1.2
Prevents
Mode Collapse
=
Final Reward
7.6
Used for PPO/GRPO Update
深入 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
Pitfall 1: The KL Dominance
如果 KL penalty 的系数 beta 设置过大(例如 0.1,且 KL 本身在 10-20 左右),惩罚项可能达到 -2.0。如果 env reward 只有 1.0,模型宁愿什么都不学(保持原始输出),也不愿为了 1.0 的奖励去承受 -2.0 的惩罚。
Pitfall 2: Format Hijacking
如果 format reward 给得太高(例如正确性给 +1,格式对给 +5),模型会迅速发现“瞎写一通但格式完美”是通往高分的捷径(Reward Hacking)。
深入 3:为什么需要 Reward Normalization?

假设有一批数据的 env reward 是 [0, 100, 0, 100],均值为 50,方差极大。如果不做归一化,梯度的方差会非常大,导致训练极不稳定。GRPO 通过直接减去 batch 均值并除以标准差来进行优势估计(Advantage Estimation),本质上自带了 Reward Normalization 的效果。

# GRPO Advantage Calculation
advantages = (rewards - rewards.mean()) / (rewards.std() + 1e-4)

12. RLHF / GRPO 为什么容易不稳定?

RLHF 比普通 SFT 难很多,因为它同时包含了分布漂移、奖励噪声、长序列 credit assignment、KL 约束、采样方差和系统延迟。问题往往不是单点故障,而是多个小偏差叠加后一起爆炸。

Reward Noise ⚠️
RM / verifier 不稳定。偶尔给错误答案高分,导致错误的高信度更新。
Policy Drift 🚀
ratio (π_θ / π_old) 变大,更新步长失控,打破信赖域。
KL Mis-scale ⚖️
Beta 太大则模型不学无术,Beta 太小则模型过度优化(Reward Hacking)。
Mask Bug 🐛
Prompt 区域被错误地纳入 loss 计算,破坏模型的指令理解能力。
Stale Rollout 🕰️
Actor 更新太快,VLLM 生成的旧数据由于通信延迟拿来训练新模型,严重 Off-policy。
Sampling Var 🎲
Temperature 过低导致无探索,过高导致随机游走。Group Size 太小导致方差估计不准。
经验上,最危险的状态不是某个指标立即爆炸,而是 reward 还在涨、但 KL 和 ratio 已经悄悄抬头。这通常意味着模型正在学会 exploit 奖励,而不是真正提升能力。
深入 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 1: old_log_prob = -10.0 (非常不自信的旧策略)
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 不降,能力不涨。