0%

LLM 训练:GRPO 算法详解

之前的 PPO 细节探讨中,我们详细介绍了 PPO 算法和 GAE 优势估计。今天我们来深入探讨 GRPO(Group Relative Policy Optimization)算法,这是 PPO 在大语言模型训练中的一个重要改进版本。

GRPO 算法概述

在 LLM 的 RLHF 训练中,PPO 虽然表现良好,但仍存在一些可以改进的地方:

  1. GAE 的复杂性:PPO 使用 GAE 计算每个 token 的优势,涉及多个超参数($\lambda$、$\gamma$)的调优
  2. 价值函数的需求:PPO 需要训练一个 Critic 模型来估计状态价值,增加计算和内存成本
  3. 训练不稳定:在某些场景下,GAE 估计的方差仍然较大,导致训练波动

GRPO 通过一个更直接的方法来解决这些问题:将序列级的奖励直接分配到 token 级别,然后通过组内相对比较来计算优势。这种方法在实践中被证明更加稳定高效。

GRPO 的核心创新

  1. 简化优势计算:不使用 GAE,而是通过组内相对奖励直接计算优势
  2. 减少计算负担:虽然仍需要参考模型,但优势计算逻辑更简洁
  3. 提高训练稳定性:通过序列奖励的分解和组内比较,减少方差
  4. Token 级别的精细控制:在每个 token 级别进行重要性采样和裁剪

GRPO 的动机

GRPO 相比 PPO 的改进方向:

问题 PPO 解法 GRPO 改进
优势计算 需要 Critic 估计价值,使用 GAE 不需要 Critic,直接使用组内相对比较
方差 GAE 通过多步累积,仍有较大方差 通过组内比较,方差更小
超参数 $\lambda, \gamma$ 需要调优 更少的超参数
计算成本 需要训练两个模型 只需要一个模型+参考模型

GRPO 的三个核心模型

与 PPO 不同,GRPO 只需要三个模型:

模型 作用 参数更新 备注
Actor 生成回答的策略模型 ✓ 需要 最终要部署的模型
Reference 参考模型,计算 KL 约束 ✗ 不需要 通常是 SFT 模型的初始状态
Reward Model 评分函数,评估整体质量 ✗ 不需要 在 RLHF 第一阶段提前训练好

关键区别:GRPO 相比 PPO 不需要 Critic 模型,这大大减少了训练的计算和内存成本。


GRPO 的算法原理

1. 序列奖励的分解

GRPO 的核心创新是如何将序列级的奖励分配到token 级别

奖励分配机制

对于长度为 $T$ 的生成序列,Reward Model 给出一个标量奖励 $r_{\text{RM}}$。GRPO 将这个奖励分配到每个 token:

即:只有最后一个 token(序列终止位置)获得奖励,中间所有 token 的奖励为 0

这与 PPO 中的 KL 惩罚方式不同(PPO 中每个 token 都有 的惩罚)。

为什么这样分配

  • 简洁性:不需要为每个 token 计算即时奖励和 KL 惩罚的组合
  • 合理性:反映了现实情况——我们只在完整回答后评分
  • 直观性:整个序列的质量用一个数字衡量,然后均等分配(或按距离衰减)

2. Token 级别的重要性采样与裁剪

这是 GRPO 与 PPO 最重要的区别之一。

PPO 的做法(token 级别)

PPO 对每个 token 计算重要性采样比率:

然后进行裁剪和加权:

特点:重要性采样是 token 级别的,优势 $A_t$ 也是 token 级别的。

GRPO 的做法(仍是 token 级别,但优势不同)

GRPO 同样对每个 token 计算重要性采样比率:

然后进行裁剪和加权:

其中 $\tilde{A}_t$ 是 token 级别的归一化优势(见下一节)。

重要:GRPO 仍然是 token 级别的重要性采样和裁剪,但优势的计算方式不同。

3. 组内相对优势计算

