🎉恭喜985硕斩获xhs实习offer🎉

春招/秋招/实习/社招/申研
大陆/美/新/澳/欧/港……
「算法+开发+产品」从现在到入职!五位清华老师全程陪你跑!
详情咨询微信:THU_LLM(备注“公众号”)
前言
书接上文,在《verl 源码走读:GRPO 的源码完整实现(上篇)》中,我们从 DeepSeek-V3 的官方训练配置出发,梳理了 GRPO 的公式推导和八个训练阶段的对应关系,逐行走读了 log_prob 和 advantage 两个核心计算单元的源码实现。
本篇聚焦最后一个核心单元——policy loss。它是整个流程里唯一对当前策略求梯度的环节,也是机制最密集的一段代码。涉及的每个机制都有独立的工程动机:loss 函数怎么注入到训练循环、clip 为什么要拆成不对称的上下界、dual-clip 那条上限卡在哪、low_var_kl 是哪个估计器、agg_loss 如何闭环上篇第 2 章的长度归一化讨论。逐个拆开。
6. GRPO 的 loss 源码实现
在进入代码之前,先用上篇的坐标系做一次定位。上篇把 GRPO 的训练流程拆成八个阶段,其中阶段一至七产出的固定项(old log prob、ref log prob、advantage)全程 no_grad,算好后存进 data。本章走读的是阶段八——actor 更新——的内部实现,也是整个流程里唯一对当前策略求梯度的环节。
首先先看一下整体的调用栈:
【图示】loss 计算调用栈
loss_fn 的绑定在 init_model 里一次性完成。普通训练走 ppo_loss,开启蒸馏时切换到 distillation_ppo_loss,两条路共享同一套注入机制:
if self.distillation_enabled:
self.loss_fn = partial(
distillation_ppo_loss,
config=actor_config,
distillation_config=distillation_config,
)
else:
self.loss_fn = partial(ppo_loss, config=actor_config)
self.actor.set_loss_fn(self.loss_fn)
distillation_ppo_loss 对应 On-Policy Distillation(OPD)——在 PPO 策略损失基础上叠加教师模型的蒸馏信号,后续会单独走读。
prepare_model_outputs 在这里算的是当前策略的 log_probs,和上篇阶段三(old log prob)走同一套 logprobs_from_logits 路径,区别在于 log_probs 是有梯度的,在 train_batch 和 mini_batch 不相等的时候,二者数值也会不一样。
ppo_loss 是薄的组装层,核心计算委托给注册表。想替换损失函数,只需注册一个新名字,ppo_loss 的组装逻辑(global_batch_info 透传、entropy 正则、KL 惩罚、metrics 聚合)完全不用动。目前注册了 "vanilla"(标准 PPO clip,本文主线)以及若干扩展变体。两种扩展机制各管一类:注册表管策略损失的变体,loss_fn 绑定管训练范式的切换。
ppo_loss 的内部实现分三层,按照从内到外的顺序,下面我们先从最核心的中间层 compute_policy_loss_vanilla 开始走读——它是重要性比率和 clip 逻辑的实际执行者,ppo_loss 的组装和 agg_loss 的聚合都依赖它的输出。ppo_loss 的外层组装(熵正则、KL 惩罚)和 agg_loss 的聚合逻辑分别在 6.4 节和 6.6 节展开。
【图示】ppo_loss 的内部实现
6.1 importance ratio 与符号链
进入 compute_policy_loss_vanilla 之前,先回顾一下它要计算的核心量。
GRPO 的策略损失(简化掉 clip,留主干)是:
其中 就是 importance ratio r,衡量当前策略相对于旧策略的概率比值。为什么在 log 空间做差再取
exp,而不是直接用概率比?原因和上篇第 4 章讲 log_prob 时一样——直接用概率相除在大词表下容易下溢,log 空间数值更稳定:
落到代码,第一步就是算这个 ratio:
negative_approx_kl = log_prob - old_log_prob
negative_approx_kl = torch.clamp(
negative_approx_kl,
min=-20.0,
max=20.0,
)
ratio = torch.exp(negative_approx_kl)
ppo_kl = verl_F.masked_mean(-negative_approx_kl, response_mask)
negative_approx_kl 这个变量名有点绕,得先把它用到的两个量说清楚。
第一个用途:算 ratio。ratio 的定义是 ,在 log 空间就是
,正好等于
log_prob - old_log_prob,直接取 exp 就对了。
第二个用途:算监控指标 ppo_kl。ppo_kl 用的是 k1 估计器——KL 散度的线性近似:
方向是“旧减新”,和 negative_approx_kl(新减旧)符号相反,所以取了个负号:masked_mean(-negative_approx_kl)。变量名里的 negative 正是在提醒这一点——它存的是 KL 的负方向。
ppo_kl 只用于监控、不参与梯度,反映单步更新幅度,训练稳定时应为接近 0 的小正数。持续增大说明更新步子迈得太大,需要降低学习率或减少每轮更新次数。
【图示】qwen3 8b gsm8k kl
注意它和 6.5 节的 kl_loss 是两个不同的 KL:ppo_kl 对比的是旧策略和当前策略(每轮都在变),kl_loss 对比的是 ref 模型和当前策略(ref 整个训练过程不变),6.7 节会并排说清楚。
clamp(-20, 20) 是数值稳定性保护——log prob 差超过 20 时 ratio 会超过 ,梯度可能爆炸。
6.2 标准 clip:不对称的上下界就是 Clip-Higher
算好 ratio 之后,下一步是施加 clip 约束。PPO 的核心目标函数是最大化:
clip 的作用是限制单步更新幅度——ratio 偏离 1 太远时截断梯度,防止策略一步跳飞。代码做梯度下降,需要最小化 loss,所以对目标函数整体取负:最大化 等价于最小化
,这就是
pg_losses1、pg_losses2 前面负号的来源,以及最后用 torch.maximum 而非 torch.minimum 的原因:
pg_losses1 = -advantages * ratio
pg_losses2 = -advantages * torch.clamp(
ratio,
1 - cliprange_low,
1 + cliprange_high,
)
clip_pg_losses1 = torch.maximum(pg_losses1, pg_losses2)
pg_clipfrac = verl_F.masked_mean(
torch.gt(pg_losses2, pg_losses1).float(),
response_mask,
)
clip 的实际行为按优势符号分两种,用具体数值看最清楚。假设 ,
ratio = 1.5(当前策略比旧策略更倾向于这个 token):
(好 response):
pg_losses1 = -1.5A,pg_losses2 = -1.2A(ratio被截到 1.2),max选pg_losses2——loss更小,梯度被截断。等价于对ratio施加上界,防止在好 response 上一步更新过猛。
(差 response):
pg_losses1 = -1.5A > 0,pg_losses2 = -1.2A(绝对值更小),max选pg_losses1——不截断,惩罚力度保留。等价于对ratio施加下界,差 response 的惩罚不会被人为压低。
关键在于 cliprange_low 和 cliprange_high 是两个独立的值,代码里优先从 config.clip_ratio_low / config.clip_ratio_high 取,只有为 None 时才回退到对称的 clip_ratio:
clip_ratio_low = (
config.clip_ratio_low
if config.clip_ratio_low is not None
else config.clip_ratio
)
clip_ratio_high = (
config.clip_ratio_high
if config.clip_ratio_high is not None
else config.clip_ratio
)
DeepSeek-V3 官方配置取 0.2 / 0.28,上界明显比下界宽——这就是 DAPO 的 Clip-Higher。
【图示】不对称 clip
为什么要把上界放宽?对称 clip 下,低概率 token 的上探空间被 卡得很死:一个当前概率很低、但其实是好选择的 token,
ratio 还没涨上去就被截断,模型很难把它的概率提起来,长期表现为熵快速坍塌、多样性枯竭。把上界单独调大()给这些低概率 token 更多上探余地,是 DAPO 缓解熵坍塌的手段。这和上篇第 4 章讲的
entropy 监控正好呼应——Clip-Higher 就是冲着“别让熵掉太快”去的。
6.3 dual-clip:给负优势的 loss 再加一道上限
dual-clip 来自 2019 年的论文 Mastering Complex Control in MOBA Games with Deep Reinforcement Learning——田渊栋团队把 PPO 用在王者荣耀 AI 训练时发现的问题:MOBA 游戏里负优势样本大量存在,标准 clip 在 且
ratio 异常大时会失控,于是专门加了这道针对负优势的硬上限。verl 把它作为标准组件内建进来,通过 clip_ratio_c 控制松紧。
标准 clip 解决了“更新步子太大”的问题,但在 时还有一个残留的失控场景。
回顾标准 clip 之后的 loss 表达式:当 时,
clip 施加的是下界 ,
ratio 过小时才会截断。但如果 ratio 异常大呢?比如 ,
ratio = 5,:
pg_losses1 = -(-2) * 5 = 10pg_losses2 = -(-2) * 1.2 = 2.4(ratio被截到上界)
max(10, 2.4) = 10——标准clip没有截断,loss = 10
ratio 异常大、advantage 为负时,loss 可以任意大,一步就能把策略推飞。这是标准 clip 在负优势侧的盲区。
dual-clip 就是为了堵这个盲区,给负优势的 loss 加一道硬上限:
pg_losses3 = -advantages * clip_ratio_c # c 默认 3.0,官方配置设为 10.0
clip_pg_losses2 = torch.min(pg_losses3, clip_pg_losses1)
pg_clipfrac_lower = verl_F.masked_mean(
torch.gt(clip_pg_losses1, pg_losses3)
* (advantages < 0).float(),
response_mask,
)
pg_losses = torch.where(
advantages < 0,
clip_pg_losses2,
clip_pg_losses1,
)
clip_ratio_c(要求 > 1.0,有 assert 守着)给 loss 设了第三条线 。因为
,所以
是个正数,是一个上限;
torch.min(pg_losses3, clip_pg_losses1) 把 loss 截在这个上限以下。沿用上面的例子,:
pg_losses3 = -(-2) * 3 = 6min(10, 6) = 6——dual-clip生效,loss被截到 6
几何上,这等价于把负优势样本的有效 ratio 封顶在 c——再大的 ratio 也只按 c 计 loss。(默认)和官方的
差别就在这道封顶线的高低:c 越大越宽松,允许更大的
ratio 不被截。官方把它从 3 调到 10,说明生产环境里负优势样本的 ratio 动态范围确实可能很大,需要更宽松的上限。
【图示】dual-clip
三条 loss 线的关系汇总: 走
clip_pg_losses1(标准 clip), 走
clip_pg_losses2(标准 clip 基础上再加一道 上限)。
在打包指标之前,compute_policy_loss_vanilla 还有最后一步——把 token 级的 pg_losses 矩阵聚合成一个标量:
pg_loss = agg_loss(
loss_mat=pg_losses,
loss_mask=response_mask,
loss_agg_mode=loss_agg_mode,
**config.global_batch_info,
)
agg_loss 的具体逻辑放在 6.6 节展开,这里只需记住它承担了两件事:按 loss_agg_mode 决定长度归一化方式(token-mean 还是 seq-mean),以及通过 **config.global_batch_info 里的 batch_num_tokens 和 dp_size 保证多卡训练下 loss 尺度一致。pg_losses 是一个 (batch_size, response_length) 的矩阵,经过 agg_loss 之后变成一个标量,才能进入反向传播。
compute_policy_loss_vanilla 在返回前把三个监控指标打包:
pg_metrics = {
"actor/pg_clipfrac": pg_clipfrac.detach().item(),
"actor/ppo_kl": ppo_kl.detach().item(),
"actor/pg_clipfrac_lower": pg_clipfrac_lower.detach().item(),
}
三个指标各管一段逻辑:ppo_kl 反映新旧策略的整体偏离(6.1 节),pg_clipfrac 反映标准 clip 的生效比例(6.2 节),pg_clipfrac_lower 反映 dual-clip 的生效比例(本节)。正常训练中 ppo_kl 应是小正数、pg_clipfrac 在 0.1~0.3 之间、pg_clipfrac_lower 接近 0。三个指标一起看,基本能定位单步更新是否健康。
【图示】qwen3 8b gsm8k pg_clipfrac
【图示】qwen3 8b gsm8k pg_clipfrac_lower
至此 compute_policy_loss_vanilla 返回,控制权交回外层的 ppo_loss。
6.4 总损失聚合:三项的组合与 KL 加在哪里
compute_policy_loss_vanilla 只负责核心的 clip 逻辑,返回 pg_loss 之后,外层的 ppo_loss 把策略损失、熵正则、KL 散度组合成最终损失:
pg_loss, pg_metrics = policy_loss_fn(...)
policy_loss = pg_loss
if entropy is not None:
entropy_loss = agg_loss(
entropy,
response_mask,
loss_agg_mode,
**config.global_batch_info,
)
policy_loss -= entropy_coeff * entropy_loss # 减:因为要最大化熵
if config.use_kl_loss:
kld = kl_penalty(log_prob, ref_log_prob, config.kl_loss_type)
kl_loss = agg_loss(
kld,
response_mask,
config.loss_agg_mode,
**config.global_batch_info,
)
policy_loss += kl_loss * config.kl_loss_coef # 加:约束偏离 ref
数学形式是:
熵项前面是减号(最大化熵、鼓励探索),KL 项前面是加号(惩罚偏离参考策略)。三项都过同一个 agg_loss,保证聚合方式一致。
加在 loss 上时,KL 直接对当前策略产生梯度:
KL 梯度是独立的一项,强度由 kl_loss_coef 单独控制,不经任何归一化。
DeepSeek-V3 GRPO 官方配置选 use_kl_loss=True + use_kl_in_reward=False,直接加在 loss 上,保留对 KL 强度的精确控制。
6.5 KL 估计器:k1/k2/k3 与 low_var_kl
6.4 节的 KL 损失需要一个具体的估计器来计算 。
kl_penalty 支持多种估计器,在看具体实现之前,先把符号约定说清楚:代码里各估计器的输入是 logprob(当前策略)和 ref_logprob(参考策略),但不同估计器内部对“谁减谁”的处理并不一致,下面遇到时会逐个点出。
k1(线性估计)
if kl_penalty in ("kl", "k1"):
return logprob - ref_logprob # 新 - 旧
注意这里是新减旧,符号和 KL 散度的常规定义(旧减新)相反,实际上是线性差值。k1 最简单,但梯度有偏,实践中用得不多。
k2(均方误差)
if kl_penalty in ("mse", "k2"):
return 0.5 * (logprob - ref_logprob).square()
平方消掉了符号,新减旧还是旧减新结果一样。k2 的梯度无偏,但方差高——训练中 KL 估计值的波动较大,对学习率敏感。
k3(low_var_kl)
if kl_penalty in ("low_var_kl", "k3"):
kl = ref_logprob - logprob # 旧 - 新,即 Δ
kl = torch.clamp(kl, min=-20, max=20)
ratio = torch.exp(kl)
kld = (ratio - kl - 1).contiguous()
return torch.clamp(kld, min=-10, max=10)
这里 kl = ref_logprob - logprob 是旧减新,即 ,代入得:

