VERL的损失函数计算方式

复制代码
def agg_loss(
    loss_mat: torch.Tensor,
    loss_mask: torch.Tensor,
    loss_agg_mode: str,
    dp_size: int = 1,
    batch_num_tokens: Optional[int] = None,
    global_batch_size: Optional[int] = None,
    loss_scale_factor: Optional[int] = None,
):
    """
    Aggregate the loss across global batch to ensure the loss is invariant to fsdp/megatron parallelism.

    NOTE: The returned loss has different behaviors for different backend:
    - FSDP: the loss is directly used for backward.
    - Megatron: the loss should be scaled by `num_microbatches` and `cp_size` for pp schedule.

    Args:
        loss_mat: micro batch loss matrix, (bs, response_length)
        loss_mask: micro batch loss mask, (bs, response_length)
        loss_agg_mode: method to aggregate the loss matrix into a scalar
        dp_size: data parallel size
        batch_num_tokens: number of valid tokens in global batch
        global_batch_size: global batch size
        loss_scale_factor: scale factor for "seq-mean-token-sum-norm" mode. If None, uses loss_mask.shape[-1].
            Set this to a constant value to ensure consistent normalization throughout training.

    Returns:
        loss: `a scalar torch.Tensor`
            aggregated loss
    """
    if loss_agg_mode == "token-mean":
        if batch_num_tokens is None:
            batch_num_tokens = loss_mask.sum()
        loss = verl_F.masked_sum(loss_mat, loss_mask) / batch_num_tokens * dp_size
    elif loss_agg_mode == "seq-mean-token-sum":
        seq_losses = torch.sum(loss_mat * loss_mask, dim=-1)  # token-sum
        seq_mask = (torch.sum(loss_mask, dim=-1) > 0).float()  # exclude fully masked sequences
        if global_batch_size is None:
            global_batch_size = seq_mask.sum()
        loss = verl_F.masked_sum(seq_losses, seq_mask) / global_batch_size * dp_size  # seq-mean
    elif loss_agg_mode == "seq-mean-token-mean":
        seq_mask = torch.sum(loss_mask, dim=-1)  # per-sequence token count
        seq_losses = torch.sum(loss_mat * loss_mask, dim=-1) / (seq_mask + 1e-8)  # token-mean
        seq_mask = (seq_mask > 0).float()  # exclude fully masked sequences
        if global_batch_size is None:
            global_batch_size = seq_mask.sum()
        loss = verl_F.masked_sum(seq_losses, seq_mask) / global_batch_size * dp_size  # seq-mean
    elif loss_agg_mode == "seq-mean-token-sum-norm":
        seq_losses = torch.sum(loss_mat * loss_mask, dim=-1)
        if loss_scale_factor is None:
            loss_scale_factor = loss_mask.shape[-1]
        loss = torch.sum(seq_losses) / loss_scale_factor
    else:
        raise ValueError(f"Invalid loss_agg_mode: {loss_agg_mode}")

    return loss

这段代码定义了一个损失聚合函数 agg_loss,主要用于在大规模分布式训练(如 FSDP 或 Megatron-LM)中,将不同 GPU 上的微批次(micro-batch)损失转换成一个全局一致的标量损失。

根据 loss_agg_mode 的不同,损失的计算逻辑可以分为以下四种模式:


1. token-mean (按 Token 取平均)

这是语言模型训练中最常用的模式(例如标准的交叉熵损失)。

  • 计算逻辑:将所有有效 token 的损失加总,然后除以全局总的 token 数量。

  • 数学表达:

  • 特点:每个 token 对梯度的贡献是平等的。如果一个序列很长,它对总损失的影响就比短序列大。

2. seq-mean-token-sum (序列间平均,序列内求和)

这种模式先计算每个序列的总损失,再对序列维度取平均。

  • 计算逻辑

    1. 对每个序列(row),把所有有效 token 的损失加在一起。

    2. 对所有序列的结果取平均值(除以全局 Batch Size)。

  • 特点:它对 Batch 中的每个"样本"给予平等的权重,而不论序列的长短。如果序列 A 有 100 个 token,序列 B 有 10 个 token,它们在最终 loss 中的权重是一样的。

3. seq-mean-token-mean (序列间平均,序列内也平均)