这是 GRPO 最核心的创新。

序列优势(第一步)

对于生成的每个序列 $i$,根据其奖励计算序列级优势:

其中:

  • $r_i$:第 $i$ 个序列的 RM 奖励
  • $\bar{r} = \frac{1}{G} \sum_{j=1}^{G} r_j$:组内(所有 $G$ 个序列)的平均奖励

含义:这个序列相对于组内平均水平的相对优势。

Token 级优势(第二步)

GRPO 将序列优势分配给序列中的每个 token。最简单的做法是均等分配

其中 $T_i$ 是第 $i$ 个序列的长度。

也可以使用折扣分配(越靠近末端权重越高):

其中 $\gamma \in [0,1]$ 是折扣因子(通常 0.99)。折扣分配的好处是给予序列末端(生成过程的最后)更高的权重,这更符合直觉:序列的最后部分对最终质量影响更大。

优势归一化

为了稳定训练,对组内的优势进行归一化:

其中:

  • $\mu_A = \frac{1}{G \cdot T} \sum_i \sum_t \tilde{A}_t^{(i)}$:所有 token 优势的平均值
  • $\sigma_A$:所有 token 优势的标准差
  • $\epsilon$:小常数防止除零

4. 完整的损失函数

GRPO 的损失函数包含两部分:

策略损失

或者使用 PPO 风格的裁剪:

KL 约束损失

注意:GRPO 仍然保留 KL 约束,防止策略偏离参考模型太远。


GRPO 与 PPO 的详细对比

模型架构对比

维度 PPO GRPO
Actor ✓ 需要训练 ✓ 需要训练
Critic ✓ 需要训练 不需要
Reference ✓ 需要冻结 ✓ 需要冻结
Reward Model ✓ 需要冻结 ✓ 需要冻结
总体模型数 4 个 3 个
可训练参数 Actor + Critic 仅 Actor

优势计算对比

维度 PPO GRPO
信息来源 单个序列的 token 奖励 + Critic 价值估计 组内序列的相对奖励
计算方式 GAE(加权累加多步 TD 误差) 组内平均 - 单序列奖励
需要模型 Critic 模型 无需额外模型
超参数 $\lambda, \gamma, \beta_{\text{vf}}$ 等 组大小 $G$、折扣 $\gamma$、权重 $\beta$
方差 中等(GAE 加权后) 较低(组内比较)
偏差 低(无模型依赖) 中等(依赖同组其他序列)

重要性采样对比

维度 PPO GRPO
采样粒度 Token 级别 Token 级别(与 PPO 相同)
参考模型 $\pi{\theta{\text{old}}}$(旧策略) $\pi_{\text{ref}}$(参考模型)
裁剪范围 Token 级别 Token 级别(与 PPO 相同)
损失聚合 先平均 token 再平均序列 先平均 token 再平均序列(相同)

关键相同点:GRPO 和 PPO 都在 token 级别进行重要性采样和裁剪,区别在于优势的计算来源。

优缺点对比

PPO 的优势

  1. 原理清晰:基于价值函数的强化学习理论
  2. 广泛应用:在各种 RL 任务中都有良好表现
  3. 灵活性强:可以适应各种奖励结构
  4. 理论支持:GAE 有深厚的理论基础

PPO 的缺陷

  1. 模型多:需要训练两个大模型(Actor + Critic)
  2. Critic 不准确:Critic 的价值估计可能不准确,导致优势估计有偏差
  3. 超参数多:GAE 的 $\lambda, \gamma$ 需要调优
  4. 计算成本高:训练 Critic 占用显存和计算力

GRPO 的优势

  1. 模型少:无需额外训练 Critic,只需 Actor
  2. 方差小:组内比较天然降低方差,不依赖模型准确性
  3. 简洁直接:优势计算逻辑简单明了
  4. 计算成本低:节省 Critic 的计算和内存

