공개용 런북의 쉬운 설명판. 명령어와 수치는 그대로 두되, 각 단계마다 “이게 무엇이고 왜 필요한지"를 먼저 풀어 썼다. 주소·계정 같은 내부 정보는
<node1-ip>식으로 가렸다.
0. 먼저, 큰 그림
이 프로젝트가 한 일을 한 문장으로 줄이면:
“혼자서는 못 드는 무거운 짐(대형 AI 모델)을 네 사람(GPU 컴퓨터 4대)이 나눠 들게 만들었다.”
- 모델 이름은 GLM-5.3-Flash. 파라미터(모델의 ‘지식’을 담은 숫자) 3,200억 개짜리 초대형 모델이다. 압축(양자화)해도 185GB라 컴퓨터 한 대(메모리 128GB)에는 들어가지 않는다.
- 그래서 모델을 4등분해서 컴퓨터 4대에 나눠 올린다. 이것을 텐서 병렬(Tensor Parallel, TP) 이라고 하고, 4대로 나누니 TP=4 다.
- 문제는 이 4대가 한 글자를 만들 때마다 서로 계산 결과를 주고받아야 한다는 것. 그래서 일반 사무실 네트워크(1Gbps)로는 어림도 없고, 초고속 전용 네트워크(200Gbps, 200배 빠름)가 필요하다.
- 구축 작업의 절반 이상이 바로 이 “초고속 전용 네트워크"를 만드는 일이었다.
전체 흐름:
[1] 컴퓨터 4대의 초고속 네트워크 카드 설정
[2] 초고속 스위치(허브) 설정 ← 가장 애먹은 부분
[3] 네 대가 서로 빠르게 통신되는지 검증
[4] 모델 파일 보관소(NAS)와 고속 임시 보관소(핫티어) 만들기
[5] 185GB 모델 다운로드 (인터넷이 느려서 5시간)
[6] 4대에 배포하고 실행
[7] 상태 감시 화면(모니터링) 설치
1. 용어 사전 — 이것만 알면 아래를 읽을 수 있다
| 용어 | 쉬운 설명 |
|---|---|
| 노드(node) | 클러스터를 이루는 컴퓨터 한 대. 여기선 node1~node4 |
| DGX Spark / GB10 | NVIDIA가 만든 책상 위 소형 AI 컴퓨터. GB10은 그 안의 칩 이름. Lenovo ThinkStation PGX는 같은 물건의 Lenovo판 |
| 통합 메모리 128GB | 이 컴퓨터는 CPU와 GPU가 메모리 128GB를 함께 쓴다. 즉 GPU 메모리 = 컴퓨터 메모리. 다른 프로그램이 메모리를 쓰면 AI 모델이 쓸 자리가 줄어든다 (뒤에서 문제가 됨) |
| NIC / 네트워크 카드 | 컴퓨터의 랜 포트. 여기 컴퓨터엔 일반 1G 포트 외에 200G 초고속 포트가 2개 더 있다(제조사 Mellanox의 ConnectX-7, 줄여서 CX-7) |
| QSFP | 초고속 포트의 규격 이름. 일반 랜선 대신 굵은 전용 케이블(DAC)을 꽂는다 |
| 스위치(switch) | 여러 컴퓨터를 연결하는 허브. 여기선 MikroTik CRS804라는 400G 스위치 |
| 브레이크아웃(breakout) 케이블 | 스위치의 400G 포트 하나를 200G 두 가닥으로 쪼개는 Y자 케이블. 스위치 포트 4개 × 2가닥 = 8가닥 → 컴퓨터 4대 × 2포트에 딱 맞는다 |
| 레인(lane) | 400G 포트는 내부적으로 50G짜리 회선 8개(레인)를 묶은 것. 200G 두 가닥 = 레인 4개씩 두 묶음 |
| 오토네고(auto-negotiation) | 양쪽 장비가 “우리 몇 G로 통신할까?“를 자동 협상하는 기능. 잘 되면 편하지만, 이번엔 자동 협상이 50G에 머물러서 수동으로 200G를 강제해야 했다 |
| MTU 9000 / 점보 프레임 | 데이터를 한 번에 보내는 봉투 크기. 기본 1500바이트 대신 9000바이트짜리 큰 봉투를 쓰면 대용량 전송 효율이 올라간다. 경로 위 모든 장비가 같은 크기로 맞춰야 한다 |
| RDMA / RoCE | 한 컴퓨터가 다른 컴퓨터의 메모리에 CPU를 거치지 않고 직접 쓰는 기술. AI 노드 간 통신의 핵심. RoCE는 “이더넷 위에서 RDMA를 한다"는 뜻 |
| 플레인(plane) A/B | 컴퓨터마다 200G 포트가 2개라, 포트1끼리 묶은 망을 A, 포트2끼리 묶은 망을 B라고 부른다. 도로 2차선과 비슷 |
| NCCL | NVIDIA가 만든 “GPU끼리 데이터 주고받기” 라이브러리. 어떤 네트워크 카드를 쓸지 환경변수로 알려줘야 한다 |
| vLLM | AI 모델을 서버로 띄워주는 프로그램. ChatGPT처럼 질문을 보내면 답을 돌려주는 API가 된다 |
| 컨테이너 / 도커(Docker) / 이미지 | 프로그램과 필요한 부품을 한 상자에 담아 어디서든 똑같이 실행하는 기술. 상자의 설계도가 “이미지”(31GB), 실제 실행 중인 상자가 “컨테이너” |
| 양자화 / NVFP4 | 모델의 숫자를 더 적은 비트로 표현해 크기를 줄이는 압축. NVFP4는 4비트 압축 방식. 덕분에 320B 모델이 185GB로 줄었다 |
| KV 캐시 | 모델이 긴 대화를 기억하기 위해 쓰는 작업 메모리. 클수록 긴 문서·많은 동시 사용자를 처리한다 |
| NAS | 네트워크에 붙은 공용 하드디스크(Synology). 느리지만 용량이 크다(7.3TB) |
| NFS | 다른 컴퓨터의 디스크를 내 컴퓨터 폴더처럼 쓰게 해주는 규약 |
| 핫티어(hot tier) | “자주 쓰는 것은 빠른 창고에, 나머지는 큰 창고에” 두는 2단 구조에서 빠른 창고. 여기선 node1의 NVMe SSD |
| tok/s (토큰/초) | AI가 글자를 만드는 속도. 토큰은 대략 한국어 1~2글자. 37 tok/s면 사람이 읽는 속도보다 훨씬 빠르다 |
2. 하드웨어와 연결 구조
| 구성 | 내용 |
|---|---|
| 컴퓨터 | DGX Spark(GB10) 4대. 각 128GB 메모리, 200G 포트 2개 |
| 초고속 스위치 | MikroTik CRS804-4DDQ-hRM (400G 포트 4개) |
| 케이블 | 400G → 200G×2 브레이크아웃 DAC 4본. 컴퓨터 한 대당 200G 두 가닥 |
| 일반 네트워크 | 별도의 1G 스위치 (인터넷, 관리용) |
| 저장소 | NAS(느림·큼) + node1 SSD 핫티어(빠름) |
그림으로:
[MikroTik CRS804 400G 스위치]
포트1 ─Y─┬─ node1 포트A 포트3 ─Y─┬─ node3 포트A
└─ node1 포트B └─ node3 포트B
포트2 ─Y─┬─ node2 포트A 포트4 ─Y─┬─ node4 포트A
└─ node2 포트B └─ node4 포트B
케이블 사양 (참고)
- 제품: NADDOD Q2Q56-400G-CU 시리즈, 0.5m, 4본. “400G 한 개를 200G 두 개로 나누는” Y자 구리 케이블
- 구리선 안에 신호선 8쌍(레인)이 들어 있고, 4쌍씩 나눠 200G 두 가닥이 된다. 전기 신호를 증폭하는 칩이 없는 패시브 케이블이라 소비전력이 거의 0(<0.1W), 대신 3m를 넘기지 못한다
- 가격대: 1m 기준 약 138달러
- 우리 환경에서의 교훈: 스위치가 이 케이블을 자동 인식하면 50G로만 붙는다. 양쪽 모두 “무조건 200G"로 고정해야 8가닥 전부 200G가 된다
3. 단계 1 — 컴퓨터 쪽 초고속 포트 설정
왜 필요한가
포트에 주소(IP)를 주고, 큰 봉투(MTU 9000)를 쓰게 하고, 200G로 고정해야 한다. 안 그러면 케이블을 꽂아도 “연결 없음"이다.
먼저 겪은 함정: 어느 포트가 진짜인가
컴퓨터 안에서는 초고속 포트가 4개로 보인다(enp1s0f0np0, enp1s0f1np1, enP2p1s0f0np0, enP2p1s0f1np1). 하지만 실제 뒷면 구멍은 2개다. 확인해 보니 이름에 f1이 들어간 두 개가 진짜 구멍이고, f0 두 개는 케이블이 없다고 나온다(ethtool -m 명령이 “No cable”). f0에 설정을 넣고 왜 안 되지 하고 헤매기 쉽다.
| 컴퓨터 안 이름 | 실제 | 용도 |
|---|---|---|
enp1s0f1np1 | 뒷면 QSFP 포트 1 | 플레인 A (예: 192.168.A.노드번호) |
enP2p1s0f1np1 | 뒷면 QSFP 포트 2 | 플레인 B (예: 192.168.B.노드번호) |
enp1s0f0np0, enP2p1s0f0np0 | 구멍 없음 | 사용 안 함 |
실제 설정 (Ubuntu의 NetworkManager 사용)
nmcli con add type ethernet ifname enp1s0f1np1 con-name roce0 \
ipv4.method manual ipv4.addresses 192.168.A.N/24 ipv4.never-default yes \
ipv6.method disabled 802-3-ethernet.mtu 9000 \
802-3-ethernet.auto-negotiate no 802-3-ethernet.speed 200000 802-3-ethernet.duplex full
읽는 법: “이 포트에 고정 IP를 주고, 인터넷 기본 경로로는 쓰지 말고(never-default), IPv6는 끄고, 봉투 크기 9000, 자동 협상 끄고 200G 고정.” 포트 2(enP2p1s0f1np1)도 똑같이, 이름만 roce1.
추가로 넣은 것:
arp_ignore=1,arp_announce=2: 포트가 2개라 “누구세요?” 질문에 엉뚱한 포트가 대답하는 혼선을 막는다.ethtool -A <포트> rx on tx on: 상대가 너무 빨리 보내면 “잠깐만"이라고 신호하는 기능(플로우컨트롤) 켜기.vm.swappiness=0: 메모리가 꽉 찼을 때 디스크로 내보내는 동작을 최소화. AI 서빙 중엔 이게 켜져 있으면 컴퓨터가 멈출 수 있다고 레시피 저자가 경고.
4. 단계 2 — 스위치 설정 (가장 애먹은 부분)
배경
MikroTik 스위치는 RouterOS라는 자체 운영체제를 쓰고, WinBox라는 윈도우 프로그램이나 명령줄로 설정한다. 400G 포트 하나는 내부적으로 레인 8개로 되어 있고, 스위치는 이걸 qsfp56-dd-1-1 ~ qsfp56-dd-1-8처럼 8개의 가짜 포트로 보여준다.
200G 두 가닥으로 쪼개려면 레인 14를 묶은 8을 묶은 -1과 레인 5-5, 이 두 개만 쓰면 된다. 나머지 6개는 꺼둔다.
실제로 벌어진 일 (순서대로)
- 처음: 케이블만 꽂으니 링크는 뜨는데 속도가 50G. 자동 협상이 “레인 하나짜리 속도"만 제안하고 있었다. 컴퓨터가 원하는 200G가 후보에 아예 없었다.
- 시도: “200G도 제안해"라고 설정(
advertise=200G-baseCR4)했더니 제안 목록이 텅 비고 링크가 끊겼다. 이 스위치의 쪼갠 포트는 자동 협상으로는 200G를 제안하지 못한다. - 해결: 자동 협상을 끄고 “무조건 200G” 로 고정. 컴퓨터 쪽도 같이 고정. → 8가닥 전부 200G로 연결.
- 두 번째 함정: 링크는 200G인데 컴퓨터끼리 ping이 안 됨. 스위치 안에서 포트들을 하나의 “가상 허브(브리지)“에 묶어야 서로 통하는데, 한 줄짜리 명령으로 8개를 한 번에 넣은 것이 아무 오류 없이 조용히 실패해 있었다. 하나씩 넣는 반복문으로 다시 추가하니 해결.
최종 명령
# 200G 서브포트 8개: 자동협상 끄고 200G 고정, 플로우컨트롤, 큰 봉투
/interface/ethernet/set [find name~"qsfp56-dd-[1-4]-(1|5)\$"] auto-negotiation=no speed=200G-baseCR4 fec-mode=auto rx-flow-control=on tx-flow-control=on l2mtu=9216 mtu=9000
# 안 쓰는 서브포트 24개 끄기
/interface/ethernet/set [find name~"qsfp56-dd-[1-4]-(2|3|4|6|7|8)\$"] disabled=yes
# 가상 허브 만들기 (STP 등 부가기능 끔)
/interface/bridge/add name=br-roce protocol-mode=none mtu=9000
# 8개 포트를 허브에 하나씩 추가 (한 줄로 넣으면 조용히 실패함)
:foreach i in=[/interface/ethernet/find name~"qsfp56-dd-[1-4]-(1|5)\$"] do={/interface/bridge/port/add bridge=br-roce interface=$i hw=yes}
확인: /interface/ethernet/monitor [find name~"qsfp56-dd-[1-4]-(1|5)\$"] once → 8개 모두 link-ok 200Gbps가 나와야 한다. 그리고 /interface/bridge/port/print에 8개가 보여야 한다.
혼잡 제어는 복잡한 PFC 대신 단순한 802.3x 플로우컨트롤 + 큰 봉투만 썼다. 이 스위치에 AI 전용 장비 4대만 물려 있어서 충분하다.
5. 단계 3 — 정말 빠른지 검증
- ping 테스트: 8972바이트짜리 큰 봉투를 “쪼개지 말고 그대로 보내라(DF)” 옵션으로 보냈을 때 성공 → 경로 전체가 큰 봉투를 지원한다는 뜻.
- RDMA 속도 테스트 (
ib_write_bw): 두 플레인에서 동시에 측정하니 98 + 98 Gb/s. 200G 포트인데 왜 100G씩이냐 하면, 이 소형 컴퓨터는 네트워크 카드와 CPU 사이 통로(PCIe)가 좁아서 포트당 100G 근처가 한계다. 다른 사용자들 보고와 일치하는 정상 수치. 참고로 일반 사무실 네트워크(1G)의 약 200배. rdma_cm방식(-R)은 오류가 나서, RoCEv2용 주소 번호(GID index 3)를 직접 지정하는 방식으로 측정했다.
6. 단계 4 — 파일 보관 구조 (콜드/핫)
왜 두 단계인가
- NAS는 7.3TB로 크지만 1G 네트워크라 초당 110MB. 185GB 모델을 4대가 각자 읽으면 30분씩 걸린다.
- node1의 SSD를 초고속 네트워크 위에서 공유하면 초당 1GB 이상. 같은 파일을 3분이면 읽는다.
그래서:
인터넷 ──(느림, 1회만)──▶ NAS(원본 보관, 콜드)
│ 서빙할 모델만 복사
▼
node1 SSD(핫티어) ──(200G 망)──▶ node2·3·4가 같은 폴더로 읽음
설정 요점
- NAS: Synology의 NFS 기능을 켜고 공유폴더를 만든다. 기본은 오래된 NFS v3만 켜져 있어서 v4.1을 따로 켜야 리눅스에서 잘 붙는다. 4대 모두
/mnt/nas-models에 마운트. - 핫티어: node1에 NFS 서버를 설치하고 SSD 폴더를 초고속 망 주소로만 공개. 3대가
/mnt/hot-models로 마운트(nconnect=8옵션 = 연결 8개로 병렬). - 실측: 핫티어 쓰기 942MB/s, 읽기 1.2GB/s.
- 보안: 공개 대상은 노드 4대 IP만, root 권한 매핑은 끄는 것(
root_squash)을 권장.
7. 단계 5 — 모델 다운로드와 배포
다운로드
- 모델 본체(
RedHatAI/GLM-5.3-Flash-NVFP4, 185GB)와 보조 모델(드래프터incoai/GLM-5.3-Flash-DFlash2, 2.3GB), 그리고 실행 프로그램 이미지(31GB)를 node3 한 대에서만 받았다. 4대가 각자 받으면 안 그래도 느린 인터넷을 4등분하게 된다. - 인터넷이 약 100Mbps라 모델 185GB에 5시간, 이미지 31GB는 중간에 세 번 끊겨(
unexpected EOF) 재시작. 다행히 받은 부분은 남아서 이어받기가 됐다.
배포 (여기서 초고속 망이 빛남)
- 모델 185GB → 핫티어 복사: 6분
- 드래프터 2.3GB → 3대: 1분 미만
- 이미지 31GB → 3대: 한 대당 4분 (
docker save | ssh | docker load, 이 방법은 암호화 때문에 200G를 다 못 쓰지만 충분)
드래프터가 뭔가
본 모델이 다음 글자를 만들기 전에, 작은 보조 모델이 “아마 이 7글자일 것"이라고 미리 찍고 본 모델이 한 번에 검사하는 기법(투기적 디코딩, 여기선 DFlash2). 맞으면 7글자를 한 번에 진행하니 속도가 오른다. 코드처럼 예측 쉬운 글은 잘 맞고, 자유로운 산문은 덜 맞는다.
8. 단계 6 — 실행
사용한 레시피
GitHub의 tonyd2wild/GLM-5.3-Flash-NVFP4-1M-KV-4x-DGX-Spark — 같은 컴퓨터 4대로 같은 모델을 돌린 사람이 정리한 실행 스크립트와 패치 모음. 이 컴퓨터 칩(GB10, 코드명 sm121)은 신형이라 표준 vLLM이 그대로는 안 돌아서, 저자가 고친 버전(이미지)을 배포한다.
핵심 설정을 말로 풀면
| 설정 | 의미 |
|---|---|
| rank 0 = node1(head), 1~3 = worker | 4대 중 한 대(head)가 질문을 받고 나머지에 일을 나눠준다 |
--tensor-parallel-size 4, --nnodes 4 | 모델을 4등분해서 4대에 |
NCCL_IB_HCA=rocep1s0f1, NCCL_SOCKET_IFNAME=enp1s0f1np1 | “GPU끼리 통신할 때 이 초고속 포트(플레인 A)를 써라” |
NCCL_IB_GID_INDEX=3, NCCL_IB_ROCE_VERSION_NUM=2 | RoCE 방식 중 v2, IPv4 주소 사용 |
--gpu-memory-utilization 0.80 | 메모리 128GB 중 80%까지만 모델이 쓴다 (뒤의 실패담 참고) |
--kv-cache-memory 24GiB, fp8_e4m3 | 대화 기억용 메모리 24GB, 압축 형식 |
--max-model-len 1048576 | 한 번에 다룰 수 있는 글 길이 최대 100만 토큰 (책 수십 권 분량) |
DFlash2 num_speculative_tokens 7 | 보조 모델이 7글자씩 미리 찍기 |
실행 순서 (레시피 저자가 강조한 규칙)
- 전부 끄고 시작: 이미 죽어가는 놈이 있으면 새로 뜬 놈이 그놈과 악수하려다 영원히 기다린다. 항상 4대 모두
docker rm -f vllm_glm53. - 메모리 비우기:
sync; echo 3 > /proc/sys/vm/drop_caches. 파일 캐시가 GPU 메모리 자리를 잡아먹고 있을 수 있다. 다운로드 같은 무거운 작업도 잠시 멈춘다. - 플러셔 실행: 부팅 내내 1분마다 캐시를 비우는 작은 스크립트. 저자가 “이게 있어야 24GB KV 캐시가 안 죽는다"고 밝혀낸 핵심 비법.
- 일꾼 먼저, 대장 나중: node4 → node3 → node2 → node1 순으로 15~20초 간격.
- 확인: node1에서
curl localhost:8000/health가 200이면 성공. 로그에GPU KV cache size: 3,895,606 tokens가 보인다.
실패담 (두 번 실패하고 세 번째 성공)
- 오류 메시지:
Free memory on device cuda:0 (103.12/121.63 GiB) ... less than desired (0.85, 103.38 GiB) - 뜻: “80%… 아니 85%를 쓰고 싶은데 지금 비어 있는 게 그보다 0.3GB 적다.”
- 원인: 이 컴퓨터는 CPU와 GPU가 메모리를 같이 쓴다. 1차는 node3에서 돌던 다운로드가, 2차는 node1의 다른 서비스(웹 UI, 모니터링)가 그 0.3GB를 잡고 있었다.
- 해결: 85% → 80% 로 낮추고 캐시를 비운 뒤 재시작. 실패하면 1~2분 안에 컨테이너가 죽으므로 금방 알 수 있다.
- 또 하나: 4대 중 한 대가 죽으면 대장(node1)도
Connection closed by peer라며 같이 종료된다. 그래서 규칙 1(전부 끄고 시작)이 중요하다.
결과
- 부팅: 컨테이너 시작부터 응답 가능까지 약 8분 (모델 읽기 216초 — 핫티어 덕분. 레시피 문서는 느린 NFS 기준 ~20분)
- 첫 질문: 261토큰을 11.1초에 (처음엔 내부 컴파일이 있어 느림)
- 그 다음 코드 생성: 400토큰을 10.7초, 초당 37토큰. 100만 토큰 문맥, 동시 3.7명 분량의 기억 공간.
9. 단계 7 — 감시 화면(모니터링)
- 각 컴퓨터에 상태 보고 프로그램 2개를 붙인다:
node_exporter(CPU·메모리·디스크),nvidia_gpu_exporter(GPU 온도·전력·클럭). - node1에 수집기(Prometheus)와 화면(Grafana)을 올리고, DGX Spark용으로 만들어진 대시보드(RodriMora/dgx-spark-grafana-dashboard)를 불러온다. 대시보드 쿼리의
vllm:을vllm_로 바꿔야 우리 vLLM 지표 이름과 맞는다. - 이 컴퓨터만의 함정:
node_exporter의 CPU 클럭 수집 기능(cpufreq)이 GB10에서 영원히 멈춘다. 멈춘 요청이 쌓여 “503 서비스 불가"가 뜬다.--no-collector.cpufreq로 끄면 정상. - Grafana 초기 비밀번호는 반드시 바꾸고, 기존 서비스와 포트가 겹치지 않게 한다.
10. 단계별 실제 걸린 시간
총 약 9시간. 그중 6시간은 인터넷 다운로드 대기였다.
| 단계 | 걸린 시간 | 한 줄 설명 |
|---|---|---|
| 컴퓨터 포트 설정 (대당) | 1~2분 | 스크립트 한 번 |
| 스위치 설정 | 약 25분 | 명령 자체는 5분, 50G 문제·브리지 조용한 실패 찾느라 20분 |
| 속도 검증 | 3분 | |
| 프로그램 이미지 받기 25~31GB | 1~5.5시간 | 인터넷 나눠 쓰면 더 느림, 세 번 끊김 |
| 모델 185GB 받기 (1대) | 약 5시간 | 초당 10MB |
| 모델 → 핫티어 복사 | 약 6분 | 초당 500MB |
| 드래프터 → 3대 | 1분 미만 | |
| 이미지 → 3대 | 대당 약 4분 | |
| AI 서버 부팅 | 약 8분 | |
| 부팅 실패 알아차리기 | 1~2분 | |
| NAS 공유 + 4대 연결 | 약 10분 | |
| 핫티어 만들기 | 약 5분 | |
| 모니터링 | 대당 3~5분 + 원인 찾기 10분 |
교훈: 인터넷이 병목이면 “한 대만 받고, 초고속 망으로 나눠준다” 가 전부다.
11. 다시 한다면 — 교훈 여섯 가지
- 진짜 포트는
f1이다.f0에 설정 넣고 “왜 링크가 안 뜨지” 하지 말 것. - MikroTik 400G 쪼개기는
-1/-5서브포트에 자동협상 끄고 200G 강제. 자동으로 두면 50G에 머문다. - RouterOS에서 여러 포트를 한 줄로 브리지에 넣으면 조용히 실패할 수 있다. 넣고 나서 반드시 목록을 출력해 확인.
- 통합 메모리 컴퓨터에서 GPU 85%는 여유가 0.3GB뿐. 다른 서비스가 같이 살면 80%.
- 핫티어 하나로 부팅 시간이 절반 이하. SSD 한 개를 초고속 망으로 공유하는 것만으로 충분하다.
- 공개 문서에는 IP·계정·비밀번호·포트 목록·공유 설정을 쓰지 말 것. 이 문서는 그것들을 모두 지운 결과다.