如何在香港服务器的 RHEL 环境里,把 Slurm 和 RDMA 搭起来,榨干 HPC 集群性能

晚上 11 点,我拎着一杯港式鸳鸯钻进将军澳机房。冷风从地板送风口钻上来,我把工单夹在臂弯,蹲在 42U 柜前听着风扇的白噪音。那天任务很直接:一套 RHEL 的小型 HPC 集群,要在 48 小时内跑出客户的分子动力学算例;要求是 Slurm 调度 + RDMA(RoCEv2) 网络,节点间延迟要压到 5 微秒量级,带宽接近线速。
这篇文章就是我那两天的实操记录:从硬件、网络、内核、驱动、Slurm 到 UCX/MPI/NCCL 的参数,一个坑一个坑踩过去。希望无论你是第一次搭集群的新手,还是想“再捏 10% 性能”的老手,都能直接复刻或按需修改。
拓扑与硬件清单
机房与网络约束(香港场景的“特殊性”)
- 机房:Equinix HK**(示意)**,双上联,ToR 100GbE。
- 跨境:研发团队在内地,Jupyter/可视化走公网—所以管理网、存储网、计算网必须分离,避免 RDMA 与办公流量互相污染。
- 交换机:Mellanox/NVIDIA SN2700(100GbE,支持 PFC/ECN)。
- RDMA 策略:RoCEv2(以太网二层三层均可,不用 IB 交换机,节省成本、布线灵活)。
集群规模(示例)
| 角色 | 数量 | 机型(示例) | CPU | 内存 | GPU | RDMA 网卡 | 本地盘 | 系统 |
|---|---|---|---|---|---|---|---|---|
| Head/Login | 1 | Supermicro 1U | AMD EPYC 7543 ×1 | 256GB | - | ConnectX-6 Dx 100GbE ×1 | 2×1.92TB NVMe | RHEL 9.3 |
| Compute | 8 | Supermicro 2U | AMD EPYC 7543 ×2 | 512GB | A100 80GB ×4 | ConnectX-6 Dx 100GbE ×1 | 4×3.84TB NVMe | RHEL 9.3 |
| 存储(NFS/对象) | 1 | 任意 | 任意 | 256GB | - | 25/100GbE | 8×SSD RAID | RHEL 9.3 |
注:如果你是 CPU-only 集群,去掉 GPU 相关的 GRES 和 NCCL 部分即可;如果是 IB(HDR/NDR),思路不变,把 RoCEv2 的参数换成 IB 的就行。
逻辑网络划分
- eth0(管理):1/10GbE,VLAN 10
- eth1(业务/公网/NAT):10GbE,VLAN 20
- ens5f0(RDMA/100GbE):VLAN 30,MTU 9000(Jumbo)
- 存储(可选):VLAN 40(与 RDMA 分离)
系统与内核准备(RHEL 9.x)
基础优化
# 订阅与基础包
subscription-manager register --auto-attach
dnf -y update
dnf -y install epel-release
dnf -y install vim tmux htop numactl lshw lsof pciutils \
ethtool iperf3 tcpdump tuned chrony \
rdma-core rdma-core-devel infiniband-diags perftest \
libibverbs libibverbs-utils librdmacm-utils
# 时间同步
systemctl enable --now chronyd
# 性能剖面:低延迟优先(compute 节点)
tuned-adm profile latency-performance
# HugePages(按需)
echo "vm.nr_hugepages=4096" > /etc/sysctl.d/90-hugepages.conf
# 关透明大页(对部分 MPI/DB 场景友好)
grubby --update-kernel=ALL --args="transparent_hugepage=never"
BIOS/固件建议(现场经验)
- 打开 Above 4G Decoding、SR-IOV(多队列更好发包)、NUMA Enabled。
- 关掉深度 C-State(或设为 Performance),内存频率锁定最高可用。
- GPU 机器打开 Resizable BAR(对新卡细微收益),风扇曲线固定在高位避免热降频。
RDMA(RoCEv2)驱动与队列配置
RHEL 9 自带 rdma-core 已可用;为了更稳的性能与工具链,我一般会装 MLNX_OFED 对应版本(与网卡固件版本匹配很关键)。如果你不方便更换内核模块,直接用系统 rdma-core + infiniband-diags 也行。
(可选)安装 MLNX_OFED(离线/在线均可)
# 以 5.x 版本为例,实际请按你 NIC 固件矩阵选择
./mlnxofedinstall --upstream-libs --force
systemctl enable --now rdma
网卡与 RoCEv2 验证
# 识别 RDMA 设备
ibv_devices
ibv_devinfo -d mlx5_0
# 查看 GID(找出 RoCEv2 对应 VLAN 的 GID 索引)
show_gids -d mlx5_0
Jumbo MTU & 中断/队列
# MTU 9000(两端与交换机须一致)
ip link set dev ens5f0 mtu 9000
# RSS 队列与中断绑核(示例:32 队列)
ethtool -l ens5f0
ethtool -L ens5f0 combined 32
# irqbalance 可关,用 script 绑核,每台略不同,此处略
PFC/ECN(端到端拥塞控制的关键)
交换机侧要开启:
- PFC 只给承载 RDMA 的优先级(比如 prio 3),避免“全局暂停风暴”。
- ECN 标记(RDMA 队列的队列深度阈值),配合主机侧 RoCEv2 ECN。
主机侧(Mellanox 工具示例):
# DSCP 可信(与交换机一致)
mlnx_qos -i ens5f0 --trust dscp
# PFC:只开优先级 3(其余关)
mlnx_qos -i ens5f0 --pfc 0,0,0,1,0,0,0,0
# DSCP -> prio 映射(示例把 26/34 映射到 prio 3)
mlnx_qos -i ens5f0 --dscp2prio 26:3 34:3
# RoCE 拥塞控制(ECN)
mlxconfig -d $(mst status -v | awk '/pciconf0/ {print $2; exit}') set CC_MODE=ECN
交换机不同厂商命令不同,但理念相同:RDMA 专用优先级 + ECN 门限。
务必端到端一致,否则“偶发超时”“延迟尾大”会把你逼疯。
UCX / MPI / NCCL 用户态栈
我更偏好 UCX + OpenMPI(科学计算)与 NCCL(AI 训练),用 HPC-X 套件省事;也可以 Spack 自建。
# UCX + OpenMPI(支持 CUDA Aware)
dnf -y install gcc gcc-c++ make hwloc-devel libevent-devel pmix libpmix
# 如果用 HPC-X,解压后 module load;这里示例源码安装
tar xf ucx-1.16.x.tar.gz && cd ucx-1.16.x
./configure --enable-mt --with-mlx5-dv --with-rc --with-ud --with-cuda=/usr/local/cuda
make -j && make install
tar xf openmpi-4.1.x.tar.gz && cd openmpi-4.1.x
./configure --with-ucx=/usr/local --with-cuda=/usr/local/cuda --enable-mpirun-prefix-by-default
make -j && make install
常用环境变量(根据你的 GID 索引与设备名调整):
export UCX_TLS=rc,ud,sm,self,cuda_copy,cuda_ipc
export UCX_NET_DEVICES=mlx5_0:1
export UCX_IB_GID_INDEX=3 # 按 show_gids 结果
export UCX_IB_TRAFFIC_CLASS=106 # 等价 DSCP 26
export NCCL_IB_HCA=mlx5_0
export NCCL_IB_GID_INDEX=3
export NCCL_SOCKET_IFNAME=ens5f0
Slurm:控制面 + 计算面
组件安装
dnf -y install mariadb-server
systemctl enable --now mariadb
dnf -y install munge munge-libs
dd if=/dev/urandom bs=1 count=256 > /etc/munge/munge.key
chown munge:munge /etc/munge/munge.key && chmod 400 /etc/munge/munge.key
systemctl enable --now munge
# Slurm(RHEL/EPEL 源)
dnf -y install slurm slurm-slurmctld slurm-slurmd slurm-slurmdbd slurm-devel
数据库与 Accounting
mysql -uroot -p -e "CREATE DATABASE slurm_acct_db;"
mysql -uroot -p -e "CREATE USER 'slurm'@'%' IDENTIFIED BY 'StrongPass!';"
mysql -uroot -p -e "GRANT ALL ON slurm_acct_db.* TO 'slurm'@'%'; FLUSH PRIVILEGES;"
/etc/slurm/slurmdbd.conf
DbdHost=login
DbdPort=6819
SlurmUser=slurm
StorageType=accounting_storage/mysql
StorageUser=slurm
StoragePass=StrongPass!
StorageLoc=slurm_acct_db
slurm.conf(核心摘录)
ClusterName=hk-rhel-hpc
SlurmctldHost=login(10.0.10.10)
SlurmUser=slurm
AuthType=auth/munge
StateSaveLocation=/var/spool/slurmctld
SlurmdSpoolDir=/var/spool/slurmd
SwitchType=switch/none
# 选择资源与亲和
SelectType=select/cons_tres
SelectTypeParameters=CR_Core_Memory
TaskPlugin=task/affinity
ProctrackType=proctrack/cgroup
# cgroup v2(RHEL9)
CgroupPlugin=cgroup/v2
SchedulerType=sched/backfill
SlurmctldPort=6817
SlurmdPort=6818
# GPU/GRES
GresTypes=gpu
AccountingStorageType=accounting_storage/slurmdbd
JobAcctGatherType=jobacct_gather/cgroup
JobAcctGatherFrequency=30
# 节点与分区(示例)
NodeName=node[01-08] Sockets=2 CoresPerSocket=32 ThreadsPerCore=2 RealMemory=500000 \
Gres=gpu:4 Feature=roce
PartitionName=compute Nodes=node[01-08] Default=YES MaxTime=7-00:00:00 State=UP
/etc/slurm/gres.conf(计算节点)
Name=gpu Type=a100 File=/dev/nvidia[0-3]
/etc/slurm/cgroup.conf
CgroupMountpoint=/sys/fs/cgroup
ConstrainCores=yes
ConstrainRAMSpace=yes
ConstrainDevices=yes
ConstrainSwapSpace=yes
AllowedDevicesFile=/etc/slurm/allowed_devices.conf
启动顺序:
# Head
systemctl enable --now slurmdbd
systemctl enable --now slurmctld
# Compute
systemctl enable --now slurmd
创建账户与 QoS(可选):
sacctmgr add cluster hk-rhel-hpc
sacctmgr add account research
sacctmgr add user alice account=research
sacctmgr add qos high pri=100 maxcpus=512
RoCEv2 连通性与基线压测
带内连通性
# 两节点互探
ping -c 3 10.0.30.21
iperf3 -s -B 10.0.30.21 # server on node01
iperf3 -c 10.0.30.21 -P 8 # client on node02
RDMA 基线(perftest)
# 读带宽
ib_read_bw -d mlx5_0 -F -R -o # server
ib_read_bw -d mlx5_0 -F -R -o 10.0.30.21
# 写带宽
ib_write_bw -d mlx5_0 -F -R -o ...
# 延迟
ib_send_lat -d mlx5_0 -F -R -o ...
示例数据(8 台 * 100GbE;单流/多流混合)
| 场景 | 配置 | 吞吐(Gb/s) | P99 延迟(µs) |
|---|---|---|---|
| TCP(MTU1500,未调优) | iperf3 -P 1 | 35–45 | 90–120 |
| TCP(MTU9000) | iperf3 -P 8 | 80–90 | 70–90 |
| RoCEv2(PFC 关、ECN 关) | ib_write_bw | 60–70 | 12–20 |
| RoCEv2(PFC=prio3、ECN 打开) | ib_write_bw | 93–98 | 3–6 |
这些值供参考,真实数字取决于机型/内核/固件/交叉拓扑。关键是**“开关一拨、曲线立起来”**,而不是死磕绝对值。
与 Slurm 的“正确打开方式”
作业脚本:MPI(UCX)
mpi_ucx.sbatch
#!/bin/bash
#SBATCH -J md-mpi
#SBATCH -p compute
#SBATCH -N 4
#SBATCH --ntasks-per-node=16
#SBATCH --exclusive
#SBATCH -t 02:00:00
module purge
# 假设你用 HPC-X,或者自行 export
export UCX_TLS=rc,ud,sm,self,cuda_copy,cuda_ipc
export UCX_NET_DEVICES=mlx5_0:1
export UCX_IB_GID_INDEX=3
export UCX_LOG_LEVEL=warn
srun --mpi=pmix_v3 --cpu-bind=cores \
/opt/ompi/bin/mpirun -np $SLURM_NTASKS -mca pml ucx -x UCX_TLS \
-x UCX_NET_DEVICES -x UCX_IB_GID_INDEX \
/opt/apps/gromacs/bin/gmx_mpi mdrun -s topol.tpr -deffnm prod
作业脚本:多卡多机 NCCL(深度学习)
nccl_multi.sbatch
#!/bin/bash
#SBATCH -J dl-nccl
#SBATCH -p compute
#SBATCH -N 4
#SBATCH --gpus-per-node=4
#SBATCH --ntasks-per-node=4
#SBATCH --cpus-per-task=8
#SBATCH --exclusive
#SBATCH -t 04:00:00
export NCCL_IB_HCA=mlx5_0
export NCCL_IB_GID_INDEX=3
export NCCL_SOCKET_IFNAME=ens5f0
export NCCL_DEBUG=INFO
export UCX_TLS=rc,ud,sm,self,cuda_copy,cuda_ipc
srun --mpi=pmix_v3 --gpu-bind=map_gpu:0,1,2,3 \
python -u train.py --epochs 90 --batch-size 256
亲和与拓扑
- --cpu-bind=cores + --gpu-bind=map_gpu:把进程/线程压在对应 NUMA/GPU 上。
- gres.conf + SelectType=cons_tres:确保 GPU 资源独占与配额正确。
- 可以给带 RDMA 的节点打 Feature=roce,队列里按需挑选:--constraint=roce。
线上“坑点”与救火手册
| 现象 | 根因 | 快速定位 | 处置 |
|---|---|---|---|
| MPI/NCCL 连接超时,偶发卡死 | GID 索引错(走到管理 VLAN) | show_gids 查 RoCEv2 入口,tcpdump 看 DSCP |
统一 UCX_IB_GID_INDEX/NCCL_IB_GID_INDEX |
| 延迟尾很大,吞吐忽高忽低 | PFC 全开导致 Pause 风暴 | ethtool -S 观察 pause 帧,交换机队列统计 |
只对 prio=3 开 PFC,其他关;ECN on |
UCX/mlx5 不可用 |
驱动/固件不匹配 | `dmesg | grep mlx5,ibv_devinfo` |
slurmd 不认 GPU |
cgroup/驱动未就绪 | nvidia-smi、slurmd -Dvvv |
gres.conf 正则、CgroupPlugin 打开 |
| 作业被拒或错计费 | MUNGE/时钟问题 | `munge -n | unmunge`,时钟偏差 |
| 带宽锁在 40–60Gb/s | MTU 不一致 | ip link show、交换机端口 MTU |
全链路 9000 |
| 训练吞吐不涨 | 亲和混乱/跨 NUMA | numactl --hardware,nvidia-smi topo -m |
固定绑核,进程与 GPU/NUMA 对齐 |
可靠性与多租户
- MUNGE key 离线保管,换班轮转;Slurm PrivateData=jobs,usage 防止跨租户窥探。
- cgroup/v2 限制 CPU/Mem/Devices,避免作业越权访问其他 GPU 与 NIC。
- HealthCheckProgram 定时跑 ib_read_bw --duration 2 + nvidia-smi -q -d ECC,失败自动 DRAIN 节点。
- SuspendProgram/ResumeProgram(可选)结合外部电源管理,闲时关机降噪省电(香港电费不便宜)。
验证:从“能跑”到“跑得漂亮”
我一般用三个层次验收:
- 链路层:ib_*_bw/lat 达标;iperf3 多流逼近线速;无明显丢包/重传。
- 通信层:OSU Micro-Benchmarks 的 osu_latency/osu_bw 符合预期。
- 业务层:挑客户的真实算例或公开基准(如 GROMACS/Amber 或 ResNet50/NVIDIA BERT),确认 TTA(time-to-answer) 改善与 成本/能耗 下降。
示例改进(真实流程复盘)
- 初始(TCP,MTU1500):GROMACS 4 节点 64 任务,34 ns/day
- 调整(MTU9000 + 绑核):提升到 41 ns/day
- 完整(RoCEv2 + PFC prio3 + ECN + UCX 参数):49–52 ns/day
- 结论:端到端把 延迟尾 压下去,收益比单纯堆核/堆卡更稳定。
现场排障清单(可直接抄)
# 设备/驱动状态
lspci | grep -i mell
ethtool -i ens5f0
mst status
ibv_devinfo -v
show_gids -d mlx5_0
# 链路统计
ethtool -S ens5f0 | egrep -i "rx|tx|pause|ecn|priority"
ip -s link show dev ens5f0
# 路由与 DSCP
tc qdisc show dev ens5f0
tcpdump -vv -i ens5f0 ip[1] & # 看 ToS/DSCP
# Slurm/节点
sinfo -R
squeue -o "%.18i %.9P %.8j %.8T %.6M %.6D %R"
scontrol show node node01
最小可运行模板(你可以先“跑通”再精调)
- RHEL 9.3 安装 + dnf install rdma-core perftest slurm munge
- MTU 9000 全链路;PFC prio=3;ECN on
- UCX + OpenMPI(或 HPC-X),设置 UCX_TLS/NET_DEVICES/GID_INDEX
- Slurm:SelectType=cons_tres、gres.conf、cgroup/v2
- perftest + OSU 验证;再上真实业务脚本
FAQ:我在香港机房踩过的 5 个梗
“交换机没开 ECN,主机开了也白搭。”
端到端一致是王道;不一致时最典型的症状是“吞吐能上但 P99 延迟炸”。
“PFC 别全开!”
只给 RDMA 队列开,其他关闭;否则容易形成Pause 风暴,造成集群抖动。
“GID 索引是王炸。”
多 VLAN、多子网时最容易走错口,脚本化 show_gids 并统一环境变量。
“MTU 只改主机没用。”
交换机口与对端都要一致;路上哪怕一个 1500,也会被“掐脖子”。
“别忘了计时与能耗。”
香港电费与托管都贵,跑同样作业,省 10% 时间就是真金白银。
附:关键配置片段汇总
/etc/sysctl.d/90-rdma.conf
net.core.rmem_max=134217728
net.core.wmem_max=134217728
net.core.netdev_max_backlog=250000
net.ipv4.tcp_timestamps=0
net.ipv4.tcp_sack=1
net.ipv4.tcp_window_scaling=1
net.ipv4.tcp_mtu_probing=1
net.ipv4.tcp_ecn=1 # 对 TCP 有益;RoCEv2 仍看 ECN 标记
RDMA 环境变量(/etc/profile.d/rdma.sh)
export UCX_TLS=rc,ud,sm,self,cuda_copy,cuda_ipc
export UCX_NET_DEVICES=mlx5_0:1
export UCX_IB_GID_INDEX=3
export UCX_IB_TRAFFIC_CLASS=106
export NCCL_IB_HCA=mlx5_0
export NCCL_IB_GID_INDEX=3
export NCCL_SOCKET_IFNAME=ens5f0
凌晨三点,最后一个作业从 PENDING 变成了 RUNNING,ib_write_bw 的曲线贴着 100Gb 直线往前走。我往后靠在走道的金属地板上,机器的风像海浪一样从脚边掠过。
这套集群从“能跑”到“跑得漂亮”,靠的不是某一个玄学参数,而是端到端一致性、最小可运行模板、度量驱动迭代。
第二天早上,客户把最新一批分子动力学算例扔进来,时间从 30 小时降到 17 个多小时。我把当晚的笔记整理成文,留给下一位在香港机房里加班的你:
别怕从简单开始,先跑通,再榨干;从链路、通信到业务层层对齐,你会很快看到“线条变直”的那一刻。