Prhub

#6526 [megatron] feat: align optimizer states and DDP grad bucket with model precision

原始 PR 作者 kolehma8 合并时间 2026-06-23 12:06 文件变更 7 提交数 1 评论 9 代码增减 +321 / -3

执行摘要

Megatron 优化器状态与 DDP 梯度桶对齐模型类型

在bf16训练中,Megatron分布式优化器保持Adam动量和梯度累积缓冲在fp32,占用了不必要的显存(3倍所需)。本PR使其可选地遵循模型dtype,类似于Megatron CLI的--use-precision-aware-optimizer等选项。最终经过讨论和实验验证,确定为可配置选项,默认保持fp32以避免收敛风险。

该PR值得精读,尤其是精度感知优化器与DDP梯度桶的联动设计。对于关注显存优化的团队,建议评估后启用,并在各自任务上进行收敛性验证。

讨论亮点

讨论主要围绕优化器精度是否应保持fp32展开:

  • wuxibin89 最初评论“optimizer should be in fp32”,认为优化器应保持fp32。
  • kolehma8 解释可以暴露为配置,并指出FSDP中已有此做法。
  • wuxibin89 澄清FSDP中他们强制模型fp32,优化器也因此fp32,并要求实验验证低精度对收敛的影响。
  • kolehma8 在Issue #6576中发布了对比实验结果,显示在此示例中数值精度不影响奖励,建议将低精度作为可选配置而非默认。
  • wuxibin89 要求解决冲突(megatron_workers.py已移除),最终批准了PR。

实现拆解

  1. 重构init_megatron_optim_configverl/utils/megatron/optimizer.py):添加bf16参数(默认True),将原先的else分支拆分为elif bf16:(bf16模式)和else:(fp32模式)。在elif bf16:内部,当检测到use_precision_aware_optimizer=True时,启用精度感知优化器,将main_grads_dtypeexp_avg_dtypeexp_avg_sq_dtype设置为对应dtype(利用PrecisionType.to_dtype转换);fp32模式则禁用精度感知优化器。fp16分支保持不变。
  2. 扩展McoreOptimizerConfigverl/workers/config/optimizer.py):新增use_precision_aware_optimizer(默认False)、main_grads_dtype(默认"fp32")、exp_avg_dtype(默认"fp32")、exp_avg_sq_dtype(默认"fp32")字段,并添加__post_init__验证dtype字符串合法性。同步更新verl/trainer/config/optim/megatron.yaml_generated_ppo_megatron_trainer.yaml默认配置。
  3. 引擎集成verl/workers/engine/megatron/transformer_impl.py):新增_resolve_override_ddp_config方法,根据优化器配置判断是否需将grad_reduce_in_fp32设为False以同步DDP梯度桶dtype;用户显式设置时优先。在_build_megatron_module中调用该方法,并在_build_optimizer调用init_megatron_optim_config时传入bf16参数。
  4. 其他调用点更新megatron_workers.py中的调用已随文件删除而移除(冲突解决后仅保留transformer_impl.py的更新)。
  5. 新增测试tests/utils/megatron/test_optimizer.py包含9个CPU单元测试,通过monkeypatchOptimizerConfig为记录器验证各模式下组装的kwargs,覆盖bf16默认fp32、bf16启用精度感知、每字段独立控制、fp16分支、fp32分支、默认参数传递、override优化器配置等场景。
文件 模块 状态 重要度
tests/utils/megatron/test_optimizer.py 测试 added 8.05
verl/utils/megatron/optimizer.py 优化器 modified 6.79
verl/workers/engine/megatron/transformer_impl.py 引擎 modified 7.24

关键符号

init_megatron_optim_config McoreOptimizerConfig.__post_init__ _resolve_override_ddp_config _base_optim_config _precision_aware_optim_config test_bf16_branch_defaults_to_fp32_optimizer_state test_bf16_branch_opt_in_enables_precision_aware_with_bf16_state test_bf16_opt_in_respects_per_field_dtypes test_fp16_branch_uses_precision_aware_but_keeps_fp32_optimizer_state

关键源码片段

tests/utils/megatron/test_optimizer.py test-coverage

9 个 CPU 单元测试验证各模式下配置组装,确保逻辑正确性

