Skip to content

GRPO 配置解读

GRPO 配置最重要的不是某个单独参数,而是这些参数是否共同构成“同一个 prompt 采样多条 response,然后做组内相对比较”。

这页原来哪里不够适合新手

原来的版本讲了 rollout.n 和 KL,但还缺三层对应关系:

  • 配置字段如何进入 RayPPOTrainer.fit()
  • adv_estimator=grpo 如何走到 compute_grpo_outcome_advantage()
  • loss_agg_mode 为什么会改变长回答的训练权重。

最小 GRPO 命令形态

典型入口:

bash
cd examples/grpo_trainer
MODEL_PATH=Qwen/Qwen3-8B \
INFER_BACKEND=vllm ROLLOUT_N=5 TRAIN_BATCH_SIZE=1024 PPO_MINI_BATCH_SIZE=256 \
bash run_qwen3_8b_fsdp.sh

examples/grpo_trainer/run_qwen3_8b_fsdp.sh 的核心配置是:

bash
algorithm.adv_estimator=grpo
algorithm.use_kl_in_reward=False
actor_rollout_ref.rollout.n=${ROLLOUT_N:-5}
actor_rollout_ref.actor.use_kl_loss=True
actor_rollout_ref.actor.kl_loss_type=low_var_kl

Megatron 版本 run_qwen3_8b_megatron.sh 算法字段相同,只是 actor/ref 改成 Megatron 并行配置。新手读算法时先看 FSDP 版,读大规模训练时再看 Megatron 版。

一张表看配置到源码

配置源码路径作用
algorithm.adv_estimator=grporay_trainer.py::compute_advantage()分派到 GRPO advantage
actor_rollout_ref.rollout.nray_trainer.py::fit()每个 prompt repeat 成 n 条采样轨迹
algorithm.norm_adv_by_std_in_grpocore_algos.py::compute_grpo_outcome_advantage()是否除以组内标准差
algorithm.use_kl_in_rewardray_trainer.py::apply_kl_penalty()KL 是否从 reward 里扣
actor_rollout_ref.actor.use_kl_lossactor update loss 路径KL 是否作为 actor loss 项
actor_rollout_ref.actor.loss_agg_modecore_algos.py::agg_loss()token/sequence loss 如何聚合
actor_rollout_ref.actor.ppo_mini_batch_sizeactor worker mini-batch对 rollout 后的 trajectory 数生效
data.train_batch_sizedataloader每步 prompt 数

rollout.n:GRPO 的地基

RayPPOTrainer.fit() 中,原始 batch 会先写 uid

text
batch.non_tensor_batch["uid"] = uuid for each prompt

然后生成 batch 会被 repeat:

text
gen_batch.repeat(repeat_times=rollout.n, interleave=True)

rollout 结束后,原始 batch 也 repeat,再与 response 合并。于是同一个 uid 下有 n 条 response。

总轨迹数是:

text
trajectory_count = data.train_batch_size * actor_rollout_ref.rollout.n

所以 ppo_mini_batch_size、显存、reward 计算耗时、actor update 耗时都要按 trajectory_count 理解,而不是只看 prompt 数。

如果 rollout.n=1compute_grpo_outcome_advantage() 会发现每个 uid 只有一个分数,源码里会把该组 mean 设为 0、std 设为 1。这样就失去了“组内相对比较”的主要意义。GRPO 通常需要 n >= 2,示例默认 5。

adv_estimator=grpo:组内 advantage 怎么算

源码在 verl/trainer/ppo/core_algos.py::compute_grpo_outcome_advantage()

text
scores = token_level_rewards.sum(dim=-1)
按 uid 收集同 prompt 的 scores
mean = mean(scores_in_group)
std = std(scores_in_group)
A_i = (score_i - mean) / (std + epsilon)
advantages = A_i * response_mask
returns = advantages

因为 reward manager 把 outcome reward 写到最后一个有效 token,sum(dim=-1) 就得到每条 response 的最终分。

algorithm.norm_adv_by_std_in_grpo=True 对应原始 GRPO 风格:减均值再除标准差。False 对应 Dr.GRPO 风格:只减均值,不除标准差。后者常和:

bash
actor_rollout_ref.actor.loss_agg_mode=seq-mean-token-sum-norm
actor_rollout_ref.actor.use_kl_loss=False
algorithm.norm_adv_by_std_in_grpo=False

