Policy loss 变体地图:先认入口,再读公式
verl 支持很多 advantage estimator 和 policy loss 变体。初学者最容易迷路的地方是:有些名字改变的是 advantages 怎么算,有些名字改变的是 actor loss 怎么算,还有些只是改变 loss 聚合或 trust region 的定义。
一句话分层:
advantage estimator: 先算 A_t
policy loss: 再用 A_t 更新 actor
loss aggregation: 最后把 token/sequence loss 聚成一个标量源码实现怎么读
1. 两个注册表
核心入口都在 verl/trainer/ppo/core_algos.py:
ADV_ESTIMATOR_REGISTRY
@register_adv_est(...)
get_adv_estimator_fn(...)
POLICY_LOSS_REGISTRY
@register_policy_loss(...)
get_policy_loss_fn(...)ray_trainer.compute_advantage() 负责根据 algorithm.adv_estimator 选 advantage 函数;actor worker 侧会根据 actor_rollout_ref.actor.policy_loss.loss_mode 选 policy loss。
所以你看到:
algorithm.adv_estimator=grpo
actor_rollout_ref.actor.policy_loss.loss_mode=gspo意思不是“GSPO advantage”,而是“用 GRPO 的 advantage,配 GSPO 的 policy loss”。
2. policy loss 的统一输入
大部分 compute_policy_loss_* 函数都接收:
| 源码变量 | 含义 |
|---|---|
old_log_prob | PPO anchor/proximal policy 的 token logprob |
log_prob | 当前 actor 的 token logprob |
advantages | 已算好的 token-level advantage |
response_mask | 有效 response token mask |
loss_agg_mode | token/sequence 聚合方式 |
config | actor 配置,含 clip、policy_loss 子配置和 global_batch_info |
rollout_is_weights | rollout correction 的可选 IS 权重 |
这套统一签名让 trainer 不用为每个算法写一套新主循环。
agg_loss() 是所有变体的地基
agg_loss() 不是小工具,它会改变训练偏好:
token-mean:
所有有效 token 等权
seq-mean-token-sum:
每条 response 的 token loss 先求和,再对 response 平均
seq-mean-token-mean:
每条 response 内先 token 平均,再对 response 平均
seq-mean-token-sum-norm:
sequence token-sum 后再除固定 horizon 或 loss_scale_factor如果你在长 CoT 训练里只看 policy loss 公式,不看 loss_agg_mode,很容易误判算法行为。长回答会不会因为 token 多而贡献更大,主要就在这里决定。
先读 vanilla,再读变体
Vanilla PPO loss
compute_policy_loss_vanilla() 是基准:
源码用最小化写法:
pg_losses1 = -advantages * ratio
pg_losses2 = -advantages * clamp(ratio, 1 - eps_low, 1 + eps_high)
pg_losses = maximum(pg_losses1, pg_losses2)其他很多变体都可以理解成:换 ratio、换 trust region、换 stop-gradient 位置、换聚合。
GPG
compute_gpg_outcome_advantage() 仍是 group baseline,但 advantage 是:
alpha = batch_size / count_nonzero(scores)
adv_i = alpha * (score_i - mean_uid) / f_normcompute_policy_loss_gpg() 更直接:
pg_losses = -log_prob * advantages它不使用 PPO ratio,也不强制 KL。官方 docs 也强调 GPG 是更简洁的 group policy gradient baseline。
GSPO
compute_policy_loss_gspo() 把 token-level ratio 换成 sequence-level 几何平均 ratio:
源码里:
negative_approx_kl_seq = sum(log_prob - old_log_prob) / seq_lengths
seq_importance_ratio = exp(stopgrad(seq_log_ratio) + log_prob - stopgrad(log_prob))后面仍然做 PPO-style clip。它关心的是“整条 response 的相对概率变化”,不只是单 token ratio。
SAPO
compute_policy_loss_sapo() 不做传统 hard clip,而是用 gate function 平滑控制 ratio:
gate = sigmoid(tau * (ratio - 1)) * (4 / tau)
pg_losses = -gate * advantagestau_pos 和 tau_neg 分别控制正优势、负优势 token 的门控强度。它适合放在 PPO clip 后作为平滑 trust region 的进阶阅读。
DPPO-TV / DPPO-KL
DPPO 变体不直接用 PPO ratio clip 作为 trust region,而是用概率差或二元 KL 判断 token 是否在有效区域:
dppo_tv:
valid_positive = (prob - old_prob) <= clip_high
valid_negative = (prob - old_prob) >= -clip_low
dppo_kl:
binary_kl = old_prob * (old_log_prob - log_prob)
+ (1 - old_prob) * log((1 - old_prob) / (1 - prob))
valid = binary_kl <= threshold, with direction conditionsloss 形态是:
truncated_ratio = clamp(ratio, max=clip_ratio_c).detach()
pg_losses = -advantages * truncated_ratio * log_prob * valid_mask这和 vanilla PPO 最大差异是:trust region 的判据从 ratio clip 换成 divergence mask。
CISPO
compute_policy_loss_cispo() 使用 clipped ratio,但把 clipped ratio stop-gradient:
clipped_ratio_sg = clamp(ratio, 1 - eps_low, 1 + eps_high).detach()
pg_losses = -clipped_ratio_sg * advantages * log_prob直觉:ratio 作为权重,不让梯度穿过 ratio 本身;梯度只通过 log_prob 更新 actor。这个设计和 rollout correction 的“IS 权重改变测度,但不要优化权重本身”很像。
GMPO / geo_mean
compute_policy_loss_geo_mean() 把 log ratio 先按 token 方向聚合成几何平均:
ratio = exp(sum(clamped_log_ratio * mask) / response_length)
advantage = sum(advantages * mask) / response_length
pg_losses = -advantage * ratio源码也特别说明目前更支持 sequence-level advantage。读它时要注意:它最后 torch.mean(pg_losses),不像大多数 loss 走 agg_loss()。
Clip-Cov / KL-Cov
这两类 loss 用 covariance 信号挑 token:
clip_cov:对一部分高 covariance token 额外置零,减少某些高影响 token 的更新。kl_cov:对高 covariance token 加 KL 项。
它们属于更偏研究实验的稳定化技巧。初学者读到 cov_all、topk、corr 时,只要先记住:它们不是改 advantage,而是在 policy loss 里按 token 选择性抑制或加正则。
OTB 和 GDPO:注意它们主要是 advantage estimator
OTB
compute_optimal_token_baseline_advantage() 是 advantage estimator,不是 policy loss。它需要 actor 额外输出:
actor_rollout_ref.actor.calculate_sum_pi_squared=True
algorithm.adv_estimator=optimal_token_baseline公式核心:
其中:
源码变量对应:
returns:G_t,reward-to-go。old_log_probs:采样 token 的log pi_t,源码用exp(old_log_probs)得到pi_t。sum_pi_squared:sum_v pi_j(v)^2。w_cumulative:累计W_t。baselines:每个 prompt group 的B_t^*。
如果启用 rollout correction,rollout_is_weights**2 会进入 W_t,用来修正 baseline 权重。
GDPO
compute_gdpo_outcome_advantage() 也是 advantage estimator。它不是先把多个 reward 维度加成一个分数,而是每个 reward 维度单独做 GRPO 归一,再加权求和:
for each reward key:
A_k = GRPO(component_reward_k)
A = sum(weight_k * A_k)
advantages = masked_whiten(A)适合有 format_reward、accuracy_reward 等多维 reward 的任务。
读源码的推荐顺序
compute_policy_loss_vanilla():先掌握 ratio、clip、dual-clip。agg_loss():理解 token/sequence 权重。compute_policy_loss_gpg()和compute_gpg_outcome_advantage():看最简单 group PG。compute_policy_loss_gspo()、geo_mean():看 sequence-level ratio。compute_policy_loss_dppo_tv()、compute_policy_loss_dppo_kl():看 divergence trust region。compute_policy_loss_cispo():看 stop-gradient clipped IS。- OTB/GDPO:回到 advantage estimator 层。
本节参考与延伸阅读
- verl 源码:
verl/trainer/ppo/core_algos.py的POLICY_LOSS_REGISTRY、ADV_ESTIMATOR_REGISTRY、agg_loss()、所有compute_policy_loss_*()、OTB/GDPO estimator。 - verl 源码:
verl/trainer/ppo/ray_trainer.py的compute_advantage()和 actor update 数据流。 - verl 官方文档:
docs/algo/gpg.md、docs/algo/dppo.md、docs/algo/otb.md、docs/algo/opd.md、docs/algo/grpo.md。 - 示例脚本:
examples/gpg_trainer/、examples/dppo_trainer/、examples/otb_trainer/、examples/gdpo_trainer/、examples/cispo_trainer/、examples/sapo_trainer/、examples/gmpo_trainer/。 - 论文:GPG: A Simple and Strong Reinforcement Learning Baseline for Model Reasoning。
- 论文:Rethinking the Trust Region in LLM Reinforcement Learning。
- 论文:Optimal Token Baseline。