Kubernetes에 배포하기

Kubernetes에 배포하기

SGLang을 단일 머신이 아니라 쿠버네티스(K8S) 클러스터에 올려서 다중 노드 분산 추론으로 돌리고 싶을 때가 있어요. 이 페이지는 LeaderWorkerSet(LWS)을 이용해 RoCE 네트워크 기반 SGLang 두 노드 추론 서비스를 쿠버네티스 클러스터에 배포하는 방법, 특히 DeepSeek-R1을 예로 든 과정을 강사 목소리로 안내해 드릴게요. YAML 설정과 명령어, 로그는 원문 그대로 보존했어요.

출처: 공식문서 - Deploy On Kubernetes

이 문서는 RoCE 네트워크 기반 SGLang 두 노드 추론 서비스를 쿠버네티스(K8S) 클러스터에 배포하는 방법을 다뤄요.

LeaderWorkerSet (LWS)는 AI/ML 추론 워크로드의 일반적인 배포 패턴을 다루기 위한 쿠버네티스 API예요. 주요 사용 사례 중 하나가 멀티호스트/멀티노드 분산 추론이에요.

SGLang은 또한 분산 모델 서빙을 위해 LWS와 함께 쿠버네티스에 배포될 수 있어요.

LWS를 사용해 쿠버네티스에 SGLang을 배포하는 방법에 대한 더 자세한 내용은 이 가이드를 참고하세요.

여기서는 DeepSeek-R1 배포를 예로 들어 설명할게요.

전제 조건

  1. 각각 두 개의 H20 시스템과 여덟 개의 GPU가 있는 쿠버네티스 노드가 최소 두 개 필요해요.
  2. 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)