Prhub

#6062 [fully_async, rollout, trainer, tool, cfg] fix: ROCm async training compatibility for AMD MI300X

原始 PR 作者 xiaohong42 合并时间 2026-04-20 16:26 文件变更 8 提交数 2 评论 9 代码增减 +39 / -12

执行摘要

修复完全异步 FSDP2 训练在 AMD ROCm 平台(MI300X 系列)的兼容性问题。

根据PR body描述,完全异步FSDP2训练在AMD ROCm平台(MI300X系列)上无法正常工作,具体表现为FSDP初始化失败(ncclSystemError)、ZMQ通信不匹配、JSON序列化错误和IPC套接字残留导致重启失败。作者在AMD Instinct MI3xx(8× GPU, 192 GB HBM each)和NVIDIA H20上进行了交叉验证,旨在使训练流程在AMD平台上稳定运行。

该PR值得精读,特别是ZMQ IPC句柄重构的设计决策,它展示了如何通过平台无关的rank算术解决硬件特定的通信不匹配问题。建议关注_get_zmq_handle方法的实现和讨论中关于错误处理与Actor命名的权衡。

讨论亮点
  1. IPC套接字清理的错误处理gemini-code-assist[bot]指出,仅捕获FileNotFoundError在多用户环境中存在风险,残留套接字文件可能因权限问题导致PermissionError,进而使训练进程终止。建议改为捕获更宽泛的OSError。作者在第二次提交中采纳了此建议,将except FileNotFoundError改为except OSError
  2. Ray Actor全局名称的潜在冲突gemini-code-assist[bot]警告,硬编码的全局名称"sandbox-execution-pool"在多租户Ray集群中可能导致碰撞,使新作业意外连接到现有池。作者xiaohong42解释,Verl训练作业通过ray.init()创建独立的Ray集群,因此跨作业的命名空间冲突不会发生;硬编码名称旨在同一训练会话内重用Actor,与同文件中TokenBucketWorker的模式一致。
  3. AMD环境变量的作用wuxibin89询问HSA_NO_SCRATCH_RECLAIM环境变量的用途。xiaohong42详细解释,该变量是AMD RCCL在MI300X GPU上的必需设置,用于控制GPU暂存内存回收,避免FSDP初始化失败。

实现拆解

  1. 添加AMD RCCL必需的环境变量:在verl/trainer/constants_ppo.pyPPO_RAY_RUNTIME_ENV中添加"HSA_NO_SCRATCH_RECLAIM": "1",以解决FSDP初始化时的ncclSystemError。该变量在非AMD平台上会被忽略。
  2. 修复JSON序列化错误:在verl/trainer/ppo/ray_trainer.py_dump_generations方法中,将json.dumps调用改为json.dumps(..., default=str),以处理NumPy 2.x中numpy.bool_不再是Python bool子类的问题。
  3. 重构ZMQ IPC句柄生成逻辑:在verl/workers/rollout/vllm_rollout/vllm_rollout.pyverl/workers/rollout/vllm_rollout/utils.pyverl/workers/rollout/vllm_rollout/vllm_async_server.py中,将句柄从基于GPU UUID改为基于(replica_rank, local_rank)。这解决了ROCm平台上因CUDA_VISIBLE_DEVICES/HIP_VISIBLE_DEVICES设置不同导致的GPU UUID不匹配问题,并避免了多副本共享节点时的套接字冲突。
  4. 清理残留的ZMQ IPC套接字文件:在verl/workers/rollout/vllm_rollout/bucketed_weight_transfer.py_init_socket_cleanup方法中,添加逻辑在绑定前和清理后删除IPC套接字文件,使用except OSError捕获权限错误等异常,防止重启时出现Address already in use错误。
  5. 修复Hydra配置路径:将verl/experimental/fully_async_policy/config/fully_async_ppo_trainer.yaml中的搜索路径从file://verl/trainer/config改为pkg://verl.trainer.config,以支持可编辑安装。
  6. 防止Ray Actor重复创建:在verl/tools/sandbox_fusion_tools.pyinit_execution_pool函数中,为ExecutionWorker添加name="sandbox-execution-pool"get_if_exists=True选项,确保在同一训练会话中重用Actor。
文件 模块 状态 重要度
verl/workers/rollout/vllm_rollout/utils.py Rollout 通信 modified 6.46
verl/workers/rollout/vllm_rollout/vllm_rollout.py Rollout 通信 modified 6.0
verl/workers/rollout/vllm_rollout/bucketed_weight_transfer.py 权重传输 modified 6.14
verl/trainer/constants_ppo.py 训练配置 modified 4.75
verl/trainer/ppo/ray_trainer.py 训练器核心 modified 4.32
verl/tools/sandbox_fusion_tools.py 工具脚本 modified 4.32
verl/workers/rollout/vllm_rollout/vllm_async_server.py 异步服务器 modified 4.18
verl/experimental/fully_async_policy/config/fully_async_ppo_trainer.yaml 实验配置 modified 2.9

关键符号

_get_zmq_handle __init__ _init_socket _cleanup _dump_generations init_execution_pool

关键源码片段

verl/workers/rollout/vllm_rollout/utils.py core-logic

修改了 vLLMColocateWorkerExtension 和 vLLMOmniColocateWorkerExtension 的 _get_zmq_handle 方法,将句柄从基于 GPU UUID 改为基于 (replica_rank, local_rank),这是解决 ROCm 通信不匹配的核心改动之一。