这种模式在两个层面上都进行了归一化。

  • 计算逻辑

    1. 对每个序列,先计算其内部有效 token 的平均损失

    2. 再对这些"平均后的序列损失"计算全局平均。

  • 特点:这是一种双重平均。它不仅让每个序列对梯度的贡献平等,还消除了序列长度对单个序列内部损失数值大小的影响。通常用于某些特定的强化学习(RLHF)场景或序列长度差异极大的任务。

4. seq-mean-token-sum-norm (带固定缩放因子的序列求和)

这是一种变体,通常为了保持梯度在不同长度设置下的数值稳定性。

  • 计算逻辑

    1. 将当前 micro-batch 的序列损失求和。

    2. 除以一个固定 的缩放因子 loss_scale_factor(通常是最大序列长度)。

  • 特点:它不像前几种模式那样动态地除以实际存在的 token 数或序列数,而是除以一个预设的常数。这能保证在训练过程中,损失函数的量级(Scale)不会随着 Batch 中 Padding 的多少而剧烈波动。


模式对比总结

模式 侧重点 适用场景
token-mean 每个 Token 权重平等 预训练(Pre-training)、指令微调(SFT)
seq-mean-token-sum 每个序列权重平等 序列级别任务,不希望长序列主导梯度
seq-mean-token-mean 极度平滑序列长度差异 强化学习、或者对 loss 稳定性要求极高时
seq-mean-token-sum-norm 固定缩放,数值稳定 分布式并行优化,防止动态 mask 导致梯度跳变

代码中的 dp_size 说明

你会发现前三种模式都乘以了 dp_size (Data Parallel Size)。这是因为在分布式训练中,每个进程只计算了自己那部分数据的 sum / global_count。为了在反向传播时得到正确的全局梯度平均值,需要通过这个系数来抵消分布式环境下求和与平均的比例关系。

您是正在调试 verl 框架(一个常用的强化学习训练框架)的代码吗?如果是的话,通常 PPO 的策略损失(Policy Loss)会偏向使用 token-mean

dapo用的哪一种?

verl 框架(以及更广泛的偏好对齐算法中),DAPO (Decoupled Clip and Dynamic Sampling Policy Optimization) 或类似的偏好优化算法默认通常使用:

token-mean (按 Token 取平均)

这是目前的主流推荐配置,原因如下:

  1. 细粒度学习 :DAPO 在设计上强调"Token-level Policy Graduate Loss"(Token 级别的策略梯度损失)。使用 token-mean 能够确保每一个生成的 token 对梯度的贡献是平等的,这有助于模型更精准地学习哪些词导致了奖励的提升。

  2. 官方推荐 :根据 verl 的官方文档和最佳实践,对于 DAPO 和最新的 GRPO 变体,建议使用 token-mean。文档中明确提到:"Setting loss_agg_mode to token-mean will mean the loss across all the tokens in all the sequences in a mini-batch."(将模式设置为 token-mean 意味着损失将跨越微批次中所有序列的所有 token 进行计算)。

  3. 算法演进 :早期的 GRPO 或某些 RL 算法可能使用 seq-mean-token-mean 来复现原始论文的行为(为了让不同长度的回复在 batch 中权重一致),但在实际大规模微调中,token-mean 往往能带来更稳定的收敛效果,因为它不会因为某个序列特别短而人为放大该序列中单个 token 的梯度。


如何在配置中确认?

如果你正在查看 verl 的配置文件(如 .yaml 文件),通常可以在 actor 的部分看到类似设置:

YAML

复制代码
actor_rollout_ref:
  actor:
    loss_agg_mode: "token-mean"  # DAPO 默认推荐值

总结

  • DAPO 默认/推荐: token-mean

  • 为什么要选它: 保证梯度在 Token 层面的一致性,符合 DAPO 细粒度优化的逻辑,且在大模型分布式训练中数值最稳定。

您是想调整 loss 计算方式来解决模型训练不收敛的问题,还是在做多机多卡的速度优化?

seq-mean-token-sum和token-mean感觉区别不大啊?

从数学公式上看,它们确实很像,但在分布式训练序列长度不均的情况下,它们的梯度行为有显著区别。

我们可以通过一个简单的例子对比一下。假设你的 Batch 里只有两个序列:

  • 序列 A:100 个有效 token。

  • 序列 B:10 个有效 token。


1. token-mean (权重随长度线性增长)

