保姆级教学:在AMD集群运行verl全过程
1. 为什么选择verl?它到底能做什么
你可能已经听说过强化学习(RL)在大模型后训练中的重要性——比如让Qwen、Llama这类模型更懂人类偏好、更会拒绝有害请求、更擅长数学推理。但真正动手跑一次RL训练,很多人卡在第一步:环境太复杂、框架不兼容、GPU支持不友好。
verl就是为解决这些问题而生的。它不是另一个学术玩具,而是字节跳动火山引擎团队在HybridFlow论文基础上开源的生产级RL训练框架,专为大型语言模型(LLMs)的PPO、DPO等后训练任务设计。更重要的是,它从底层就考虑了AMD GPU集群的实际部署需求。
简单说,verl帮你把三件难事变简单了:
- 算法灵活:不用重写整个训练循环,几行配置就能切换PPO、KTO、Rejection Sampling等流程;
- 框架友好:不强制你换掉已有的vLLM推理服务或FSDP训练栈,而是“插件式”集成;
- AMD原生支持:不像很多RL框架只认NVIDIA CUDA,verl通过ROCm生态深度适配MI300系列GPU,通信、显存、算子全链路优化。
如果你正在AMD集群上做模型对齐、奖励建模或函数调用微调,又不想自己从零搭分布式RL流水线——verl很可能就是你现在最需要的那个“开箱即用”的答案。
2. 环境准备:AMD集群上的必要基础组件
在AMD集群上运行verl,不是简单pip install verl就能完事。它依赖一套协同工作的底层设施。我们按实际部署顺序,一项一项理清楚。
2.1 确认ROCm与驱动版本
首先确认你的节点已安装ROCm 6.2+和对应内核驱动。MI300系列GPU必须使用ROCm 6.2或更高版本,低版本会出现NCCL通信失败、HIP kernel launch timeout等典型报错。
检查命令:
rocm-smi --version hipconfig --version预期输出应类似:
ROCm Version: 6.2.0 HIP version: 6.2.22222注意:不要混用不同ROCm小版本(如6.2.0和6.2.1),容器内外版本需严格一致。建议统一使用官方推荐的
rocm/vllm:rocm6.2_mi300_ubuntu20.04_py3.9_vllm_0.6.4基础镜像。
2.2 安装并验证Ray集群管理器
verl使用Ray作为分布式任务调度核心。Ray版本是成败关键——文档明确要求Ray ≥ 2.40,且2.20以下已被弃用。低于2.40会导致ray job submit命令不可用、Actor生命周期管理异常等问题。
安装命令(在宿主机和容器内均需执行):
pip uninstall -y ray pip install "ray[default] >= 2.40.0" --upgrade验证是否正常:
ray start --head --dashboard-host=0.0.0.0 --port=6379 --dashboard-port=8265 ray status你应该看到类似输出:
======== Cluster status: 2025-04-05 10:23:45 ======== Node IP address: 10.124.46.192 Ray runtime context: Dashboard URL: http://10.124.46.192:8265 Node ID: 1a2b3c4d5e6f7g8h9i0j... Is head node: True Workers: 0 Resources: {'CPU': 128.0, 'GPU': 8.0, 'memory': 512.0}如果ray status报错“Failed to connect to GCS”,大概率是防火墙阻断了6379端口,需联系集群管理员放行。
2.3 配置InfiniBand网络与NCCL参数
AMD集群通常采用InfiniBand互联。verl在多节点训练中高度依赖NCCL进行GPU间梯度同步,因此必须正确识别网卡并设置GID索引。
运行以下命令确认IB设备:
ibstat ibdev2netdev典型输出:
CA 'mlx5_0' status: active (4) CA 'mlx5_1' status: active (4) ...然后在环境变量中固定指定这些设备(注意:不是rdma0/rdma1,而是mlx5_*):
export NCCL_IB_HCA=mlx5_0,mlx5_1,mlx5_2,mlx5_3,mlx5_4,mlx5_5,mlx5_8,mlx5_9 export NCCL_IB_GID_INDEX=3 export NCCL_CROSS_NIC=0 export NCCL_PROTO=Simple小贴士:
NCCL_IB_GID_INDEX=3适用于RoCEv2和IB网络混合场景;若纯IB环境,可尝试2或1,以iblinkinfo显示的GID列表为准。
3. 镜像构建与容器启动:一步到位的AMD适配方案
verl官方未提供预编译的AMD镜像,但提供了完整的Dockerfile.rocm。我们不建议直接在宿主机裸跑——容器化能彻底隔离ROCm、PyTorch、vLLM等组件的版本冲突。
3.1 构建verl ROCm镜像
进入verl源码根目录(假设路径为~/projects/verl_upstream),执行:
cd docker docker build -f Dockerfile.rocm -t verl.rocm .该Dockerfile已预置:
- PyTorch 2.3.0+rocm6.2
- vLLM 0.6.4(MI300优化版)
- verl主仓库代码(git clone + pip install -e .)
- 必要的HIP、RCCL、MIOpen依赖
构建成功后,用docker images | grep verl确认镜像存在。
3.2 启动带GPU权限的容器
AMD GPU容器需挂载特殊设备与权限。以下命令启动一个交互式调试容器(用于验证):
docker run -it --rm \ --device /dev/kfd --device /dev/dri \ --group-add video \ --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \ --shm-size=128g \ -v $HOME:/workspace \ -w /workspace \ -e HIP_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \ -e ROCR_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \ -e CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \ verl.rocm bash进入容器后,立即验证关键组件:
# Python内验证 import torch print(torch.__version__) # 应输出 2.3.0+rocm6.2 print(torch.cuda.is_available()) # 应为 True print(torch.cuda.device_count()) # 应为 8(MI300X) import vllm print(vllm.__version__) # 应为 0.6.4 import verl print(verl.__version__) # 应输出类似 0.1.0.dev0全部通过,说明容器环境已就绪。
4. 多节点Ray集群搭建:SLURM脚本逐行解析
真实训练必然跨节点。我们不再手动ray start,而是用SLURM统一调度——这是HPC集群的标准做法。下面这份slurm_script.sh不是黑盒,我们拆解每一部分的真实作用。
4.1 SLURM作业头配置说明
#SBATCH --nodes=2 #SBATCH --ntasks-per-node=2 #SBATCH --gpus-per-node=8 #SBATCH --cpus-per-task=28 #SBATCH --nodelist=gpu-[0,1]--nodes=2:申请2个计算节点(1个head + 1个worker)--ntasks-per-node=2:每个节点启动2个独立进程(用于后续docker exec并发控制)--gpus-per-node=8:MI300X单卡8 GPU,必须显式声明--nodelist=gpu-[0,1]:精确指定节点名,避免SLURM随机分配到不兼容硬件
4.2 容器启动阶段的关键点
脚本中这一段常被忽略,却是AMD训练稳定的核心:
docker run --rm -d \ -e HIP_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \ -e ROCR_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \ -e CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \ --device /dev/dri \ --device /dev/kfd \ --device /dev/infiniband \ --group-add video \ .../dev/dri和/dev/kfd是AMD GPU访问必需设备节点;--group-add video赋予容器访问GPU视频加速能力(影响vLLM推理性能);- 所有
VISIBLE_DEVICES必须完全一致,否则vLLM会报HIP_ERROR_INVALID_VALUE。
4.3 Ray集群初始化逻辑
脚本自动获取head节点IP并启动:
head_node_ip=$(srun --nodes=1 --ntasks=1 -w "$head_node" hostname --ip-address) srun --nodes=1 --ntasks=1 -w "$head_node" \ docker exec "${CONTAINER_NAME}" \ ray start --head --node-ip-address="$head_node_ip" --port=6379 \ --dashboard-port=8266 \ --num-cpus=28 --num-gpus=8 --block &这里有两个易错细节:
--node-ip-address必须是节点物理IP,不能是127.0.0.1或localhost;--num-gpus=8必须与--gpus-per-node一致,否则Ray无法正确分配GPU资源。
worker节点启动时,务必等待head完全就绪(脚本中sleep 10很关键),否则worker会因连接超时退出。
4.4 集群连通性自检
脚本末尾的Python测试段不是摆设:
import ray ray.init(address="auto") print("Number of nodes:", len(ray.nodes())) for node in ray.nodes(): print("Node:", node["NodeManagerHostname"], "Alive:", node["Alive"]) ray.shutdown()它验证三件事:
- 所有节点是否注册到GCS(Global Control Store);
- 每个节点的GPU资源是否被正确识别(
ray.nodes()中含"resources": {"GPU": 8.0}); - 跨节点Actor能否通信(为后续PPO rollout打基础)。
如果这里失败,请回查NCCL_IB_HCA、防火墙、ray start日志(位于/tmp/ray/session_latest/logs/)。
5. 数据与模型准备:避开常见陷阱
verl训练前需准备好高质量的SFT+RM数据集和基础模型。AMD环境下有特殊注意事项。
5.1 数据预处理:Parquet格式是首选
verl默认读取Parquet格式数据(比JSONL快3倍以上,内存占用低50%)。使用官方提供的预处理脚本:
docker exec multinode_verl_training \ python3 examples/data_preprocess/gsm8k.py --local_dir ../data/gsm8k该脚本会:
- 下载GSM8K原始数据;
- 使用
datasets库切分train/test; - 保存为
train.parquet和test.parquet,字段包含prompt、chosen、rejected等。
陷阱提醒:不要用
pandas.read_json手动转Parquet!verl的DataLoader依赖Arrow schema一致性。务必用官方脚本,或确保schema完全匹配:# 正确schema示例 schema = pa.schema([ ('prompt', pa.string()), ('chosen', pa.string()), ('rejected', pa.string()), ('reward_chosen', pa.float32()), ('reward_rejected', pa.float32()) ])
5.2 模型加载:HuggingFace模型的AMD适配要点
verl支持任意HF模型,但AMD上需注意:
- 模型必须支持FlashAttention-2:MI300的矩阵运算加速依赖FA2,Qwen2、Llama3等新架构默认开启;
- 禁用
torch.compile:ROCm 6.2下torch.compile(mode="max-autotune")会导致kernel crash,训练脚本中应注释掉; - 权重精度选择:FP16在MI300上不稳定,推荐BF16(需模型本身支持)或FP32(小模型可用)。
验证模型能否加载:
docker exec multinode_verl_training \ python3 -c " from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained( 'Qwen/Qwen2-7B-Instruct', torch_dtype='bfloat16', device_map='auto', trust_remote_code=True ) print('Model loaded successfully on AMD GPU') "若报错HIP out of memory,说明device_map='auto'未正确识别GPU数量,需显式指定device_map={'': 0}并配合HIP_VISIBLE_DEVICES。
6. 启动PPO训练:从配置到监控的完整流程
一切就绪后,最后一步是启动训练。我们以GSM8K数学推理对齐为例,详解关键参数含义。
6.1 核心训练命令拆解
docker exec multinode_verl_training \ python3 -m verl.trainer.main_ppo \ data.train_files=../data/gsm8k/train.parquet \ data.val_files=../data/gsm8k/test.parquet \ actor_rollout_ref.model.path=Qwen/Qwen2-7B-Instruct \ actor_rollout_ref.rollout.name=vllm \ actor_rollout_ref.rollout.gpu_memory_utilization=0.9 \ actor_rollout_ref.rollout.tensor_model_parallel_size=2 \ critic.model.path=Qwen/Qwen2-7B-Instruct \ trainer.nnodes=2 \ trainer.n_gpus_per_node=8 \ trainer.total_epochs=15 \ trainer.logger=['console','wandb']参数精讲:
actor_rollout_ref.rollout.name=vllm:指定使用vLLM作为rollout引擎,而非HuggingFace generate——这是verl在AMD上实现高吞吐的关键;tensor_model_parallel_size=2:将模型按层切分到2个GPU组(每组4卡),缓解单卡显存压力;gpu_memory_utilization=0.9:vLLM显存占用率,MI300X建议0.85~0.92,过高会OOM,过低则浪费算力;trainer.nnodes=2和trainer.n_gpus_per_node=8:必须与SLURM申请资源严格一致,否则verl无法正确初始化FSDP。
6.2 训练过程监控与日志定位
训练启动后,实时日志输出到容器标准输出。但更关键的是结构化日志:
- Ray Dashboard:访问
http://<head_node_ip>:8266,查看Actor状态、GPU利用率、任务队列; - W&B仪表盘:若启用
logger=['console','wandb'],所有loss、KL散度、reward分数自动同步; - 本地日志文件:容器内
/tmp/verl_logs/目录下生成ppo_train_YYYYMMDD_HHMMSS.log,含详细step级指标。
重点关注三项指标是否收敛:
actor/kl_divergence:应缓慢下降至0.01~0.05区间;reward/chosen_mean:随训练逐步上升,表明模型输出更符合人类偏好;rollout/throughput_tokens_per_sec:MI300X双节点理想值>12000 token/s(vLLM + 8卡)。
若kl_divergence持续为0,检查algorithm.kl_ctrl.kl_coef是否过小;若throughput低于5000,检查vllm是否启用PagedAttention及gpu_memory_utilization设置。
7. 常见问题排查:AMD集群专属解决方案
即使按上述步骤操作,仍可能遇到AMD特有问题。以下是高频故障与实测有效的解法。
7.1 NCCL超时与通信失败
现象:训练启动后卡在Initializing process group,日志出现NCCL_TIMEOUT或Connection reset by peer。
解决方案:
- 在
slurm_script.sh中添加:export NCCL_ASYNC_ERROR_HANDLING=0 export NCCL_SOCKET_TIMEOUT=1200000000 export NCCL_IB_DISABLE=0 - 确保所有节点时间同步:
sudo chronyc makestep; - 若使用RoCE,关闭ECN:
sudo rdma link set dev roce0 eth_pcp 0。
7.2 vLLM推理卡死或返回空字符串
现象:rollout阶段vllm进程无响应,或生成结果全为空。
解决方案:
- 强制指定vLLM后端:
export VLLM_ATTENTION_BACKEND=FLASHINFER(MI300X推荐); - 降低
max_num_seqs:从默认256改为128,缓解显存碎片; - 在启动命令中加入
--enable-prefix-caching,提升长上下文推理稳定性。
7.3 FSDP训练崩溃:RuntimeError: Expected all tensors to be on the same device
现象:critic模型FSDP初始化时报错,提示GPU设备不一致。
解决方案:
- 在
verl/trainer/main_ppo.py中找到setup_fsdp函数,强制指定device_id:fsdp_config = dict( sharding_strategy=ShardingStrategy.FULL_SHARD, cpu_offload=CPUOffload(offload_params=False), auto_wrap_policy=size_based_auto_wrap_policy, device_id=torch.cuda.current_device(), # ← 关键修复行 ) - 或在启动命令中添加:
actor_rollout_ref.actor.fsdp_config.device_id=0。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。