多节点训练

上次更新:06/10/2025。

作者:Xibin WuYusheng Su

选项 1:手动启动

设置多节点 Ray 集群

  1. 使用 ray start --head --dashboard-host=0.0.0.0 启动头节点,其中需要关注两个地址:

  • GCS 地址: ray start --address=<address>,工作节点需要连接到这个地址。

  • Dashboard 地址: <address>:8265,你可以向集群提交作业到这个地址。

https://github.com/eric-haibin-lin/verl-community/blob/main/docs/ray/head.png?raw=true
  1. 使用 ray start --address=<address> 启动工作节点,地址来自上面获取的。

https://github.com/eric-haibin-lin/verl-community/blob/main/docs/ray/worker.png?raw=true
  1. 现在,你应该看到集群中有 2 个节点,使用 ray status 检查。

https://github.com/eric-haibin-lin/verl-community/blob/main/docs/ray/status.png?raw=true
  1. 此外,你还可以在浏览器中通过上面获取的地址访问 Dashboard。

防火墙规则可能需要配置以访问 Dashboard,如果遇到任何问题,请联系你的网络管理员。

https://github.com/eric-haibin-lin/verl-community/blob/main/docs/ray/overview.png?raw=true

提交作业到 Ray 集群

  1. 使用 Dashboard 地址向集群提交 Ray 作业。

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 \
    ...
https://github.com/eric-haibin-lin/verl-community/blob/main/docs/ray/submit.png?raw=true
  1. 然后,你可以使用以下命令检查作业状态:

  • ray job list:列出提交到集群的所有作业。

  • ray job logs <Submission ID>:查询作业的日志。

  • ray job status <Submission ID>:查询作业的状态。

  • ray job stop <Submission ID>:请求停止作业。

  • ray job list | grep submission_id | grep JobStatus | grep RUNNING | grep -oP ‘raysubmit_[^’'’”]+’ | head -n 1:获取运行中作业的最新提交 ID(這裡的 submission_id 是指前面提到的 Submission ID)。

  • ray job logs <Submission ID> –follow:添加 --follow 参数以启用日志的连续流式传输。

  1. 你还可以在 /tmp/ray/session_latest/logs/ 中访问驱动程序/任务/Actor 日志,驱动程序日志为 job-driver-raysubmit_<Submission ID>.log

  2. 我们强烈推荐你在多节点训练中使用 Dashboard 来查看作业详情,因为它提供了一种更结构化的方式来查看作业信息。

https://github.com/eric-haibin-lin/verl-community/blob/main/docs/ray/job.png?raw=true 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 examples README

步骤 1:设置 SkyPilot

SkyPilot 可以支持不同的云,这里以 GCP 为例。安装 skypilot

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
https://github.com/yottalabsai/open-source/blob/main/static/verl/setup_skypilot.png?raw=true

步骤 2:准备数据集

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,内容如下:

workdir: .  将同步当前目录中的所有数据到远程集群。
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 Dashboard 使用

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 启动命令)。
# 然后,你的训练脚本只在 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 "正在启动 Ray Head 节点..."
    # 检查是否已经有 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

    # 等待所有 Worker 节点加入集群。
    while [ $(ray nodes | grep NODE_ID | wc -l) -lt $num_nodes ]; do
      echo "等待所有节点加入... ($(ray nodes | grep NODE_ID | wc -l)/$num_nodes)"
      sleep 5
    done

    # Head 节点执行训练脚本。
    echo "在 Head 节点上执行训练脚本..."

    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 "正在启动 Ray Worker 节点..."

    # 检查是否已经有 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 "Rank $SKYPILOT_NODE_RANK 的节点设置和 Ray 启动脚本完成。"
export WANDB_API_KEY=<your-wandb-api-key>
sky launch -c verl --secret WANDB_API_KEY verl-cluster.yml
https://github.com/yottalabsai/open-source/blob/main/static/verl/running_job.png?raw=true https://github.com/yottalabsai/open-source/blob/main/static/verl/running_job_1.png?raw=true https://github.com/yottalabsai/open-source/blob/main/static/verl/finished.png?raw=true

