这篇文章把「Agentic RL / LLM-RL 训练里常见的 Policy Gradient (PG) loss 到底由哪些组件拼起来」讲清楚,重点解释:
- PPO-clip:为什么要 clip、clip 住了哪些情况、什么时候梯度为 0
- Dual-clip:它在 PPO-clip 基础上到底“多 clip 了什么”,解决什么不稳定
- Entropy / KL:为什么要加正则、权重怎么理解
- 聚合(aggregate):token/sequence/group 维度的 sum/mean 会如何改变梯度尺度
本文偏“工程视角”:你看完应该能把这些项在代码里正确实现出来,并能解释训练曲线为什么会那样。
补充说明:下面不少“实现细节/监控指标/聚合模式”的表述,我会刻意对齐 verl 生态里常见写法。
0. 背景:我们在优化什么
强化学习(policy gradient)最朴素的目标是最大化期望回报:
$$
\max_\theta \ \mathbb{E}{\tau \sim \pi\theta} [R(\tau)]
$$
但直接对回报做梯度很难,于是我们引入可优化的代理目标(surrogate objective),用“概率比 + 优势函数”把“回报最大化”转成可微的 loss。
当你训练的是 LLM(token 级动作)或 Agent(工具调用/检索/规划动作),形式几乎一样,只是:
- 动作可能是 token,也可能是工具调用、检索 query、规划步骤
- 优势可能来自 reward model、对比评分器、规则评测、人工偏好、任务成功率等
1. 记号与核心中间量(非常重要)
一条轨迹里第 $t$ 步:
- 状态:$s_t$(例如 prompt + history)
- 动作:$a_t$(token / 工具调用 / 选择哪个候选等)
- 新策略:$\pi_\theta(a_t|s_t)$
- 旧策略:$\pi_{\text{old}}(a_t|s_t)$
- 优势:$A_t$(“这步动作比平均水平好多少”)
重要采样比(importance ratio):
$$
r_t = \frac{\pi_\theta(a_t|s_t)}{\pi_{\text{old}}(a_t|s_t)}
= \exp(\log \pi_\theta(a_t|s_t) - \log \pi_{\text{old}}(a_t|s_t))
$$
为什么要 ratio?
- 你的数据通常是用旧策略采样的(rollout 时的策略),但你想更新新策略
- ratio 允许你在“旧数据”上估计“新策略”目标(近似、但有效)
2. Vanilla Policy Gradient(从这里出发)
最常见的一种 PG 代理目标(最大化形式):
$$
L_{\text{PG}}(\theta) = \mathbb{E}[\log \pi_\theta(a_t|s_t) \cdot A_t]
$$
工程里我们一般写成“最小化 loss”,所以常见写法是取负号:
$$
\text{loss}{\text{PG}} = -\mathbb{E}[\log \pi\theta(a_t|s_t) \cdot A_t]
$$
问题是:如果你直接这么做,更新会非常不稳定。
原因直观上是:$\log \pi_\theta$ 的梯度可能会推动策略一步跨得很大,尤其当 $A_t$ 大、或某些动作在旧策略里概率极小导致 ratio 爆炸时。
3. PPO-clip:用 clip 做“近似 trust region”
PPO 的核心代理目标(最大化形式):
$$
L_{\text{clip}}(\theta)=
\mathbb{E}\big[\min\big(r_t A_t,\ \text{clip}(r_t, 1-\epsilon, 1+\epsilon)A_t\big)\big]
$$
其中 $\epsilon$ 通常是 0.1 或 0.2。
在工程实现里,你几乎一定会看到最小化 loss 的等价写法(HF PPO / verl 常用):
$$
L^{\text{PPO}} = \mathbb{E}_t[\max(-r_t\hat A_t,\ -\text{clip}(r_t,1-\epsilon,1+\epsilon)\hat A_t)]
$$
它就是上面 maximize 版本取负号后,把 min 翻转成 max 得到的。
3.1 直觉:限制“新策略相对旧策略”的变化幅度
- $r_t$ 表示“新策略把这个动作概率放大/缩小了多少”
- clip 把 $r_t$ 限制在 $[1-\epsilon, 1+\epsilon]$
所以 PPO-clip 做的事情是:你可以变,但别变太猛。
3.2 什么时候 clip 会“生效”(梯度变 0 或被截断)
把情况按 $A_t$ 的符号拆开看最清楚:
- 当 $A_t > 0$(这个动作是“好动作”,我们希望提升概率)
- 如果 $r_t > 1+\epsilon$:提升得太猛了,会被 clip,等价于“这部分梯度被截掉”
- 当 $A_t < 0$(这个动作是“坏动作”,我们希望降低概率)
- 如果 $r_t < 1-\epsilon$:降低得太猛了,会被 clip
你会发现:PPO 的 clip 本质是在限制“朝着对优势有利的方向”更新过头。
3.3 训练时看什么:reward 曲线优先,其次看这些“稳定性指标”
PPO-clip 的 loss 是 surrogate objective,不一定和“任务真的变好”单调对应;实践里更应该关注 rewards curve(你定义的任务 reward / RM reward / success rate 等),loss curve 只当作辅助信号。
除了 reward 曲线,建议至少监控这些量(很多训练框架会直接给出):
actor entropy:熵太快掉到很低,常见是探索不足/模式塌缩的前兆。KL(对 reference / SFT / old policy 的偏离):漂移过大常见会让输出质量崩。clip fraction:被 clip 的样本比例(token-level 或 seq-level,看实现)。advantage的统计量:均值/方差/分位数,尤其注意优势刻度突然变大或分布漂移。
clip fraction 怎么解读?
- 直觉:被裁剪的样本通常“不给有效梯度”,等价于 有效 batch 变小。clip fraction 越高,更新越被少数未裁剪样本主导,学习容易停滞或变得不稳定。
- 实践经验:0.1-0.4 往往是比较常见的区间(不是硬规则,取决于任务/优势刻度/学习率等)。
clip fraction 和 KL 的耦合(非常实用的排查思路)
- clip fraction 高 + KL 也高:常见是步子太猛(学习率大、PPO epoch 多、优势刻度过大、reward 过陡等)。
- clip fraction 高但 KL 不高:常见是对同一批数据复用太多(epoch 太多 / 复用过度),把 $r_t$ 推到边界但整体分布偏离并不大。
- clip fraction 长期接近 0:常见是步子太小(学习率小、优势过小、KL/entropy 系数过大把更新压住)。
3.4 policy_loss 曲线怎么看(注意符号约定)
不同代码对 policy_loss 的符号约定不完全一致:有的记录“要最小化的 loss”(常见为负),有的记录“maximize 的 surrogate”(常见为正)。
一个可操作的解读方式是:
- 如果
policy_loss(最小化形式)长期非常负:通常意味着优势项在推动策略快速提升,但也可能是 critic 跟不上 actor(critic 低估,adv 偏大),容易引入不稳定。 - 如果
policy_loss(最小化形式)长期为较大的正:通常是“坏动作占比大/优势为负占主导”,策略在变差或学不到东西的信号。 - 更理想的趋势:在 0 附近上下震荡,说明 actor/critic 在互相追赶并逐步稳定。
4. Dual-clip:为什么 PPO-clip 还不够
在实践里,很多人会再加一个 dual-clip,主要解决这样一种现象:
当 $A_t < 0$(坏动作),但 $r_t$ 可能非常大(新策略反而大幅提高了坏动作的概率)时,PPO-clip 在某些写法下对这块的“保护”不够,loss/梯度可能会被放大,导致训练震荡。
你可以把 dual-clip 理解成:对某些极端 case 再加一道上限/下限,避免目标函数在异常 ratio 下过度放大。
4.1 Verl/HF 常见写法(最小化 loss 形式,对齐更容易)
先把 PPO 写成最小化 loss(忽略期望符号,只写单个样本/单个 token 的形式):
$$
L^{\text{PPO}}=\max(-r_t\hat A_t,\ -\text{clip}(r_t,1-\epsilon,1+\epsilon)\hat A_t)
$$
Dual-clip(常见引用:arXiv:1912.09729)在很多实现里会对负优势样本再加一道“上界保护”:
$$
L^{\text{dual-clip}}=\min(L^{\text{PPO}},\ -c\cdot \hat A_t), \quad c>1
$$
直觉解释:
- 当 $\hat A_t < 0$ 时,$-c\hat A_t$ 是正数,它会把某些极端情况下的巨大正 loss 截住(避免 ratio 异常大导致 loss/梯度过度放大)。
- 当 $\hat A_t \ge 0$ 时,这个额外项通常不启用或不会改变结果(不同代码会用条件分支实现)。
4.2 参数 $c$ 怎么选
经验上很多实现会用 2 到 5 的量级(比如 3)。它不是越大越好:
- $c$ 太小:保护太强,学习可能变慢
- $c$ 太大:保护太弱,起不到稳定作用
5. Entropy:为什么要鼓励“更随机”
entropy bonus 常见形式(加入到最小化 loss):
$$
\text{loss} \leftarrow \text{loss} - \beta \cdot H(\pi_\theta)
$$
5.1 从 logits 计算 token-level entropy(verl 常见写法)
很多实现会直接从 logits 计算每个 token 的熵(注意输出仍然是 $[B,T]$,后面要走 agg_loss 聚合):
$$
H = \log\sum \exp(\text{logits}) - \sum p\cdot \text{logits}, \quad p=\text{softmax}(\text{logits})
$$
1 | import torch |
直觉:
- 在早期训练,鼓励探索(别过早塌缩到某一种输出模式)
- 在 LLM 训练里,entropy 太低会导致输出变得呆板,甚至出现 mode collapse
怎么调参:
- $\beta$ 太大:模型“太随机”,reward 上不去
- $\beta$ 太小:容易塌缩或过拟合到奖励漏洞
6. KL:为什么用 KL 约束,而不是 JS
很多 LLM-RL(尤其 RLHF / RLAIF / 各种 agentic RL 变体)都会加 KL 正则,典型是约束当前策略别偏离参考策略 $\pi_{\text{ref}}$(常见是 SFT 模型或某个 frozen baseline):
$$
\text{loss} \leftarrow \text{loss} + \beta \cdot \text{KL}(\pi_\theta ;||; \pi_{\text{ref}})
$$
6.1 KL 在工程上“很好算”
在 token-level 的实现里,你往往只需要:
$$
\text{KL} \approx \mathbb{E}[\log \pi_\theta(a|s) - \log \pi_{\text{ref}}(a|s)]
$$
(实际会有更精细的估计方式,但核心是:你只要能拿到 logprob 就行)
而 JS 散度需要混合分布:
$$
\text{JS}(P||Q) = \tfrac{1}{2}\text{KL}(P||M) + \tfrac{1}{2}\text{KL}(Q||M),\ \ M=\tfrac{1}{2}(P+Q)
$$
这意味着你要显式构造/评估 $M$,工程上更麻烦、也更贵。
6.2 KL 更符合“单向约束”的需求
在 RLHF 里,你常见的真实需求是:
“在提高 reward 的同时,别偏离 reference 太远。”
这本质是一个单向的、带约束的优化问题,KL 非常自然。
JS 虽然对称、有界,但它的对称性并不一定是你想要的,而且在高维动作空间(比如大词表)里,JS 的数值/梯度性质也未必更稳定。
6.3 更重要的一点:PPO/TRPO 的理论也经常围绕 KL
TRPO 的 trust region 约束是直接写 KL 的;PPO 也是在用 clip 近似这个约束。
所以当你把 PPO-clip + KL penalty 放在一起看,会发现它们在做同一类事情:
- PPO-clip:约束“这一步更新别太大”
- KL penalty:约束“整体别偏离 reference 太远”
6.4 KL 放在 reward 里 vs 放在 loss 里(RLHF/LLM-RL 很常见)
在 LLM-RL 里,KL 有两种非常常见的“落点”:
- 加在 loss 里:
policy_loss += kl_loss * kl_coef(verl/HF 里常见)。 - 写进 token-level reward(reward shaping):把 KL 看成一个“密集惩罚”,例如常见形式
$$
r_t = r_{\text{RM}} - \beta \log\frac{\pi_\theta(a_t|s_t)}{\pi_{\text{SFT/ref}}(a_t|s_t)}
$$
其中 $r_{\text{RM}}$ 往往只在序列最后一个 token 非零(或以序列级 reward 形式给出),而 KL 惩罚是 token 级的“每步都扣分”。两者目标一致:限制偏离,但优化动态、日志解读、以及和 advantage/GAE 的耦合会有差异。
7. 聚合(Aggregate):token / sequence / group 的差别
LLM 的 logprob、advantage 很多时候都是 token-level 的张量(形状 $[B, T]$)。
你最终需要把它聚合成一个标量 loss 才能 backward()。常见聚合方式:
- token mean:对所有有效 token 取平均
- sequence mean:先对每个序列求平均,再对 batch 平均
- token sum:对 token 求和(注意会放大梯度尺度)
- group mean(GRPO 常见):先在 group 内聚合,再在 group 间聚合
核心结论:聚合方式会改变梯度的等效尺度,从而影响你对学习率、clip、KL 系数等超参的感受。
如果你把 mean 换成 sum,很多时候你会发现“同样的超参突然不稳定了”,原因往往就是梯度整体变大了。
7.1 常见 loss_agg_mode 的数学定义(带 mask)
设 loss 矩阵 $L\in\mathbb{R}^{B\times T}$,mask $M\in{0,1}^{B\times T}$(为 1 的位置计入损失):
token-mean- $$\mathcal{L}=\frac{\sum_{i=1}^{B}\sum_{j=1}^{T}L_{i,j}M_{i,j}}{\sum_{i=1}^{B}\sum_{j=1}^{T}M_{i,j}}$$
seq-mean-token-sum- $$\mathcal{L}=\frac{1}{B}\sum_{i=1}^{B}\Big(\sum_{j=1}^{T}L_{i,j}M_{i,j}\Big)$$
seq-mean-token-mean(很多论文把它当成“sample-level loss”)- $$\mathcal{L}=\frac{1}{B}\sum_{i=1}^{B}\Big(\frac{\sum_{j=1}^{T}L_{i,j}M_{i,j}}{\sum_{j=1}^{T}M_{i,j}}\Big)$$
seq-mean-token-sum-norm(一种“按长度做归一化”的 token-sum)- $$\mathcal{L}=\frac{\sum_{i=1}^{B}\sum_{j=1}^{T}L_{i,j}M_{i,j}}{T}$$
7.2 和 GRPO / DAPO / DrGRPO 的对应(为什么会影响长 CoT 稳定性)
以 verl 文档/实现里的口径总结(不同实现会有细微差异,但核心直觉一致):
- GRPO 原始论文常用
seq-mean-token-mean(sample-level),在长 CoT 场景可能更不稳定。 - DrGRPO 倾向使用
seq-mean-token-sum-norm(降低长度偏置/数值不稳)。 - DAPO 倾向使用
token-mean(直接在 token 维度平均,梯度尺度更直观)。
如果你的任务输出长度差异很大(例如 deep research 报告长短不一),聚合方式几乎一定会影响训练是否稳定,甚至会直接改变“模型更偏好长输出还是短输出”(长度偏置)。
7.3 GSPO/GRPO:序列级 ratio、长度偏置与 stop-gradient trick
很多 LLM-RL 的不稳定来自两个因素叠加:
- token-wise ratio 在长序列上更容易数值爆炸/塌缩
- 长序列天然贡献更多 token,导致“长输出占更多梯度份额”(长度偏置)
以 GSPO 的一种常见写法为例,会先定义序列级的归一化 ratio:
$$
s_i(\theta)=\Big(\frac{\pi_\theta(y_i|x)}{\pi_{\text{old}}(y_i|x)}\Big)^{1/|y_i|}
=\exp\Big[\frac{1}{|y_i|}\sum_t \log\frac{\pi_\theta(y_{i,t}|x,y_{i,<t})}{\pi_{\text{old}}(y_{i,t}|x,y_{i,<t})}\Big]
$$
再把它“分摊回 token 级”,并用 stop_gradient(记作 sg[·])避免破坏梯度结构:
$$
\log s_{i,t}(\theta)=\text{sg}[\log s_i(\theta)] + \log\pi_\theta(y_{i,t}|\cdot) - \text{sg}[\log\pi_\theta(y_{i,t}|\cdot)]
$$
直觉:让每个样本(序列)在 ratio 上先被长度归一化,再决定 token 级梯度怎么分配,从而缓解长度偏置和 token-wise 数值问题。
9. PyTorch 伪代码:把这些项拼起来(可直接对照你用的框架)
下面给一个“最常见”的结构示例,假设你已经有:
logp_new: 当前策略对采样动作的 logprob,形状[B, T]logp_old: rollout 时旧策略 logprob(需要detach),形状[B, T]advantages: 优势(需要detach),形状[B, T]mask: 有效 token mask(padding 位置为 0),形状[B, T]entropy: 可选,形状[B, T]logp_ref: 参考策略 logprob(可选),形状[B, T]
1 | import torch |
9.1 读代码时最容易踩坑的点
- 最大化 vs 最小化:论文写 maximize,代码写 minimize;
min/max会翻转。 - advantages / logp_old 要 detach:它们是“常数”,不应对它们反传。
- mask 一定要做:padding token 不能算进 loss。
- 聚合方式会改变梯度尺度:mean/sum 会影响你对 lr/clip/kl 的直觉。
9.2 如果你用的是 verl:如何“真的改到代码里”
modern_genai_bilibili 的笔记里给了一个很实用的路线图(这里总结成可执行的 checklist):
- 全局搜
.backward(),先找到训练主循环真正反传的 loss(通常是loss.backward())。 - 在
core_algos.py(或同名文件)里找compute_policy_loss,它通常是 “PG loss 的注册中心”。 - 通过类似
@register_policy_loss("xxx")的注册机制添加新的pg_loss变体(例如把 entropy/KL/特殊 ratio 写法组合进去)。 - 如果你要改的是 training logic(比如动态采样、rollout 管理、异步 agent loop),通常需要自定义 Trainer(笔记里提到的
RayDAPOTrainer就属于这类)。
10. 这和 Agentic RL / Deep Research 有什么关系
如果你做的是“deep research agent”(检索 + 阅读 + 归纳 + 写作),你不一定要微调 LLM 本体。
更现实的路线是:
- LLM 用 API(当成 black-box policy 或 planner)
- 你训练的是 agent 的决策层:何时检索、检索什么、读哪些文档、如何分解任务、如何分配预算
- reward 来自任务指标:答案质量、引用覆盖、事实一致性、时间/成本、用户满意度等
这时 PPO/GRPO 这些 loss 仍然是核心工具,只是动作空间从“token”扩展到了“工具与策略”。
如果你具体做的是“deep research 报告型任务”,建议尽早把评测闭环定下来:
- RACE:评估报告质量(覆盖度/洞察/指令遵循/可读性),并用相对评分避免“打分膨胀”。
- FACT:逐条核查“每一个引用是否支撑对应陈述”,把 citation trustworthiness 从纯生成质量里拆出来单独评。
11. 小结
- PPO-clip 的本质:用 ratio + clip 做稳定的 surrogate objective
- dual-clip 的本质:对极端 case 再加一道保护,减少震荡
- entropy/KL 的本质:一个防塌缩,一个防漂移
- aggregate 的本质:改变梯度尺度,影响所有超参的手感