import pytest
import torch
from omegaconf import OmegaConf
from unittest.mock import MagicMock
from verl.utils.megatron import optimizer as opt_mod
from verl.utils.megatron.optimizer import init_megatron_optim_configdef _base_optim_config(**overrides):
    # 基础优化配置字典(OmegaConf)
    cfg = {
        "optimizer": "adam",
        "lr": 1e-3,
        "min_lr": 0.0,
        "clip_grad": 1.0,
        "weight_decay": 0.01,
    }
    cfg.update(overrides)
    return OmegaConf.create(cfg)def _precision_aware_optim_config(**overrides):
    # 启用精度感知优化器的配置
    fields = {
        "use_precision_aware_optimizer": True,
        "main_grads_dtype": "bf16",
        "exp_avg_dtype": "bf16",
        "exp_avg_sq_dtype": "bf16",
    }
    fields.update(overrides)
    return _base_optim_config(**fields)@pytest.fixture
def captured_args(monkeypatch):
    # 替换 OptimizerConfig 为记录器,捕获实际传入的 kwargs
    captured: dict = {}
    def _fake(**kwargs):
        captured.clear()
        captured.update(kwargs)
        return MagicMock(name="OptimizerConfig", **kwargs)
    monkeypatch.setattr(opt_mod, "OptimizerConfig", _fake)
    return captureddef test_bf16_branch_defaults_to_fp32_optimizer_state(captured_args):
    # 默认 bf16 模式下优化器状态保持 fp32(精度感知未启用)
    init_megatron_optim_config(_base_optim_config(), fp16=False, bf16=True)
    assert captured_args["bf16"] is True
    assert captured_args["params_dtype"] is torch.bfloat16
    # 不应出现精度感知相关的键
    assert "use_precision_aware_optimizer" not in captured_args
    assert "main_grads_dtype" not in captured_args
    assert "exp_avg_dtype" not in captured_args
    assert "exp_avg_sq_dtype" not in captured_argsdef test_bf16_branch_opt_in_enables_precision_aware_with_bf16_state(captured_args):
    # 显式启用精度感知优化器后,main_grads_dtype 等应变为 bf16
    init_megatron_optim_config(
        _precision_aware_optim_config(), fp16=False, bf16=True
    )
    assert captured_args["use_precision_aware_optimizer"] is True
    assert captured_args["main_grads_dtype"] is torch.bfloat16
    assert captured_args["exp_avg_dtype"] is torch.bfloat16
    assert captured_args["exp_avg_sq_dtype"] is torch.bfloat16
    # master_params_dtype 保留 fp32(TE FusedAdam 限制)
    assert "main_params_dtype" not in captured_args
verl/utils/megatron/optimizer.py dependency-wiring

核心函数 init_megatron_optim_config 的重构,增加 bf16 参数和精度感知分支

import torch
from megatron.core.optimizer import OptimizerConfig
from verl.utils.logger import print_rank_0
from verl.utils.torch_dtypes import PrecisionTypedef init_megatron_optim_config(
    optim_config: dict,
    use_distributed_optimizer: bool = True,
    fp16: bool = False,
    bf16: bool = True, # 默认 True 对应 bf16 模式,但精度感知优化器默认关闭
) -> OptimizerConfig:
    # 基础配置项
    optim_args = {
        "optimizer": optim_config.optimizer,
        "lr": optim_config.lr,
        "min_lr": optim_config.min_lr,
        "clip_grad": optim_config.clip_grad,
        "weight_decay": optim_config.weight_decay,
        "use_distributed_optimizer": use_distributed_optimizer,
    }
    if fp16:
        # fp16 分支:启用精度感知优化器,但 Adam 动量保持 fp32(默认)
        optim_args.update({
            "bf16": False,
            "fp16": True,
            "params_dtype": torch.float16,
            "initial_loss_scale": 32768,
            "min_loss_scale": 1,
            "use_precision_aware_optimizer": True,
            "store_param_remainders": False,
        })
    elif bf16:
        # bf16 分支:模型参数为 bf16,但优化器状态默认 fp32(向后兼容)
        optim_args.update({
            "bf16": True,
            "params_dtype": torch.bfloat16,
        })
        # 精度感知优化器为可选项:仅在配置中设置 use_precision_aware_optimizer=True 后启用
        if optim_config.get("use_precision_aware_optimizer", False):
            optim_args.update({
                "use_precision_aware_optimizer": True,
                # 从配置中读取各字段类型,默认 fp32(即不降低精度)
                "main_grads_dtype": PrecisionType.to_dtype(
                    optim_config.get("main_grads_dtype", "fp32")
                ),
                "exp_avg_dtype": PrecisionType.to_dtype(
                    optim_config.get("exp_avg_dtype", "fp32")
                ),
                "exp_avg_sq_dtype": PrecisionType.to_dtype(
                    optim_config.get("exp_avg_sq_dtype", "fp32")
                ),
            })
    else:
        # fp32 分支:禁用精度感知优化器(Megatron 要求关闭时 dtype 必须为 fp32)
        optim_args.update({
            "bf16": False,
            "fp16": False,
            "params_dtype": torch.float32,
        })
    # 允许用户通过 override_optimizer_config 完全覆盖最终的任何配置项
    override_config = optim_config.get("override_optimizer_config", {})
    if override_config:
        for k, v in override_config.items():
            optim_args[k] = v
    print_rank_0(f"optimizer config after override: {optim_args}")
    config = OptimizerConfig(**optim_args)
    return config