在 GCP 上检查集群

https://github.com/yottalabsai/open-source/blob/main/static/verl/gcp_instances.png?raw=true

检查 Ray Dashboard

我们可以在 Ray Dashboard 上查看集群,该 Dashboard 在 GCP Head 节点上运行:

`console $ sky status --endpoint 8265 verl 1.2.3.4:8265 `

https://github.com/yottalabsai/open-source/blob/main/static/verl/ray_dashboard_overview.png?raw=true https://github.com/yottalabsai/open-source/blob/main/static/verl/ray_dashboard_jobs.png?raw=true https://github.com/yottalabsai/open-source/blob/main/static/verl/ray_dashboard_cluster.png?raw=true

检查模型的检查点

# 登录 Head 节点
ssh verl
# 全局步数会变化。从训练日志中查找正确的路径,例如:
cd ~/sky_workdir/checkpoints/verl_examples/gsm8k/
# 然后列出内容以查找检查点,例如:
ls -R .
https://github.com/yottalabsai/open-source/blob/main/static/verl/saved_model.png?raw=true

选项 3:通过 Slurm 启动

Ray 为用户提供了 这个 官方教程,用于在 Slurm 上启动 Ray 集群。我们已经在多节点设置下的 Slurm 集群上验证了 GSM8K 示例,使用以下步骤。

  1. [可选] 如果你的集群支持 Apptainer 或 Singularity 并且你希望使用它,将 Verl 的 Docker 镜像转换为 Apptainer 镜像。或者,使用集群上可用的包管理器设置环境,或使用其他容器运行时(例如通过 Slurm 的 OCI 支持)。

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
  1. 遵循 GSM8K 示例 来准备数据集和模型检查点。

  2. 根据你的集群信息修改 examples/slurm/ray_on_slurm.slurm

  3. 使用 sbatch 将作业脚本提交到 Slurm 集群。

请注意,Slurm 集群设置因集群而异。如果你遇到任何问题,请参考 Ray 的 Slurm 用户指南 中的常见注意事项。

如果你更改了 Slurm 资源规范,请确保在作业脚本中相应地更新环境变量(如果需要)。

选项 4:通过 dstack 启动

dstackai/dstack 是一个开源的容器编排器,可以简化跨云提供商和本地环境的分布式训练,而无需使用 K8S 或 Slurm。

前提条件

安装 dstack 后,使用 dstack init 将目录初始化为仓库。

mkdir myproject && cd myproject
dstack init

创建舰队

在提交分布式训练作业之前,请创建 dstack 舰队

运行 Ray 集群任务

创建舰队后,定义一个 Ray 集群任务,例如在 ray-cluster.dstack.yml 中:

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: verlai/verl:app-verl0.6-transformers4.56.1-sglang0.5.2-mcore0.13.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 Dashboard 端口
ports:
    - 8265

resources:
    gpu: 80GB:8
    shm_size: 128GB

# 将检查点保存到实例上
volumes:
    - /checkpoints:/checkpoints

现在,如果你使用 dstack apply 运行此任务,它将自动转发 Ray 的 Dashboard 端口到 localhost:8265

dstack apply -f ray-cluster.dstack.yml

只要 dstack apply 已附加,你就可以使用 localhost:8265 向 Ray 集群提交作业以执行

提交 Ray 作业

在你可以提交 Ray 作业之前,请确保本地安装了 ray

pip install ray

现在你可以将训练作业提交到 Ray 集群,该集群可在 localhost:8265 访问:

