spark-vllm-docker 集群网络配置

DGX Spark 网络配置

以下指南从双节点集群开始,但同样适用于更大的集群。

6-8 节点 Spark 集群的示例请参见这篇帖子
请记住,张量并行的 vLLM 部署通常在节点数为 2 的幂(例如 2、4 或 8 个节点)时效果最好。3 节点 mesh 主要用于流水线并行或数据并行。

本指南假设节点命名为 sparkspark2,但你可以使用任何名称。
IP 地址也是如此:我们使用 192.168.177.0/24 子网,为两个节点分别分配 .11.12,但你可以使用任何 IP 地址,只要它们在同一子网内即可。

DGX Spark ConnectX 的特殊之处

DGX Spark 的 ConnectX 配置相当独特。

要达到 200G 的传输速度,ConnectX 网卡需要约 x8 条 PCIe 5.0 通道。

然而,由于硬件限制,DGX Spark SOC 每个设备最多只能提供 x4 条 PCIe 通道。
因此,为了在单线缆连接上达到 200G,每个物理端口共享同一对 PCIe5 x4 连接。
每条 PCIe 5 x4 链路由两个以太网接口和两个 RoCE 接口表示:

eugr@spark:~$ ibdev2netdev
rocep1s0f0 port 1 ==> enp1s0f0np0 (Down)
rocep1s0f1 port 1 ==> enp1s0f1np1 (Up)
roceP2p1s0f0 port 1 ==> enP2p1s0f0np0 (Down)
roceP2p1s0f1 port 1 ==> enP2p1s0f1np1 (Up)

在这种情况下,单根线缆插在靠外侧的 QSFP 端口(从背面看是右侧那个)。
该端口关联了两对"孪生"接口:

  • 以太网:enp1s0f1np1enP2p1s0f1np1
  • RoCE/IB:rocep1s0f1roceP2p1s0f1

每个孪生接口代表一条 PCIe x4 链路,可提供最高 100G 的链路速度。

对于 vLLM,我们需要 RoCE 之上的 RDMA,因此以太网速度并不那么重要,这就是为什么我们可以只给其中一个端口分配 IP - 在本例中是 enp1s0f1np1
然而,要在 NCCL RDMA 模式下获得完整带宽,我们需要同时利用两个 RoCE 孪生接口。通过将 NCCL_IB_HCA 设置为两个 RoCE 接口来实现:export NCCL_IB_HCA=rocep1s0f1,roceP2p1s0f1

./launch-cluster.sh 会自动完成这项设置,连同接口的自动发现一起,所以只要你正确配置了以太网接口,vLLM 就会利用两个 RoCE 孪生接口。

另外,请注意,使用两个端口连接两台 Spark 并不会带来明显的带宽优势,因此单连接就足够了。
如果你通过菊花链方式连接 3 台 Spark,每对 Spark 之间只能维持 100G。

不使用交换机将 3 台 Spark 连接成 mesh 集群

三台 Spark 可以在不使用独立 RoCE 交换机的情况下连接成集群。
但是,所有三台 Spark 都需要通过其 10G 以太网端口(RJ-45,不是 QSFP)处于同一个有线网络中。处于同一无线网络应该也可以,但不推荐,且未经测试。

你需要确保它们按以下方式连接:一台 Spark 的 port 0 应连接到另一台 Spark 的 port 1(与非 mesh 配置不同)。
示意图示例:

block-beta columns 1 block:Spark3 columns 2 Title3["Spark 3"]:2 s3p0["Port 0<br>192.168.187.13<br>192.168.188.13"] s3p1["Port 1<br>192.168.197.13<br>192.168.198.13"] end space block:Spark2 columns 2 Title2["Spark 2"]:2 s2p0["Port 0<br>192.168.197.12<br>192.168.198.12"] s2p1["Port 1<br>192.168.177.12<br>192.168.178.13"] end space block:Spark1 columns 2 Title1["Spark 1"]:2 s1p0["Port 0<br>192.168.177.11<br>192.168.178.11"] s1p1["Port 1<br>192.168.187.11<br>192.168.188.11"] end s1p0 <--> s2p1 s2p0 <--> s3p1 s3p0 <--> s1p1

使用交换机在集群中连接超过 2 台 Spark