GRPO 的缺陷

  1. 需要分组:必须采样一个组的序列,组大小 $G$ 需要调优
  2. 依赖组质量:如果组内所有序列都很差,相对优势可能无法有效指导
  3. 序列奖励稀疏:不能充分利用中间步骤的反馈信息
  4. 较少应用:目前应用不如 PPO 广泛

GRPO 的实现细节

1. 分组采样

GRPO 的关键是在每次更新时采样一个的序列。

采样策略

  1. 输入:一个 batch 的 prompts,共 $B$ 个
  2. 采样:对每个 prompt,采样 $G$ 个序列(即 $G$ 个不同的回答)
  3. 结果:得到 $B \times G$ 个序列

组大小选择

  • $G = 4$:计算效率高,但相对优势估计可能不够准确
  • $G = 8 \sim 16$:平衡效率和准确性(推荐)
  • $G = 32$:优势估计准确,但计算成本大

实践建议:根据显存和计算资源选择,通常 $G = 8$ 或 $G = 16$。

2. 奖励获取与归一化

奖励获取流程

对每个序列:

  1. Actor 生成:Actor 生成完整序列
  2. RM 评分:将序列输入 RM,得到标量奖励 $r_{\text{RM}}$
  3. 记录:保存奖励值

奖励归一化

1
2
3
4
5
6
# 对每个 prompt 的 G 个序列的奖励进行归一化
for prompt_idx in range(B):
rewards = [r[prompt_idx, :] for r in reward_scores] # G 个奖励
mu = np.mean(rewards)
sigma = np.std(rewards)
normalized_rewards = (rewards - mu) / (sigma + 1e-8)

为什么归一化

  • 不同的 prompt 可能有不同的奖励尺度
  • 归一化后的优势在 0 附近,梯度更稳定

3. Token 优势计算

算法步骤

对于第 $i$ 个序列(长度 $T_i$,奖励 $r_i$):

步骤 1:计算序列优势

步骤 2:分配到每个 token(选择一种)

方式 A:均等分配

方式 B:折扣分配

步骤 3:全局归一化

Python 伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def compute_advantages(rewards, sequence_lengths, discount=0.99):
"""
Args:
rewards: shape (B, G) - B 个 prompts,每个 G 个序列的奖励
sequence_lengths: shape (B, G) - 每个序列的长度
discount: 折扣因子

Returns:
advantages: shape (B, G, T_max) - 每个 token 的优势
"""
B, G, T_max = rewards.shape[0], rewards.shape[1], sequence_lengths.max()

# 全局平均奖励
global_mean = rewards.mean()

# 序列优势
seq_advantages = rewards - global_mean # shape (B, G)

# token 级优势(折扣分配)
token_advantages = np.zeros((B, G, T_max))

for b in range(B):
for g in range(G):
T = sequence_lengths[b, g]
A_seq = seq_advantages[b, g]

# 计算折扣和
discount_sum = sum(discount**(T-1-t) for t in range(T))

# 分配到每个 token
for t in range(T):
token_advantages[b, g, t] = (
discount**(T-1-t) * A_seq / discount_sum
)

# 全局归一化
mean_adv = token_advantages.mean()
std_adv = token_advantages.std()
normalized_advantages = (token_advantages - mean_adv) / (std_adv + 1e-8)

return normalized_advantages

4. 损失计算与反向传播

Token 级别的损失计算

对于第 $i$ 个序列的第 $t$ 个 token:

重要性采样比率

PPO 风格的裁剪损失

其中 $\epsilon = 0.2$(裁剪范围)。

KL 约束

计算方式(对于语言模型):

在实际实现中,通常只计算被生成 token 处的 KL:

总体损失

单个序列的损失(先 token 后序列平均):

整个组的损失(序列平均):