这正是上篇第 2 章公式里那个 reverse-KL 估计,对所有取值恒非负(当且仅当 时取 0)。
代码里有两道 clamp:第一道 clamp(-20, 20) 作用在 Δ 上,防止 exp 溢出();第二道
clamp(-10, 10) 作用在最终结果上,兜底极端值对训练的冲击。两道保护缺一不可——第一道管数值溢出,第二道管异常样本。
straight-through:k3+
k3 的问题是梯度有偏,k2 梯度无偏但方差高。k3+ 用 straight-through 技巧把两者的优点合并:
forward_score = kl_penalty_forward(
logprob,
ref_logprob,
"k3",
) # 前向用 k3
backward_score = 0.5 * (logprob - ref_logprob).square() # 反向用 k2
return (
backward_score
- backward_score.detach()
+ forward_score.detach()
)
为什么这个表达式能做到“前向值来自 k3、梯度来自 k2”?拆开看:
forward_score.detach():k3 的值,但梯度被切断backward_score.detach():k2 的值,梯度也被切断backward_score:k2 的值,梯度保留
整个表达式的前向值 = backward_score - backward_score + forward_score = k3 的值;反向传播时只有 backward_score 有梯度,所以梯度来自 k2。
官方默认没开 +,直接用 k3。但理解这个技巧的存在,有助于在训练不稳定时知道还有一个“梯度更准确”的备选。
四个估计器的比较汇总如下:
exp(旧减新) - (旧减新) - 1 | ||||
6.6 loss_agg_mode:长度偏置与第 2 章的闭环
KL 损失、熵损失、策略损失三项都要经过 agg_loss 聚合成标量,聚合方式的选择直接决定了长 response 和短 response 在梯度上的相对权重。
用一个具体例子贯穿四种模式。batch 里有两条 response,单卡(dp_size=1):
response A:长度 8,token 级 loss均为 1,均值 1.0,总和 8response B:长度 2,token 级 loss均为 5,均值 5.0,总和 10
token-mean
数学公式:

