home

스위치 없이 B300 클러스터 구성

Author
고석현 / CEO
Category
Hands-on
Tags
MLOps
Published
2026/04/12
5 more properties

DGX B300 ConnectX-8 기반 800G 네트워크에서 소규모 클러스터를 스위치 없이 구성하는 방법

이번 글에서는 실제 환경에서 스위치 없이 소규모 800G 네트워크를 비용 효율적으로 구성하고, Ethernet 기반으로 구성하는 방법을 정리합니다.

1. B300 네트워크 구조 이해

DGX B300은 다음과 같은 네트워크 구성을 가집니다.
ConnectX-8 (총 8포트)
OSFP 기반
최대 800Gb/s
InfiniBand / Ethernet 겸용
ConnectX-7 (총 4포트)
BlueField-3 DPU x 2
최대 400Gb/s

2. 800G 네트워크의 비용적 문제

엔비디아의 레퍼런스 네트워크로 구조로 800G 를 구성할 수 있지만, 네트워크 장비의 출고 지연과 비용은 큰 문제입니다.
https://developer.nvidia.com/ko-kr/blog/nvidia-connectx-8-supernics-advance-ai-platform-architecture-with-pcie-gen6-connectivity/ 해당 규격의 대한 정보는 위 엔비디아 공식 블로그 페이지에서도 알 수 있습니다. 하지만 해당 규격의 광통신 모듈은 매우 비싸고 구성에 따라 다르지만 800G - 듀얼 400G 2포트 분할 구성에서 하나의 라인당 현재 천만원 이상입니다. 다행히 DGX B300의 포트는 이더넷 모드를 지원하기 때문에 표준적인 800G OSFP 규격도 인식합니다.
https://www.fibermall.com/sale-460634-800g-osfp-acc-3m-flt.htm 위와같은 DAC(direct Access Cable) 케이블을 사용할 경우 광모듈 가격대비 1/10 비용으로 연결할 수 있습니다. 제조사에서는 IB NDR 전용 DAC로 판매하고 있지만 실제로는 Ethernet 표준 규격의 Ethernet_Consortium_LL_50G_RS_FEC_PLR -(272,257+1) FEC로 인식되는것을 알 수 있습니다. 즉 Ethernet 모드에서 사용할 수 있습니다.
root@b300-01:~# mlxlink -d /dev/mst/mt4131_pciconf0
Operational Info
State : Active Physical state : LinkUp Speed : IB-NDR Width : 4x FEC : Ethernet_Consortium_LL_50G_RS_FEC_PLR -(272,257+1) Loopback Mode : No Loopback Auto Negotiation : ON
필요한 포트들을 확인하여 IB → Ethernet 으로 전환합니다.
mlxconfig -y -d /dev/mst/mt4131_pciconf0 set LINK_TYPE_P1=2 LINK_TYPE_P2=2 mlxconfig -y -d /dev/mst/mt4131_pciconf1 set LINK_TYPE_P1=2 LINK_TYPE_P2=2 mlxconfig -y -d /dev/mst/mt4131_pciconf2 set LINK_TYPE_P1=2 LINK_TYPE_P2=2 mlxconfig -y -d /dev/mst/mt4131_pciconf3 set LINK_TYPE_P1=2 LINK_TYPE_P2=2 mlxconfig -y -d /dev/mst/mt4131_pciconf4 set LINK_TYPE_P1=2 LINK_TYPE_P2=2 mlxconfig -y -d /dev/mst/mt4131_pciconf5 set LINK_TYPE_P1=2 LINK_TYPE_P2=2 mlxconfig -y -d /dev/mst/mt4131_pciconf6 set LINK_TYPE_P1=2 LINK_TYPE_P2=2 mlxconfig -y -d /dev/mst/mt4131_pciconf7 set LINK_TYPE_P1=2 LINK_TYPE_P2=2
Bash
복사
그리고 다음과 같은 명령어를 통해서 ConnectX-8 포트의 정보를 확인합니다.
mst status -v
BlueField3(rev:1) /dev/mst/mt41692_pciconf1.1 cc:00.1 mlx5_19 net-ibp204s0f1 1 BlueField3(rev:1) /dev/mst/mt41692_pciconf1 cc:00.0 mlx5_18 net-ibp204s0f0 1 BlueField3(rev:1) /dev/mst/mt41692_pciconf0.1 53:00.1 mlx5_9 net-ibp83s0f1 0 BlueField3(rev:1) /dev/mst/mt41692_pciconf0 53:00.0 mlx5_8 net-ibp83s0f0 0 ConnectX8(rev:0) /dev/mst/mt4131_pciconf7.1 ed:00.1 mlx5_23 net-enp237s0f1np1 1 ConnectX8(rev:0) /dev/mst/mt4131_pciconf7 ed:00.0 mlx5_22 net-eno6np0 1 ConnectX8(rev:0) /dev/mst/mt4131_pciconf6.1 dc:00.1 mlx5_21 net-enp220s0f1np1 1 ConnectX8(rev:0) /dev/mst/mt4131_pciconf6 dc:00.0 mlx5_20 net-eno8np0 1 ConnectX8(rev:0) /dev/mst/mt4131_pciconf5.1 b9:00.1 mlx5_17 net-enp185s0f1np1 1 ConnectX8(rev:0) /dev/mst/mt4131_pciconf5 b9:00.0 mlx5_16 net-eno9np0 1 ConnectX8(rev:0) /dev/mst/mt4131_pciconf4.1 97:00.1 mlx5_15 net-enp151s0f1np1 1 ConnectX8(rev:0) /dev/mst/mt4131_pciconf4 97:00.0 mlx5_14 net-eno7np0 1 ConnectX8(rev:0) /dev/mst/mt4131_pciconf3.1 70:00.1 mlx5_13 net-enp112s0f1np1 0 ConnectX8(rev:0) /dev/mst/mt4131_pciconf3 70:00.0 mlx5_12 net-eno11np0 0 ConnectX8(rev:0) /dev/mst/mt4131_pciconf2.1 5f:00.1 mlx5_11 net-enp95s0f1np1 0 ConnectX8(rev:0) /dev/mst/mt4131_pciconf2 5f:00.0 mlx5_10 net-eno13np0 0 ConnectX8(rev:0) /dev/mst/mt4131_pciconf1.1 39:00.1 mlx5_7 net-enp57s0f1np1 0 ConnectX8(rev:0) /dev/mst/mt4131_pciconf1 39:00.0 mlx5_6 net-eno12np0 0 ConnectX8(rev:0) /dev/mst/mt4131_pciconf0.1 17:00.1 mlx5_1 net-enp23s0f1np1 0 ConnectX8(rev:0) /dev/mst/mt4131_pciconf0 17:00.0 mlx5_0 net-eno10np0 0 ConnectX7(rev:0) /dev/mst/mt4129_pciconf0.3 27:00.3 mlx5_5 net-ibs301f3 0 ConnectX7(rev:0) /dev/mst/mt4129_pciconf0.2 27:00.2 mlx5_4 net-ibs301f2 0 ConnectX7(rev:0) /dev/mst/mt4129_pciconf0.1 27:00.1 mlx5_3 net-ibs301f1 0 ConnectX7(rev:0) /dev/mst/mt4129_pciconf0 27:00.0 mlx5_2 net-ibo4 0
Bash
복사
현재 우리가 세팅하려는 8개의 포트는 각각 내부적으로 두개의 400G Ethernet 포트로 인식됩니다.
/dev/mst/mt4131_pciconf0 /dev/mst/mt4131_pciconf0.1 … /dev/mst/mt4131_pciconf7 /dev/mst/mt4131_pciconf7.1
형태의 네이밍 규칙으로 되어있는것을 알수 있습니다. 물리적인 포트위치와 다를수 있기 때문에 직접 확인해보면 다음과 같습니다.
물리적으로 1 2 3 4 5 6 7 8 순서로 포트를 가정하면
mst 명령어에서 확인되는 번호와는
1 2 3 4 5 6 7 8
7 4 6 5 0 3 1 2 순서쌍으로 연결이 되는것을 알 수 있습니다. 또한 하나의 포트는 두개의 이더넷 인터페이스로 나뉘어져 있기 때문에 다음과 같은 연결맵을 얻을 수 있습니다.
DEFAULT_CARD_IFACES = { 0: ("eno10np0", "enp23s0f1np1"), # pciconf0 1: ("eno12np0", "enp57s0f1np1"), # pciconf1 2: ("eno13np0", "enp95s0f1np1"), # pciconf2 3: ("eno11np0", "enp112s0f1np1"), # pciconf3 4: ("eno7np0", "enp151s0f1np1"), # pciconf4 5: ("eno9np0", "enp185s0f1np1"), # pciconf5 6: ("eno8np0", "enp220s0f1np1"), # pciconf6 7: ("eno6np0", "enp237s0f1np1"), # pciconf7 }
Bash
복사
관리를 위해서 간단한 규칙을 적용해서 연결을 해보겠습니다. 10.0.0.0/16 을 기반으로 여러개의 서브넷을 규칙적으로 만들어서 물리적 케이블 연결 인터페이스마다 적절하게 인가합니다.
예를들어 10.112.1.0/30 같은 서브넷을 설정합니다.
10.1[서버1][서버2].[연결의숫자].[케이블양끝구분] 항상 작은 서버 번호가 앞으로 옵니다. 케이블 양끝 구분은 항상 번호가 작은 서버가 송신(1)이라고 가정하고 크면 수신이(2) 됩니다.
10.112.1.1 은 1번과 2번 서버의 1번째 송신부 연결입니다. 10.112.1.2 은 1번과 2번 서버의 1번째 수신부 연결입니다. 10.123.3.1 은 2번과 3번 서버의 3번째 송신부 연결입니다. 10.123.3.2 은 2번과 3번 서버의 3번째 수신부 연결입니다. 10.114.4.1 은 1번과 4번 서버의 4번째 송신부 연결입니다. 10.114.4.2 은 1번과 4번 서버의 4번째 수신부 연결입니다.
간단한 스크립트를 작성하여 서버별로 어떤 인터페이스에 어떤 서브셋을 인가할지 자동생성 하도록 작성합니다.
# 케이블 번호 → card idx → (np0, np1): # 케이블1: card7 eno6np0 / enp237s0f1np1 # 케이블2: card4 eno7np0 / enp151s0f1np1 # 케이블3: card6 eno8np0 / enp220s0f1np1 # 케이블4: card5 eno9np0 / enp185s0f1np1 # 케이블5: card0 eno10np0 / enp23s0f1np1 # 케이블6: card3 eno11np0 / enp112s0f1np1 # 케이블7: card1 eno12np0 / enp57s0f1np1 # 케이블8: card2 eno13np0 / enp95s0f1np1 # ===== LINK PLAN ===== # srv1(cable1→card7) <-> srv2(cable1→card7) L1 # np0: eno6np0 10.1.1.1/30 <-> eno6np0 10.1.1.2/30 # np1: enp237s0f1np1 10.1.2.1/30 <-> enp237s0f1np1 10.1.2.2/30 # srv1(cable2→card4) <-> srv2(cable2→card4) L2 # np0: eno7np0 10.1.3.1/30 <-> eno7np0 10.1.3.2/30 # np1: enp151s0f1np1 10.1.4.1/30 <-> enp151s0f1np1 10.1.4.2/30 # srv1(cable3→card6) <-> srv3(cable1→card7) L3 # np0: eno8np0 10.1.5.1/30 <-> eno6np0 10.1.5.2/30 # np1: enp220s0f1np1 10.1.6.1/30 <-> enp237s0f1np1 10.1.6.2/30 # srv1(cable4→card5) <-> srv3(cable2→card4) L4 # np0: eno9np0 10.1.7.1/30 <-> eno7np0 10.1.7.2/30 # np1: enp185s0f1np1 10.1.8.1/30 <-> enp151s0f1np1 10.1.8.2/30 # srv2(cable3→card6) <-> srv3(cable3→card6) L5 # np0: eno8np0 10.1.9.1/30 <-> eno8np0 10.1.9.2/30 # np1: enp220s0f1np1 10.1.10.1/30 <-> enp220s0f1np1 10.1.10.2/30 # srv2(cable4→card5) <-> srv3(cable4→card5) L6 # np0: eno9np0 10.1.11.1/30 <-> eno9np0 10.1.11.2/30 # np1: enp185s0f1np1 10.1.12.1/30 <-> enp185s0f1np1 10.1.12.2/30 ## ===== server1 ===== # --- link up --- ip link set eno6np0 up mtu 9000 ip link set enp237s0f1np1 up mtu 9000 ip link set eno7np0 up mtu 9000 ip link set enp151s0f1np1 up mtu 9000 ip link set eno8np0 up mtu 9000 ip link set enp220s0f1np1 up mtu 9000 ip link set eno9np0 up mtu 9000 ip link set enp185s0f1np1 up mtu 9000 # --- flush --- ip addr flush dev eno6np0 ip addr flush dev enp237s0f1np1 ip addr flush dev eno7np0 ip addr flush dev enp151s0f1np1 ip addr flush dev eno8np0 ip addr flush dev enp220s0f1np1 ip addr flush dev eno9np0 ip addr flush dev enp185s0f1np1 # --- addr add --- ip addr add 10.1.1.1/30 dev eno6np0 ip addr add 10.1.2.1/30 dev enp237s0f1np1 ip addr add 10.1.3.1/30 dev eno7np0 ip addr add 10.1.4.1/30 dev enp151s0f1np1 ip addr add 10.1.5.1/30 dev eno8np0 ip addr add 10.1.6.1/30 dev enp220s0f1np1 ip addr add 10.1.7.1/30 dev eno9np0 ip addr add 10.1.8.1/30 dev enp185s0f1np1 # --- static routes --- ip route add 10.1.10.0/30 via 10.1.1.2 dev eno6np0 ip route add 10.1.11.0/30 via 10.1.1.2 dev eno6np0 ip route add 10.1.12.0/30 via 10.1.1.2 dev eno6np0 ip route add 10.1.9.0/30 via 10.1.1.2 dev eno6np0 ## ===== server2 ===== ...
Bash
복사
위와같은 수동 설정으로 스위치를 일부 대체하는 네트워크 설정이 논리적으로 되었지만
실제로 NCCL의 동작 방식에서는 NCCL_SOCKET_IFNAME 환경변수로 전달된 가장 첫번째 인터페이스를 IP를 기준으로 동작합니다. full mesh 상태의 연결이리도 1번 인터페이스에 모든 노드가 접근 가능하도록 정적 라우팅을 추가할 필요가 있습니다.
# --- static routes --- ip route add 10.1.10.0/30 via 10.1.1.2 dev eno6np0 ip route add 10.1.11.0/30 via 10.1.1.2 dev eno6np0 ip route add 10.1.12.0/30 via 10.1.1.2 dev eno6np0 ip route add 10.1.9.0/30 via 10.1.1.2 dev eno6np0
Bash
복사
아래는 코드의 git repo 입니다.
이제 Slurm 등 적절한 클러스터 매니저 시스템을 통해서 네트워크 장비 없이도 2~8 노드 정도의 소규모 클러스터에서 훈련을 시작할 수 있습니다. 예시 코드처럼 12개의 DAC 케이블을 이용하면 4노드의 DGX B300 장비를 각각 400G x 4 개의 연결 1600G의 full mesh로 세팅 가능 합니다.
export NCCL_SOCKET_IFNAME=dummy0,eno6np0,enp237s0f1np1,eno7np0,enp151s0f1np1,eno8np0,enp220s0f1np1,eno9np0,enp185s0f1np1 srun -N4 --ntasks-per-node=8 --gpus-per-task=1 --mpi=pmi2 \ bash -lc ' GPU=${SLURM_LOCALID} docker run --rm --net=host --ipc=host \ ...... '
Bash
복사
하지만 이런 별도 여러 서브셋과 연결을 포함한 네트워크 토폴로지는 NCCL에서 기본적으로 지원하는 범위가 아닙니다.
GID (Global Identifier) 연결 식별을 네트워크 토폴로지에 따라 커스텀 연결하게 주입하는 패치를 진행하여 빌드하면 NCCL 에서도 동작이 가능합니다. 하지만 해당 내용은 이번 컨텐츠의 범위를 벗어남으로 좀 더 단순한 예제를 공유합니다.
DGX B300 의 Ethernet 모드에서도 RoCE v2를 이용한 RDMA 연결이 가능합니다.
GPU RDMA(Remote Direct Memory Access) 하지만 해당 경우에도 IP 기반의 RDMA 구현이기 때문에 위와 동일한 통신 설정은 필요합니다. 간단하게 두개의 노드를 8 케이블, 16 연결로 연결한 경우를 가정해보겠습니다.
1-1 > 2-1 1-2 > 2-2 1-3 > 2-3 1-4 > 2-4 1-5 > 2-5 1-6 > 2-6 1-7 > 2-7 1-8 > 2-8
Bash
복사
위 스크립트를 이용하여 적당한 아이피 설정 세트를 설정합니다.
## ===== server1 ===== # --- link up --- ip link set eno6np0 up mtu 9000 .... ip link set enp95s0f1np1 up mtu 9000 # --- flush --- ip addr flush dev eno6np0 .... ip addr flush dev enp95s0f1np1 # --- addr add --- ip addr add 10.112.1.1/30 dev eno6np0 ip addr add 10.112.2.1/30 dev enp237s0f1np1 .... ip addr add 10.112.15.1/30 dev eno13np0 ip addr add 10.112.16.1/30 dev enp95s0f1np1 ## ===== server2 ===== ....
Bash
복사
export NCCL_IB_HCA=mlx5_22:1,mlx5_23:1,mlx5_14:1,mlx5_15:1,mlx5_20:1,mlx5_21:1,mlx5_16:1,mlx5_17:1,mlx5_0:1,mlx5_1:1,mlx5_12:1,mlx5_13:1,mlx5_6:1,mlx5_7:1,mlx5_10:1,mlx5_11:1
Bash
복사
Ethernet 모드에서도 NCCL_IB_HCA 값을 통해서 직접 인터페이스를 명시적으로 설정하는것이 좋습니다.
srun -N2 --ntasks-per-node=1 \ --gres=gpu:8 \ --mpi=pmix -u -l \ --container-image=ghcr.io/coreweave/nccl-tests:13.2.0-devel-ubuntu24.04-nccl2.29.7-1-7112046 \ --container-mounts=/tmp:/tmp,/dev/shm:/dev/shm \ --container-writable \ bash -c ' export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 export CUDA_DEVICE_ORDER=PCI_BUS_ID export NCCL_IB_DISABLE=0 export NCCL_NET_PLUGIN=none export NCCL_TUNER_PLUGIN=none export NCCL_CROSS_NIC=0 export NCCL_SOCKET_NTHREADS=4 export NCCL_NSOCKS_PERTHREAD=4 export UCX_TLS=tcp export UCX_NET_DEVICES=eno1 export PMIX_MCA_gds=hash export PMIX_SYSTEM_TMPDIR=/tmp # bootstrap은 관리망 export NCCL_SOCKET_IFNAME=eno1 export OMPI_MCA_oob_tcp_if_include=eno1 export OMPI_MCA_btl_tcp_if_include=eno1 export OMPI_MCA_coll=^ucc,hcoll export OMPI_MCA_pml=ob1 export OMPI_MCA_btl=self,tcp export OMPI_MCA_osc=pt2pt export NCCL_IB_HCA=mlx5_22:1,mlx5_23:1,mlx5_14:1,mlx5_15:1,mlx5_20:1,mlx5_21:1,mlx5_16:1,mlx5_17:1,mlx5_0:1,mlx5_1:1,mlx5_12:1,mlx5_13:1,mlx5_6:1,mlx5_7:1,mlx5_10:1,mlx5_11:1 echo "rank=${SLURM_PROCID} node=$(hostname) IB_HCA=${NCCL_IB_HCA}" /opt/nccl-tests/build/all_reduce_perf -b 512M -e 16G -f 2 -g 8 -n 20 '
Bash
복사
충분히 큰 (-b 512M -e 16G) 인자값이 전달되었을때 네트워크 성능을 최대로 확인 가능합니다. 이론상 최대 값 400G x 16 = 6400Gbps 800GB / s
slurm + nccl + all_reduce_perf 실측값 787GB / s 이론값 대비 약 98%
0: # Out of bounds values : 0 OK 0: # Avg bus bandwidth : 787.211 0: # 0: # Collective test concluded: all_reduce_perf 0: 1: b300-02:2817748:2817748 [7] NCCL INFO ENV/Plugin: Closing env plugin ncclEnvDefault 0: b300-01:3810159:3810159 [7] NCCL INFO ENV/Plugin: Closing env plugin ncclEnvDefault
Bash
복사

마무리

DGX B200에서 DGX B300 세대로 넘어오면서 네트워크 포트가 크게 증가하였고 B300 GPU의 VRAM 이 크게 증가하여 288Gb 수준이 되면서 2~4대 수준의 노드에서도 연구 수준에서 충분히 큰 모델을 다룰 수 있을만큼 메모리 크기와 연산 성능이 향상 되었습니다. 연구 및 실험 목적에서는 대규모 IB 및 400G, 800G 이더넷 스위치를 다루거나 구하기 어렵다면 위와 같은 방식 시도해볼만 합니다.
하지만 커넥터 광모듈은 제조사 마다 호환 여부가 상이하기 때문에 동일 스펙 케이블에서도 제조사의 구분에 따라 호환에 문제가 있을 수 있습니다. 또한 엔비디아의 표준 세팅이 아니기 때문에 공식 기술지원 및 보증에서 불이익이 있을 수 있습니다.