调试指标:从异常现象定位问题
LLM post-training 的调试不要只盯最终 accuracy。RL 训练像一条流水线:数据、rollout、reward、KL、advantage、loss、显存和吞吐任何一段出问题,最后曲线都会很怪。
这页的目标是帮你做到两件事:
- 看到一个指标,知道它从哪段源码产生。
- 看到一个异常,知道先看样例、再看分布、最后改哪个配置。
指标从哪里来
训练主循环在 verl/trainer/ppo/ray_trainer.py::fit()。一轮 step 大致是:
rollout -> reward -> old_log_prob -> ref_log_prob -> values -> KL/reward -> advantage -> update_critic -> update_actor -> metrics关键指标来源如下:
| 指标 | 源码来源 | 说明 |
|---|---|---|
critic/score/* | metric_utils.compute_data_metrics() | token_level_scores.sum(-1),原始 rule reward/RM 分数 |
critic/rewards/* | metric_utils.compute_data_metrics() | token_level_rewards.sum(-1),真正用于 advantage 的 reward |
critic/advantages/* | compute_advantage() + core_algos.py | PPO 是 GAE;GRPO 是组内相对分数 |
critic/returns/* | core_algos.compute_gae_advantage_return() 等 | critic 学习目标;GRPO 通常等于 advantages |
critic/vf_explained_var | metric_utils.compute_data_metrics() | 只对有 critic 的 PPO 有意义 |
actor/entropy | ray_trainer.py::_compute_old_log_prob() 后聚合 | old policy 在 response token 上的熵 |
actor/pg_clipfrac | core_algos.compute_policy_loss_vanilla() | PPO clip 被触发的 token 比例 |
actor/ppo_kl | core_algos.compute_policy_loss_vanilla() | 当前 actor 与 old policy 的近似差异 |
actor/reward_kl_penalty | ray_trainer.py::apply_kl_penalty() | reward-side KL 时记录 |
response_length/* | metric_utils.compute_data_metrics() | response token 长度统计 |
timing_s/* | ray_trainer.py 里的 marked_timer() | 各阶段 wall time |
perf/throughput | metric_utils.compute_throughout_metrics() | 每 GPU token/s |
training/rollout_probs_diff_* | verl/utils/debug/metrics.py | rollout logprob 与 actor old logprob 差异 |
rollout_corr/* | rollout_corr_helper.py | rollout correction 开启后才有 |
reward manager 的额外返回也会进入指标链路。比如 reward function 返回 {"score": 1.0, "acc": True, "pred": "42"},score 用于训练,acc/pred 会进入验证聚合,产生 val-core/{data_source}/acc/mean@N、best@N、maj@N 等。
第一原则:先分清 score、reward、advantage
| 名字 | 代表什么 | 什么时候会不一样 |
|---|---|---|
score | scorer 原始分 | reward manager 直接写入 token_level_scores |
reward | 训练实际用分 | algorithm.use_kl_in_reward=True 时会扣 KL |
advantage | policy gradient 的方向和强度 | PPO 用 GAE;GRPO 用组内相对分 |
如果 algorithm.use_kl_in_reward=False,critic/rewards/* 通常等于 critic/score/*。Qwen3 GRPO 示例就是这种:KL 放在 actor loss 侧,而不是 reward 侧。
Reward 排查树
看到 critic/score/mean 长期全 0,不要先调学习率。按这个顺序:
- 抽样打印 prompt、response、ground truth、parsed answer、score。
- 检查 parquet 字段:
data_source是否能被default_compute_score()分派,reward_model.ground_truth格式是否和 scorer 对齐。 - 检查格式:GSM8K 默认找
#### number,MATH 默认找最后一个\boxed{}。 - 检查长度:
response_length/clip_ratio高时,最后答案可能被截断。 - 检查 scorer 返回:dict 必须有
score;最好同时返回acc、pred、format_reward。
常见修法:
| 现象 | 优先改什么 |
|---|---|
| 全 0 | 修 parser、prompt instruction、data_source、ground_truth |
| 很快全 1 | 查验证泄漏、reward 是否太宽、任务是否太简单 |
| 格式错很多 | 强化 prompt 格式,或在 reward 里拆 format_reward / accuracy_reward |
| 答案被截断 | 增大 data.max_response_length,或加 overlong/length 策略 |
KL 排查树
先判断 KL 放在哪里:
| 位置 | 配置 | 指标 |
|---|---|---|
| reward-side KL | algorithm.use_kl_in_reward=True | actor/reward_kl_penalty、critic/rewards 低于 critic/score |
| loss-side KL | actor_rollout_ref.actor.use_kl_loss=True | actor loss 侧生效,GRPO 示例常用 |
KL 快速升高时按顺序查:
- actor 学习率
actor_rollout_ref.actor.optim.lr是否太大。 kl_coef是否太小:reward-side 看algorithm.kl_ctrl.kl_coef,loss-side 看actor_rollout_ref.actor.kl_loss_coef。old_log_probs是否正确重算;如果启用 rollout correction,再看rollout_log_probs是否存在。- rollout 权重是否及时同步,异步/离线/多后端时看
training/rollout_probs_diff_*或rollout_corr/*。 - reward 是否太尖锐:全靠单一 0/1 reward 时,policy 可能猛追少数高分样本。
常见修法:
| 现象 | 可尝试配置 |
|---|---|
| KL 暴涨 | 降 actor_rollout_ref.actor.optim.lr,升 kl_loss_coef 或 algorithm.kl_ctrl.kl_coef |
| reward-side KL 后 reward 变负很多 | 降 algorithm.kl_ctrl.kl_coef,或改用 loss-side KL |
| rollout/train 概率差大 | 开 actor_rollout_ref.rollout.calculate_log_probs=True,再配置 algorithm.rollout_correction |
Advantage 排查树
PPO 和 GRPO 最大差异在这里。
PPO/GAE。 algorithm.adv_estimator=gae,需要 critic。core_algos.compute_gae_advantage_return() 用 values、gamma、lam 把最终 reward 传播到 response token。
排查顺序:
critic/score/*是否有分布。critic/rewards/*是否被 KL 扣到没信号。critic/advantages/max/min是否几乎为 0。critic/vf_explained_var是否长期很差。critic/vf_clipfrac、critic/vf_loss是否异常大。
GRPO/RLVR。 algorithm.adv_estimator=grpo,不训练 critic。compute_grpo_outcome_advantage() 按 uid 分组:
A_i = (R_i - group_mean) / (group_std + eps)排查顺序:
actor_rollout_ref.rollout.n是否大于 1。- 同组 reward 是否有差异;全对/全错都会让 advantage 弱。
algorithm.norm_adv_by_std_in_grpo是否符合 recipe。loss_agg_mode是否让长回答权重异常。
常见修法:
| 现象 | PPO | GRPO/RLVR |
|---|---|---|
| advantage 很弱 | 查 critic/value、KL、reward | 增大 rollout.n,提高采样温度,考虑 DAPO 动态采样 |
| advantage 极端 | 降 reward 尖锐度,查 value | 检查组内 std 很小导致归一化放大 |
| critic 指标很差 | 调 critic lr/batch,检查 value loss | GRPO 没 critic,不看 vf 指标 |
Clipfrac 与学习率
actor/pg_clipfrac 来自 core_algos.compute_policy_loss_vanilla(),表示 PPO clipped objective 有多少 token 触发了 clip。它高时说明新旧策略差异太大,PPO 经常踩刹车。
排查顺序:
actor/pg_clipfrac高,同时actor/ppo_kl高:actor 更新太猛。actor/pg_clipfrac高,但 reward 不涨:可能 advantage 噪声大或 reward 错。actor/pg_clipfrac长期接近 0:可能学习率太小、advantage 太弱、clip 太宽或 reward 没信号。
常见修法:
| 现象 | 可尝试配置 |
|---|---|
| clipfrac 很高 | 降 actor_rollout_ref.actor.optim.lr,降 ppo_epochs,增大 batch,增强 KL |
| clipfrac 接近 0 且 reward 不动 | 查 reward/advantage,再考虑升 lr |
| 长回答导致 clip 异常 | 检查 loss_agg_mode 和 response_length/clip_ratio |
Entropy:探索是否过早塌缩
actor/entropy 在 ray_trainer.py 重算 old logprob 时从 entropys 聚合得到。它反映 response token 分布是否还保留多样性。
排查顺序:
- entropy 快速下降,KL 上升:policy 可能过快变窄。
- entropy 很高但 reward 不涨:采样很随机,但没有学到有效偏好。
- GRPO 中 entropy 低且组内全相同:
rollout.n再大也没有差异。
常见修法:
| 现象 | 可尝试配置 |
|---|---|
| entropy 掉太快 | 降 actor lr,增强 KL,必要时设 actor_rollout_ref.actor.entropy_coeff |
| entropy 太高 | 降采样温度或检查 reward 信号是否太弱 |
| GRPO 组内太像 | 提高 rollout sampling 多样性,确认 rollout.n 生效 |
Response Length 排查树
长度指标来自 metric_utils.compute_data_metrics()。
| 指标 | 含义 |
|---|---|
response_length/mean | 平均生成长度 |
response_length/clip_ratio | response 打满 max_response_length 的比例 |
response_length_non_aborted/* | 排除 0 长 response 后的长度 |
response/aborted_ratio | response 长度为 0 的比例 |
prompt_length/clip_ratio | prompt 是否接近最大长度 |
排查顺序:
response_length/clip_ratio高:答案可能被截断,reward parser 也可能拿不到 final answer。- response 越来越长但 reward 不涨:reward 可能鼓励啰嗦,或 loss 聚合偏向长回答。
response/aborted_ratio高:rollout 后端、EOS、采样参数或服务状态要查。prompt_length/clip_ratio高:prompt 被截断会直接破坏题目。
常见修法:
| 现象 | 可尝试配置 |
|---|---|
| 大量打满长度 | 增大 data.max_response_length,或使用 DAPO overlong_buffer |
| 输出越来越长 | 检查 reward,调 loss_agg_mode,加入长度惩罚 |
| prompt 被截断 | 增大 data.max_prompt_length,清洗过长样本,谨慎设置 data.truncation |
Throughput 与显存排查树
性能指标来自 metric_utils.compute_timing_metrics() 和 compute_throughout_metrics()。
| 指标 | 先看什么 |
|---|---|
timing_s/gen 高 | rollout backend、max_response_length、rollout.n、vLLM/SGLang 并行 |
timing_s/ref 高 | reference logprob micro batch / dynamic batch |
timing_s/values 高 | PPO critic 负担;GRPO 没有这一段 |
timing_s/update_actor 高 | actor micro batch、sequence length、FSDP/Megatron 并行 |
timing_s/update_critic 高 | critic micro batch、critic model size |
perf/throughput 低 | 总 token/s/GPU 低,要结合各阶段 timing 看 |
显存和 batch size 要分四层:
data.train_batch_size:prompt 数。actor_rollout_ref.rollout.n:response 数放大倍数。actor_rollout_ref.actor.ppo_mini_batch_size:全局 actor mini-batch。ppo_micro_batch_size_per_gpu或ppo_max_token_len_per_gpu:单 GPU 真实容量。
常见修法:
| 现象 | 可尝试配置 |
|---|---|
| rollout OOM | 降 actor_rollout_ref.rollout.gpu_memory_utilization,降 max_response_length,调 TP |
| actor update OOM | 降 actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu,或降 ppo_max_token_len_per_gpu |
| critic OOM | 降 critic.ppo_micro_batch_size_per_gpu,启用 checkpointing/offload |
gen 很慢 | 调 rollout.tensor_model_parallel_size、max_num_seqs、max_num_batched_tokens,缩短 response |
update_actor 很慢 | 开 use_remove_padding=True、use_dynamic_bsz=True,调 micro batch |
docs/perf/perf_tuning.rst 的核心建议是:算法 batch 是全局概念,micro batch / max token len 是性能和显存概念。不要用“改 micro batch”来期待算法收敛行为发生本质变化。
PPO vs GRPO/RLVR 的调试差异
| 问题 | PPO/GAE | GRPO/RLVR |
|---|---|---|
| 是否有 critic | 有 | 通常没有 |
| 重点 advantage 指标 | critic/vf_explained_var、values、returns | 组内 reward 差异、rollout.n |
| KL 常见位置 | reward-side 或 loss-side 都可能 | Qwen3 GRPO 示例是 loss-side |
| batch 直觉 | rollout.n 常为 1 | rollout.n 是核心 |
| 常见失败 | critic 学不好、KL/reward 尺度不合 | 全对/全错组、reward 方差不足、长度偏置 |
| 先看源码 | compute_gae_advantage_return() | compute_grpo_outcome_advantage() |
如果你用 PPO 脚本调 GRPO 思路,会误盯 critic;如果你用 GRPO 思路调 PPO,又会忽略 value baseline 和 critic loss。这是新手最常见的错位。
一个实际排查顺序
每次训练不对劲,按这个顺序走:
- 看样例:prompt、response、ground truth、parsed pred、score。
- 看 reward 分布:
critic/score/mean/max/min。 - 看 reward 是否被改写:
critic/rewards/*vscritic/score/*。 - 看 advantage:PPO 查 value/returns,GRPO 查组内差异和
rollout.n。 - 看 policy update:
actor/pg_clipfrac、actor/ppo_kl、actor/entropy。 - 看长度:
response_length/clip_ratio、response/aborted_ratio。 - 看性能:
timing_s/*、perf/throughput。 - 最后再改配置,每次只改一两个变量。
本节参考与延伸阅读
verl/trainer/ppo/ray_trainer.pyverl/trainer/ppo/core_algos.pyverl/trainer/ppo/metric_utils.pyverl/trainer/ppo/rollout_corr_helper.pyverl/utils/debug/metrics.pyverl/workers/reward_manager/naive.pyverl/workers/reward_manager/dapo.pyexamples/ppo_trainer/run_qwen3_8b_fsdp.shexamples/grpo_trainer/run_qwen3_8b_fsdp.shdocs/perf/perf_tuning.rstdocs/perf/best_practices.rstdocs/algo/ppo.mddocs/algo/grpo.mddocs/algo/dapo.md- PPO 论文
- DeepSeekMath / GRPO 论文
- DAPO 论文