其中 i 是 response 编号,t 是 token 位置, 是 token 级
loss, 是
mask(有效 token 为 1,padding 为 0),分母是全局所有有效 token 数。
loss = (
masked_sum(loss_mat, loss_mask)
/ batch_num_tokens
* dp_size
)
token-mean 的本质是把所有 token 打散成一个大池子求平均,每个 token 权重相等。这意味着一条有 8 个 token 的 response,自然就占了池子里 8/10 的份额;一条有 2 个 token 的 response 只占 2/10。
代入例子:
response A 的 8 个 token,每个 loss = 1,贡献,占总
loss的 8/18≈44%,但只是 2 条 response 里的 1 条(50%)response B 的 2 个 token,每个 loss = 5,贡献,占总
loss的 10/18≈56%
最终 loss = 1.8,而 B 的 token 级均值是 5.0——如果两条 response 等权,结果应该是 3.0;但因为 A 的 token 数是 B 的 4 倍,A 把结果往下拉,B 的高 loss 被“稀释”了。
换句话说,token-mean 下,一条 response 能影响梯度的程度,取决于它有多少个 token,而不是“它是一条独立的 response”。长 response 多占 token 池份额,所以天然权重大。
这正是 V3 版公式没有 的含义——不做长度归一化,让 token 数自然决定权重。
seq-mean-token-sum
数学公式:
其中 N 是 batch 内有效序列数,先对每条序列内所有有效 token 的 loss 求和,再对 N 条序列平均。
seq_losses = torch.sum(loss_mat * loss_mask, dim=-1)
seq_mask = (torch.sum(loss_mask, dim=-1) > 0).float()
loss = (
masked_sum(seq_losses, seq_mask)
/ global_batch_size
* dp_size
)
代入例子:
每条序列等权,但长序列内部 token 仍然累加,所以 A(总和 8)和 B(总和 10)都如实反映,序列间不受长度影响,序列内仍有长度偏置。
seq-mean-token-sum-norm
数学公式:
在 seq-mean-token-sum 基础上再除一个缩放因子 ,默认是
loss_mask.shape[-1](padding 后的最大序列长度,本例中为 8)。
loss = (
masked_sum(seq_losses, seq_mask)
/ global_batch_size
* dp_size
)
loss /= loss_scale_factor # 默认 loss_mask.shape[-1]
代入例子:
注意这个默认值会随 batch 变化——如果希望训练过程中归一化行为一致,应该手动设成固定常数。
seq-mean-token-mean
数学公式:
其中 是第 i 条 response 的有效 token 数,先在每条序列内做 token 平均,再对 N 条序列平均。这里有个容易看晕的地方——
seq_mask 在同一段代码里语义变了两次:
seq_mask = torch.sum(loss_mask, dim=-1)
# 第一次:每条序列的有效 token 数(整数)
seq_losses = (
torch.sum(loss_mat * loss_mask, dim=-1)
/ (seq_mask + 1e-8)
)
# token mean
seq_mask = (seq_mask > 0).float()
# 第二次:重新赋值为 0/1 掩码,排除全掩码序列
loss = (
masked_sum(seq_losses, seq_mask)
/ global_batch_size
* dp_size
)
代入例子:
每条序列等权,且长短 response 内部完全拉平——A 的均值 1.0、B 的均值 5.0,两者在最终 loss 里权重完全相同。对应上篇第 2 章 Math 版公式(带 归一化项)。
四种模式同一批数据的结果对比:
token-mean | |||
seq-mean-token-sum | |||
seq-mean-token-sum-norm | seq-mean 基础上压缩绝对值 | ||
seq-mean-token-mean |
同样的数据,四种模式结果相差最大 8 倍。选错模式不会报错,但梯度尺度和长度偏置完全不同。
官方配置用 token-mean,这也是 DAPO 的 Token-Level Loss 主张——长 CoT 推理里,长 response 的每个 token 都携带有效信号,不该被 seq-mean 稀释成“一条算一票”。
6.7 训练指标的含义
ppo_loss 返回的 metrics 字典汇集了整条 loss 链路上的监控信号。下面逐个拆解每个指标的计算来源,对照公式和代码一起看。
actor/ppo_kl
来自 compute_policy_loss_vanilla,估计新旧策略的偏离程度:
negative_approx_kl = log_prob - old_log_prob
ppo_kl = masked_mean(-negative_approx_kl, response_mask)
# 等价于 mean(old_log_prob - log_prob),
# 即 D_KL(π_old || π_θ) 的样本估计
actor/pg_clipfrac
来自 compute_policy_loss_vanilla,统计标准 clip 生效的 token 比例:
pg_clipfrac = masked_mean(
torch.gt(pg_losses2, pg_losses1).float(),
response_mask,
)
actor/pg_clipfrac_lower
来自 compute_policy_loss_vanilla,统计 dual-clip 生效的 token 比例,且只在 的 token 上统计:
pg_clipfrac_lower = masked_mean(
torch.gt(clip_pg_losses1, pg_losses3)
* (advantages < 0).float(),
response_mask,
)
actor/pg_loss
来自 ppo_loss,经过 agg_loss 聚合后的策略梯度损失标量,是 compute_policy_loss_vanilla 返回的 pg_loss 直接记录进 metrics:
metrics["actor/pg_loss"] = Metric(
value=pg_loss,
aggregation=AggregationType.SUM,
)

