多节点训练 ========== 上次更新时间:2025 年 10 月 6 日。 作者:`Xibin Wu `_,`Yusheng Su `_。 选项 1:手动启动 ------------------------------ 设置多节点 Ray 集群 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1. 使用 ``ray start --head --dashboard-host=0.0.0.0`` 启动主节点。您需要关注两个地址: - GCS 地址:``ray start --address=
``,工作节点应连接到此地址。 - Dashboard 地址:``
:8265``,您应该将作业提交到集群。 .. image:: https://github.com/eric-haibin-lin/verl-community/blob/main/docs/ray/head.png?raw=true 2. 使用上面获取的 ``ray start --address=
`` 启动工作节点。 .. image:: https://github.com/eric-haibin-lin/verl-community/blob/main/docs/ray/worker.png?raw=true 3. 现在,您应该通过 ``ray status`` 看到集群有两个节点。 .. image:: https://github.com/eric-haibin-lin/verl-community/blob/main/docs/ray/status.png?raw=true 4. 此外,您可以通过上面获取的地址在浏览器中访问仪表板。 *可能需要配置防火墙规则才能访问仪表板,如有问题,请联系您的网络管理员。* .. image:: https://github.com/eric-haibin-lin/verl-community/blob/main/docs/ray/overview.png?raw=true 将作业提交到 Ray 集群 ~~~~~~~~~~~~~~~~~~~~~~~~~ 1. 使用上面获取的仪表板地址将 Ray 作业提交到集群。 .. code-block:: bash ray job submit --address="http://127.0.0.1:8265" \ --runtime-env=verl/trainer/runtime_env.yaml \ --no-wait \ -- \ python3 -m verl.trainer.main_ppo \ trainer.n_gpus_per_node=8 \ trainer.nnodes=2 \ ... .. image:: https://github.com/eric-haibin-lin/verl-community/blob/main/docs/ray/submit.png?raw=true 2. 然后,您可以使用以下命令检查作业状态: - ray job list:列出提交到集群的所有作业。 - ray job logs :查询作业的日志。 - ray job status :查询作业的状态。 - ray job stop :请求停止作业。 - ray job list | grep submission_id | grep JobStatus | grep RUNNING | grep -oP 'raysubmit_[^'\''"]+' | head -n 1:获取正在运行的作业的最新提交 ID。 - ray job logs --follow:向 ray job logs 命令添加 ``--follow`` 参数以启用连续日志流。 3. 您还可以从 ``/tmp/ray/session_latest/logs/`` 访问驱动程序/任务/Actor 日志,驱动程序日志是 ``job-driver-raysubmit_.log``。 4. 我们强烈建议您从仪表板查看多节点训练中的作业详情,因为它提供了更结构化的方式来查看作业信息。 .. image:: https://github.com/eric-haibin-lin/verl-community/blob/main/docs/ray/job.png?raw=true .. image:: https://github.com/eric-haibin-lin/verl-community/blob/main/docs/ray/job_detail.png?raw=true 选项 2:通过 SkyPilot 在 Kubernetes 或云上启动 ------------------------------------------------------ .. note:: SkyPilot 示例配置文件已包含在 `examples/skypilot/ `_ 目录中: - ``verl-ppo.yaml`` - 使用 GSM8K 数据集的 PPO 训练 - ``verl-grpo.yaml`` - 使用 MATH 数据集的 GRPO 训练 - ``verl-multiturn-tools.yaml`` - 多轮工具使用训练 有关详细使用说明,请参阅 `SkyPilot 示例 README `_。 步骤 1:设置 SkyPilot ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SkyPilot 支持不同的云,我们以 GCP 为例。 `安装 skypilot `_ .. code-block:: bash conda create -y -n sky python=3.10 conda activate sky pip install "skypilot[gcp]" conda install -c conda-forge google-cloud-sdk gcloud init # 如果您没有凭据文件,请运行此命令。 # 这将生成 ~/.config/gcloud/application_default_credentials.json。 gcloud auth application-default login # 检查 GCP 凭据是否已正确设置。 sky check gcp .. image:: https://github.com/yottalabsai/open-source/blob/main/static/verl/setup_skypilot.png?raw=true 步骤 2:准备数据集 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: bash git clone https://github.com/volcengine/verl.git cd examples/data_preprocess python3 gsm8k.py --local_save_dir ~/data/gsm8k 步骤 3:使用 SkyPilot 提交作业 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1. 创建一个 SkyPilot YAML 文件 ``verl-cluster.yml``,内容如下: .. parsed-literal:: workdir: . 将同步当前目录下的所有数据到远程集群。 .. code-block:: yaml resources: accelerators: L4:1 # 每个节点有 1 个 L4 GPU image_id: docker:verlai/verl:base-verl0.5-cu126-cudnn9.8-torch2.7.0-fa2.7.4 memory: 64+ # 每个节点有 64 GB 内存 ports: 8265 # Ray 仪表板暴露的端口 num_nodes: 2 # 集群大小 # --------------- 工作目录同步 (workdir) --------------- # 定义要同步到远程集群的本地工作目录。 # 这里,'.' 表示同步 sky submit 命令当前运行所在目录。 workdir: . # --------------- (secrets) --------------- secrets: ## 您的 wandb API 密钥 ## WANDB_API_KEY: null # --------------- 文件挂载/数据上传 (file_mounts) --------------- # 如果您的数据集(gsm8k 文件夹)是本地的,则需要将其上传到远程集群。 file_mounts: # 远程路径(相对于远程用户的家目录):本地路径 # /remote/dir1/file: /local/dir1/file data/gsm8k: ~/data/gsm8k # --------------- 环境设置 (setup) --------------- # 在远程集群的每个节点上运行的命令,用于设置环境(例如,安装依赖项)。这些命令直接在 Docker 内部运行。 setup: | rm -rf verl git clone https://github.com/volcengine/verl.git cd verl pip3 install -v -e .[vllm] # --------------- 运行命令 (run) --------------- # 在远程集群上执行的实际任务命令。 # 此脚本将首先启动 Ray 集群(在 Head 和 Worker 节点上执行不同的 ray start 命令)。 # 然后,您的训练脚本将仅在 Head 节点上运行 (SKYPILOT_NODE_RANK == 0)。 run: | # 获取 Head 节点的 IP 和节点总数(由 SkyPilot 注入的环境变量)。 head_ip=`echo "$SKYPILOT_NODE_IPS" | head -n1` num_nodes=`echo "$SKYPILOT_NODE_IPS" | wc -l` # 这里 num_nodes 应该等于 2。 # 登录 wandb python3 -c "import wandb; wandb.login(relogin=True, key='$WANDB_API_KEY')" # 根据节点角色启动 Ray(Head=0,Worker>0)。 # 此逻辑是标准的 Ray 集群启动脚本。 if [ "$SKYPILOT_NODE_RANK" == "0" ]; then # Head 节点启动 Ray Head。 echo "Starting Ray head node..." # 检查 Ray Head 是否已在运行,以避免重复启动。 ps aux | grep ray | grep 6379 &> /dev/null || ray start --head --disable-usage-stats \ --port=6379 \ --dashboard-host=0.0.0.0 \ --dashboard-port=8265 # 等待所有工作节点加入集群。 while [ $(ray nodes | grep NODE_ID | wc -l) -lt $num_nodes ]; do echo "Waiting for all nodes to join... ($(ray nodes | grep NODE_ID | wc -l)/$num_nodes)" sleep 5 done # Head 节点执行训练脚本。 echo "Executing training script on head node..." python3 -m verl.trainer.main_ppo \ data.train_files=data/gsm8k/train.parquet \ data.val_files=data/gsm8k/test.parquet \ data.train_batch_size=256 \ data.max_prompt_length=512 \ data.max_response_length=256 \ actor_rollout_ref.model.path=Qwen/Qwen2.5-0.5B-Instruct \ actor_rollout_ref.actor.optim.lr=1e-6 \ actor_rollout_ref.actor.ppo_mini_batch_size=64 \ actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=4 \ actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu=8 \ actor_rollout_ref.rollout.tensor_model_parallel_size=1 \ actor_rollout_ref.rollout.name=vllm \ actor_rollout_ref.rollout.gpu_memory_utilization=0.4 \ actor_rollout_ref.ref.log_prob_micro_batch_size_per_gpu=4 \ critic.optim.lr=1e-5 \ critic.model.path=Qwen/Qwen2.5-0.5B-Instruct \ critic.ppo_micro_batch_size_per_gpu=4 \ algorithm.kl_ctrl.kl_coef=0.001 \ trainer.logger=['console','wandb'] \ trainer.val_before_train=False \ trainer.default_hdfs_dir=null \ trainer.n_gpus_per_node=1 \ trainer.nnodes=2 \ trainer.save_freq=20 \ trainer.test_freq=20 \ trainer.total_epochs=2 \ trainer.project_name=verl_examples \ trainer.experiment_name=experiment_name_gsm8k else # 等待 Ray Head 启动。 sleep 10 # 增加等待时间以确保 Head 启动完成。 # Worker 节点启动 Ray Worker。 echo "Starting Ray worker node..." # 检查 Ray Worker 是否已在运行,以避免重复启动。 ps aux | grep ray | grep $head_ip:6379 &> /dev/null || ray start --address $head_ip:6379 --disable-usage-stats # 在 `ray start` 之后添加 sleep,以确保 Ray 有足够的时间成为守护进程 sleep 5 # 确保 Worker 成功连接到 Head。 fi # 这里没有向 Worker 节点添加任何命令;Worker 的主要任务是启动 Ray 并等待 Head 节点分配任务。 echo "Node setup and Ray start script finished for rank $SKYPILOT_NODE_RANK." .. code-block:: bash export WANDB_API_KEY= sky launch -c verl --secret WANDB_API_KEY verl-cluster.yml .. image:: https://github.com/yottalabsai/open-source/blob/main/static/verl/running_job.png?raw=true .. image:: https://github.com/yottalabsai/open-source/blob/main/static/verl/running_job_1.png?raw=true .. image:: https://github.com/yottalabsai/open-source/blob/main/static/verl/finished.png?raw=true **在 GCP 上检查集群** .. image:: https://github.com/yottalabsai/open-source/blob/main/static/verl/gcp_instances.png?raw=true **检查 Ray Dashboard** 我们可以通过 GCP 主节点在 RAY Dashboard 上看到集群: ```console $ sky status --endpoint 8265 verl 1.2.3.4:8265 ``` .. image:: https://github.com/yottalabsai/open-source/blob/main/static/verl/ray_dashboard_overview.png?raw=true .. image:: https://github.com/yottalabsai/open-source/blob/main/static/verl/ray_dashboard_jobs.png?raw=true .. image:: https://github.com/yottalabsai/open-source/blob/main/static/verl/ray_dashboard_cluster.png?raw=true **检查模型的检查点** .. code-block:: bash # 登录主节点 ssh verl # 全局步数会变化。请从训练日志中查找正确路径。 cd ~/sky_workdir/checkpoints/verl_examples/gsm8k/ # 然后列出内容以找到检查点,例如: ls -R . .. image:: https://github.com/yottalabsai/open-source/blob/main/static/verl/saved_model.png?raw=true 选项 3:通过 Slurm 启动 ------------------------------ Ray 为用户提供了`这个 `_ 官方教程,用于在 Slurm 之上启动 Ray 集群。我们已在多节点设置下,在 Slurm 集群上使用以下步骤验证了 :doc:`GSM8K 示例<../examples/gsm8k_example>`。 1. [可选] 如果您的集群支持 `Apptainer 或 Singularity `_ 并且您希望使用它们,请将 verl 的 Docker 镜像转换为 Apptainer 镜像。或者,使用集群上可用的包管理器设置环境,或使用您可用的其他容器运行时(例如,通过 `Slurm 的 OCI 支持 `_)。 .. code:: bash apptainer pull /your/dest/dir/vemlp-th2.4.0-cu124-vllm0.6.3-ray2.10-te1.7-v0.0.3.sif docker://verlai/verl:vemlp-th2.4.0-cu124-vllm0.6.3-ray2.10-te1.7-v0.0.3 2. 按照 :doc:`GSM8K 示例<../examples/gsm8k_example>` 准备数据集和模型检查点。 3. 修改 `examples/slurm/ray_on_slurm.slurm `_ 以反映您集群的专属信息。 4. 使用 `sbatch` 将作业脚本提交到 Slurm 集群。 请注意,Slurm 集群的设置可能会有所不同。如果遇到任何问题,请参考 Ray 的 `Slurm 用户指南 `_ 以了解常见注意事项。 如果您更改了 Slurm 资源规格,请务必在必要时更新作业脚本中的环境变量。 选项 4:通过 dstack 启动 ------------------------------ `dstackai/dstack `_ 是一个开源容器编排器,可简化跨云提供商和本地环境的分布式训练,而无需使用 K8S 或 Slurm。 先决条件 ~~~~~~~~~~~~ 一旦 dstack `安装 `_ 完成,请使用 ``dstack init`` 将目录初始化为一个仓库。 .. code-block:: bash mkdir myproject && cd myproject dstack init **创建一个 fleet (集群)** 在提交分布式训练作业之前,请创建一个 `dstack` `fleet (集群) `_。 运行 Ray 集群任务 ~~~~~~~~~~~~~~~~~~~~~~ 创建 cluster (集群) 后,定义一个 Ray 集群任务,例如在 ``ray-cluster.dstack.yml`` 中: .. code-block:: yaml type: task name: ray-verl-cluster nodes: 2 env: - WANDB_API_KEY - PYTHONUNBUFFERED=1 - CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 image: whatcanyousee/verl:ngc-cu124-vllm0.8.5-sglang0.4.6-mcore0.12.0-te2.2 commands: - git clone https://github.com/volcengine/verl - cd verl - pip install --no-deps -e . - pip install hf_transfer hf_xet - | if [ $DSTACK_NODE_RANK = 0 ]; then python3 examples/data_preprocess/gsm8k.py --local_save_dir ~/data/gsm8k python3 -c "import transformers; transformers.pipeline('text-generation', model='Qwen/Qwen2.5-7B-Instruct')" ray start --head --port=6379; else ray start --address=$DSTACK_MASTER_NODE_IP:6379 fi # 暴露 Ray 仪表板端口 ports: - 8265 resources: gpu: 80GB:8 shm_size: 128GB # 将检查点保存在实例上 volumes: - /checkpoints:/checkpoints 现在,如果您通过 `dstack apply` 运行此任务,它将自动将 Ray 的仪表板端口转发到 `localhost:8265`。 .. code-block:: bash dstack apply -f ray-cluster.dstack.yml 只要 `dstack apply` 正在运行,您就可以使用 `localhost:8265` 提交 Ray 作业以供执行。 提交 Ray 作业 ~~~~~~~~~~~~~~~ 在提交 Ray 作业之前,请确保在本地安装 `ray`: .. code-block:: shell pip install ray 现在,您可以将训练作业提交到 Ray 集群,该集群可以通过 ``localhost:8265`` 访问: .. code-block:: shell $ RAY_ADDRESS=http://localhost:8265 $ ray job submit \ -- python3 -m verl.trainer.main_ppo \ data.train_files=/root/data/gsm8k/train.parquet \ data.val_files=/root/data/gsm8k/test.parquet \ data.train_batch_size=256 \ data.max_prompt_length=512 \ data.max_response_length=256 \ actor_rollout_ref.model.path=Qwen/Qwen2.5-7B-Instruct \ actor_rollout_ref.actor.optim.lr=1e-6 \ actor_rollout_ref.actor.ppo_mini_batch_size=64 \ actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=4 \ actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu=8 \ actor_rollout_ref.rollout.tensor_model_parallel_size=1 \ actor_rollout_ref.rollout.gpu_memory_utilization=0.4 \ actor_rollout_ref.ref.log_prob_micro_batch_size_per_gpu=4 \ critic.optim.lr=1e-5 \ critic.model.path=Qwen/Qwen2.5-7B-Instruct \ critic.ppo_micro_batch_size_per_gpu=4 \ algorithm.kl_ctrl.kl_coef=0.001 \ trainer.project_name=ppo_training \ trainer.experiment_name=qwen-2.5-7B \ trainer.val_before_train=False \ trainer.n_gpus_per_node=8 \ trainer.nnodes=2 \ trainer.default_local_dir=/checkpoints \ trainer.save_freq=10 \ trainer.test_freq=10 \ trainer.total_epochs=15 2>&1 | tee verl_demo.log \ trainer.resume_mode=disable 有关 `dstack` 如何工作的更多详细信息,请查阅其 `文档 `_。 如何调试? --------------------- Ray Distributed Debugger VSCode 扩展(推荐) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1. 从 Ray 2.39 开始,Anyscale 推出了 `Ray Distributed Debugger `_ VSCode 扩展。请按照扩展的安装说明进行操作,然后使用您之前获取的仪表板 URL 添加您的集群。 .. image:: https://github.com/eric-haibin-lin/verl-community/blob/main/docs/ray/debugger.png?raw=true :alt: Ray Distributed Debugger VSCode 扩展截图 2. 先决条件。 确保已安装以下组件(有关更多详细信息,请参阅扩展的 README): - Visual Studio Code - `ray[default]` >= 2.9.1 - `debugpy` >= 1.8.0 .. image:: https://github.com/aoshen524/verl/blob/main/docs/start/c7098b755ff689859837773a916c857.png?raw=true :alt: 带有 Ray 先决条件的 VSCode 3. 环境变量。 要启用事后调试,请设置: .. code-block:: bash export RAY_DEBUG_POST_MORTEM=1 .. admonition:: 注意 :class: important 在启动 Ray 之前,请务必删除任何旧的标志: - `RAY_DEBUG=legacy` - `--ray-debugger-external` 4. 配置断点。在代码中设置 `breakpoint()` 并将作业提交到集群。然后,扩展将显示断点信息。 1. 在远程函数中插入 `breakpoint()` 调用。 2. 将作业提交到集群。 扩展将检测活动的断点并在 VSCode 中显示它们。 .. image:: https://github.com/aoshen524/verl/blob/main/docs/start/4ddad74395c79a1402331c0ce73316f.png?raw=true :alt: 在 VSCode 中检测到断点 **注意:**断点仅在用 `@ray.remote` 装饰的函数内部受支持。 5. 启动调试器。 直接从命令行运行您的作业(不要使用 `launch.json`): .. code-block:: bash python job.py 6. 附加到断点。 一旦进程命中第一个 `breakpoint()`,请点击 VSCode 侧边栏中的 Ray Distributed Debugger 图标以附加调试器。 .. image:: https://github.com/aoshen524/verl/blob/main/docs/start/4ddad74395c79a1402331c0ce73316f.png?raw=true :alt: 将 VSCode 调试器附加到 Ray 进程 7. 使用多个 `breakpoint()` 进行调试。 对于每个后续任务,首先断开当前调试器会话,然后再次单击扩展图标以附加到下一个断点。 .. image:: https://github.com/aoshen524/verl/blob/main/docs/start/6e83c910a62c82fecb89c6619e001cd.png?raw=true :alt: 断开并重新连接调试器 旧版 Ray Debugger ~~~~~~~~~~~~~~~~~~~ 1. Ray 有一个内置的旧版 `debugger `_,允许您调试分布式应用程序。要启用调试器,请使用 ``RAY_DEBUG=legacy`` 和 ``--ray-debugger-external`` 启动 Ray 集群。 .. code-block:: bash # 启动主节点 RAY_DEBUG=legacy ray start --head --dashboard-host=0.0.0.0 --ray-debugger-external # 启动工作节点 RAY_DEBUG=legacy ray start --address='10.124.46.192:6379' --ray-debugger-external 2. 在代码中设置断点,并将作业提交到集群。然后运行 ``ray debug`` 等待断点: .. image:: https://github.com/eric-haibin-lin/verl-community/blob/main/docs/ray/legacy.png?raw=true AMD 集群上的多节点训练 --------------------------------------------------------------------------------------- 如果您想在 AMD 集群上使用 Docker/Podman 容器通过 slurm 进行多节点训练,可以使用以下脚本。 如果您在使用 AMD GPU 运行 verl 时遇到任何问题,请联系 `Yusheng Su `_。 .. note:: 1. 在以下脚本中,您需要使用 ``podman`` 或 ``docker``。我们稍后将发布 apptainer 脚本。 2. 如果您想使用 ``podman``,只需将脚本中的 ``docker`` 替换为 ``podman``。 该脚本包含以下步骤: 1. SLURM 配置 2. 环境设置 3. Docker/Podman 容器设置 4. Ray 集群初始化 5. 数据预处理 6. 模型设置 7. 训练启动 slurm_script.sh ~~~~~~~~~~~~~~~~~~~~ .. code-block:: bash #!/bin/bash #SBATCH --job-name=verl-ray-on-slurm #SBATCH --nodes=2 #SBATCH --ntasks-per-node=2 #SBATCH --mem=200G #SBATCH --time=30-00:00:00 #SBATCH --gpus-per-node=8 #SBATCH --cpus-per-task=28 #SBATCH --output=../verl_log/slurm-%j.out #SBATCH --error=../verl_log/slurm-%j.err #SBATCH --nodelist=gpu-[0,1] # 加载必要的模块 ### 运行此设置 # [Cluster]: 使用 docker # docker pull docker.io/rocm/vllm:rocm6.2_mi300_ubuntu20.04_py3.9_vllm_0.6.4 ########################################################################## ###以下设置应在不同的项目和集群中配置### ########################################################################## ### 项目 CONTAINER_NAME="multinode_verl_training" IMG="verl.rocm" DOCKERFILE="docker/Dockerfile.rocm" # echo $PWD verl_workdir="${HOME}/projects/verl_upstream" export TRANSFORMERS_CACHE="${HOME}/.cache/huggingface" export HF_HOME=$TRANSFORMERS_CACHE ### 集群网络设置 export NCCL_DEBUG=TRACE export GPU_MAX_HW_QUEUES=2 export TORCH_NCCL_HIGH_PRIORITY=1 export NCCL_CHECKS_DISABLE=1 # export NCCL_IB_HCA=rdma0,rdma1,rdma2,rdma3,rdma4,rdma5,rdma6,rdma7 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 CUDA_DEVICE_MAX_CONNECTIONS=1 export NCCL_PROTO=Simple export RCCL_MSCCL_ENABLE=0 export TOKENIZERS_PARALLELISM=false export HSA_NO_SCRATCH_RECLAIM=1 ########################################################################## ### 用于 rocm 和训练脚本 export HIP_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 export ROCR_VISIBLE_DEVICES=$HIP_VISIBLE_DEVICES export CUDA_VISIBLE_DEVICES=$HIP_VISIBLE_DEVICES # 构建并启动 Docker 容器 srun bash -c " # 遇到任何错误时退出 set -e # 清理悬空镜像(标签为 的镜像) docker image prune -f # 需要先拉取 docker docker pull docker.io/rocm/vllm:rocm6.2_mi300_ubuntu20.04_py3.9_vllm_0.6.4 if ! docker images --format "{{.Repository}}:{{.Tag}}" | grep -q "${IMG}"; then echo \"Building ${IMG} image...\" docker build -f \"${DOCKERFILE}\" -t \"${IMG}\" . else echo \"${IMG} image already exists, skipping build\" fi # 删除旧容器(如果存在) docker rm \"${CONTAINER_NAME}\" 2>/dev/null || true # 检查网络设备 ibdev2netdev # 启动 docker docker run --rm -d \ -e HYDRA_FULL_ERROR=1 \ -e HIP_VISIBLE_DEVICES=${HIP_VISIBLE_DEVICES} \ -e ROCR_VISIBLE_DEVICES=${ROCR_VISIBLE_DEVICES} \ -e CUDA_VISIBLE_DEVICES=${CUDA_VISIBLE_DEVICES} \ -e NCCL_DEBUG=${NCCL_DEBUG} \ -e GPU_MAX_HW_QUEUES=${GPU_MAX_HW_QUEUES} \ -e TORCH_NCCL_HIGH_PRIORITY=${TORCH_NCCL_HIGH_PRIORITY} \ -e NCCL_CHECKS_DISABLE=${NCCL_CHECKS_DISABLE} \ -e NCCL_IB_HCA=${NCCL_IB_HCA} \ -e NCCL_IB_GID_INDEX=${NCCL_IB_GID_INDEX} \ -e NCCL_CROSS_NIC=${NCCL_CROSS_NIC} \ -e CUDA_DEVICE_MAX_CONNECTIONS=${CUDA_DEVICE_MAX_CONNECTIONS} \ -e NCCL_PROTO=${NCCL_PROTO} \ -e RCCL_MSCCL_ENABLE=${RCCL_MSCCL_ENABLE} \ -e TOKENIZERS_PARALLELISM=${TOKENIZERS_PARALLELISM} \ -e HSA_NO_SCRATCH_RECLAIM=${HSA_NO_SCRATCH_RECLAIM} \ -e TRANSFORMERS_CACHE=${TRANSFORMERS_CACHE} \ -e HF_HOME=${HF_HOME} \ --network host \ --device /dev/dri \ --device /dev/kfd \ --device /dev/infiniband \ --group-add video \ --cap-add SYS_PTRACE \ --security-opt seccomp=unconfined \ --privileged \ -v \${HOME}:\${HOME} \ -v \${HOME}/.ssh:/root/.ssh \ -w "${verl_workdir}" \ --shm-size 128G \ --name \"${CONTAINER_NAME}\" \ \"${IMG}\" \ tail -f /dev/null echo \"Container setup completed\" " # (可选):如果您不想使用 root 模式,并希望将自己分配为用户 # 请将 `-e HOST_UID=$(id -u)` 和 `-e HOST_GID=$(id -g)` 添加到上面的 docker 启动脚本中。 ### Ray 在训练前启动节点 # 获取节点名称 nodes_array=($(scontrol show hostnames "$SLURM_JOB_NODELIST" | tr '\n' ' ')) head_node=${nodes_array[0]} head_node_ip=$(srun --nodes=1 --ntasks=1 -w "$head_node" hostname --ip-address) # 如果检测到主节点 IP 中存在空格字符,我们将 # 将其转换为 ipv4 地址。此步骤是可选的。 if [[ "$head_node_ip" == *" "* ]]; then IFS=' ' read -ra ADDR <<<"$head_node_ip" if [[ ${#ADDR[0]} -gt 16 ]]; then head_node_ip=${ADDR[1]} else head_node_ip=${ADDR[0]} fi echo "检测到 IPV6 地址。我们将 IPV4 地址分割为 $head_node_ip" fi port=6379 ip_head=$head_node_ip:$port export ip_head echo "Head IP: $ip_head" # 确保我们在 Ray 初始化之前设置了环境变量 # 打印所有环境变量 printenv echo "在 $head_node 启动 HEAD" srun --nodes=1 --ntasks=1 -w "$head_node" \ docker exec "${CONTAINER_NAME}" \ ray start --head --node-ip-address="$head_node_ip" --port=$port \ --dashboard-port=8266 \ --num-cpus "${SLURM_CPUS_PER_TASK}" --num-gpus "${SLURM_GPUS_PER_NODE}" --block & # 可选,但在某些 Ray 版本 < 1.0 中可能有用。 sleep 10 # 工作节点以外的节点数量 worker_num=$((SLURM_JOB_NUM_NODES - 1)) for ((i = 1; i <= worker_num; i++)); do node_i=${nodes_array[$i]} echo "调试:在节点 node_i = ${node_i} 上启动工作节点" if [ -z "$node_i" ]; then echo "错误:工作节点 $i 的节点名称为空" continue fi echo "在 $node_i 启动 WORKER $i" srun --nodes=1 --ntasks=1 -w "$node_i" \ docker exec "${CONTAINER_NAME}" \ ray start --address "$ip_head" --num-cpus "${SLURM_CPUS_PER_TASK}" --num-gpus "${SLURM_GPUS_PER_NODE}" --block & sleep 5 done # Ray 初始化测试(检查以上执行过程中是否有错误) echo "在 slurm 节点上测试 Ray 初始化..." docker exec "${CONTAINER_NAME}" python3 -c ' import ray try: ray.init(address="auto") print("\n=== Ray 集群状态 ===") print(f"节点数: {len(ray.nodes())}") for node in ray.nodes(): print("节点: {}, 状态: {}".format(node["NodeManagerHostname"], node["Alive"])) # print(f"节点: {node}") ray.shutdown() print("Ray 初始化成功!") except Exception as e: print(f"Ray 初始化失败: {str(e)}") ' echo "=== Ray 测试完成 ===" ###### # 运行数据预处理 echo "开始数据预处理..." docker exec "${CONTAINER_NAME}" \ python3 "examples/data_preprocess/gsm8k.py" "--local_save_dir" "../data/gsm8k" echo "开始数据预处理..." docker exec "${CONTAINER_NAME}" \ python3 "examples/data_preprocess/math_dataset.py" "--local_dir" "../data/math" train_files="../data/gsm8k/train.parquet" val_files="../data/gsm8k/test.parquet" # 下载并测试模型 echo "加载模型..." docker exec "${CONTAINER_NAME}" \ python3 -c "import transformers; transformers.pipeline('text-generation', model='Qwen/Qwen2-7B-Instruct')" MODEL_PATH="Qwen/Qwen2-7B-Instruct" # 在 pipeline 测试后设置模型路径 MODEL_PATH="Qwen/Qwen2.5-0.5B-Instruct" echo "== 数据和模型加载完成 ==" echo "开始训练..." docker exec "${CONTAINER_NAME}" \ python3 -c "import transformers; transformers.pipeline('text-generation', model='Qwen/Qwen2-7B-Instruct')" MODEL_PATH="Qwen/Qwen2-7B-Instruct" PYTHONUNBUFFERED=1 srun --overlap --nodes=${SLURM_NNODES} --ntasks=1 -w "$head_node" \ docker exec "${CONTAINER_NAME}" \ python3 -m verl.trainer.main_ppo \ data.train_files=$train_files \ data.val_files=$val_files \ data.train_batch_size=1024 \ data.max_prompt_length=1024 \ data.max_response_length=1024 \ actor_rollout_ref.model.path=$MODEL_PATH \ actor_rollout_ref.model.enable_gradient_checkpointing=False \ actor_rollout_ref.actor.optim.lr=1e-6 \ actor_rollout_ref.model.use_remove_padding=True \ actor_rollout_ref.actor.ppo_mini_batch_size=256 \ actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=8 \ actor_rollout_ref.model.enable_gradient_checkpointing=True \ actor_rollout_ref.actor.fsdp_config.param_offload=False \ actor_rollout_ref.actor.fsdp_config.optimizer_offload=False \ actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu=16 \ actor_rollout_ref.rollout.tensor_model_parallel_size=2 \ actor_rollout_ref.rollout.name=vllm \ actor_rollout_ref.rollout.gpu_memory_utilization=0.9 \ actor_rollout_ref.ref.log_prob_micro_batch_size_per_gpu=16 \ actor_rollout_ref.ref.fsdp_config.param_offload=True \ critic.optim.lr=1e-5 \ critic.model.use_remove_padding=True \ critic.model.path=$MODEL_PATH \ critic.model.enable_gradient_checkpointing=False \ critic.ppo_micro_batch_size_per_gpu=8 \ critic.model.fsdp_config.param_offload=False \ critic.model.fsdp_config.optimizer_offload=False \ algorithm.kl_ctrl.kl_coef=0.0001 \ trainer.critic_warmup=0 \ trainer.logger='["console","wandb"]' \ trainer.project_name='verl_example' \ trainer.experiment_name='Qwen2.5-32B-Instruct_function_rm' \ trainer.n_gpus_per_node=${SLURM_GPUS_PER_NODE} \ trainer.val_before_train=False \ trainer.nnodes=${SLURM_NNODES} \ trainer.save_freq=-1 \ trainer.test_freq=10 \ trainer.total_epochs=15 使用上面的 slurm_script.sh 运行多节点训练 ~~~~~~~~~~~~~~~~~~~~ 只需 sbatch 您的 slurm_script.sh .. code-block:: bash sbatch slurm_script.sh