Prhub

#7050 [rocm] feat: enable DeepSeek-V4-Flash GRPO on AMD GPUs

原始 PR 作者 PeterYang12 合并时间 2026-08-07 16:36 文件变更 8 提交数 1 评论 3 代码增减 +180 / -4

执行摘要

在 AMD GPU 上启用 DeepSeek-V4-Flash GRPO 训练。

PR 标题与 body 明确目标是让 DeepSeek-V4-Flash 的 GRPO 训练在 AMD GPU 上可跑通。实现上主要解决三类障碍:vLLM 引擎以 dummy 权重启动时在 ROCm 上会清零 expert 并行路由映射且权重 refit 不恢复;MLA 稀疏注意力缓存的 bf16 wo_a 副本不会随权重更新;MXFP4 refit 缺少 AITER 后端支持。此外作者在评论中回应了 Megatron 版本对齐问题,将镜像中的 Megatron-LM 固定到 NVIDIA 官方提交,避免版本不匹配带来的兼容性补丁。

值得精读。核心看点有两个:一是 restore_moe_expert_maps 以“布局是纯函数、就地位写回维持 CUDA graph 地址”的思路修复 RL 循环中推理引擎的隐藏状态,设计注释非常清晰;二是 refit 触发点(update_weights_from_ipc 的 step 1 与 process_quanted_weights_after_loading)的选择。阅读时建议同时核对当前 vLLM 版本中这些内部 API 是否仍然存在,并考虑补充一个 CPU 或 ROCm 冒烟测试。

讨论亮点

HollowMan6 在 review 中直接提问:是否在跑 DSv4 时使用了 NVIDIA 侧指定的 Megatron 提交,并认为改动更像是版本不匹配的兼容性修复,使用这些提交时并不需要。作者回复其实际使用的是 radixark 的 Megatron-Bridge fork 与较早的 Megatron-LM 提交,随后在评论中表示“Aligned with NVIDIA”,最终镜像参数与 NVIDIA 提交对齐。结论是版本差异导致的问题通过升级依赖解决,而不是保留适配层。

gemini-code-assist 对早期 megatron_hc_head_contraction.patch 提出性能建议:hyper-connection 残差流的加权和在 transformer 前向热路径上使用 Python 循环,应改为广播加 sum(dim=2) 的向量化写法以避免多次中间张量分配。该建议针对的 patch 未出现在合并后的文件清单中(合并落地的是 non-leaf-fix.patch),因此这条意见的采纳情况不明,但体现了热路径避免 Python 循环的优化原则。

最终由 wuxibin89 approve,无未解决的阻塞性讨论。

实现拆解

  1. 新增 verl/utils/vllm/rocm_vllm_moe_expert_map.py,提供 restore_moe_expert_maps(model):遍历所有 RoutedExperts 层,利用 determine_expert_map 按并行布局重建 _expert_mapexpert_mask,并就地写回以保持 CUDA graph 地址有效;形状不一致时抛错。该修复被接入 verl/workers/rollout/vllm_rollout/utils.pyupdate_weights_from_ipc:在进入权重加载流程的第一步,若 torch.version.hip 非空则对所有模型(含 drafter)先重建映射,保证后续 bucketed 权重 refit 的路由正确。
  2. verl/utils/vllm/vllm_quant_utils.py 新增 clear_rocm_attention_weight_caches,并在 process_quanted_weights_after_loading 开头调用:只对 ROCm + DeepSeek-V4 模型删除模块上的 _dsv4_wo_a_bf16 缓存,让 vLLM 在后续前向中惰性重建,避免注意力继续使用上一轮权重派生的 bf16 副本。
  3. verl/utils/vllm/vllm_fp4_utils.py_refittable_mxfp4_backends 增加 AITER_MXFP4_BF16:ROCm 的 AITER 后端权重转换可通过 replace_parameter 回放,满足 refit 可重放条件,从而在 RL 权重更新后能重新生成 MXFP4 推理布局。
  4. 新增 docker/rocm/Dockerfile.rocm.deepseek-v4docker/rocm/patches/non-leaf-fix.patch:基于 vLLM ROCm 官方镜像,安装 ROCm 7.2.3 的 apex 与 transformer_engine wheel 及 mbridge、flash-linear-attention 等依赖,固定 Megatron-LM 到 fd1121b8… 并应用补丁 —— 补丁将分布式优化器的 model_param.view(-1)[...] 改为 detach().view(-1)[...],避免 torch.optim 收到非叶子张量而报错。
  5. examples/grpo_trainer/run_deepseek_v4_flash_megatron.shMODEL_PATHTRAIN_FILETEST_FILE 从硬编码改为 ${VAR:-默认值},便于在 AMD 环境覆盖路径;examples/grpo_trainer/README.md 将 DeepSeek-V4-Flash 的硬件支持列从 nvidia 更新为 nvidia, amd。