它把所有序列看作一个超长的 Token 串。

  • 计算逻辑

  • 实质:序列 A 的权重是 100/110,序列 B 的权重只有 10/110。

  • 直观感受 :序列 A 的梯度比 B 强 10 倍。如果你的数据里有极长和极短的回复,长回复会主导整个模型的更新方向。

2. seq-mean-token-sum (序列权重平等)

它强制每个样本(序列)对梯度的贡献是一样的。

  • 计算逻辑 (实际上代码里是先求和再除以全局 global_batch_size,效果一致)。

  • 实质 :序列 A 和序列 B 各占总 Loss 的 50%

  • 直观感受 :虽然序列 B 只有 10 个词,但它和 100 个词的序列 A 平起平坐


核心区别总结

特性 token-mean seq-mean-token-sum
谁更重要? 长序列更重要(Token 越多权重越大) 每个样本都一样重要
梯度稳定性 比较稳定,因为分母是总 Token 数 如果某个序列特别短且 Loss 异常,可能会产生梯度爆炸
训练侧重 侧重于学习把每一个词预测对 侧重于把每一个完整的句子/回答写好
典型算法 GPT 预训练、SFT 强化学习(如 PPO/DAPO),因为每个完整回答对应一个 Reward

为什么你会觉得"区别不大"?

如果你训练时的 Padding 很少 ,或者所有序列的 长度非常接近(比如都截断到了 512),那么这两者的分母和缩放比例几乎是等价的,计算出来的 Loss 确实差别很小。

但在 RLHF 阶段,模型生成的回复长度波动很大(有的思维链 CoT 回复 2000 词,有的拒绝回答只有 10 词):

  • token-mean,模型会拼命学习如何优化那个 2000 词的长回复。

  • seq-mean-token-sum,模型会同等对待这长、短两个回答。

建议:

如果你在做 DAPO,且数据集中回复长度分布不均,建议坚持用 token-mean。因为它在分布式环境下(不同卡上的 token 数量不同时)计算出的梯度方向最稳健。

你想看下这两种模式在多卡并行(FSDP/Megatron)时是如何处理不同卡之间数据不平衡的吗?

Dr.GRPO里面用的什么?

verl 框架中,Dr.GRPO (即 "GRPO Done Right") 默认使用的是:

seq-mean-token-sum-norm (带固定缩放因子的序列求和)

这是 Dr.GRPO 论文和 verl 框架针对该算法专门推荐的模式。


为什么 Dr.GRPO 要用这种模式?

Dr.GRPO 相比于原始的 GRPO,核心改进之一就是消除"长度偏置(Length Bias)"。我们可以对比一下:

  1. 原始 GRPO (seq-mean-token-mean)

    • 做法:先对每个序列内所有 token 的 loss 取平均,再在序列间取平均。

    • 问题:这会导致"短答案"的梯度被异常放大(因为分母小),模型为了降低 loss,会倾向于生成更长的回复来稀释单个 token 的 loss。

  2. DAPO (token-mean)

    • 做法:所有 token 平等,除以当前 batch 的总有效 token 数。

    • 问题:虽然缓解了长度偏置,但分母是动态的(取决于当前采样出了多少 token),这在分布式训练中会导致梯度不稳定性。

  3. Dr.GRPO (seq-mean-token-sum-norm)

    • 做法 :将所有 token 的 loss 求和,然后除以一个固定的常数 (通常是配置中的 max_prompt_length + max_response_length 或者一个预设的 loss_scale_factor)。

    • 优势

      • 无偏性:它不再除以动态的 token 数量,从而保证了梯度的期望值不会因为生成长度的变化而产生偏差。

      • 惩罚一致性 :对于错误的 token,无论它出现在长回复还是短回复中,其梯度惩罚的力度是绝对一致的。


配置建议

如果你想在 verl 中完全复现 Dr.GRPO 的效果,除了设置 loss_agg_mode,通常还需要配合以下参数:

配置项 Dr.GRPO 推荐值 作用
loss_agg_mode "seq-mean-token-sum-norm" 使用固定因子归一化,消除长度偏置
use_kl_loss False Dr.GRPO 通常不直接把 KL 加在 Loss 里,而是作为 Reward 惩罚
norm_adv_by_std False 甚至可能关闭优势函数的标准差归一化(视具体实现而定)

