最近核对:2026-10-10 · 进阶阅读 · 先读:PPO clipping

一个 token 的概率已经涨了不少,下一次更新还要不要继续推它?不同方法的答案并不相同:有的停下来,有的仍然更新、只是限制权重,还有的逐渐减弱影响。

只想先分清区别,读前面的表和三个例子就够了。公式与 CPU 检查可以按需展开。这里讨论策略更新中的 clipping,不是图文模型 CLIP,也不是 gradient clipping。

先看裁剪发生在哪儿

固定一条已采样回答和它的前缀,令 $\rho=\pi_\theta(a\mid s)/\pi_b(a\mid s)$。$\pi_b$ 是生成数据的 behavior policy,不是 Reference;advantage $A$ 在本次求导中固定。

方法 主要改哪里 读代码时看什么
PPO / 常见 GRPO 沿 advantage 方向走得够远后,截平这一项 min 选中了哪条分支
DAPO 的 Clip-Higher 分别设置上下阈值 不是同时放宽两边
Dual-clip PPO 再限制负 advantage 的极端惩罚 第二个 max 只用于 $A<0$
CISPO 限制乘在 log-prob 前的权重 权重要 detach,log-prob 不能断梯度
SAPO 平滑地减弱远离当前采样策略的更新 求导后的权重,不只是目标函数(objective function)值
GSPO 先汇总成回答级比值,再裁剪 mask、长度归一化和裁剪粒度

DAPO和 GSPO已有完整小例子。这里补另外三个容易混淆的做法;它们不是必须依次学会、依次替换的升级路线。

Dual-clip:降低概率,也别一次推得太猛

假设旧策略给某个动作 2% 的概率,当前策略给 10%,所以 $\rho=5$;它的 $A=-1$。普通 PPO 仍保留 $-5$ 这一项,要把升高的概率压下去。比值很大时,这个纠正也可能很大。

Dual-clip PPO 原论文的算法部分再给负 advantage 的目标加一个下限。取演示值 $c=3$,目标从 $-5$ 变成 $\max(-5,-3)=-3$;此时这一项进入平坦区。$A>0$ 时不加这层限制。

为什么是 max,不是再取一次 min?

先记普通 PPO 的最大化目标为 $J_{\rm PPO}$,则:

$$ J_{\rm dual}=\begin{cases} \max(J_{\rm PPO},cA),&A<0,\\ J_{\rm PPO},&A\geq0, \end{cases}\qquad c>1. $$

$A$ 为负,$cA$ 也是负数。取 max 是不让目标继续向负无穷掉;写成 min 反而没有达到这个目的。代码最小化 $-J$ 时,符号与选择操作也要一起换。

它处理的是一种极端权重,不会修复错误 reward 或无限陈旧的数据。论文场景是游戏中的分布式 RL;不能把该场景的效果直接当成语言模型(language model, LM)上的保证。

CISPO:权重到顶了,梯度还能继续走

仍用一个 token。旧概率 0.5、当前概率 0.7,$\rho=1.4$,$A=1$。PPO 上界设为 1.2 时,这一项已经平了。MiniMax-M1 §3.1的 CISPO 则把裁剪后的比值当作固定权重,再乘当前 log-prob。

为便于对比,我们自选上界 1.2,并且不设有效下界。本例对当前 log-prob 的目标梯度仍是 $+1.2$,而不是 0:涨得再多,权重不再跟着增大,但更新没有因为越过这条线就停止。

detach 到底放在哪里?

$$ J_{\rm CISPO}=\operatorname{sg}(\hat\rho)A\log\pi_\theta(a\mid s), \qquad \hat\rho=\operatorname{clip}(\rho,0,1.2). $$

所以 $\partial J/\partial\log\pi_\theta=\hat\rho A$。sg 表示 stop-gradient;不是把整个乘积都 detach。没有这一步,未达上界时还会多出权重自身的导数。不能用“前向数值看着一样”来验证实现。

这是局部目标,不是完整 CISPO trainer。原文还采用组相对 advantage 和有效 token 聚合。裁剪改变了权重;这里也没有补偿完整的前缀分布变化,不能称为无偏的整条轨迹 IS。$A=0$、action mask 或数值下溢仍可能让梯度为零,“不因越界丢掉 token”不等于“每个 token 永远有信号”。

SAPO:不突然关掉,而是逐渐减弱

这里的 SAPO 指 Soft Adaptive Policy Optimization v2。它使用平滑函数替代硬裁剪,并允许正负 advantage 使用不同的 $\tau$。这个 $\tau$ 控制更新衰减的快慢,不是生成时的采样温度。