要连接超过 2 台 Spark,你需要一台合适的交换机,例如 Microtik CRS812-DDQMikrotik CRS804-DDQ
搭建 6-8 节点 Spark 集群的示例请参考这篇帖子

网络配置

双 Spark 或使用 QSFP 交换机的多台 Spark

假设两者都使用靠右侧的 QSFP 端口连接(从背面看)。

spark 上创建 /etc/netplan/40-cx7.yaml

network:
  version: 2
  ethernets:
    enp1s0f1np1:
      dhcp4: no
      dhcp6: no        # 显式禁用 DHCPv6
      link-local: []   # 将 link-local 地址限制为仅静态 IPv4
      mtu: 9000
      addresses: [192.168.177.11/24]
    enP2p1s0f1np1:
      dhcp4: no
      dhcp6: no
      link-local: []
      mtu: 9000
      addresses: [192.168.178.11/24]

spark2 上创建 /etc/netplan/40-cx7.yaml

network:
  version: 2
  ethernets:
    enp1s0f1np1:
      dhcp4: no
      dhcp6: no        # 显式禁用 DHCPv6
      link-local: []   # 将 link-local 地址限制为仅静态 IPv4
      mtu: 9000
      addresses: [192.168.177.12/24]
    enP2p1s0f1np1:
      dhcp4: no
      dhcp6: no
      link-local: []
      mtu: 9000
      addresses: [192.168.178.12/24]

不要在两个"孪生"接口上使用相同的子网 - 这会干扰自动发现并搞乱路由。

然后在每个节点上运行:

sudo chmod 600 /etc/netplan/40-cx7.yaml
sudo netplan apply

设置免密码 ssh。在 spark 上:

wget https://raw.githubusercontent.com/NVIDIA/dgx-spark-playbooks/refs/heads/main/nvidia/connect-two-sparks/assets/discover-sparks
chmod +x discover-sparks
./discover-sparks

MTU 设置(测试):

sudo ip link set dev enp1s0f1np1 mtu 9000

3 节点 mesh

3 节点 mesh 的配置方式与双机集群或使用 QSFP 交换机的集群不同。

假设你的 Spark 按照上面的示意图连接:

spark1 上创建 /etc/netplan/40-cx7.yaml

network:
  version: 2
  ethernets:
    enp1s0f0np0:
      dhcp4: no
      dhcp6: no        # 显式禁用 DHCPv6
      link-local: []   # 将 link-local 地址限制为仅静态 IPv4
      mtu: 9000
      addresses: [192.168.177.11/24]
    enP2p1s0f0np0:
      dhcp4: no
      dhcp6: no
      link-local: []
      mtu: 9000
      addresses: [192.168.178.11/24]
    enp1s0f1np1:
      dhcp4: no
      dhcp6: no        # 显式禁用 DHCPv6
      link-local: []   # 将 link-local 地址限制为仅静态 IPv4
      mtu: 9000
      addresses: [192.168.187.11/24]
    enP2p1s0f1np1:
      dhcp4: no
      dhcp6: no
      link-local: []
      mtu: 9000
      addresses: [192.168.188.11/24]

spark2 上创建 /etc/netplan/40-cx7.yaml

network:
  version: 2
  ethernets:
    enp1s0f0np0:
      dhcp4: no
      dhcp6: no        # 显式禁用 DHCPv6
      link-local: []   # 将 link-local 地址限制为仅静态 IPv4
      mtu: 9000
      addresses: [192.168.197.12/24]
    enP2p1s0f0np0:
      dhcp4: no
      dhcp6: no
      link-local: []
      mtu: 9000
      addresses: [192.168.198.12/24]
    enp1s0f1np1:
      dhcp4: no
      dhcp6: no        # 显式禁用 DHCPv6
      link-local: []   # 将 link-local 地址限制为仅静态 IPv4
      mtu: 9000
      addresses: [192.168.177.12/24]
    enP2p1s0f1np1:
      dhcp4: no
      dhcp6: no
      link-local: []
      mtu: 9000
      addresses: [192.168.178.12/24]

spark3 上创建 /etc/netplan/40-cx7.yaml