测试与配套:本次没有新增测试文件;验证依赖 PR body 中的 step3 实验指标(rollout_probs 皮尔逊相关系数 0.996、log_ppl 差异 0.0014 等)以及 AMD 环境的实际运行。Dockerfile 与 patch 属于基础设施配套,未接入 CI 工作流。

文件 模块 状态 重要度
verl/utils/vllm/rocm_vllm_moe_expert_map.py 权重同步 added 7.49
verl/utils/vllm/vllm_quant_utils.py 量化设施 modified 6.35
verl/workers/rollout/vllm_rollout/utils.py 采样引擎 modified 5.48
docker/rocm/Dockerfile.rocm.deepseek-v4 镜像构建 added 4.57
docker/rocm/patches/non-leaf-fix.patch 镜像补丁 added 4.37
verl/utils/vllm/vllm_fp4_utils.py 量化设施 modified 4.18
examples/grpo_trainer/run_deepseek_v4_flash_megatron.sh 示例脚本 modified 3.01
examples/grpo_trainer/README.md 使用文档 modified 1.32

关键符号

restore_moe_expert_maps clear_rocm_attention_weight_caches process_quanted_weights_after_loading update_weights_from_ipc _refittable_mxfp4_backends

关键源码片段

verl/utils/vllm/rocm_vllm_moe_expert_map.py core-logic

新增文件,核心修复:ROCm 上 dummy 权重初始化会清零 expert 并行路由映射,refit 权重流不恢复,导致 token 全部路由到本地 expert 0。该函数按并行布局重建映射并就地写回。

# verl/utils/vllm/rocm_vllm_moe_expert_map.pyimport logging
import oslogger = logging.getLogger(__name__)
logger.setLevel(os.getenv("VERL_LOGGING_LEVEL", "WARN"))
​
​
def restore_moe_expert_maps(model):
    """重建每个 ``RoutedExperts`` 层的 EP 路由映射(就地写回)。    rollout 引擎以 dummy 权重启动,在 ROCm 上 int32 的 ``_expert_map`` 与
    ``expert_mask`` 缓冲会被初始化为 0,而 refit 权重流并不会恢复它们 ——
    这些映射只由并行布局决定,不来自 checkpoint。清零的映射会把所有 token
    路由到本地 expert 0,而不是真正的归属 rank。    这里选择就地写回(in-place),保证此前捕获的 CUDA graph 地址仍然有效;
    该函数可安全地在每次 refit 时调用:映射是布局的纯函数,重建要么是
    无操作、要么是修复。没有启用 expert 并行的层会被跳过。
    """
    from vllm.model_executor.layers.fused_moe import RoutedExperts
    from vllm.model_executor.layers.fused_moe.expert_map_manager import determine_expert_map
​
    repaired = 0
    for module in model.modules():
        if not isinstance(module, RoutedExperts):
            continue
        manager = getattr(module, "expert_map_manager", None)
        if manager is None or not module.moe_config.moe_parallel_config.use_ep:
            continue
​
        # 不直接调用 ExpertMapManager._calculate_expert_maps:它每次都会把
        # fused shared experts 折进 local_num_experts,重放会虚增专家数。
        _, expert_map, expert_mask = determine_expert_map(
            ep_size=manager.ep_size,
            ep_rank=manager.ep_rank,
            global_num_experts=manager.global_num_experts,
            expert_placement_strategy=manager.placement_strategy,
            num_fused_shared_experts=manager.num_fused_shared_experts,
            return_expert_mask=manager.rocm_aiter_enabled,
        )