一起出现。

use_kl_in_rewarduse_kl_loss:KL 放在哪里

verl 里常见两种 KL:

放置配置源码直觉
reward-side KLalgorithm.use_kl_in_reward=Trueray_trainer.py::apply_kl_penalty()先把 reward 扣掉 KL,再算 advantage
loss-side KLactor_rollout_ref.actor.use_kl_loss=Trueactor loss 路径 + core_algos.kl_penalty()policy loss 旁边额外加 KL loss

Qwen3 GRPO 示例默认:

text
algorithm.use_kl_in_reward=False
actor_rollout_ref.actor.use_kl_loss=True
actor_rollout_ref.actor.kl_loss_type=low_var_kl

这意味着 critic/score/*critic/rewards/* 通常相同;KL 约束主要体现在 actor 更新和 actor/*kl* 指标里。

RLOO、ReMax、REINFORCE++ 一些脚本更常见 reward-side KL。读脚本不要只看算法名,一定要看 KL 放在哪里。

loss_agg_mode:长回答是否天然更重

actor_rollout_ref.actor.loss_agg_mode 最终进入 verl/trainer/ppo/core_algos.py::agg_loss()

模式源码含义小白理解
token-mean所有有效 token 的 loss 求和后除以全局 token 数每个 token 权重近似相同,长回答贡献更多 token
seq-mean-token-sum每条 response 先 token-sum,再对 response 平均每条回答一个总分,长回答的 token loss 会累加
seq-mean-token-mean每条 response 先 token-mean,再对 response 平均每条回答权重相同,长短回答更公平
seq-mean-token-sum-normtoken-sum 后再除以固定或默认 horizonDr.GRPO/DAPO 常用,减轻长度归一化偏差

为什么这对 reasoning 很重要?长 CoT 可能有更多 token。如果 loss 聚合方式让长回答天然占更大权重,模型可能学会“写更长”而不是真的“答更对”。DAPO/Dr.GRPO 讨论的很多稳定性问题,都和这里有关。

reward 方差与动态采样

GRPO 依赖组内差异。如果同一个 prompt 的 n 条 response 全对或全错:

text
score_i - group_mean ≈ 0

advantage 就很弱。DAPO 的动态采样会过滤掉这种“组内没有差异”的 prompt,直到凑够有效训练 batch。当前 Qwen3 GRPO 最小示例不默认开启 DAPO 动态采样;读 docs/algo/dapo.md 时再把它看作 recipe 进阶。

batch size 与显存

读 GRPO batch size 时按 4 层拆:

  1. data.train_batch_size:prompt 数。
  2. rollout.n:每个 prompt 的 response 数。
  3. actor_rollout_ref.actor.ppo_mini_batch_size:actor 更新时的全局 mini-batch,面向 response/trajectory。
  4. ppo_micro_batch_size_per_gpuppo_max_token_len_per_gpu:每 GPU 实际 forward/backward 容量。

use_dynamic_bsz=True 时,脚本主要用 ppo_max_token_len_per_gpu 控制显存。它是性能/显存参数,不应该改变算法语义。

新手避坑

  • rollout.n=1 不适合作为 GRPO 学习实验。
  • 视觉脚本可能默认 Geo3K,并带 data.image_key=images,不适合作为纯文本最小例子。
  • DAPO-style 脚本里的 reward_manager=dapooverlong_buffer、动态采样是 recipe 细节,不是 GRPO 必需项。
  • Megatron/VeOmni/MindSpeed 脚本包含大量硬件并行配置,先把 FSDP Qwen3-8B 示例读懂。
  • 如果 reward 全 0 或全 1,不是 GRPO 公式坏了,通常是 reward 解析或采样多样性没有给学习信号。

本节参考与延伸阅读

  • examples/grpo_trainer/README.md
  • examples/grpo_trainer/run_qwen3_8b_fsdp.sh
  • examples/grpo_trainer/run_qwen3_8b_megatron.sh
  • verl/trainer/ppo/ray_trainer.py
  • verl/trainer/ppo/core_algos.py
  • docs/algo/grpo.md
  • docs/algo/dapo.md
  • docs/examples/config.rst
  • DeepSeekMath / GRPO paper
  • DAPO paper

面向源码阅读的 verl 学习文档。