Kubernetes에 배포하기
Kubernetes에 배포하기
SGLang을 단일 머신이 아니라 쿠버네티스(K8S) 클러스터에 올려서 다중 노드 분산 추론으로 돌리고 싶을 때가 있어요. 이 페이지는 LeaderWorkerSet(LWS)을 이용해 RoCE 네트워크 기반 SGLang 두 노드 추론 서비스를 쿠버네티스 클러스터에 배포하는 방법, 특히 DeepSeek-R1을 예로 든 과정을 강사 목소리로 안내해 드릴게요. YAML 설정과 명령어, 로그는 원문 그대로 보존했어요.
이 문서는 RoCE 네트워크 기반 SGLang 두 노드 추론 서비스를 쿠버네티스(K8S) 클러스터에 배포하는 방법을 다뤄요.
LeaderWorkerSet (LWS)는 AI/ML 추론 워크로드의 일반적인 배포 패턴을 다루기 위한 쿠버네티스 API예요. 주요 사용 사례 중 하나가 멀티호스트/멀티노드 분산 추론이에요.
SGLang은 또한 분산 모델 서빙을 위해 LWS와 함께 쿠버네티스에 배포될 수 있어요.
LWS를 사용해 쿠버네티스에 SGLang을 배포하는 방법에 대한 더 자세한 내용은 이 가이드를 참고하세요.
여기서는 DeepSeek-R1 배포를 예로 들어 설명할게요.
전제 조건
- 각각 두 개의 H20 시스템과 여덟 개의 GPU가 있는 쿠버네티스 노드가 최소 두 개 필요해요.
- K8S 클러스터에 LWS가 올바르게 설치돼 있는지 확인하세요. 아직 설정되지 않았다면 설치 지침을 따르세요. 참고: LWS 버전이 ≤0.5.x라면
LWS_WORKER_INDEX를 얻기 위해 Downward API를 사용해야 해요. 이 기능의 네이티브 지원은 v0.6.0에서 도입됐거든요.
기본 예시
기본 예시 문서는 Deploy Distributed Inference Service with SGLang and LWS on GPUs를 참고하세요.
다만 그 문서는 기본적인 NCCL 소켓 모드만 다뤄요.
이 섹션에서는 RDMA 시나리오에 맞게 설정을 약간 수정해 볼게요.
RDMA RoCE 사례
- 환경을 확인하세요.
[root@node1 ~]# ibstatus
Infiniband device 'mlx5_bond_0' port 1 status:
default gid: fe80:0000:0000:0000:0225:9dff:fe64:c79a
base lid: 0x0
sm lid: 0x0
state: 4: ACTIVE
phys state: 5: LinkUp
rate: 200 Gb/sec (2X NDR)
link_layer: Ethernet
Infiniband device 'mlx5_bond_1' port 1 status:
default gid: fe80:0000:0000:0000:0225:9dff:fe6e:c3ec
base lid: 0x0
sm lid: 0x0
state: 4: ACTIVE
phys state: 5: LinkUp
rate: 200 Gb/sec (2X NDR)
link_layer: Ethernet
Infiniband device 'mlx5_bond_2' port 1 status:
default gid: fe80:0000:0000:0000:0225:9dff:fe73:0dd7
base lid: 0x0
sm lid: 0x0
state: 4: ACTIVE
phys state: 5: LinkUp
rate: 200 Gb/sec (2X NDR)
link_layer: Ethernet
Infiniband device 'mlx5_bond_3' port 1 status:
default gid: fe80:0000:0000:0000:0225:9dff:fe36:f7ff
base lid: 0x0
sm lid: 0x0
state: 4: ACTIVE
phys state: 5: LinkUp
rate: 200 Gb/sec (2X NDR)
link_layer: Ethernet
- k8s에 배포할
lws.yaml파일을 준비하세요.
apiVersion: leaderworkerset.x-k8s.io/v1
kind: LeaderWorkerSet
metadata:
name: sglang
spec:
replicas: 1
leaderWorkerTemplate:
size: 2
restartPolicy: RecreateGroupOnPodRestart
leaderTemplate:
metadata:
labels:
role: leader
spec:
dnsPolicy: ClusterFirstWithHostNet
hostNetwork: true
hostIPC: true
containers:
- name: sglang-leader
image: sglang:latest
securityContext:
privileged: true
env:
- name: NCCL_IB_GID_INDEX
value: "3"
command:
- python3
- -m
- sglang.launch_server
- --model-path
- /work/models
- --mem-fraction-static
- "0.93"
- --torch-compile-max-bs
- "8"
- --max-running-requests
- "20"
- --tp
- "16" # Size of Tensor Parallelism
- --dist-init-addr
- $(LWS_LEADER_ADDRESS):20000
- --nnodes
- $(LWS_GROUP_SIZE)
- --node-rank
- $(LWS_WORKER_INDEX)
- --trust-remote-code
- --host
- "0.0.0.0"
- --port
- "40000"
resources:
limits:
nvidia.com/gpu: "8"
ports:
- containerPort: 40000
readinessProbe:
tcpSocket:
port: 40000
initialDelaySeconds: 15
periodSeconds: 10
volumeMounts:
- mountPath: /dev/shm
name: dshm
- name: model
mountPath: /work/models
- name: ib
mountPath: /dev/infiniband
volumes:
- name: dshm
emptyDir:
medium: Memory
- name: model
hostPath:
path: '< your models dir >' # modify it according your models dir
- name: ib
hostPath:
path: /dev/infiniband
workerTemplate:
spec:
dnsPolicy: ClusterFirstWithHostNet
hostNetwork: true
hostIPC: true
containers:
- name: sglang-worker
image: sglang:latest
securityContext:
privileged: true
env:
- name: NCCL_IB_GID_INDEX
value: "3"
command:
- python3
- -m
- sglang.launch_server
- --model-path
- /work/models
- --mem-fraction-static
- "0.93"
- --torch-compile-max-bs
- "8"
- --max-running-requests
- "20"
- --tp
- "16" # Size of Tensor Parallelism
- --dist-init-addr
- $(LWS_LEADER_ADDRESS):20000
- --nnodes
- $(LWS_GROUP_SIZE)
- --node-rank
- $(LWS_WORKER_INDEX)
- --trust-remote-code
resources:
limits:
nvidia.com/gpu: "8"
volumeMounts:
- mountPath: /dev/shm
name: dshm
- name: model
mountPath: /work/models
- name: ib
mountPath: /dev/infiniband
volumes:
- name: dshm
emptyDir:
medium: Memory
- name: ib
hostPath:
path: /dev/infiniband
- name: model
hostPath:
path: /data1/models/deepseek_v3_moe
---
apiVersion: v1
kind: Service
metadata:
name: sglang-leader
spec:
selector:
leaderworkerset.sigs.k8s.io/name: sglang
role: leader
ports:
- protocol: TCP
port: 40000
targetPort: 40000
- 그다음
kubectl apply -f lws.yaml을 사용하면 다음과 같은 출력을 얻을 수 있어요.
NAME READY STATUS RESTARTS AGE
sglang-0 0/1 Running 0 9s
sglang-0-1 1/1 Running 0 9s
sglang 리더(sglang-0)의 상태가 1/1로 바뀌어 Ready가 될 때까지 기다리세요.
kubectl logs -f sglang-0 명령으로 리더 노드의 로그를 볼 수 있어요.
성공하면 다음과 같은 출력이 보일 거예요.
[2025-02-17 05:27:24 TP1] Capture cuda graph end. Time elapsed: 84.89 s
[2025-02-17 05:27:24 TP6] max_total_num_tokens=712400, chunked_prefill_size=8192, max_prefill_tokens=16384, max_running_requests=50, context_len=163840
[2025-02-17 05:27:24 TP0] max_total_num_tokens=712400, chunked_prefill_size=8192, max_prefill_tokens=16384, max_running_requests=50, context_len=163840
[2025-02-17 05:27:24 TP7] max_total_num_tokens=712400, chunked_prefill_size=8192, max_prefill_tokens=16384, max_running_requests=50, context_len=163840
[2025-02-17 05:27:24 TP3] max_total_num_tokens=712400, chunked_prefill_size=8192, max_prefill_tokens=16384, max_running_requests=50, context_len=163840
[2025-02-17 05:27:24 TP2] max_total_num_tokens=712400, chunked_prefill_size=8192, max_prefill_tokens=16384, max_running_requests=50, context_len=163840
[2025-02-17 05:27:24 TP4] max_total_num_tokens=712400, chunked_prefill_size=8192, max_prefill_tokens=16384, max_running_requests=50, context_len=163840
[2025-02-17 05:27:24 TP1] max_total_num_tokens=712400, chunked_prefill_size=8192, max_prefill_tokens=16384, max_running_requests=50, context_len=163840
[2025-02-17 05:27:24 TP5] max_total_num_tokens=712400, chunked_prefill_size=8192, max_prefill_tokens=16384, max_running_requests=50, context_len=163840
[2025-02-17 05:27:24] INFO: Started server process [1]
[2025-02-17 05:27:24] INFO: Waiting for application startup.
[2025-02-17 05:27:24] INFO: Application startup complete.
[2025-02-17 05:27:24] INFO: Uvicorn running on http://0.0.0.0:40000 (Press CTRL+C to quit)
[2025-02-17 05:27:25] INFO: 127.0.0.1:48908 - "GET /get_model_info HTTP/1.1" 200 OK
[2025-02-17 05:27:25 TP0] Prefill batch. #new-seq: 1, #new-token: 7, #cached-token: 0, cache hit rate: 0.00%, token usage: 0.00, #running-req: 0, #queue-req: 0
[2025-02-17 05:27:32] INFO: 127.0.0.1:48924 - "POST /generate HTTP/1.1" 200 OK
[2025-02-17 05:27:32] The server is fired up and ready to roll!
시작에 성공하지 못했다면 다음 단계를 따라 남은 문제가 있는지 확인해 주세요. 감사해요.
디버그 (Debug)
- NCCL 통신 문제인지 확인하려면
NCCL_DEBUG=TRACE를 설정하세요.
이것이 대부분의 NCCL 관련 문제를 해결해 줄 거예요.
공지: 컨테이너 환경에서 NCCL_DEBUG=TRACE가 효과가 없다는 걸 발견했지만, 프로세스가 멈췄거나 진단하기 어려운 문제를 겪고 있다면 다른 컨테이너 이미지로 전환해 보세요. 일부 이미지는 표준 오류 출력을 제대로 처리하지 못할 수 있어요.
RoCE 시나리오
- 클러스터 환경에 RDMA 장치가 있는지 확인하세요.
- 클러스터의 노드에 RoCE가 있는 Mellanox NIC가 있는지 확인하세요. 이 예시에서는 Mellanox ConnectX 5 모델 NIC를 사용하고 적절한 OFED 드라이버가 설치되어 있어요. 아니라면 Install OFED Driver 문서를 참조해 드라이버를 설치하세요.
- 환경을 확인하세요.
$ lspci -nn | grep Eth | grep Mellanox
0000:7f:00.0 Ethernet controller [0200]: Mellanox Technologies MT43244 BlueField-3 integrated ConnectX-7 network controller [15b3:a2dc] (rev 01)
0000:7f:00.1 Ethernet controller [0200]: Mellanox Technologies MT43244 BlueField-3 integrated ConnectX-7 network controller [15b3:a2dc] (rev 01)
0000:c7:00.0 Ethernet controller [0200]: Mellanox Technologies MT43244 BlueField-3 integrated ConnectX-7 network controller [15b3:a2dc] (rev 01)
0000:c7:00.1 Ethernet controller [0200]: Mellanox Technologies MT43244 BlueField-3 integrated ConnectX-7 network controller [15b3:a2dc] (rev 01)
0001:08:00.0 Ethernet controller [0200]: Mellanox Technologies MT43244 BlueField-3 integrated ConnectX-7 network controller [15b3:a2dc] (rev 01)
0001:08:00.1 Ethernet controller [0200]: Mellanox Technologies MT43244 BlueField-3 integrated ConnectX-7 network controller [15b3:a2dc] (rev 01)
0001:a2:00.0 Ethernet controller [0200]: Mellanox Technologies MT43244 BlueField-3 integrated ConnectX-7 network controller [15b3:a2dc] (rev 01)
0001:a2:00.1 Ethernet controller [0200]: Mellanox Technologies MT43244 BlueField-3 integrated ConnectX-7 network controller [15b3:a2dc] (rev 01)
- OFED 드라이버를 확인하세요.
ofed_info -s
OFED-internal-23.07-0.5.0:
- RDMA 링크 상태를 표시하고 IB 장치를 확인하세요.
$ rdma link show
8/1: mlx5_bond_0/1: state ACTIVE physical_state LINK_UP netdev reth0
9/1: mlx5_bond_1/1: state ACTIVE physical_state LINK_UP netdev reth2
10/1: mlx5_bond_2/1: state ACTIVE physical_state LINK_UP netdev reth4
11/1: mlx5_bond_3/1: state ACTIVE physical_state LINK_UP netdev reth6
$ ibdev2netdev
8/1: mlx5_bond_0/1: state ACTIVE physical_state LINK_UP netdev reth0
9/1: mlx5_bond_1/1: state ACTIVE physical_state LINK_UP netdev reth2
10/1: mlx5_bond_2/1: state ACTIVE physical_state LINK_UP netdev reth4
11/1: mlx5_bond_3/1: state ACTIVE physical_state LINK_UP netdev reth6
- 호스트에서 RoCE 네트워크 속도를 테스트하세요.
yum install qperf
# for server:
execute qperf
# for client
qperf -t 60 -cm1 <server_ip> rc_rdma_write_bw
- 컨테이너에서 RDMA 접근 가능 여부를 확인하세요.
# ibv_devices
# ibv_devinfo
성공의 핵심 (Keys to success)
- 위 YAML 설정에서 NCCL 환경 변수에 주의하세요. 더 오래된 버전의 NCCL에서는 NCCL_IB_GID_INDEX 환경 설정을 확인해야 해요.
- NCCL_SOCKET_IFNAME도 중요한데, 컨테이너 환경에서는 보통 문제가 되지 않아요.
- 어떤 경우에는 GLOO_SOCKET_IFNAME을 올바르게 설정해야 해요.
- NCCL_DEBUG는 문제 해결에 필수적이지만, 컨테이너 안에서는 때때로 에러 로그를 표시하지 않는다는 걸 발견했어요. 사용 중인 Docker 이미지와 관련이 있을 수 있어요. 필요하면 이미지를 바꿔보세요.
- Ubuntu 18.04 기반 Docker 이미지는 호환성 문제가 있는 경향이 있으니 피하세요.
남은 이슈 (Remaining issues)
- 쿠버네티스, Docker, 또는 Containerd 환경에서는 성능 저하를 막기 위해 hostNetwork를 사용해요.
- 성능 저하를 막기 위해 privileged 모드를 사용하는데, 이건 안전하지 않아요. 게다가 컨테이너 환경에서는 완전한 GPU 격리를 달성할 수 없어요.
TODO
더 알아보기 (Learn more)
- Docker로 개발하기 — 도커 컨테이너에서 SGLang 개발/디버깅
- 벤치마크와 프로파일링 — 배포 후 성능 측정