​
        for name, rebuilt in (("_expert_map", expert_map), ("expert_mask", expert_mask)):
            live = getattr(module, name, None)
            if live is None or rebuilt is None:
                continue
            if live.shape != rebuilt.shape:
                # 布局形状对不上属于硬错误:说明 rollout 引擎底下的 expert
                # 布局发生了变更,不能静默覆盖。
                raise RuntimeError(
                    f"Rebuilt {name} is {tuple(rebuilt.shape)} but the live buffer is "
                    f"{tuple(live.shape)}; the expert layout changed under the rollout engine."
                )
            live.copy_(rebuilt.to(device=live.device))
            repaired += 1
​
    if repaired:
        logger.info("Rebuilt %d expert-parallel routing buffers on the rollout model", repaired)
    return repaired
verl/utils/vllm/vllm_quant_utils.py core-logic

核心路径修改:新增 clear_rocm_attention_weight_caches,并在 process_quanted_weights_after_loading 中调用,保证 refit 后注意力不使用上一轮权重派生的 bf16 wo_a 副本。

# verl/utils/vllm/vllm_quant_utils.py(节选)def clear_rocm_attention_weight_caches(model):
    """丢弃 vLLM 从已加载权重派生的 bf16 ``wo_a`` 副本。    ``rocm_aiter_mla_sparse._get_cached_wo_a_bf16`` 会把反量化后的 ``wo_a``
    缓存在模块上,并假设权重是静态的。refit 更新了权重但不会更新缓存,
    导致注意力继续使用上一轮的副本。重建是惰性的,所以这里只需要删除缓存。    只有 ROCm 上的 DeepSeek-V4 会构建该缓存,因此下方先做平台与模型守卫。
    """
    if torch.version.hip is None or not is_deepseek_v4_model(model):
        return
​
    for module in model.modules():
        if hasattr(module, "_dsv4_wo_a_bf16"):
            del module._dsv4_wo_a_bf16
​
​
def process_quanted_weights_after_loading(model, reload_state):
    """重新应用 prepare_quanted_weights_for_loading 撤消的推理布局。"""
    clear_rocm_attention_weight_caches(model) # ROCm DSV4:先失效注意力缓存
    apply_mxfp8_transformation_after_loading(model)
    reload_state = reload_state or {}
    process_fp8_weights_after_loading(reload_state.get("fp8_layers") or [])
    process_mxfp4_moe_weights_after_loading(reload_state.get("mxfp4_moe_modules") or [])
verl/workers/rollout/vllm_rollout/utils.py dependency-wiring

权重 refit 的入口点:在 update_weights_from_ipc 的 step 1 中按平台插入 restore_moe_expert_maps 调用,是所有 rollout 权重更新的必经路径。

# verl/workers/rollout/vllm_rollout/utils.py(update_weights_from_ipc 节选)def update_weights_from_ipc(self, peft_config: dict = None, base_sync_done=False, use_shm: bool = False):
    """更新 rollout 模型的权重。"""
    from verl.workers.rollout.vllm_rollout.bucketed_weight_transfer import BucketedWeightReceiver
​
    if self.device is None:
        # vLLM worker 在非 CUDA 平台(如 NPU)上可能不设置 self.device,
        # 回退到当前加速器上的 local rank。
        self.device = torch.device(f"{get_device_name()}:{self.local_rank}")
​
    # =========================== step 1: prepare for weight loading ===========================
    quant_reload_states = None
​
    # 引擎以 dummy 权重启动,ROCm 上其初始化会把整型缓冲清零 —— 包括 expert
    # 并行路由映射,而权重流不会恢复它们。在 refit 之前修复,保证 rollout 路由正确。
    if torch.version.hip is not None:
        from verl.utils.vllm.rocm_vllm_moe_expert_map import restore_moe_expert_maps
​
        for model in self._iter_all_models():
            restore_moe_expert_maps(model)