verl/workers/engine/megatron/transformer_impl.py core-logic

新增 _resolve_override_ddp_config 确保 DDP 梯度桶与精度感知优化器一致,并更新 _build_optimizer 调用

def _resolve_override_ddp_config(self):
    # 保持 DDP 梯度桶 dtype 与优化器的梯度缓冲 dtype 一致
    # 当精度感知优化器启用且 main_grads_dtype 低于 fp32 时,
    # 自动设置 grad_reduce_in_fp32=False,除非用户已显式指定
    from verl.utils.torch_dtypes import PrecisionType
​
    override_ddp_config = dict(self.engine_config.override_ddp_config or {})
    opt_cfg = self.optimizer_config
    if (
        opt_cfg is not None
        and getattr(opt_cfg, "use_precision_aware_optimizer", False)
        and PrecisionType.to_dtype(getattr(opt_cfg, "main_grads_dtype", "fp32")) != torch.float32
        and "grad_reduce_in_fp32" not in override_ddp_config
    ):
        override_ddp_config["grad_reduce_in_fp32"] = False
    return override_ddp_configdef _build_megatron_module(self):
    # ... 其他代码 ...
    override_ddp_config = self._resolve_override_ddp_config() # 新增调用
    module, updated_tf_config = make_megatron_module(
        ...
        override_ddp_config=override_ddp_config, # 使用解析后的配置
        ...
    )
    # ...def _build_optimizer(self):
    # ...
    optim_config_megatron = init_megatron_optim_config(
        self.optimizer_config,
        use_distributed_optimizer=self.engine_config.use_distributed_optimizer,
        fp16=self.param_dtype == torch.float16,
        bf16=self.param_dtype == torch.bfloat16, # 新增:传入 bf16 标志
    )
    # ...

评论区精华

优化器精度是否应保持 fp32 设计

wuxibin89 最初评论“optimizer should be in fp32”认为应保持 fp32;kolehma8 提议暴露为配置并引用 FSDP 做法;wuxibin89 澄清 FSDP 中模型强制 fp32 所以优化器为 fp32。

结论:经过实验验证,决定将低精度作为可选配置,默认保留 fp32。 · 已解决

收敛性验证要求 正确性

wuxibin89 要求实验验证低精度对收敛的影响;kolehma8 在 Issue #6576 中发布对比结果,显示无明显影响。

结论:实验支撑了选项的可行性,最终确定为配置而非默认。 · 已解决

冲突解决(megatron_workers.py 移除) other

wuxibin89 指出 megatron_workers.py 已移除,要求解决冲突。

结论:kolehma8 rebase 解决冲突,仅保留 transformer_impl.py 中的改动。 · 已解决

风险与影响

  1. 数值收敛风险:启用bf16优化器状态可能导致某些场景下训练不收敛,虽然实验显示无影响,但默认关闭降低了风险,用户需自行验证。
  2. DDP梯度桶一致性风险_resolve_override_ddp_config确保了一致性,但依赖于main_grads_dtype配置。默认main_grads_dtype为fp32时不会修改grad_reduce_in_fp32,安全。
  3. TransformerEngine限制:TE FusedAdam不支持bf16 master weights,因此main_params_dtype保留fp32,无法进一步省显存。
  4. 向后兼容性:默认行为未变,不影响现有用户。
  5. 测试覆盖:CPU单元测试覆盖配置逻辑,但缺乏端到端集成测试。
  • 用户影响:使用Megatron后端的用户可通过配置降低优化器状态显存(约1/3),默认不启用无影响。
  • 系统影响:新增配置项和内部逻辑,不改变现有训练流程。
  • 团队影响:增加维护复杂性但代码结构化清晰,测试覆盖主要分支。
默认关闭,需显式启用 依赖 TE FusedAdam 需要额外验证收敛性 仅 Megatron 后端 缺乏端到端 CI 测试

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论