network:
  version: 2
  ethernets:
    enp1s0f0np0:
      dhcp4: no
      dhcp6: no        # 显式禁用 DHCPv6
      link-local: []   # 将 link-local 地址限制为仅静态 IPv4
      mtu: 9000
      addresses: [192.168.187.13/24]
    enP2p1s0f0np0:
      dhcp4: no
      dhcp6: no
      link-local: []
      mtu: 9000
      addresses: [192.168.188.13/24]
    enp1s0f1np1:
      dhcp4: no
      dhcp6: no        # 显式禁用 DHCPv6
      link-local: []   # 将 link-local 地址限制为仅静态 IPv4
      mtu: 9000
      addresses: [192.168.197.13/24]
    enP2p1s0f1np1:
      dhcp4: no
      dhcp6: no
      link-local: []
      mtu: 9000
      addresses: [192.168.198.13/24]

然后运行(在每台 Spark 上):

sudo chmod 600 /etc/netplan/40-cx7.yaml
sudo netplan apply

免密码 SSH 和基准测试

设置免密码 ssh。在第一台 spark 上:

wget https://raw.githubusercontent.com/NVIDIA/dgx-spark-playbooks/refs/heads/main/nvidia/connect-two-sparks/assets/discover-sparks
chmod +x discover-sparks
./discover-sparks

连接基准测试(使用 perftest 包):

spark2 节点上运行接收端:

ib_write_bw -d rocep1s0f1 --report_gbits -q 4 -R --force-link IB

然后在 spark 上运行:

$ ib_write_bw 192.168.177.12 -d rocep1s0f1 --report_gbits -q 4 -R --force-link IB
---------------------------------------------------------------------------------------
                    RDMA_Write BW Test
 Dual-port       : OFF          Device         : rocep1s0f1
 Number of qps   : 4            Transport type : IB
 Connection type : RC           Using SRQ      : OFF
 PCIe relax order: ON
 ibv_wr* API     : ON
 TX depth        : 128
 CQ Moderation   : 1
 Mtu             : 1024[B]
 Link type       : IB
 Max inline data : 0[B]
 rdma_cm QPs     : ON
 Data ex. method : rdma_cm
---------------------------------------------------------------------------------------
 local address: LID 0000 QPN 0x03ec PSN 0xb680ae
 local address: LID 0000 QPN 0x03ed PSN 0x808800
 local address: LID 0000 QPN 0x03ee PSN 0x5b694a
 local address: LID 0000 QPN 0x03ef PSN 0xe2efd1
 remote address: LID 0000 QPN 0x03eb PSN 0x75f6ee
 remote address: LID 0000 QPN 0x03ec PSN 0x436140
 remote address: LID 0000 QPN 0x03ed PSN 0x81698a
 remote address: LID 0000 QPN 0x03ee PSN 0x4a8b11
---------------------------------------------------------------------------------------
 #bytes     #iterations    BW peak[Gb/sec]    BW average[Gb/sec]   MsgRate[Mpps]
 65536      20000            111.72             111.71             0.213070
---------------------------------------------------------------------------------------

延迟测试:

spark2 节点上运行接收端:

ib_write_lat -d rocep1s0f1 --report_gbits -R --force-link IB

然后在 spark 上运行:

ib_write_lat 192.168.177.12 -d rocep1s0f1 --report_gbits -R --force-link IB
---------------------------------------------------------------------------------------
                    RDMA_Write Latency Test
 Dual-port       : OFF          Device         : rocep1s0f1
 Number of qps   : 1            Transport type : IB
 Connection type : RC           Using SRQ      : OFF
 PCIe relax order: OFF
 ibv_wr* API     : ON
 TX depth        : 1
 Mtu             : 1024[B]
 Link type       : IB
 Max inline data : 220[B]
 rdma_cm QPs     : ON
 Data ex. method : rdma_cm
---------------------------------------------------------------------------------------
 local address: LID 0000 QPN 0x02ee PSN 0xb0c21c
 remote address: LID 0000 QPN 0x02ee PSN 0x14568b
---------------------------------------------------------------------------------------
 #bytes #iterations    t_min[usec]    t_max[usec]  t_typical[usec]    t_avg[usec]    t_stdev[usec]   99% percentile[usec]   99.9% percentile[usec]
 2       1000          1.42           1.93         1.47                1.47             0.00            1.57                    1.93
---------------------------------------------------------------------------------------

NCCL 测试

双 Spark 或通过 QSFP 交换机连接的 Spark

