최첨단 규모의 모델을 로컬에서 실행하려면 모든 가중치를 GPU 메모리에 유지해야 합니다. 가장 큰 모델들은 더 이상 한 대의 장치에 들어가지 않습니다. ConnectX-8 RDMA를 통해 연결된 두 개의 NVIDIA DGX Stations가 텐서 병렬성(tensor parallelism)을 사용하여 모델 가중치와 계산을 두 GPU에 분할함으로써 단일 엔드포인트를 통해 하나의 모델을 서비스합니다.
The NVIDIA GLM-5.3 NVFP4 모델은 Z.ai의 mixture-of-experts (MoE) 모델의 양자화된 버전으로, 7,530억 개의 매개변수를 가지고 있으며 토큰당 400억 개가 활성화됩니다. 희소 활성화(Sparse activation)는 계산량을 줄이지만, 각 토큰이 다른 전문가에게 라우팅될 수 있으므로 모든 모델 가중치는 여전히 사용 가능해야 합니다. 약 464 GB인 이 체크포인트는 단일 GB300 GPU의 HBM3e 252 GB를 초과합니다. 이 튜토리얼은 CPU 가중치 오프로드(CPU weight offload) 기능을 비활성화하고 두 개의 DGX Stations를 사용하여 가중치를 GPU 메모리에 유지합니다.
시작하기 전에, Connect Two DGX Stations 플레이북을 사용하여 두 스테이션을 연결하십시오.
이 게시물은 vLLM을 사용하여 두 개의 NVIDIA DGX Stations에서 GLM-5.3을 로컬로 서비스하고, 요청을 보내며, 동시 부하(concurrent load) 하에서의 동작을 측정하는 방법을 보여줍니다. 텐서 병렬성 (TP=2)은 모델의 텐서를 두 GB300 GPU에 분할합니다. 두 스테이션 모두 모든 요청을 계산하며, Station A를 통해 하나의 OpenAI-호환 API로 답변이 반환됩니다. 마지막에는 작동하는 엔드포인트와 부하 하에서의 동작 기준선(baseline)을 갖게 될 것입니다.
Two stations serve one model: each GPU holds and computes part of the model over the selected RDMA connection. Their memories remain distinct.
그림 1. 두 스테이션이 하나의 모델을 서비스합니다: 각 GPU는 선택된 RDMA 연결을 통해 모델의 일부를 보유하고 계산합니다. 이들의 메모리는 독립적으로 유지됩니다.
- 두 개의 GB300 DGX Station이 Linux를 실행하며, Connect Two DGX Stations playbook에 따라 연결 및 검증되었으며, 양쪽 레일 모두에서 GPU-memory RDMA 테스트가 포함됩니다.
- 두 Station 모두 동일한 Linux 사용자 이름으로 SSH 및 sudo 접근 권한을 가집니다. Docker와 NVIDIA Container Toolkit은 DGX Station 소프트웨어 스택에 사전 설치되어 있습니다. 컨테이너 레지스트리, Hugging Face, 패키지 저장소로의 아웃바운드 접속이 허용되어야 합니다.
- 각 Station에는 체크포인트 복사본을 위한 충분한 디스크 공간(최소 464 GB)과 vLLM 컨테이너 이미지 및 임시 파일을 위한 여유 공간이 필요합니다.
- 모델 카드에 명시된 NVIDIA Open Model Agreement와 원본 GLM-5.3 라이선스 약관의 수락이 필요합니다. SPEED-Bench는 NVIDIA Evaluation Dataset License Agreement를 따릅니다.
- Hugging Face 계정과 읽기 전용 토큰이 필요하며, 게이티드(gated) cais/hle 데이터셋에 접근할 수 있어야 합니다: 로그인하여 해당 페이지를 열고 Agree and access repository를 선택합니다. 5단계에서 이 데이터셋으로부터 SPEED-Bench 프롬프트를 다운로드합니다.
동반되는 비디오는 동일한 워크플로우를 따릅니다.
Video 1. 준비, 추론, 벤치마킹 및 종료 과정 안내
이 튜토리얼은 노트북과 같은 별도의 컴퓨터를 사용하여 두 Station을 관리합니다. 해당 컴퓨터에서 각 Station으로 SSH 세션을 여는 것부터 시작하십시오. 또는 각 Station의 로컬 터미널에서 작업할 수 있습니다. 'On A.'로 시작하지 않는 한, 이 단계의 모든 명령은 두 Station 모두에서 실행해야 합니다.
두 Station은 서로 다른 역할로 동일한 컨테이너와 체크포인트를 실행합니다: Station A (rank 0)는 API를 호스팅하고, Station B (rank 1)는 API 서버 없이 워커만 실행합니다.
먼저 Docker 접근을 확인하고 나중에 사용할 네트워크 값을 수집하십시오:
docker version
ibdev2netdev
ip -br addr
참고: docker version 출력에 Server 섹션이 없는 경우, 클라이언트가 Docker 데몬에 접근할 수 없습니다. 오류가 permission denied인 경우, 다음 Docker 설치 후 절차를 따르십시오: 사용자 계정을 sudo usermod -aG docker "$USER" 명령을 사용하여 docker 그룹에 추가하고, exit을 실행한 다음 다시 연결하여 docker version을 재실행하십시오. docker 그룹은 root 수준의 권한을 부여합니다.
|---|
The ibdev2netdev 출력에는 두 개의 직접 레일인 cx8r0과 cx8r1이 나열됩니다. 서비스 트래픽을 위해 그중 하나(예: cx8r1)를 선택하고, 각 스테이션에 대해 다음 두 가지 사항을 기록하십시오:
RDMA 장치: 레일 이름으로 끝나는 ibdev2netdev 라인에서 확인합니다. 예시(mlx5_1 port 1 ==> cx8r1 (Up))에서 장치는 mlx5_1입니다.
IPv4 주소: 레일 이름으로 시작하는 ip -br addr 라인에서 확인합니다. 예시(cx8r1 UP 192.168.101.1/24)에서 주소는 192.168.101.1입니다.
레일 이름과 장치는 두 스테이션 모두 동일하며, IPv4 주소만 다릅니다.
다음으로, 각 스테이션에 ~/glm53.env를 생성하십시오. 이 파일은 나중에 명령에서 사용되는 값을 저장합니다:
다음 블록을 붙여넣고, 따옴표를 유지한 채 <...> 플레이스홀더 각각을 기록한 값으로 대체하십시오.
RANK="<NODE_RANK>" # Station A에서는 RANK="0", Station B에서는 RANK="1"
HEAD_IP="<STATION_A_IPV4_ADDRESS>" # Station A에서 기록한 IPv4 주소; 두 파일에 동일하게 적용
WORKER_IP="<STATION_B_IPV4_ADDRESS>" # Station B에서 기록한 IPv4 주소; 두 파일에 동일하게 적용
...
예를 들어, cx8r1의 기본 주소를 사용하면 스테이션 A의 파일은 다음과 같이 시작합니다. 스테이션 B의 파일은 RANK="1"을 제외하고는 동일합니다:
RANK="0"
HEAD_IP="192.168.101.1"
WORKER_IP="192.168.101.2"
...
MODEL_HOST는 홈 디렉토리에서 구성되며, Step 2에서는 체크포인트를 SSH를 통해 Station B의 같은 경로로 복사합니다.
참고: 이후 단계에서는 source ~/glm53.env로 시작하여 이 값들을 로드합니다. 새로운 SSH 세션을 열 때마다 이 명령을 다시 실행하십시오.
두 스테이션(Stations) 모두에서 값을 로드하고 스토리지, 연결성 및 기본 메모리를 확인합니다:
source ~/glm53.env
mkdir -p "$MODEL_HOST" && df -h "$MODEL_HOST"
ethtool "$RAIL_IFACE"
...
이러한 점검은 NCCL 타임아웃으로 나타나기 전에 케이블링 및 주소 지정 오류를 포착합니다. 400000Mb/s 링크, MTU 9000, 성공적인 jumbo-frame ping, 그리고 동기화된 시간을 예상해야 합니다. nvidia-smi는 GB300에서 컴퓨팅 프로세스가 표시되지 않아야 합니다. 디스플레이 GPU가 있는 스테이션의 경우, Xorg가 두 GPU 모두에 몇 MiB로 나타날 수 있습니다.
팁: nvidia-smi가 보여주는 GPU 메모리 사용량을 기록해 두세요. 4단계에서는 이를 로드된 모델과 비교합니다. |
|---|
2단계는 A에서 B로 SSH를 통해 체크포인트를 복사하므로, A는 B에 비밀번호 없는(passwordless) 접근 권한이 필요합니다. A에서 패스프레이즈가 없는 키를 생성하고(자세한 내용은 SSH keys 참조), B의 호스트 지문(host fingerprint)을 확인한 후 공개 키를 B로 복사하고 로그인을 확정합니다:
ssh-keygen -t ed25519
ssh-copy-id "$WORKER_IP"
ssh -o BatchMode=yes "$WORKER_IP" hostname
텐서 병렬성(Tensor parallelism)은 두 랭크(ranks) 모두가 동일한 소프트웨어를 동일한 가중치에 대해 실행하도록 요구합니다. 양쪽 스테이션에서 고정된 이미지(pinned image)를 가져오는 것부터 시작합니다:
docker pull "$IMG" &&
docker image inspect "$IMG" --format '{{.Architecture}} {{.RepoDigests}}'
이미지 다운로드는 몇 분이 걸릴 수 있습니다. 일치하는 arm64 다이제스트(digests)는 두 랭크가 동일한 소프트웨어를 실행한다는 의미입니다.
A에서 고정된 체크포인트를 다운로드합니다. 약 464 GB에 달하며, 1 Gb/s에서는 대략 한 시간이 걸리고, 10 Gb/s에서는 약 6분이 걸립니다:
mkdir -p "$MODEL_HOST"
docker run --rm --network host -v "$MODEL_HOST:/model" \
--entrypoint bash "$IMG" -lc \
...
hf download가 Downloaded를 출력하면, 이 파일들을 B로 복사합니다. A에서:
ssh "$WORKER_IP" "mkdir -p '$MODEL_HOST'" &&
rsync -aH --info=progress2 --exclude '.cache/' \
"$MODEL_HOST/" "$WORKER_IP:$MODEL_HOST/"
각 디스크는 전체 체크포인트를 유지하며, vLLM은 로드 시점에 가중치를 GPU에 분할하고 추가적인 양자화(quantization)는 수행하지 않습니다.
A에서 두 복사본을 비교한 다음(이 확인 과정은 SSH를 통해 B의 파일 목록을 읽습니다), 체크포인트를 검증합니다:
(
set -euo pipefail
L=$(find "$MODEL_HOST" -path '*/.cache' -prune -o
...
MODEL_READY는 두 스테이션 모두 예상 레이아웃에서 동일한 파일 이름과 크기를 가지고 있음을 의미합니다. MIXED_PRECISION은 모든 텐서가 아닌 MoE(Mixture-of-Experts)-블록의 선형 가중치와 활성화 값에 대해 NVFP4 양자화(quantization)를 목표로 합니다.
이 단계는 두 대의 머신을 하나의 서비스로 만듭니다. 동일한 실행 스크립트가 두 스테이션 모두에서 실행되며, ~/glm53.env 파일을 읽고 각 스테이션에 vLLM 랭크(rank)를 하나씩 시작합니다. 적응할 때 가장 중요한 설정 두 가지는 다음과 같습니다: --max-model-len 98304, 이는 토큰의 컨텍스트 상한선으로 입력과 출력을 모두 포함하며, 그리고 --gpu-memory-utilization 0.93은 vLLM에 GPU 메모리의 93%를 할당합니다. 다른 플래그를 변경하면 한 번에 하나씩 변경하고 다시 테스트하세요.
최신 권장 구성은 NVIDIA의 Local AI recipe repository를 참조하십시오.
보안 참고 사항: --network host와 함께 --host 0.0.0.0을 사용하면 관리 인터페이스를 포함하여 모든 스테이션 A 인터페이스에서 인증되지 않은 API가 노출됩니다. 더 광범위한 접근 전에 포트를 의도된 클라이언트로 방화벽 처리하고, 인증 및 TLS를 추가하십시오. |
|---|
두 스테이션 모두에서 ~/launch-glm53.sh라는 이름의 파일을 생성합니다:
이 스크립트는 추측 디코딩(speculative decoding)의 한 형태인 Multi-Token Prediction (MTP)을 활성화합니다: 모델의 MTP 레이어는 단계당 네 개의 토큰을 초안 작성하고, vLLM은 이를 수락하기 전에 전체 모델로 검증하며, 따라서 각 단계에서 여러 토큰을 생성할 수 있습니다. 이득은 초안이 얼마나 자주 수락되는지에 따라 달라지며, 이는 프롬프트에 따라 다릅니다.
FP8 KV-캐시(KV-cache) 저장은 16비트 저장 방식에 비해 캐시 메모리를 절반으로 줄여 더 많은 토큰을 위한 공간을 남깁니다. 서버가 이미 본 프롬프트 접두사(prefix)를 다시 처리하는 것을 방지하기 위해 Prefix caching이 활성화됩니다. 아래 스크립트를 붙여넣으십시오. 이 스크립트는 구성을 확인하고, 선택된 레일의 RoCE GID 인덱스를 찾은 다음, 컨테이너 내에서 해당 스테이션의 vLLM 랭크를 시작합니다:
set -euo pipefail
source "$HOME/glm53.env"
case "$RANK" in 0|1) ;; *) echo "Invalid RANK" >&2; exit 1;; esac
...
스크립트를 먼저 B에서 실행한 다음, A에서 실행하십시오.
bash ~/launch-glm53.sh
각 명령어는 컨테이너 ID를 출력하고 즉시 반환됩니다. 서비스가 아직 준비되지 않았다는 의미입니다. B의 워커(worker)는 A가 연결되기를 기다린 다음, 각 스테이션은 로컬 스토리지에서 가중치(weights) 일부를 로드하고, 커널(kernels)을 준비하며, CUDA 그래프를 캡처합니다. 이 과정은 몇 분이 걸립니다.
A에서 서비스가 준비되는 시점을 확인하려면 로그를 따라갑니다 (--since는 이전 실행의 출력을 숨깁니다):
docker logs --follow --since "$(docker inspect \
--format '{{.State.StartedAt}}' "$CONTAINER")" "$CONTAINER"
로그에 Application startup complete가 표시되면, 추적을 중지하려면 Ctrl+C를 누릅니다. 서비스는 계속 실행됩니다.
이 시점에서 하나의 모델이 두 대의 머신에 걸쳐 분산됩니다. 어느 GPU도 완전한 사본을 가지고 있지 않으며, 단독으로 요청에 응답할 수 없습니다. 모든 토큰은 RoCE 레일(RoCE rail)을 통해 부분적인 결과를 교환하기 위해 두 랭크(ranks) 모두를 필요로 합니다.
남아 있는 GPU 메모리는 KV 캐시(KV cache)를 보유하며, 이는 진행 중인 요청(in-flight requests)의 컨텍스트를 저장합니다. 시작 로그는 이 용량(GPU KV 캐시 크기)을 보고합니다. 모든 진행 중인 요청이 이를 공유하므로, 더 긴 프롬프트나 더 많은 사용자는 더 많은 메모리를 필요로 합니다. 메모리가 부족하면 --max-model-len 값을 낮추거나 한 번에 보내는 요청 수를 줄여야 합니다.
A에서 API를 확인합니다 (127.0.0.1은 A 자신입니다):
docker exec "$CONTAINER" curl -fsS --max-time 10 \
"http://127.0.0.1:$PORT/health" >/dev/null && echo HEALTH_OK
HEALTH_OK는 스테이션 A가 응답한다는 것을 증명합니다. 다음으로, 두 스테이션 모두에서 랭크들이 TCP 소켓(TCP sockets)에 의존하기보다는 GPU RDMA를 통해 통신하는지 확인해야 합니다. 이 확인은 로처(launcher)가 NCCL_DEBUG=INFO를 통해 기록한 NCCL 로그를 읽으며, 동일한 블록은 로드된 메모리를 보여줍니다:
docker exec "$CONTAINER" bash -lc '
set -euo pipefail
files=(/tmp/nccl_*.log)
...
RDMA_CHECK_OK는 경고 없이 확인이 통과했음을 의미합니다. nvidia-smi에서는 A가 VLLM::Worker_TP0 프로세스를, B가 VLLM::Worker_TP1 프로세스를 보여주며, GPU당 하나의 랭크(rank)를 나타냅니다. 시작 로그는 GPU당 약 218 GiB의 가중치를 보고하며, 이는 체크포인트(checkpoint)의 대략 절반에 해당하여 모델이 두 GPU에 걸쳐 분산되었음을 확인시켜 줍니다.
| 측정 항목 | 스테이션 A | 스테이션 B |
|---|
| 사용된 GB300 GPU 메모리 (GiB) | 0.01 → 231.35 | 0.00 → 231.34 |
표 1. 요청이 실행되지 않은 상태에서 모델 로드 전후의 GPU 메모리. 값에는 가중치(weights), KV 캐시(KV cache), 런타임 버퍼(runtime buffers)가 포함됩니다.
A에서 OpenAI 호환 API를 통해 채팅 요청을 보냅니다:
docker exec "$CONTAINER" curl -fsS --max-time 120 \
"http://127.0.0.1:$PORT/v1/chat/completions" \
-H 'Content-Type: application/json' \
...
응답은 다음과 같은 형태를 띨 것입니다:
**추세:** 판매량은 2분기에 급증한 후 3분기에 부분적으로 감소했지만, 1분기 수준을 유지했습니다.
**1분기 → 2분기 백분율 변화:** (180 - 120) / 120 × 100 = **50% 증가**
finish_reason: stop
finish_reason: stop은 모델이 토큰 제한에 도달하는 대신 답변을 완료했음을 의미합니다. 요청은 chat_template_kwargs에서 reasoning_effort를 low로 설정합니다. GLM-5.3은 또한 high와 max도 허용하며, 이는 답변 전에 더 오래 추론할 수 있게 하지만 지연 시간(latency)을 증가시키는 대가를 치릅니다. 런처가 도구 호출(tool calling)을 활성화(--enable-auto-tool-choice)했기 때문에, OpenAI tools API를 사용하는 에이전트 프레임워크도 동일한 엔드포인트를 호출할 수 있습니다.