Prhub

#1928 fix: avoid applying rollout temperature to critic values

原始 PR 作者 Baiyu-Su 合并时间 2026-05-30 12:09 文件变更 2 提交数 1 评论 1 代码增减 +62 / -5

执行摘要

修复 critic 值头被错误应用 rollout 温度缩放

get_responses() 函数被策略 logit 提取和值 head 提取共用。策略 logit 需要温度缩放以重建 rollout logprob,但 critic 的值输出是标量预测,不应除以 rollout 采样温度。原始实现无条件应用温度缩放,导致 critic 值被错误缩放,影响 PPO 等算法的 value loss 计算。

该 PR 是一个轻量的值得注意的 bug 修复,设计清晰(添加可选参数控制温度),适合作为 code review 中“小改解决大问题”的范例。建议合并并关注相关训练结果。

讨论亮点

合著者 zhuzilin 评论 "nice catch!",表明该 bug 的发现和修复受到认可,未产生争议。

实现拆解

  1. 修改 get_responses 函数签名:在 slime/backends/megatron_utils/loss.py 中为 get_responses 新增 apply_temperature: bool = True 参数,保持向后兼容。
  2. 添加条件判断:将 if args.rollout_temperature != 1.0 改为 if apply_temperature and args.rollout_temperature != 1.0,确保温度缩放仅在被允许时应用。
  3. 更新文档字符串:调整函数和参数说明,反映 rollout_temperature 的可选性。
  4. 修改 get_values 调用:在 get_values 内部调用 get_responses 时传递 apply_temperature=False,从而禁用温度缩放。同时更新 get_values 的文档字符串,说明温度缩放已禁用。
  5. 新增单元测试:创建 tests/test_value_temperature.py,在零 GPU 环境下使用 monkeypatch 模拟依赖,验证当 rollout_temperature=0.5 时,get_values 输出的值头结果 [2.0, 3.0] 与原始 logits 对应位置一致,证明未应用缩放。
文件 模块 状态 重要度
slime/backends/megatron_utils/loss.py 后端核心 modified 6.57
tests/test_value_temperature.py 值温度 added 6.26

关键符号

get_responses get_values

关键源码片段

slime/backends/megatron_utils/loss.py core-logic

核心逻辑变更:`get_responses` 新增 `apply_temperature` 参数,`get_values` 调用时禁用温度缩放。

# slime/backends/megatron_utils/loss.py
def get_responses(
    logits: torch.Tensor,
    *,
    args: Namespace,
    unconcat_tokens: list[torch.Tensor],
    total_lengths: list[int],
    response_lengths: list[int],
    max_seq_lens: list[int] | None = None,
    apply_temperature: bool = True, # 新增参数,默认开启温度缩放(保持向后兼容)
) -> Iterator[tuple[torch.Tensor, torch.Tensor]]:
    ...
    # 只有 apply_temperature 为 True 且温度不为 1.0 时才缩放
    if apply_temperature and args.rollout_temperature != 1.0:
        logits = logits.div(args.rollout_temperature)
    ...def get_values(
    logits: torch.Tensor,
    *,
    args: Namespace,
    unconcat_tokens: list[torch.Tensor],
    total_lengths: list[int],
    response_lengths: list[int],
    with_entropy: bool = False,
    non_loss_data: bool = True,
    max_seq_lens: list[int] | None = None,
) -> dict[str, list[torch.Tensor]]:
    ...
    # 调用 get_responses 时显式禁用温度缩放,因为值输出不应被缩放
    for logits_chunk, tokens_chunk in get_responses(
        logits,
        args=args,
        unconcat_tokens=unconcat_tokens,
        total_lengths=total_lengths,
        response_lengths=response_lengths,
        max_seq_lens=max_seq_lens,
        apply_temperature=False,
    ):
        ...
tests/test_value_temperature.py test-coverage

新增零 GPU 单元测试,验证 get_values 不应用 rollout_temperature。

# tests/test_value_temperature.py
import sys
import types
from argparse import Namespace
import pytest
import torchNUM_GPUS = 0 # 零 GPU 测试,无需硬件def test_get_values_does_not_apply_rollout_temperature(monkeypatch):
    # 模拟 megatron 模块依赖
    previous_loss = sys.modules.pop("slime.backends.megatron_utils.loss", None)
    previous_cp_utils = sys.modules.pop("slime.backends.megatron_utils.cp_utils", None)
    mpu_stub = types.SimpleNamespace(
        get_context_parallel_world_size=lambda: 1,
        get_context_parallel_rank=lambda: 0,
    )
    megatron_mod = types.ModuleType("megatron")
    core_mod = types.ModuleType("megatron.core")
    core_mod.mpu = mpu_stub
    monkeypatch.setitem(sys.modules, "megatron", megatron_mod)
    monkeypatch.setitem(sys.modules, "megatron.core", core_mod)
    try:
        from slime.backends.megatron_utils.loss import get_values
        args = Namespace(qkv_format="thd", rollout_temperature=0.5, allgather_cp=False)
        logits = torch.tensor([[[1.0], [2.0], [3.0], [4.0]]], dtype=torch.float32)
        tokens = [torch.tensor([10, 11, 12, 13], dtype=torch.long)]
        _, result = get_values(
            logits,
            args=args,
            unconcat_tokens=tokens,
            total_lengths=[4],
            response_lengths=[2],
        )
        # 断言值输出未被缩放,即 [2.0, 3.0] 而非 [4.0, 6.0]
        torch.testing.assert_close(result["values"][0], torch.tensor([2.0, 3.0]))
    finally:
        # 恢复模块状态
        if previous_loss is None:
            sys.modules.pop("slime.backends.megatron_utils.loss", None)
        else:
            sys.modules["slime.backends.megatron_utils.loss"] = previous_loss
        if previous_cp_utils is None:
            sys.modules.pop("slime.backends.megatron_utils.cp_utils", None)
        else:
            sys.modules["slime.backends.megatron_utils.cp_utils"] = previous_cp_utils

评论区精华

bug 修复确认 正确性

合著者 zhuzilin 在 PR 中评论 "nice catch!",确认了 bug 的存在和修复的合理性。

结论:BUG 修复被认可,无其他讨论。 · 已解决

风险与影响

变更较小且语义明确,风险低。但需注意:所有调用 get_responses 的代码路径(包括策略 logprob 计算等)默认保持 apply_temperature=True,行为不变。get_values 是唯一需要传递 False 的显式调用,已在当前 PR 中覆盖。如果未来新增其他调用 get_responses 的路径,需注意温度设置。

影响范围:影响 critic value 头输出,从而影响 PPO/GRPO 等算法中的 value loss 和优势函数计算。修复后 value 输出更准确,训练更稳定。零 GPU 测试确保 CI 中不会因缺少 GPU 而跳过验证。

低风险变更 新增测试覆盖

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论