来自 https://build.nvidia.com/spark/nccl/stacked-sparks

# 安装依赖并构建 NCCL
sudo apt-get update && sudo apt-get install -y libopenmpi-dev
git clone -b v2.30u1 https://github.com/NVIDIA/nccl.git ~/nccl/
cd ~/nccl/
make -j src.build NVCC_GENCODE="-gencode=arch=compute_121,code=sm_121"

# 设置环境变量
export CUDA_HOME="/usr/local/cuda"
export MPI_HOME="/usr/lib/aarch64-linux-gnu/openmpi"
export NCCL_HOME="$HOME/nccl/build/"
export LD_LIBRARY_PATH="$NCCL_HOME/lib:$CUDA_HOME/lib64/:$MPI_HOME/lib:$LD_LIBRARY_PATH"

构建 NCCL 测试套件:

# 克隆并构建 NCCL 测试
git clone https://github.com/NVIDIA/nccl-tests.git ~/nccl-tests/
cd ~/nccl-tests/
make MPI=1

在两个节点上测试:

# 设置网络接口环境变量(使用你的活动接口)
export UCX_NET_DEVICES=enp1s0f1np1
export NCCL_SOCKET_IFNAME=enp1s0f1np1
export OMPI_MCA_btl_tcp_if_include=enp1s0f1np1
export NCCL_IB_HCA=rocep1s0f1,roceP2p1s0f1
export NCCL_IB_DISABLE=0

# 在两个节点上运行 all_gather 性能测试
mpirun -np 2 -H 192.168.177.11:1,192.168.177.12:1 \
  --mca plm_rsh_agent "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" \
  -x LD_LIBRARY_PATH=$LD_LIBRARY_PATH \
  $HOME/nccl-tests/build/all_gather_perf -b 16G -e 16G -f 2

3 节点 mesh

# 安装依赖并构建 NCCL
sudo apt-get update && sudo apt-get install -y libopenmpi-dev
git clone -b v2.30u1 https://github.com/NVIDIA/nccl.git ~/nccl/
cd ~/nccl/
make -j src.build NVCC_GENCODE="-gencode=arch=compute_121,code=sm_121"

# 设置环境变量
export CUDA_HOME="/usr/local/cuda"
export MPI_HOME="/usr/lib/aarch64-linux-gnu/openmpi"
export NCCL_HOME="$HOME/nccl/build/"
export LD_LIBRARY_PATH="$NCCL_HOME/lib:$CUDA_HOME/lib64/:$MPI_HOME/lib:$LD_LIBRARY_PATH"

构建 NCCL 测试套件:

# 克隆并构建 NCCL 测试
git clone https://github.com/NVIDIA/nccl-tests.git ~/nccl-tests/
cd ~/nccl-tests/
make MPI=1

在所有三个节点上测试(将 spark1spark2spark3 替换为非 QSFP 接口上的实际主机名或 IP 地址):

# 设置环境变量
export CUDA_HOME="/usr/local/cuda"
export MPI_HOME="/usr/lib/aarch64-linux-gnu/openmpi"
export NCCL_HOME="$HOME/nccl/build/"
export LD_LIBRARY_PATH="$NCCL_HOME/lib:$CUDA_HOME/lib64/:$MPI_HOME/lib:$LD_LIBRARY_PATH"

# 对于 3 节点 mesh,OOB 通信必须使用 10G 接口!
export UCX_NET_DEVICES=enP7s7
export NCCL_SOCKET_IFNAME=enP7s7
export OMPI_MCA_btl_tcp_if_include=enP7s7
export NCCL_IB_HCA=rocep1s0f0,roceP2p1s0f0,rocep1s0f1,roceP2p1s0f1
export NCCL_IB_DISABLE=0

# 在所有三个节点上运行 all_gather 性能测试
mpirun -np 3 -H spark1:1,spark2:1,spark3:1 \
  --mca plm_rsh_agent "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" \
  -x LD_LIBRARY_PATH=$LD_LIBRARY_PATH -x NCCL_IB_MERGE_NICS=0 -x NCCL_NET_PLUGIN=none -x NCCL_IB_SUBNET_AWARE_ROUTING=1 \
  $HOME/nccl-tests/build/all_gather_perf -b 16G -e 16G -f 3

此处评论已关闭