$ 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 分布式调试器 VSCode 扩展(推荐)

  1. 从 Ray 2.39 开始,Anyscale 引入了 Ray 分布式调试器 VSCode 扩展。按照扩展的安装说明操作,然后使用之前获得的 Dashboard URL 添加你的集群到扩展中。

    Ray Distributed Debugger VSCode 扩展截图
  2. 先决条件.

    确保以下已安装(有关更多详细信息,请查看扩展 README):

    • Visual Studio Code

    • ray[default] >= 2.9.1

    • debugpy >= 1.8.0

    VSCode 与 Ray 先决条件
  3. 环境变量.

    要启用事后调试(post-mortem debugging),请设置:

    export RAY_DEBUG_POST_MORTEM=1
    

    注意

    在启动 Ray 之前,请移除任何遗留标志:

    • RAY_DEBUG=legacy

    • –ray-debugger-external

  4. 配置断点. 在你的代码中设置 breakpoint() 调用,然后向集群提交作业。然后,扩展将显示断点信息。

    1. 在远程函数中插入 breakpoint() 调用。

    2. 向集群提交作业。

    扩展将检测活动断点,并在 VSCode 中显示它们。

    VSCode 中的检测到的断点

    注意: 断点仅在用 @ray.remote 装饰的函数中受支持。

  5. 启动调试器.

    直接从命令行运行你的作业(不要使用 launch.json):

    python job.py
    
  6. 附加到断点.

当进程第一次命中 breakpoint() 时,点击 VSCode 侧边栏中的 Ray 分布式调试器图标来附加调试器。

将 VSCode 调试器附加到 Ray 进程
  1. 调试多个 breakpoint().

    对于每个后续任务,首先断开当前调试器会话,然后再次点击扩展图标以附加到下一个断点。

    断开连接和重新连接调试器

传统 Ray 调试器

  1. Ray 有一个内置的传统 调试器,允许你调试分布式应用。要启用调试器,使用 RAY_DEBUG=legacy--ray-debugger-external 启动 Ray 集群。

# 启动 Head 节点
RAY_DEBUG=legacy ray start --head --dashboard-host=0.0.0.0 --ray-debugger-external
# 启动 Worker 节点
RAY_DEBUG=legacy ray start --address='10.124.46.192:6379' --ray-debugger-external
  1. 在你的代码中设置断点,然后向集群提交作业。然后运行 ray debug 等待断点:

https://github.com/eric-haibin-lin/verl-community/blob/main/docs/ray/legacy.png?raw=true

AMD 集群上的多节点训练

如果你想在 Slurm 与 Docker/Podman 容器的 AMD 集群上运行多节点训练,可以使用以下脚本。

如果你在使用 AMD GPU 运行 Verl 时遇到任何问题,请联系 Yusheng Su

Note

  1. 你需要在以下脚本中使用 podmandocker。稍后我们将发布 Apptainer 脚本。

  2. 如果你想使用 podman,只需在以下脚本中将 docker 替换为 podman

这个脚本包括以下步骤:

  1. SLURM 配置

  2. 环境设置

  3. Docker/Podman 容器设置

  4. Ray 集群初始化

  5. 数据预处理

  6. 模型设置

  7. 训练启动

slurm_script.sh

   #!/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]


   # 加载必要模块
   ### 运行此设置
   # [集群]:使用 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

       # 清理悬空的图像(具有 <none> 标签的图像)
       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 \"正在构建 ${IMG} 镜像...\"
           docker build -f \"${DOCKERFILE}\" -t \"${IMG}\" .
       else
           echo \"${IMG} 镜像已存在,跳过构建\"
       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 \"容器设置完成\"
   "
       # (可选):如果你不想使用根模式并且需要将自己分配为用户
       # 请在上面的 Docker 启动脚本中添加 `-e HOST_UID=$(id -u)` 和 `-e HOST_GID=$(id -g)`。





   ### 训练前启动 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 \"IP Head: $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

   # Head 节点以外的 Worker 节点数
   worker_num=$((SLURM_JOB_NUM_NODES - 1))

   for ((i = 1; i <= worker_num; i++)); do
       node_i=${nodes_array[$i]}
       echo \"Debug:  node_i = \${node_i} 启动 Worker\"
       if [ -z \"$node_i\" ]; then
           echo \"错误:Worker $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(f\"节点: {node['NodeManagerHostname']}, 状态: {node['Alive']}\")
           # print(f\"Node: {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 -c \"import transformers; transformers.pipeline('text-generation', model='Qwen/Qwen2-7B-Instruct')\"
   MODEL_PATH=\"Qwen/Qwen2-7B-Instruct\"

   # 模型路径设置后
   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

sbatch slurm_script.sh