总结

  • 如果你追求训练最稳定、逻辑最严密 的 Dr.GRPO 复现,请使用 seq-mean-token-sum-norm

  • 如果你只是普通的 RLHF 或 SFT,token-mean 通常是效果和性能最平衡的选择。

你是准备在 DeepSeek R1 这种推理模型上尝试 Dr.GRPO 吗?如果是的话,这种 Loss 聚合方式对防止模型变得"越来越啰嗦(Verbosity Bias)"非常有帮助。

verl 框架以及 DeepSeek 相关的技术演进中,GSPO (Group Relative Policy Optimization) 本质上是 GRPO 的一个变体(有时在代码实现中,GSPO 指代加入了更严格组内优势规范化的版本)。

对于 GSPO,它通常遵循与 Dr.GRPO 相同的哲学,即使用:

seq-mean-token-sum-norm (或者在某些实现中为 token-mean)

但最关键的点不在于 token-sum 还是 token-mean,而在于它如何处理**组内(Group)**的归一化。


GSPO 的核心逻辑区别

GSPO 和 GRPO 的核心在于**"组内相对比较"**。假设一个 Prompt 生成了 G 个回答(一个 Group):

  1. 优势计算(Advantage):

    GSPO 会计算这 G 个回答的奖励(Reward),然后减去这组的平均值,再除以标准差。

  2. 损失聚合(Loss Aggregation):

    由于 Advantages 已经在组内进行了归一化,梯度的幅度已经通过 R 的相对差异控制住了。此时,为了防止长度偏置(Length Bias),GSPO 倾向于使用 seq-mean-token-sum-norm。


为什么 GSPO 特别依赖这种 Loss 聚合?

如果你在 GSPO 中使用标准的 token-mean,会发生以下情况:

  • 长答案陷阱 :如果一组回答中,一个正确的答案非常长,而一个错误的答案非常短。在 token-mean 下,长答案的每一个 token 贡献的梯度虽然被总数平均了,但因为它 token 多,总的梯度更新量依然可能主导模型,导致模型学会"通过变长来骗取更高分"。

  • Dr.GRPO 的修正 :Dr.GRPO(以及改进后的 GSPO)通过固定分母的 seq-mean-token-sum-norm,确保无论模型生成多长,梯度在 token 维度的"力度"是恒定的。

如何选择?

verl 中运行 GSPO 时,你可以根据你的目标选择:

你的目标 推荐模式 原因
严谨复现 DeepSeek R1 风格 seq-mean-token-sum-norm 彻底消除长度对梯度的不公平贡献
常规强化学习训练 token-mean 实现最简单,在大多数任务中收敛最快
模型回复长度非常固定 seq-mean-token-sum 逻辑简单,各样本权重绝对平等

总结

GSPO 在最新实践中首选 seq-mean-token-sum-norm

它的核心目的是:通过组内比较解决"好坏"问题,通过固定因子归一化解决"长短"偏置问题。

你是发现使用默认设置训练时,模型的回复变得越来越长(Verbosity Explosion)了吗?如果是这样,切换到 seq-mean-token-sum-norm 通常能立即缓解这个问题。

相关推荐
蜡笔小新..19 小时前
从零学习 RL :初识强化学习
人工智能·强化学习·rl
QiZhang | UESTC2 天前
RL4LLM
大模型·强化学习·rl4llm
nju_spy2 天前
动手学强化学习上交张伟楠(一)导论 + 多臂老虎机 MAB(ε-greedy+上置信界+汤普森采样)
人工智能·python·强化学习·actor-critic·多臂老虎机·汤普森采样·探索与利用
程序员Agions3 天前
程序员武学修炼手册(三):融会贯通——从写好代码到架构设计
前端·程序员·强化学习
索木木4 天前
强化学习与思维链
大模型·sft·强化学习·思维链
nju_spy5 天前
RL4LLM_Survey 强化学习在大语言模型后训练综述
人工智能·强化学习·reinforce·ppo·数据异质性·大模型后训练·奖励函数
AI-Frontiers6 天前
小白也能看懂的LLM-RL算法:PPO/DPO/GRPO/GSPO
强化学习·大模型训练
超的小宝贝6 天前
机器学习期末复习
深度学习·机器学习·强化学习
空山新雨后、6 天前
深度学习VS强化学习:预测与决策的本质差异
人工智能·深度学习·强化学习