Prhub

#6957 [algo] fix: normalize critic value loss over the global mini-batch, not per micro-batch

原始 PR 作者 abtonmoy 合并时间 2026-07-07 16:56 文件变更 9 提交数 1 评论 2 代码增减 +311 / -10

执行摘要

修复 Critic 损失微批归一化导致的梯度膨胀

Critic值损失默认按每个微批独立归一化,导致梯度累积膨胀约K倍(K为微批数),且随use_dynamic_bsz等配置变化,等效于隐式缩放Critic学习率。Actor损失已通过ppo_loss传递全局归一化信息避免此问题,Critic却遗漏了。详见PR#6957 body中的重现数据。

推荐精读,理解损失归一化一致性的关键设计决策。该 PR 清晰地演示了如何从 Actor 对齐 Critic,并包含详尽测试。Review 中关于不变异 config 对象的建议也值得在类似场景中推广。

讨论亮点

Review 中 gemini-code-assist 指出不应变异共享的 config 对象来传输动态批量数据,而应直接透传参数,避免副作用和竞态条件。作者采纳建议,改为直接传递四个参数,并移除了 config.global_batch_info 的写入及 CriticConfig 中不再使用的 global_batch_info 字段。这一改进使接口更加清晰安全。

实现拆解

  1. 修改value_loss函数(verl/workers/utils/losses.py):从data字段中提取dp_size、batch_num_tokens、global_batch_size,并检测config.loss_scale_factor;当任何全局归一化参数激活时,指标聚合方式设为SUM,否则保持MEAN;将这些参数传递给compute_value_loss。
  2. 修改compute_value_loss函数(verl/trainer/ppo/core_algos.py):新增可选参数dp_size、batch_num_tokens、global_batch_size、loss_scale_factor,并在调用agg_loss时转发它们,使损失计算能使用全局token数而非本地微批token数进行归一化。
  3. 扩展CriticConfig(verl/workers/config/critic.py):添加loss_scale_factor字段,默认None,用于seq-mean-token-sum-norm模式。
  4. 修改YAML配置:在verl/trainer/config/critic/critic.yaml和四个生成配置中增加loss_scale_factor项,继承自Actor的同名字段。
  5. 新增CPU回归测试(tests/trainer/ppo/test_value_loss_normalization_on_cpu.py):用无模型的数值模拟验证微批不变性、可变长度、DP_SIZE模拟FSDP规约、指标聚合路径、loss_scale_factor转发等,共26个测试通过。
文件 模块 状态 重要度
verl/workers/utils/losses.py 损失函数 modified 6.63
verl/trainer/ppo/core_algos.py 核心算法 modified 6.22
verl/workers/config/critic.py 配置定义 modified 4.49
tests/trainer/ppo/test_value_loss_normalization_on_cpu.py 测试 added 7.76
verl/trainer/config/critic/critic.yaml 配置模板 modified 3.25

关键符号

value_loss compute_value_loss CriticConfig

关键源码片段

verl/workers/utils/losses.py core-logic

核心修改入口,前向传递全局归一化参数并调整指标聚合

def value_loss(config: CriticConfig, model_output, data: TensorDict, dp_group=None):
    '''value loss'''
    vpreds = no_padding_2_padding(model_output['values'], data)
​
    # 提取全局归一化参数(从 data 中获取)
    dp_size = data['dp_size']
    batch_num_tokens = data['batch_num_tokens']
    global_batch_size = data['global_batch_size']
​
    # 当任何全局归一化参数激活时,使用 SUM 聚合
    if (
        dp_size > 1
        or batch_num_tokens is not None
        or global_batch_size is not None
        or config.loss_scale_factor is not None
    ):
        metric_aggregation = AggregationType.SUM
    else:
        metric_aggregation = AggregationType.MEAN
​
    # 选择字段并转换为 padded tensor
    data = data.select('values', 'returns', 'response_mask').to_padded_tensor()
    values = data['values']
    returns = data['returns']
    response_mask = data['response_mask'].to(bool)
​
    vf_loss, vf_clipfrac = compute_value_loss(
        vpreds=vpreds,
        values=values,
        returns=returns,
        response_mask=response_mask,
        cliprange_value=config.cliprange_value,
        loss_agg_mode=config.loss_agg_mode,
        dp_size=dp_size,
        batch_num_tokens=batch_num_tokens,
        global_batch_size=global_batch_size,
        loss_scale_factor=config.loss_scale_factor,
    )
​
    metrics = {
        'critic/vf_loss': Metric(value=vf_loss, aggregation=metric_aggregation),
        'critic/vf_clipfrac': vf_clipfrac.detach().item(),
        'critic/vpred_mean': masked_mean(vpreds, response_mask).detach().item(),
    }
​
    return vf_loss, metrics

评论区精华

避免共享 config 对象变异,直接传递参数 设计

gemini-code-assist 指出不应将动态批量数据写入共享的 `config` 对象,应直接透传参数以避免副作用和竞态条件。

结论:作者接受建议,改为直接传递 `dp_size`、`batch_num_tokens`、`global_batch_size`、`loss_scale_factor` 四个参数,并移除了 `config.global_batch_info` 的写入及 `CriticConfig` 中不再使用的 `global_batch_info` 字段。 · 已解决

风险与影响

低风险。核心改动是添加参数透传,未改变损失计算的数学公式。多GPU FSDP规约的模拟测试采用算术模拟而非真实多卡环境,但核心数值行为已得到验证。若 loss_scale_factor 在 seq-mean-token-sum-norm 模式下未正确设置,仍可能产生不一致(与 Actor 相同的限制)。新增的 CPU 测试覆盖了所有损失聚合模式和边界情况。

直接影响所有使用 Critic 的 PPO 训练(adv_estimator=gae, use_critic=true),修正后训练梯度与微批划分无关,训练更稳定。指标报告方式调整,critic/vf_loss 在全局归一化时显示全局小批均值。对不使用 Critic 的 GRPO 等无影响。配置新增 loss_scale_factor 字段,向后兼容(默认 None)。

核心损失函数变更 梯度数值行为改变 配置新增字段 FSDP 模拟测试有限

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论