目标函数与真正的梯度权重

令 $p=\sigma(\tau(\rho-1))$,单项最大化目标和梯度为:

$$ J_{\rm SAPO}=\frac{4}{\tau}pA, \qquad \frac{\partial J}{\partial\log\pi_\theta} =\underbrace{4p(1-p)}_{w(\rho)}\rho A. $$

$w$ 在 $\rho=1$ 时等于 1;偏离时减小。$w$ 和完整系数 $w\rho A$ 不是一回事:前者不超过 1,不代表后者的绝对值也不超过 1。近似的序列级解释有额外假设,不能把这个逐 token 公式直接说成 GSPO。

取教学参数 $\tau=2$、$A=1$:$\rho=1$ 时,目标对 log-prob 的梯度是 1;$\rho=1.4$ 时约为 1.1979。它相对未衰减的 1.4 变弱了,但没有在 1.2 处突然变成零。极端比值下,浮点 sigmoid 仍可能饱和;“平滑”不保证永远有可用梯度,也不是严格的 KL 约束。

用同一个输入检查梯度

下面比较的是最大化目标对选中 log-prob 的局部导数。最小化 loss 时取负号;之后还要反传过 log-softmax 和整个模型。不同方法的原始 loss 值不能当作一把质量尺子。

固定 $A=1,\rho=1.4$ 局部导数
PPO,上界 1.2 0
Dual-clip,$c=3$ 0,正 advantage 分支没变
CISPO,上界 1.2 1.2
SAPO,$\tau=2$ 约 1.1979
在 CPU 上运行这个对照

示例假设输入是同形状的有限数值,省略 mask、归约、KL、rollout 和优化器;函数本身没有做完整的输入检查,不是可直接投入训练的 loss API。比值取温和范围,避免把指数溢出混入这次机制演示。

import math
import torch

def local_surrogates(log_prob, old_log_prob, advantage):
    fixed_advantage = advantage.detach()
    ratio = (log_prob - old_log_prob.detach()).exp()
    ordinary = torch.minimum(
        ratio * fixed_advantage,
        ratio.clamp(0.8, 1.2) * fixed_advantage,
    )
    dual = torch.where(
        fixed_advantage < 0,
        torch.maximum(ordinary, 3.0 * fixed_advantage),
        ordinary,
    )
    capped_weight = ratio.clamp(0.0, 1.2).detach()
    smooth = 2.0 * torch.sigmoid(2.0 * (ratio - 1.0))
    return {
        "ppo": ordinary,
        "dual": dual,
        "cispo": capped_weight * fixed_advantage * log_prob,
        "sapo": smooth * fixed_advantage,
    }

current = torch.tensor(math.log(0.7), dtype=torch.float64, requires_grad=True)
old = torch.tensor(math.log(0.5), dtype=torch.float64)
advantage = torch.tensor(1.0, dtype=torch.float64)
terms = local_surrogates(current, old, advantage)
derivatives = {
    name: torch.autograd.grad(term, current, retain_graph=True)[0].item()
    for name, term in terms.items()
}
assert derivatives["ppo"] == derivatives["dual"] == 0
assert math.isclose(derivatives["cispo"], 1.2)
assert math.isclose(derivatives["sapo"], 1.197894300513649, rel_tol=1e-12)

想试负 advantage,就把当前概率改为 0.1、旧概率改为 0.02、优势改为 −1,并去掉针对上一组数字的三个断言。PPO 和 Dual-clip 的目标分别为 −5、−3;对当前 log-prob 的导数分别为 −5、0。自动化测试另覆盖两种符号、零 advantage、多个比值和断梯度位置。

换算法前,先记下这些数

  • 正负 advantage 分开看:哪一边的 ratio 尾部更长?哪些位置真正进入平坦区?
  • 除了“是否被 clip”,记录更新系数的分布。CISPO 的权重到顶比例,不等于 PPO 的零梯度比例。
  • 保持数据、生成预算、mask、loss reduction 和评估一致。一次同时改五个开关,很难解释改善来自哪儿。
  • 同时查 reward、长度、KL 和失败样本。训练曲线更平滑,并不等于答案更可靠。

本页只验证局部公式与 CPU 梯度,不报告模型效果或 GPU 吞吐。继续看异步轨迹的旧数据问题,或回到后训练总览选择自己的阅读路线。

本页术语 2
目标函数 objective function
训练直接优化的数学量;它不一定等于最终产品目标
语言模型 language model, LM
对文本序列建模,用上下文预测或生成后续内容