Python 伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def compute_loss(logits, reference_logits, advantages, token_ids, beta=0.05, epsilon=0.2):
"""
Args:
logits: shape (B, G, T, V) - Actor 模型输出
reference_logits: shape (B, G, T, V) - Reference 模型输出
advantages: shape (B, G, T) - 归一化优势
token_ids: shape (B, G, T) - 生成的 token IDs
beta: KL 权重
epsilon: 裁剪范围

Returns:
loss: 标量损失
"""
B, G, T, V = logits.shape

# 计算 log 概率
log_probs = log_softmax(logits, dim=-1) # shape (B, G, T, V)
ref_log_probs = log_softmax(reference_logits, dim=-1)

# 选择生成 token 的概率
chosen_log_probs = log_probs.gather(-1, token_ids.unsqueeze(-1)).squeeze(-1)
chosen_ref_log_probs = ref_log_probs.gather(-1, token_ids.unsqueeze(-1)).squeeze(-1)

# 重要性采样比率
log_ratio = chosen_log_probs - chosen_ref_log_probs # log(pi/pi_ref)
ratio = exp(log_ratio) # pi/pi_ref

# PPO 裁剪损失
clipped_ratio = clamp(ratio, 1 - epsilon, 1 + epsilon)
policy_loss = -min(ratio * advantages, clipped_ratio * advantages)

# KL 散度(简化版,只计算生成的 token)
kl_loss = log_ratio # 近似

# 总损失
total_loss = policy_loss + beta * kl_loss

# 求平均
return total_loss.mean()

5. 超参数设置

关键超参数

超参数 典型值 范围 说明
$G$ (组大小) 8-16 4-32 更大的组更稳定但计算成本高
$\epsilon$ (裁剪范围) 0.2 0.1-0.3 限制重要性采样比率的范围
$\beta$ (KL 权重) 0.05 0.01-0.2 平衡奖励追求和分布约束
$\gamma$ (折扣因子) 0.99 0.95-1.0 控制到末端 token 的权重衰减
学习率 5e-7 1e-7-1e-6 较小的学习率,通常比 SFT 小 100 倍
批大小 $B=8, G=16$ - 总共 $B \times G$ 个序列

调优建议

  1. $G$ 的选择

    • 显存充足:使用 $G = 16$ 或更大
    • 显存紧张:使用 $G = 4$ 或 $G = 8$
    • 避免 $G$ 过小($< 4$),否则相对优势不可靠
  2. $\beta$ 的调优

    • 若 KL 散度过大:增大 $\beta$
    • 若训练不动(模型变化太慢):减小 $\beta$
    • 推荐从 $0.05$ 开始,根据 KL 散度监测值调整
  3. 学习率的选择

    • GRPO 的学习率通常比 SFT 小 100-1000 倍
    • 从 5e-7 开始,根据梯度范数调整
    • 避免学习率过大,否则训练不稳定
  4. 折扣因子 $\gamma$

    • $\gamma = 0.99$:末端 token 权重约为前端的 $0.37$ 倍
    • $\gamma = 1.0$:均等分配(最简单)
    • 推荐从 $\gamma = 1.0$ 开始,必要时调整

GRPO 的完整训练流程

算法伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
算法:GRPO 训练

初始化:
- Actor 模型 π_θ(可以从 SFT 初始化)
- Reference 模型 π_ref(冻结,使用 SFT 模型或 Actor 的检查点)
- Reward Model R(冻结,提前训练)

主循环:
for epoch = 1 to num_epochs:
for batch_idx = 1 to num_batches:

# 步骤 1: 采样
prompts = get_batch() # B 个 prompts
sequences = []
log_probs = []

for g = 1 to G:
# 使用 Actor 生成 G 个回答
seq_g, logprobs_g = generate_from_actor(prompts, π_θ)
sequences.append(seq_g)
log_probs.append(logprobs_g)

# 步骤 2: 获取奖励
rewards = reward_model.score(sequences) # shape (B, G)

# 步骤 3: 计算优势
advantages = compute_advantages(rewards, sequences) # shape (B, G, T)