def _get_zmq_handle(self) -> str:
    """Get ZMQ handle for communication.
    Uses replica_rank + local_rank to form handle so it matches the sender side
    regardless of CUDA_VISIBLE_DEVICES differences, and avoids collisions
    when multiple replicas share the same node.
    """
    replica_rank = os.environ.get("VERL_REPLICA_RANK", "0") # 从环境变量获取副本 rank,默认为 "0"
    return f"ipc:///tmp/rl-colocate-zmq-replica-{replica_rank}-rank-{self.local_rank}.sock" # 基于副本 rank 和本地 rank 构建句柄
verl/workers/rollout/vllm_rollout/vllm_rollout.py core-logic

修改了 VLLMRollout 类的 __init__ 方法,重构了 ZMQ 句柄生成逻辑,使用 replica_rank 和 node-local rank 替代 GPU UUID,并添加了详细注释解释原因。

def __init__(self, config, replica_rank=-1):
    # ... 省略 rank 计算等初始化代码 ...
    # Use replica_rank + node-local rank to form ZMQ handle instead of GPU UUID,
    # because CheckpointEngineWorker and vLLM worker may see different GPU UUIDs
    # when CUDA_VISIBLE_DEVICES differs between processes (common on ROCm/AMD).
    # Must use node-local rank (not rollout_rank) so it matches vLLM worker's
    # local_rank on every node. Include replica_rank to avoid collisions when
    # multiple replicas share a node.
    local_rank = self.rollout_rank % local_world_size # 计算节点本地 rank
    self.zmq_handle = f"ipc:///tmp/rl-colocate-zmq-replica-{self.replica_rank}-rank-{local_rank}.sock" # 构建新句柄
verl/workers/rollout/vllm_rollout/bucketed_weight_transfer.py core-logic

在 BucketedWeightSender 的 _init_socket 和 _cleanup 方法中添加了 IPC 套接字文件清理逻辑,防止重启时出现 'Address already in use' 错误,并根据 review 反馈改进了错误处理。

def _init_socket(self):
    """Initialize ZMQ REQ socket and bind."""
    if self.zmq_handle.startswith("ipc://"):
        ipc_path = self.zmq_handle[len("ipc://") :] # 提取 IPC 文件路径
        try:
            os.remove(ipc_path) # 尝试删除残留文件
        except OSError: # 捕获所有文件系统错误(包括 PermissionError)
            pass # 忽略错误,避免进程崩溃
    self.socket = self.zmq_context.socket(zmq.REQ)
    self.socket.bind(self.zmq_handle)

评论区精华

IPC 套接字清理的错误处理 正确性

gemini-code-assist[bot] 指出仅捕获 FileNotFoundError 在多用户环境中存在风险,残留文件可能导致 PermissionError 并使训练进程终止。建议改为捕获更宽泛的 OSError。

结论:作者在第二次提交中采纳建议,将 except FileNotFoundError 改为 except OSError,以更优雅地处理文件系统错误。 · 已解决

Ray Actor 全局名称的潜在冲突 设计

gemini-code-assist[bot] 警告硬编码全局名称 'sandbox-execution-pool' 在多租户 Ray 集群中可能导致碰撞,使新作业意外连接到现有池。xiaohong42 解释 Verl 训练作业通过 ray.init() 创建独立集群,跨作业冲突不会发生;硬编码名称旨在同一会话内重用 Actor,与现有模式一致。

结论:作者认为当前设计是合理的,未作修改,因为 Verl 的作业隔离机制降低了风险。 · 已解决

AMD 环境变量的作用 question

wuxibin89 询问 HSA_NO_SCRATCH_RECLAIM 环境变量的用途。xiaohong42 详细解释该变量是 AMD RCCL 在 MI300X GPU 上的必需设置,用于控制 GPU 暂存内存回收,避免 FSDP 初始化失败。

结论:通过解释澄清了该变量的必要性和平台特异性。 · 已解决

风险与影响

  1. 平台兼容性风险:新增的HSA_NO_SCRATCH_RECLAIM环境变量是AMD特有的,在NVIDIA或Ascend平台上会被忽略,但需确保不会意外干扰其他硬件。
  2. ZMQ句柄逻辑变更风险:ZMQ IPC句柄从基于GPU UUID改为基于(replica_rank, local_rank),虽然解决了ROCm不匹配问题,但需确保在所有部署场景(如单节点、多节点、多副本)下都能正确匹配,避免通信失败。
  3. IPC套接字清理风险:清理残留套接字文件时捕获OSError可能掩盖其他文件系统问题,但权衡后认为防止重启失败更重要。
  4. Ray Actor重用风险:硬编码的Actor名称在同一Ray集群内可能导致意外重用,但作者解释Verl的作业隔离机制降低了此风险。
  5. 测试覆盖不足:由于改动针对ROCm特定运行时行为,无法在CI中无AMD硬件的情况下充分测试,可能隐藏平台特定bug。
  1. 用户影响:使完全异步FSDP2训练能够在AMD ROCm平台(如MI300X)上稳定运行,扩展了硬件支持范围。用户无需修改代码,所有改动为内部实现细节。
  2. 系统影响:提升了跨平台兼容性,ZMQ句柄逻辑的改进也使多节点、多副本部署更健壮。环境变量和JSON序列化的修复是通用改进,对所有平台有益。
  3. 团队影响:为后续在AMD硬件上开展训练任务铺平道路,减少了平台特定的调试开销。
跨平台兼容性 核心通信路径变更 缺少硬件特定测试

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论