​
    if self._is_qat_model:
        # QAT(compressed-tensors):必须在接收任何 bucket 之前准备好权重加载
        from verl.utils.qat import prepare_qat_for_load_weights
​
        for model in self._iter_all_models():
            prepare_qat_for_load_weights(model, device=self.device)
        logger.info("QAT: prepare_qat_for_load_weights completed")
    elif self._is_modelopt_qat:
        from verl.utils.modelopt.vllm_modelopt_patch import prepare_modelopt_for_weight_reload
​
        prepare_modelopt_for_weight_reload(self.model_runner.model, device=self.device)
        logger.info("ModelOpt: prepare_modelopt_for_weight_reload completed")
    elif is_fp8_model(self.model_runner.vllm_config):
        from verl.utils.vllm.vllm_quant_utils import prepare_quanted_weights_for_loading
​
        quant_reload_states = [
            (model, prepare_quanted_weights_for_loading(model)) for model in self._iter_all_models()
        ]
    else:
        # TODO(wuxibin): 更新的 vllm 版本不再需要。
        for model in self._iter_all_models():
            patch_vllm_moe_model_weight_loader(model)

评论区精华

Megatron 版本对齐与兼容性改动是否必要 question

HollowMan6 询问是否使用 NVIDIA 指定 commits 运行 DSv4,并认为改动像版本不匹配的兼容补丁。作者回复实际使用 radixark 的 Megatron-Bridge fork 与较早的 Megatron-LM 提交,随后在评论中宣布 Aligned with NVIDIA。

结论:最终 Dockerfile 将 Megatron-LM 固定为 fd1121b8、Megatron-Bridge 固定为 v0.5.0,与 NVIDIA 提交对齐,版本差异问题通过升级依赖解决。 · 已解决

hyper-connection 收缩的 Python 循环应向量化 性能

gemini-code-assist 对早期的 megatron_hc_head_contraction.patch 建议用广播与 sum(dim=2) 替代热路径中的 Python 循环,避免多次中间张量分配。

结论:该建议针对的 patch 未出现在合并后的文件清单,实际合入的是 non-leaf-fix.patch;建议未被直接采纳,但体现了热路径避免 Python 循环的优化原则。 · outdated

风险与影响

上游 API 耦合:restore_moe_expert_maps 直接依赖 vLLM 内部符号(RoutedExpertsexpert_map_managerdetermine_expert_map),clear_rocm_attention_weight_caches 依赖 _dsv4_wo_a_bf16 属性名;vLLM 任一版本升级都可能使这些假设失效,而该 PR 没有配套测试拦截。

测试缺失:本次改动无新增单测或集成测试,ROCm 路径在 CI 中不可达;update_weights_from_ipcprocess_quanted_weights_after_loading 是 rollout 权重同步的核心路径,回归面较大。

镜像可维护性:Dockerfile.rocm.deepseek-v4 强绑定 ROCm 7.2.3 wheel、固定 commits 与 non-leaf-fix.patch;Megatron-LM 优化器内部逻辑演进后 patch 可能冲突或不再必要。

影响面控制良好:两处运行时修复都有 torch.version.hipis_deepseek_v4_model 守卫,非 ROCm 平台无行为变化;AITER_MXFP4_BF16 只在 AITER 后端被选择时进入回灌路径。

用户侧:AMD GPU 用户可基于新镜像与 run_deepseek_v4_flash_megatron.sh 直接拉起 DeepSeek-V4-Flash GRPO,脚本支持通过环境变量覆盖模型与数据集路径;README 同步声明 amd 支持。
系统侧:新增一套 ROCm 运行时依赖(AITER、flash-linear-attention、mbridge 等)与镜像构建路径,需要维护。
团队侧:建立“权重 refit 后必须修复推理引擎内部派生状态”的模式(路由映射 + 量化缓存 + 注意力缓存),对后续新模型与新硬件支持有参考价值。

缺少测试覆盖 依赖 vLLM 内部 API 平台限定逻辑(ROCm) 新镜像未接入 CI 外部依赖版本锁定

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论