# 步骤 4: 前向传播
actor_outputs = actor(sequences) # 获取 Actor 的 logits
ref_outputs = reference_model(sequences) # 获取 Reference 的 logits

# 步骤 5: 计算损失
loss = 0
for each token:
ratio = exp(log_pi_theta - log_pi_ref)
clipped_ratio = clip(ratio, 1-ε, 1+ε)
policy_loss += min(ratio * A, clipped_ratio * A)
kl_loss += (log_pi_theta - log_pi_ref)

total_loss = policy_loss + β * kl_loss

# 步骤 6: 反向传播和更新
optimizer.zero_grad()
total_loss.backward()
optimizer.step()

# 步骤 7: 监测
log(loss, kl_div, reward_mean, reward_std)

训练流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
┌─────────────────────────────────────────────────────┐
│ 输入:Batch of Prompts │
└─────────────────────┬───────────────────────────────┘

┌─────────────────────┐
│ Actor 采样 G 个序列 │ (并行,每个 prompt)
└──────────┬──────────┘

┌────────────────────────────────┐
│ Reward Model 评分每个序列 │
│ 得到 (B, G) 的奖励矩阵 │
└────────────┬───────────────────┘

┌────────────────────────────────┐
│ 计算序列优势(组内相对比较) │
│ A_i^seq = r_i - mean(r) │
└────────────┬───────────────────┘

┌────────────────────────────────┐
│ 分配到 Token 级优势 │
│ A_t = discount^(T-t) * A_seq │
└────────────┬───────────────────┘

┌────────────────────────────────┐
│ 全局归一化优势 │
│ A_hat = (A - mean) / std │
└────────────┬───────────────────┘

┌────────────────────────────────┐
│ 计算 PPO 损失(token 级别) │
│ L = min(r*A, clip(r)*A) │
│ + β * KL(π_θ || π_ref) │
└────────────┬───────────────────┘

┌────────────────────────────────┐
│ 反向传播和梯度更新 │
│ θ ← θ - α ∇L │
└────────────┬───────────────────┘

┌──────────────────┐
│ 输出:更新的 Actor │
└──────────────────┘

GRPO 完整数值示例

让我们用一个具体例子演示整个计算过程。

假设条件

  • 批大小:$B = 1$(1 个 prompt)
  • 组大小:$G = 2$(2 个序列)
  • 序列长度:$T_1 = 3, T_2 = 4$(两个序列长度不同)
  • 奖励:$r_1 = 0.8, r_2 = 0.6$(第一个序列更好)
  • 折扣因子:$\gamma = 0.99$
  • 裁剪范围:$\epsilon = 0.2$
  • KL 权重:$\beta = 0.05$

计算过程

步骤 1: 计算序列优势

1
2
3
4
5
6
7
8
序列 1: r_1 = 0.8, T_1 = 3
序列 2: r_2 = 0.6, T_2 = 4

平均奖励: r_bar = (0.8 + 0.6) / 2 = 0.7

序列优势:
A_1^seq = 0.8 - 0.7 = 0.1
A_2^seq = 0.6 - 0.7 = -0.1

解释:序列 1 相对于平均水平好一点,序列 2 相对于平均水平差一点。

步骤 2: 分配到 Token 级(使用折扣分配)

折扣分配公式

其中分母是折扣权重的和。

序列 1 计算($T_1 = 3$):

1
2
3
4
5
6
折扣权重: γ^(3-1)=0.9801, γ^(3-2)=0.99, γ^(3-3)=1.0
权重和: 0.9801 + 0.99 + 1.0 = 2.9701

Token 1: A_1^(1) = (0.9801 * 0.1) / 2.9701 = 0.0330
Token 2: A_2^(1) = (0.99 * 0.1) / 2.9701 = 0.0333
Token 3: A_3^(1) = (1.0 * 0.1) / 2.9701 = 0.0337

序列 2 计算($T_2 = 4$):