【图示】qwen3 8b gsm8k pg_loss
actor/entropy_loss
来自 ppo_loss,response 部分所有 token 熵的聚合值:
if entropy is not None:
entropy_loss = agg_loss(
entropy,
response_mask,
loss_agg_mode,
**config.global_batch_info,
)
metrics["actor/entropy_loss"] = Metric(
value=entropy_loss,
aggregation=metric_aggregation,
)
kl_loss
来自 ppo_loss,当前策略与 ref 模型的 KL 散度,用 6.5 节的估计器计算后经 agg_loss 聚合:
if config.use_kl_loss:
kld = kl_penalty(
log_prob,
ref_log_prob,
config.kl_loss_type,
)
kl_loss = agg_loss(
kld,
response_mask,
config.loss_agg_mode,
**config.global_batch_info,
)
metrics["kl_loss"] = Metric(
value=kl_loss,
aggregation=metric_aggregation,
)

【图示】qwen3 8b gsm8k kl_loss
最后把两个容易混淆的 KL 并排说清楚:
actor/ppo_kl:,新旧策略之间,反映单步更新幅度,每轮更新都会重置
kl_loss:,当前策略与 SFT 初始点之间,反映训练全程的累积偏离
小结
读完六个小节的代码细节,退一步把完整的公式和实现对应关系收束一遍。GRPO 的策略损失完整展开是:
其中:
其中 对应
compute_policy_loss_vanilla, 和
由外层
ppo_loss 组装。三项对应三层实现:
最外层 ppo_loss 负责组装——从 data 里取出固定项(old_log_prob、ref_log_prob、advantages),通过注册表派发到具体的策略损失函数,再把 、熵正则、KL 惩罚三项加总成
。
中间层 compute_policy_loss_vanilla 负责核心 clip 逻辑,三段代码各管一件事:
ratio与符号链:,在 log 空间做差保证数值稳定
标准 clip:上下界不对称(,
),上界放宽给低概率 token 更多上探空间,即 DAPO 的 Clip-Higher
dual-clip:仅在时生效,把负优势样本的
loss封顶在(官方
),堵住标准
clip在负优势侧ratio异常大时的失控盲区
最底层 agg_loss 负责把 token 级 loss 矩阵聚合成标量,四种模式的核心差异是长度偏置:token-mean 对应上篇第 2 章 V3 版公式(长 response 贡献更大),seq-mean-token-mean 对应 Math 版(每条 response 等权)。分布式归一化靠 dp_size 抵消后续 all_reduce(AVG) 的平均,保证多卡 loss 尺度和单卡一致。
全文总结
两篇围绕同一条链路:公式拆解 → 八个训练阶段 → 三个核心计算单元。
GRPO 相对于预训练,改变的不是前向骨架,而是梯度系数——从无条件的 变为由
advantage 定符号、ratio 定尺度的 。额外代价只有两处:
rollout 采样(一次推理)和固定项准备(全程 no_grad)。
log_prob & entropy:logp 命中“标准 loss”判据所以直接复用生态 CE 算子,entropy 没命中只能自行实现,代价是绕不开整张概率矩阵、显存峰值翻倍。
advantage:去掉 critic 后,优势估计退化成组内统计量。两个沉默的陷阱:单样本组 advantage 尺度特殊、全组 reward 相同时 advantage 归零对梯度零贡献——不报错,是 DAPO Dynamic Sampling 要过滤的对象。
policy loss:三层实现——ppo_loss(组装)→ compute_policy_loss_vanilla(ratio、标准 clip、dual-clip)→ agg_loss(长度归一化)。DAPO 三件套在这两篇里全部对上号:Dynamic Sampling(上篇第 5 章)、Clip-Higher(6.2 节)、Token-Level Loss(6.6 节)。
本文刻意留了一条线索没展开——distillation_ppo_loss 对应的 On-Policy Distillation(OPD),在 loss_fn 绑定层就和标准 RL 分叉,后续有时间再单独走读。
作者:鲸饮未吞海
链接:https://zhuanlan.zhihu.com/p/2044076539684319813
春招/秋招/实习/社招/申研
大陆/美/新/澳/欧/港……
「算法+开发+产品」从现在到入职!五位清华老师全程陪你跑!
详情咨询微信:THU_LLM(备注“公众号”)

夜雨聆风