本文聚焦的实际工作与贡献
- 针对 每节点非全满 GPU 的场景(例如两台 8 卡节点,每节点仅用 4 卡),提出并实现了按 GPU-node 维度精确设置
HIP_VISIBLE_DEVICES的方案。 - 在 SGLang 内部按照 Slurm GPU ID 进行设备分配,确保跨节点与跨 NUMA 的拓扑一致性。
- 修复并明确了 server 配置(
MASTER_ADDR/PORT、nccl-init-addr、缓存目录隔离等),避免多节点下的初始化/缓存冲突。 - 在 MI210 节点(每节点 8 卡,构成两个 NUMA node)上设计并执行 decode throughput 的对比实验,衡量同 NUMA 与跨 NUMA 的性能差异。
背景与目标
在 HPC 集群里,Slurm 是最常见的资源调度器,AMD GPU 使用 ROCm 与 RCCL(PyTorch 层面仍通过 NCCL 接口)完成加速与通信。本文整理如何在多节点且每节点并非占满全部 GPU的场景下,稳定部署 SGLang 推理服务。
节点与拓扑(MI210)
- 节点:每节点 8 张 MI210,划分为 两个 NUMA node。
- 互连:MI210 最多支持 3 个 Infinity Fabric 插槽;同 NUMA 与 跨 NUMA 的连接带宽差异显著。
- 实验重点:比较同 NUMA vs 跨 NUMA 的 decode throughput 表现。
前置环境
- 操作系统/内核:按集群默认(常见为 RHEL/CentOS/Ubuntu LTS)。
- 驱动/库:
ROCm对应版本,PyTorch的 ROCm 构建(pip/conda 安装对应 rocm 版本)。 - 通信:
RCCL(以 NCCL 环境变量配置)、IB 或 RoCE 网卡(例如ib0)。 - 调度:Slurm 配置了
GresTypes=gpu,节点标注了 GPU 型号(如mi250)。
非全满 GPU 分配的关键点
当每节点只使用部分 GPU(例如 3/8),务必确保:
- 使用
--gres=gpu:<type>:<count>精确申请数量,并避免与其他作业冲突。 - 正确限制进程可见设备:
HIP_VISIBLE_DEVICES与(若集群自动设置)CUDA_VISIBLE_DEVICES的一致性。 - 跨节点通信仅在分配到的进程间进行,
WORLD_SIZE/RANK和MASTER_ADDR/MASTER_PORT正确配置。
按 GPU-node 设置 HIP_VISIBLE_DEVICES
在两台 8 卡节点、每节点仅用 4 卡的布局中,建议每节点 1 个进程,并在该进程内设置对应的 HIP_VISIBLE_DEVICES:
# 依据 Slurm 暴露的变量优先确定可见设备
if [[ -n "${SLURM_JOB_GPUS}" ]]; then
export HIP_VISIBLE_DEVICES=${SLURM_JOB_GPUS}
elif [[ -n "${CUDA_VISIBLE_DEVICES}" ]]; then
# 某些集群即便是 AMD GPU 也会设置 CUDA_VISIBLE_DEVICES
export HIP_VISIBLE_DEVICES=${CUDA_VISIBLE_DEVICES}
else
# 回退:若每节点使用 4 卡且按 0..3 编号(示例),可手动指定
export HIP_VISIBLE_DEVICES=0,1,2,3
fi
echo "[INFO] HIP_VISIBLE_DEVICES=${HIP_VISIBLE_DEVICES}"
如果需要更细粒度控制(例如按 NUMA 划分),可在作业提交前为不同节点提前规划索引映射,并在 node_rank 维度上分别设置。
常用 Slurm 环境变量
SLURM_NODELIST:当前作业的节点列表。SLURM_NNODES:节点数量。SLURM_PROCID/SLURM_NODEID:当前任务在作业内的全局/节点内序号。SLURM_JOB_GPUS:当前节点分配到本作业的 GPU 索引(需要 Slurm 版本支持)。
推荐环境变量配置(ROCm/AMD)
# 仅暴露分配到的 GPU(Slurm 提供的索引)
export HIP_VISIBLE_DEVICES=${SLURM_JOB_GPUS}
# 通信相关(RCCL 通过 NCCL 环境变量)
export NCCL_DEBUG=INFO
export NCCL_SOCKET_IFNAME=ib0 # 视集群网卡名调整
export NCCL_IB_DISABLE=0
export NCCL_P2P_DISABLE=0
export RDMAV_FORK_SAFE=1
# 某些环境下可提升 ROCm 稳定性/性能
export HSA_FORCE_FINE_GRAIN_PCIE=1
export HSA_ENABLE_SDMA=1
多节点通信参数
MASTER_ADDR=$(scontrol show hostnames $SLURM_NODELIST | head -n 1)
MASTER_PORT=30000 # 选择空闲端口
WORLD_SIZE=${SLURM_NNODES}
RANK=${SLURM_PROCID} # 若每节点 1 个 task,RANK=节点序号
export MASTER_ADDR MASTER_PORT WORLD_SIZE RANK
SGLang 部署思路与服务配置
在 SGLang 的多卡/多节点部署中,常见做法是:每节点运行一个进程,按需设置 tensor parallel (TP) / pipeline parallel (PP) / data parallel (DP),并通过分布式后端完成跨节点通信。针对 AMD/ROCm,后端通常为 PyTorch 的 NCCL(内部衔接 RCCL)。
- 明确
MASTER_ADDR/MASTER_PORT(或nccl-init-addr),建议使用首节点 hostname 与固定端口。 - 为不同节点设置
OUTLINES_CACHE_DIR等缓存路径,避免多节点下 sqlite 冲突。
示例一:直接使用 SGLang 的服务器入口
python -m sglang.launch_server \
--model /path/to/model \
--host 0.0.0.0 \
--port ${MASTER_PORT} \
--tensor-parallel-size 3 \ # 每节点使用 3 张 GPU(示例)
--pipeline-parallel-size 1 \
--distributed-backend nccl \ # AMD/ROCm 场景通常仍设置为 nccl
--log-level info
示例二:使用 torchrun 包装(便于传递 RANK 等)
torchrun --nproc_per_node=1 --nnodes=${WORLD_SIZE} --node_rank=${RANK} \
--master_addr=${MASTER_ADDR} --master_port=${MASTER_PORT} \
-m sglang.launch_server \
--model /path/to/model \
--host 0.0.0.0 --port ${MASTER_PORT} \
--tensor-parallel-size 3 --pipeline-parallel-size 1 --distributed-backend nccl
完整 sbatch 模板(非全满 GPU,多节点)
#!/bin/bash
#SBATCH -J sglang-amd
#SBATCH -p compute # 队列名视集群而定
#SBATCH -N 2 # 2 个节点(每节点 8 卡,仅使用 4 卡)
#SBATCH --ntasks-per-node=1 # 每节点一个进程
#SBATCH --gres=gpu:mi210:4 # 每节点申请 4 张 AMD MI210
#SBATCH --cpus-per-task=16
#SBATCH -t 02:00:00
#SBATCH --exclusive
module purge
module load rocm # 或具体版本: rocm/5.7
module load python/3.10 # 若使用集群模块管理
# Python 环境(conda/pip)按需激活
# source ~/miniconda3/bin/activate sglang-rocm
MASTER_ADDR=$(scontrol show hostnames $SLURM_NODELIST | head -n 1)
MASTER_PORT=30000
WORLD_SIZE=${SLURM_NNODES}
RANK=${SLURM_PROCID}
export MASTER_ADDR MASTER_PORT WORLD_SIZE RANK
# 设置每节点可见设备(优先用 Slurm 提供,其次 CUDA_VISIBLE_DEVICES,最后手工)
if [[ -n "${SLURM_JOB_GPUS}" ]]; then
export HIP_VISIBLE_DEVICES=${SLURM_JOB_GPUS}
elif [[ -n "${CUDA_VISIBLE_DEVICES}" ]]; then
export HIP_VISIBLE_DEVICES=${CUDA_VISIBLE_DEVICES}
else
export HIP_VISIBLE_DEVICES=0,1,2,3
fi
export NCCL_DEBUG=INFO
export NCCL_SOCKET_IFNAME=ib0
export NCCL_IB_DISABLE=0
export NCCL_P2P_DISABLE=0
export RDMAV_FORK_SAFE=1
export HSA_FORCE_FINE_GRAIN_PCIE=1
echo "[INFO] Nodes: ${WORLD_SIZE}, Rank: ${RANK}, Master: ${MASTER_ADDR}:${MASTER_PORT}"
echo "[INFO] HIP_VISIBLE_DEVICES=${HIP_VISIBLE_DEVICES}"
# 方式一:直接启动 SGLang Server
python -m sglang.launch_server \
--model /path/to/model \
--host 0.0.0.0 \
--port ${MASTER_PORT} \
--tensor-parallel-size 4 \ # 每节点 4 卡
--pipeline-parallel-size 1 \
--distributed-backend nccl \
--log-level info
# 方式二:如需 torchrun(取消上面并启用下面)
# torchrun --nproc_per_node=1 --nnodes=${WORLD_SIZE} --node_rank=${RANK} \
# --master_addr=${MASTER_ADDR} --master_port=${MASTER_PORT} \
# -m sglang.launch_server \
# --model /path/to/model \
# --host 0.0.0.0 --port ${MASTER_PORT} \
# --tensor-parallel-size 3 --pipeline-parallel-size 1 --distributed-backend nccl
实战脚本:多节点完整 sbatch(已注释,含路由器/多 worker)
下列片段摘自完整的 multinode.sbatch(已在仓库根目录提供下载),采用“每节点独立 SGLang worker(本地 TP) + sglang_router 跨节点做 DP”的架构,适合每节点 非全满 GPU 的 MI210 环境,并便于做同/跨 NUMA 的吞吐对比。
作业头与资源申请(示例)
#!/usr/bin/env bash
#SBATCH -p faculty
#SBATCH --qos=gtqos
#SBATCH --nodes 2
#SBATCH --ntasks=2
#SBATCH --gpus-per-node=4
#SBATCH --cpus-per-task=64
#SBATCH --mem=256G
#SBATCH -t 12:00:00
#SBATCH -J qwen30b_tp4_dp2
# 注意:以上参数需按你的集群队列/CPU/内存情况调整。
精确设置每节点可见 GPU
set_local_gpus() {
if [[ -n "${SLURM_STEP_GPUS:-}" ]]; then
HIP_VISIBLE_DEVICES="${SLURM_STEP_GPUS}"
elif [[ -n "${SLURM_JOB_GPUS:-}" ]]; then
HIP_VISIBLE_DEVICES="${SLURM_JOB_GPUS}"
else
HIP_VISIBLE_DEVICES="${HIP_DEVICES_DEFAULT:-0,1,2,3}"
fi
export HIP_VISIBLE_DEVICES
export ROCR_VISIBLE_DEVICES="$HIP_VISIBLE_DEVICES"
export HSA_VISIBLE_DEVICES="$HIP_VISIBLE_DEVICES"
export CUDA_VISIBLE_DEVICES="$HIP_VISIBLE_DEVICES"
}
启动每节点 worker(本地 TP)并隔离缓存
srun --nodes=1 --nodelist="$node" --gres="gpu:${WORKER_GPUS_PER_NODE}" \
--ntasks=1 --cpus-per-task="$SLURM_CPUS_PER_TASK" \
bash -lc "$(declare -f set_local_gpus); set_local_gpus; \
export OUTLINES_CACHE_DIR=\"/tmp/\$(hostname -s)_cache_${SLURM_JOB_ID}\"; \
python3 -m sglang.launch_server \
--tp-size $TP_SIZE --pp-size $PP_SIZE \
--model-path $MODEL_PATH --host 0.0.0.0 --port $port \
--disable-radix-cache --enable-dp-attention \
--device cuda" &
路由器(跨节点 DP)与健康检查
python3 -m sglang_router.launch_router \
--worker-urls "${WORKER_URLS[@]}" --host 0.0.0.0 --port "$ROUTER_PORT" \
--policy "$ROUTER_POLICY" &
# 等待端口就绪
until nc -z localhost "$ROUTER_PORT"; do sleep 0.5; done
curl -s "http://localhost:${ROUTER_PORT}/healthy" | jq .
srun 直接构成单一 server 的多节点实例(--tp/--nnodes/--node-rank/--nccl-init-addr)。本脚本改为“路由器 + 多 worker”模式,在每节点 GPU 非全满时更易于资源隔离与稳定运行。常见坑与排查
- 端口冲突:
MASTER_PORT被占用时,进程可能在不同节点卡住。换一个未占用端口。 - 网卡选择错误:
NCCL_SOCKET_IFNAME填错会导致带宽过低或连接失败。确认 IB 网卡名(如ib0)。 - GPU 可见性不一致:确保
HIP_VISIBLE_DEVICES与 Slurm 的分配一致,避免越界访问。 - PyTorch 与 ROCm 版本不匹配:升级或降级到兼容组合。
- 节点混用导致性能抖动:尽量选择同型号、同拓扑的节点。
小结
核心在于:Slurm 精准分配(非全满)、ROCm/RCCL 环境变量正确、MASTER/RANK/WORLD_SIZE 配置无误,并根据 SGLang 版本选择合适的入口与并行策略。按本文模板做变量替换,基本能在大多数 AMD 集群上稳定跑通。
参考
- Multi-Node LLM Inference with SGLang on SLURM-Enabled Clusters [0]:链接