1
2
3
4
5
6
7
折扣权重: γ^(4-1)=0.9703, γ^(4-2)=0.9801, γ^(4-3)=0.99, γ^(4-4)=1.0
权重和: 0.9703 + 0.9801 + 0.99 + 1.0 = 3.9404

Token 1: A_1^(2) = (0.9703 * (-0.1)) / 3.9404 = -0.0246
Token 2: A_2^(2) = (0.9801 * (-0.1)) / 3.9404 = -0.0249
Token 3: A_3^(2) = (0.99 * (-0.1)) / 3.9404 = -0.0251
Token 4: A_4^(2) = (1.0 * (-0.1)) / 3.9404 = -0.0254

观察

  • 序列 1 的最后 token (0.0337) 权重高于最前 token (0.0330)
  • 序列 2 的所有优势都是负的(因为序列质量较差)

步骤 3: 全局归一化

所有 token 的优势值:

1
2
3
4
5
6
7
8
9
序列 1: [0.0330, 0.0333, 0.0337]
序列 2: [-0.0246, -0.0249, -0.0251, -0.0254]

合并: [0.0330, 0.0333, 0.0337, -0.0246, -0.0249, -0.0251, -0.0254]

均值: μ_A = (0.0330 + 0.0333 + 0.0337 - 0.0246 - 0.0249 - 0.0251 - 0.0254) / 7
= 0 / 7 = 0 (近似)

方差: σ_A ≈ 0.0291

归一化后(减去均值、除以标准差):

1
2
序列 1: [0.1134, 0.1145, 0.1159]  (正值,鼓励)
序列 2: [-0.0846, -0.0856, -0.0863, -0.0873] (负值,惩罚)

步骤 4: 计算 PPO 损失

假设 Actor 和 Reference 模型在序列 1 的第 1 个 token 处:

  • Actor log_prob: -1.5
  • Reference log_prob: -1.6
  • 生成的 token ID

计算过程

1
2
3
4
5
6
7
8
9
10
log_ratio = -1.5 - (-1.6) = 0.1
ratio = exp(0.1) ≈ 1.105

优势: A_hat_1^(1) = 0.1134

无裁剪: ratio * A_hat = 1.105 * 0.1134 ≈ 0.1253
有裁剪: clip(ratio, 0.8, 1.2) = 1.105
clipped * A_hat = 1.105 * 0.1134 ≈ 0.1253

PPO loss: min(0.1253, 0.1253) = 0.1253

步骤 5: KL 散度

假设 KL 散度计算(简化):

1
2
3
4
5
KL_1^(1) = log_ratio = 0.1

总 KL 损失 (简化,仅示意):
L_KL = β * log_ratio_sum / num_tokens
= 0.05 * (所有 log_ratio 之和) / 7

最终损失

1
2
3
4
序列 1 损失: L_1 = (L_policy_1 + β * L_KL_1) / 3
序列 2 损失: L_2 = (L_policy_2 + β * L_KL_2) / 4

总损失: L = (L_1 + L_2) / 2

关键观察

  • 序列 1 的优势为正,鼓励增加这些 token 的概率
  • 序列 2 的优势为负,抑制这些 token 的概率
  • Token 级别的优势差异(末端 > 前端)反映了对最终质量的贡献

GRPO 的收敛性与稳定性

方差分析

GRPO 相比 PPO 的方差优势来自于:

  1. 组内相对比较

    • PPO 使用单个序列的价值函数差分 $\delta_t$,仅与该序列相关
    • GRPO 使用组内所有序列的平均,$E[\text{Var}(A_t)]$ 更小
  2. 不依赖模型准确性

    • PPO 的 GAE 依赖 Critic 的准确性
    • GRPO 直接使用真实奖励,无模型误差
  3. 折扣分配的效果

    • 给予末端更高权重,减少信号的不确定性

收敛性证明

