GRPO 配置解读
GRPO 配置最重要的不是某个单独参数,而是这些参数是否共同构成“同一个 prompt 采样多条 response,然后做组内相对比较”。
这页原来哪里不够适合新手
原来的版本讲了 rollout.n 和 KL,但还缺三层对应关系:
- 配置字段如何进入
RayPPOTrainer.fit()。 adv_estimator=grpo如何走到compute_grpo_outcome_advantage()。loss_agg_mode为什么会改变长回答的训练权重。
最小 GRPO 命令形态
典型入口:
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.shexamples/grpo_trainer/run_qwen3_8b_fsdp.sh 的核心配置是:
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_klMegatron 版本 run_qwen3_8b_megatron.sh 算法字段相同,只是 actor/ref 改成 Megatron 并行配置。新手读算法时先看 FSDP 版,读大规模训练时再看 Megatron 版。
一张表看配置到源码
| 配置 | 源码路径 | 作用 |
|---|---|---|
algorithm.adv_estimator=grpo | ray_trainer.py::compute_advantage() | 分派到 GRPO advantage |
actor_rollout_ref.rollout.n | ray_trainer.py::fit() | 每个 prompt repeat 成 n 条采样轨迹 |
algorithm.norm_adv_by_std_in_grpo | core_algos.py::compute_grpo_outcome_advantage() | 是否除以组内标准差 |
algorithm.use_kl_in_reward | ray_trainer.py::apply_kl_penalty() | KL 是否从 reward 里扣 |
actor_rollout_ref.actor.use_kl_loss | actor update loss 路径 | KL 是否作为 actor loss 项 |
actor_rollout_ref.actor.loss_agg_mode | core_algos.py::agg_loss() | token/sequence loss 如何聚合 |
actor_rollout_ref.actor.ppo_mini_batch_size | actor worker mini-batch | 对 rollout 后的 trajectory 数生效 |
data.train_batch_size | dataloader | 每步 prompt 数 |
rollout.n:GRPO 的地基
在 RayPPOTrainer.fit() 中,原始 batch 会先写 uid:
batch.non_tensor_batch["uid"] = uuid for each prompt然后生成 batch 会被 repeat:
gen_batch.repeat(repeat_times=rollout.n, interleave=True)rollout 结束后,原始 batch 也 repeat,再与 response 合并。于是同一个 uid 下有 n 条 response。
总轨迹数是:
trajectory_count = data.train_batch_size * actor_rollout_ref.rollout.n所以 ppo_mini_batch_size、显存、reward 计算耗时、actor update 耗时都要按 trajectory_count 理解,而不是只看 prompt 数。
如果 rollout.n=1,compute_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():
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 风格:只减均值,不除标准差。后者常和:
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_reward 与 use_kl_loss:KL 放在哪里
verl 里常见两种 KL:
| 放置 | 配置 | 源码 | 直觉 |
|---|---|---|---|
| reward-side KL | algorithm.use_kl_in_reward=True | ray_trainer.py::apply_kl_penalty() | 先把 reward 扣掉 KL,再算 advantage |
| loss-side KL | actor_rollout_ref.actor.use_kl_loss=True | actor loss 路径 + core_algos.kl_penalty() | policy loss 旁边额外加 KL loss |
Qwen3 GRPO 示例默认:
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-norm | token-sum 后再除以固定或默认 horizon | Dr.GRPO/DAPO 常用,减轻长度归一化偏差 |
为什么这对 reasoning 很重要?长 CoT 可能有更多 token。如果 loss 聚合方式让长回答天然占更大权重,模型可能学会“写更长”而不是真的“答更对”。DAPO/Dr.GRPO 讨论的很多稳定性问题,都和这里有关。
reward 方差与动态采样
GRPO 依赖组内差异。如果同一个 prompt 的 n 条 response 全对或全错:
score_i - group_mean ≈ 0advantage 就很弱。DAPO 的动态采样会过滤掉这种“组内没有差异”的 prompt,直到凑够有效训练 batch。当前 Qwen3 GRPO 最小示例不默认开启 DAPO 动态采样;读 docs/algo/dapo.md 时再把它看作 recipe 进阶。
batch size 与显存
读 GRPO batch size 时按 4 层拆:
data.train_batch_size:prompt 数。rollout.n:每个 prompt 的 response 数。actor_rollout_ref.actor.ppo_mini_batch_size:actor 更新时的全局 mini-batch,面向 response/trajectory。ppo_micro_batch_size_per_gpu或ppo_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=dapo、overlong_buffer、动态采样是 recipe 细节,不是 GRPO 必需项。 - Megatron/VeOmni/MindSpeed 脚本包含大量硬件并行配置,先把 FSDP Qwen3-8B 示例读懂。
- 如果 reward 全 0 或全 1,不是 GRPO 公式坏了,通常是 reward 解析或采样多样性没有给学习信号。
本节参考与延伸阅读
examples/grpo_trainer/README.mdexamples/grpo_trainer/run_qwen3_8b_fsdp.shexamples/grpo_trainer/run_qwen3_8b_megatron.shverl/trainer/ppo/ray_trainer.pyverl/trainer/ppo/core_algos.pydocs/algo/grpo.mddocs/algo/dapo.mddocs/examples/config.rst- DeepSeekMath / GRPO paper
- DAPO paper