GRPO 的收敛性可以通过以下论证:

  1. 策略改进:每次更新时,$\mathbb{E}[r_t A_t] \geq 0$ 当 $A_t > 0$
  2. KL 约束限制:防止策略离开信任域(trust region)
  3. 优势无偏估计:组内比较提供无偏的相对信号

定理(简化版):在合理的假设下,GRPO 的策略序列收敛到满足 KL 约束的最优策略。


GRPO 在实践中的表现

DeepSeek 的实验结果

根据 DeepSeek 的研究报告,GRPO 在以下指标上表现出色:

数学推理任务(GSM8K/MATH)

  • 收敛速度:更快(通常 10-50% 的步数)
  • 最终效果:相当或优于 PPO(特别是在数学问题上)
  • 训练稳定性:更稳定,奖励波动更小

通用能力(基准测试)

  • 不会显著退化:通过 KL 约束保持基础能力
  • 任务特定性强:在目标任务上显著改进,日常闲聊改进不大
  • 内存节省:相比 PPO 节省 20-30% 的显存(无需 Critic)

什么情况下 GRPO 更好

  1. 有明确的 RM 评分:任务有定量的奖励信号(数学、代码、写作评分等)
  2. 大规模模型:模型越大,Critic 的省省成本越显著
  3. 单轮生成:适合生成单个长序列的场景(如数学推导、代码)

GRPO 可能不如 PPO 的情况

  1. 奖励信号不清晰:RM 评分方差大、不稳定
  2. 组大小受限:显存不足,无法采样足够大的组
  3. 多步交互任务:需要按步骤获得反馈的任务

常见问题与解答

Q1: GRPO 是否需要参考模型?

A: 是的。GRPO 需要参考模型来计算 KL 约束,防止策略漂移。虽然 GRPO 不需要 Critic,但仍需要 Reference 模型(通常是 SFT 模型的冻结副本)。

Q2: GRPO 的组大小 G 应该怎么选?

A:

  • 最小值:$G \geq 4$(否则相对优势不可靠)
  • 推荐值:$G = 8 \sim 16$(平衡准确性和效率)
  • 最大值:受显存限制,通常 $G \leq 32$

根据经验,$G$ 增加一倍通常能稍微改进效果但计算成本翻倍。

Q3: GRPO 会过度拟合组内序列吗?

A: 有可能。如果组大小 $G$ 太小,相对优势的估计可能不稳定。解决方案:

  1. 增加 $G$ 的大小
  2. 跨 batch 计算优势(不仅限于单个 batch 内的序列)
  3. 加入额外的正则化

Q4: GRPO 的 KL 散度约束怎么调?

A: 观察 KL 散度的值:

  • 若 KL > 0.1:$\beta$ 太小,增大 $\beta$
  • 若 KL < 0.01:$\beta$ 太大,减小 $\beta$
  • 目标范围:0.01-0.05

Q5: GRPO vs PPO,我应该选哪个?

A: 根据以下决策树:

1
2
3
4
5
6
7
8
9
10
11
12
计算资源充足?
├─ 是 → PPO(通用性强,理论支持好)
└─ 否 → GRPO(节省 Critic 显存)

任务类型?
├─ 数学/代码 → GRPO 可能更好
├─ 通用闲聊 → PPO 或 GRPO 都可
└─ 多步交互 → PPO 更安全

显存约束是否严格?
├─ 是 → GRPO
└─ 否 → PPO 或 GRPO

总结

GRPO 代表了 LLM RLHF 训练的一个重要方向,其核心贡献包括:

  1. 简化优势计算:从复杂的 GAE 到简单的组内相对比较
  2. 减少计算负担:不需要额外的 Critic 模型
  3. 提高稳定性:组内比较天然降低方差
  4. 实用高效:在大规模模型上表现更好

关键理解

  • GRPO 仍然是 token 级别的重要性采样和裁剪(与 PPO 相同)
  • GRPO 的创新在于优势的计算方式(组内相对而非 GAE)
  • GRPO 和 PPO 是互补的方法,根据具体场景选择

推荐阅读