TPU7x 가속기에서 멀티 호스트 SGLang TorchTPU를 사용하여 GKE에서 Qwen3-Coder-480B 서빙

이 튜토리얼에서는 Google Kubernetes Engine (GKE)에서 전문가 혼합 코딩 모델인 Qwen3-Coder-480B (Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8)를 배포하고 서빙하는 방법을 보여줍니다. 배포에서는 멀티 호스트 TPU7x (Ironwood) 가속기와 TorchTPU (torchtpu) 및 프리필 디코딩 (PD) 분할이 포함된 SGLang 서빙 프레임워크를 사용합니다.

이 튜토리얼에 설명된 배포 구성에서는 각각 2x2x2 토폴로지로 정렬된 멀티 호스트 tpu7x-standard-4t 노드 풀 2개를 프로비저닝합니다 (노드 풀당 노드 2개에 TPU7x 칩 8개, 총 TPU7x 칩 16개). Kubernetes LeaderWorkerSet API를 사용하여 다음을 수행합니다.

  • 텐서 동시 로드 및 전문가 동시 로드를 사용 설정한 상태에서 별도의 TPU7x 슬라이스에서 분할된 멀티 호스트 미리 채우기 및 디코딩 서버를 실행합니다.
  • SGLang 분리 라우터를 통해 OpenAI 호환 요청을 라우팅합니다.

목표

  • GKE 클러스터와 멀티 호스트 TPU7x 노드 풀을 설정합니다.
  • 사용자 인증 정보를 구성하고 LeaderWorkerSet 컨트롤러를 설치합니다.
  • 멀티 호스트 SGLang 프리필, 디코딩, 라우터 워크로드를 배포합니다.
  • SGLang 라우터 서비스에 추론 요청을 보냅니다.
  • 서빙 벤치마크 워크로드를 실행합니다.
  • 삭제

비용

이 문서에서는 비용이 청구될 수 있는Google Cloud구성요소를 사용합니다.

프로젝트 사용량을 기준으로 예상 비용을 산출하려면 [가격 계산기]((https://cloud.google.com/products/calculator)를 사용하세요.

이 문서에 설명된 태스크를 완료했으면 만든 리소스를 삭제하여 청구가 계속되는 것을 방지할 수 있습니다. 자세한 내용은 삭제를 참조하세요.

시작하기 전에

  • Google Cloud CLI를 설치하고 초기화하고 kubectl를 설치합니다. Cloud Shell을 사용하는 경우 두 도구 모두 사전 설치되어 있습니다.

  • 이 튜토리얼을 사용하려면 Hugging Face 액세스 토큰이 필요합니다. Hugging Face에서 무료 계정에 가입할 수 있습니다. 계정이 있으면 액세스 토큰을 생성합니다.

    1. Hugging Face에 오신 것을 환영합니다 페이지에서 계정 아바타를 클릭하고 액세스 토큰을 선택합니다.
    2. 액세스 토큰 페이지에서 새 토큰 만들기를 클릭합니다.
    3. 읽기 토큰 유형을 선택하고 토큰 이름을 입력합니다.
    4. 액세스 토큰이 표시됩니다. 토큰을 안전한 장소에 저장합니다.

  • Google Cloud 프로젝트에서 Kubernetes Engine API, Compute Engine API, Cloud TPU API, Artifact Registry API를 사용 설정합니다.

    API 사용 설정

  • 프로젝트에 TPU 용량이 있는 리전에 디스크 할당량 및 외부 IP 주소 할당량이 충분한지 확인합니다.

    1. Google Cloud 콘솔에서 할당량 및 시스템 한도 페이지로 이동합니다.

      할당량 및 시스템 한도로 이동

    2. 사전 입력 및 디코딩 포드에서 모델 캐싱에 사용하는 4개의 700GiB 임시 볼륨에 사용 가능한 Google Cloud Hyperdisk Balanced 할당량이 2,800GiB 이상인지 확인합니다.

    3. 사용 중인 리전 외부 IPv4 주소로 필터링하고 대상 리전에 충분한 IP 주소 할당량이 있는지 확인합니다.

튜토리얼을 완료하는 데 필요한 권한을 얻으려면 관리자에게 프로젝트에 대한 다음 IAM 역할을 부여해 달라고 요청하세요.

역할 부여에 대한 자세한 내용은 프로젝트, 폴더, 조직에 대한 액세스 관리를 참조하세요.

커스텀 역할이나 다른 사전 정의된 역할을 통해 필요한 권한을 얻을 수도 있습니다.

Compute Engine 기본 서비스 계정에 프로젝트에 대한 다음 Identity and Access Management (IAM) 역할이 있는지 확인합니다.

환경 변수 설정

이 튜토리얼 전체에서 사용하는 환경 변수를 내보냅니다.

export CLUSTER_NAME="YOUR_CLUSTER_NAME"
export PROJECT_ID="YOUR_PROJECT_ID"
export REGION="YOUR_REGION"
export ZONE="YOUR_ZONE"
export RESERVATION="YOUR_RESERVATION_NAME"
export HF_TOKEN="YOUR_HF_TOKEN"
export SGLANG_IMAGE="lmsysorg/sglang:v0.1.0-tpu"

다음을 바꿉니다.

  • YOUR_CLUSTER_NAME: GKE 클러스터의 이름입니다.
  • YOUR_PROJECT_ID: Google Cloud프로젝트의 ID
  • YOUR_REGION: 클러스터를 배포하려는 리전입니다(예: us-central1).
  • YOUR_ZONE: TPU7x 노드 풀을 배포할 영역입니다(예: us-central1-c).
  • YOUR_RESERVATION_NAME: 예약을 사용하는 경우 TPU7x 예약의 이름입니다.
  • YOUR_HF_TOKEN: Hugging Face 액세스 토큰입니다.
  • YOUR_SGLANG_TPU_IMAGE: 커스텀 SGLang TorchTPU 컨테이너 이미지를 사용하는 경우 이미지의 URI입니다. 그렇지 않으면 기본 SGLang TorchTPU 이미지입니다.

GKE 클러스터 만들기

일반 모드에서 GKE 클러스터를 만들고 프리필 및 디코딩 분리를 위해 멀티 호스트 TPU7x 노드 풀 2개를 연결하려면 다음 단계를 완료합니다.

  1. LeaderWorkerSet 컨트롤러 및 SGLang 분리 라우터에 충분한 CPU 및 메모리 리소스를 제공하는 기본 노드 풀에 e2-standard-4 노드를 사용하는 리전 GKE 클러스터를 만듭니다.

    gcloud container clusters create $CLUSTER_NAME \
      --project=$PROJECT_ID \
      --location=$REGION \
      --machine-type=e2-standard-4
  2. 멀티 호스트 TPU7x 노드 풀에 필요한 2x2x2 가속기 토폴로지를 사용하여 높은 처리량의 워크로드 배치 정책을 만듭니다.

    gcloud compute resource-policies create workload-policy tpu7x-2x2x2-workload-policy \
        --type=HIGH_THROUGHPUT \
        --accelerator-topology=2x2x2 \
        --project=${PROJECT_ID} \
        --region=${REGION}
  3. 각각 2개의 tpu7x-standard-4t 노드 (노드 풀당 2x2x2 토폴로지에 8개의 TPU7x 칩, 총 16개의 TPU7x 칩)를 포함하는 2개의 멀티 호스트 TPU7x 노드 풀 (tpu7x-pool-0 및 tpu7x-pool-1)을 만듭니다.

    gcloud container node-pools create tpu7x-pool-0 \
        --cluster=$CLUSTER_NAME \
        --location=$REGION \
        --node-locations=$ZONE \
        --machine-type=tpu7x-standard-4t \
        --num-nodes=2 \
        --placement-policy=tpu7x-2x2x2-workload-policy \
        --reservation-affinity=specific \
        --reservation=$RESERVATION \
        --project=$PROJECT_ID
    
    gcloud container node-pools create tpu7x-pool-1 \
        --cluster=$CLUSTER_NAME \
        --location=$REGION \
        --node-locations=$ZONE \
        --machine-type=tpu7x-standard-4t \
        --num-nodes=2 \
        --placement-policy=tpu7x-2x2x2-workload-policy \
        --reservation-affinity=specific \
        --reservation=$RESERVATION \
        --project=$PROJECT_ID

    특정 예약을 사용하는 대신 다음 플래그를 전달하여 스팟 용량 또는 Flex-start VM을 사용하여 노드 풀을 만들 수도 있습니다.

    • 스팟 VM: 스팟 용량을 사용하려면 --reservation-affinity=specific 및 --reservation=${RESERVATION}를 --spot로 바꿉니다. 스팟 용량을 사용하는 인스턴스는 노드 풀이 생성된 후 생성되는 데 시간이 걸릴 수 있으며 워크로드 실행 중에 선점될 수 있습니다.
    • Flex-start VM: --num-nodes=2, --reservation-affinity=specific, --reservation=${RESERVATION}을 --flex-start, --enable-autoscaling, --num-nodes=0, --min-nodes=0, --max-nodes=2로 바꿔 Flex-start 소비 옵션을 사용하여 노드 풀을 프로비저닝합니다. 자세한 내용은 TPU 및 Flex-start VM으로 소규모 일괄 워크로드 실행을 참조하세요.
  4. 클러스터와 통신하도록 kubectl 사용자 인증 정보를 구성합니다.

    gcloud container clusters get-credentials $CLUSTER_NAME --location=${REGION} --project=${PROJECT_ID}

사용자 인증 정보 구성 및 LeaderWorkerSet 설치

멀티 호스트 SGLang 배포를 위해 클러스터를 준비하려면 다음 단계를 완료하세요.

  1. Hugging Face 액세스 토큰을 저장할 hf-secret이라는 Kubernetes 보안 비밀을 만듭니다.

    kubectl create secret generic hf-secret \
        --from-literal=hf_api_token=${HF_TOKEN}
  2. 클러스터에 LeaderWorkerSet CustomResourceDefinition 및 컨트롤러를 설치합니다.

    kubectl apply --server-side -k github.com/kubernetes-sigs/lws/config/default?ref=main
    echo "Waiting for LWS controller to be ready..."
    kubectl wait --for=condition=Available --timeout=300s deployment/lws-controller-manager -n lws-system

멀티 호스트 SGLang 서버 배포

워크로드 매니페스트 (qwen3-coder-480b-multihost-disagg-v7x.yaml)는 두 개의 2x2x2 TPU7x 노드 풀에서 분리된 프리필 및 디코딩 서빙을 조정하기 위해 다음 리소스를 정의합니다.

  • StorageClass (hyperdisk-balanced-tpu): Google Cloud Hyperdisk Balanced 스토리지에서 지원하는 임시 700GiB ReadWriteOnce 볼륨을 프로비저닝합니다. 4개의 사전 입력 및 디코딩 포드는 각각 /data에서 자체 볼륨을 마운트하여 다운로드한 Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 체크포인트를 로컬에 저장합니다.
  • 프리필 풀 (sglang-prefill-service 및 sglang-tpu-prefill): 2x2x2 TPU7x 노드 풀 (TensorCore 16개를 제공하는 TPU 칩 8개) 하나에만 고정된 size: 2 (리더 포드 1개 및 워커 포드 1개)의 LeaderWorkerSet를 실행합니다. LeaderWorkerSet는 "cloud.google.com/gke-nodepool"로 설정된 leaderworkerset.sigs.k8s.io/exclusive-topology 주석을 사용합니다. 포드는 --disaggregation-mode=prefill, --tensor-parallel-size=16, --ep-size=16 플래그를 사용하여 sglang.launch_server를 실행하고 HTTP 포트 8000 및 부트스트랩 포트 8998를 노출합니다.
  • 디코딩 풀 (sglang-decode-service 및 sglang-tpu-decode): 두 번째 2x2x2 TPU7x 노드 풀(TensorCore 16개를 제공하는 TPU 칩 8개)에 고정된 size: 2의 LeaderWorkerSet를 실행합니다. LeaderWorkerSet는 --disaggregation-mode=decode, --tensor-parallel-size=16, --ep-size=16 플래그를 사용하여 sglang.launch_server를 실행합니다.
  • 분리 라우터 (sglang-router 및 sglang-service): --pd-disaggregation 플래그를 사용하여 sglang_router.launch_router 모듈을 실행하는 CPU 배포를 실행하고 포트 8000에서 ClusterIP 서비스를 노출합니다. 라우터는 프롬프트를 전달하고 sglang-prefill-service 서비스와 sglang-decode-service 서비스 간의 KV 캐시 전송을 조정합니다.

멀티 호스트 SGLang 서버를 배포하고 모니터링하려면 다음 단계를 완료합니다.

  1. 워크로드 매니페스트를 다운로드합니다.

    curl -LO https://raw.githubusercontent.com/GoogleCloudSamples/AIHypercomputerSamples/main/tpu/inference/qwen3-coder/qwen3-coder-480b-multihost-disagg-v7x.yaml

    또는 다음 콘텐츠로 qwen3-coder-480b-multihost-disagg-v7x.yaml이라는 파일을 만듭니다.

    # ==============================================================================
    # Serve Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 on GKE / Ironwood (TPU v7x)
    # using SGLang TorchTPU with Prefill/Decode Disaggregation, multi-host
    # ==============================================================================
    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: hyperdisk-balanced-tpu
    provisioner: pd.csi.storage.gke.io
    volumeBindingMode: WaitForFirstConsumer
    allowVolumeExpansion: true
    parameters:
      type: hyperdisk-balanced
    ---
    # ==============================================================================
    # PREFILL POOL: Internal ClusterIP Service exposing SGLang Prefill Endpoint
    # ==============================================================================
    apiVersion: v1
    kind: Service
    metadata:
      name: sglang-prefill-service
      labels:
        app: sglang-prefill
    spec:
      type: ClusterIP
      selector:
        app: sglang-prefill
        role: leader
      ports:
        - name: http
          protocol: TCP
          port: 8000
          targetPort: 8000
        - name: bootstrap
          protocol: TCP
          port: 8998
          targetPort: 8998
    ---
    # ==============================================================================
    # PREFILL POOL: LeaderWorkerSet (TPU v7x, 1 slice = 2 nodes, 8 chips / 16 TensorCores)
    # ==============================================================================
    apiVersion: leaderworkerset.x-k8s.io/v1
    kind: LeaderWorkerSet
    metadata:
      name: sglang-tpu-prefill
      annotations:
        leaderworkerset.sigs.k8s.io/exclusive-topology: "cloud.google.com/gke-nodepool"
      labels:
        app: sglang-prefill
    spec:
      replicas: 1
      rolloutStrategy:
        type: RollingUpdate
      leaderWorkerTemplate:
        size: 2
        restartPolicy: RecreateGroupOnPodRestart
        leaderTemplate:
          metadata:
            labels:
              app: sglang-prefill
              role: leader
          spec: &prefill-pod-spec
            securityContext:
              fsGroup: 65534
            nodeSelector:
              cloud.google.com/gke-tpu-accelerator: tpu7x
              cloud.google.com/gke-tpu-topology: 2x2x2
            tolerations:
              - key: "google.com/tpu"
                operator: "Exists"
                effect: "NoSchedule"
            containers:
              - name: sglang
                image: $SGLANG_IMAGE
                command:
                  - /bin/bash
                  - -c
                  - |
                    set -ex
                    ulimit -c 0
                    LEADER_POD="${LWS_LEADER_ADDRESS%%.*}"
                    DNS_SUFFIX="${LWS_LEADER_ADDRESS#*.}"
                    HOSTS="${LWS_LEADER_ADDRESS}"
                    for ((i = 1; i < LWS_GROUP_SIZE; i++)); do
                      HOSTS="${HOSTS},${LEADER_POD}-${i}.${DNS_SUFFIX}"
                    done
                    export TPU_WORKER_HOSTNAMES="${HOSTS}"
                    export TPU_WORKER_ID="${LWS_WORKER_INDEX}"
                    export TPU_TOPOLOGY="${TPU_TOPOLOGY:-2x2x2}"
                    echo "=== [prefill rank ${LWS_WORKER_INDEX}/${LWS_GROUP_SIZE}] hosts=${TPU_WORKER_HOSTNAMES} ==="
                    exec python3 -m sglang.launch_server \
                      --device=tpu \
                      --host=0.0.0.0 \
                      --port=8000 \
                      --model-path=Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 \
                      --tensor-parallel-size=16 \
                      --ep-size=16 \
                      --nnodes="${LWS_GROUP_SIZE}" \
                      --node-rank="${LWS_WORKER_INDEX}" \
                      --dist-init-addr="${LWS_LEADER_ADDRESS}:20000" \
                      --attention-backend=pallas_tpu_rpa_batched \
                      --mem-fraction-static=0.8 \
                      --max-running-requests=256 \
                      --chunked-prefill-size=8192 \
                      --max-total-tokens=786432 \
                      --context-length=9216 \
                      --enable-torch-compile \
                      --torch-compile-max-bs=256 \
                      --tpu-compile-tracer=direct \
                      --sampling-defaults=openai \
                      --enable-fused-qk-norm-rope \
                      --disaggregation-mode=prefill \
                      --disaggregation-transfer-backend=tpu \
                      --download-dir=/data \
                      --tpu-compile-tokens=8192 \
                      --disable-radix-cache
                env:
                  - name: HF_TOKEN
                    valueFrom:
                      secretKeyRef:
                        name: hf-secret
                        key: hf_api_token
                        optional: true
                resources:
                  requests:
                    cpu: "16"
                    memory: 128Gi
                    google.com/tpu: "4"
                  limits:
                    memory: 768Gi
                    google.com/tpu: "4"
                ports:
                  - containerPort: 8000
                    name: http
                  - containerPort: 8998
                    name: bootstrap
                startupProbe:
                  httpGet:
                    path: /health
                    port: 8000
                  initialDelaySeconds: 60
                  periodSeconds: 15
                  timeoutSeconds: 10
                  failureThreshold: 236
                readinessProbe:
                  httpGet:
                    path: /health
                    port: 8000
                  periodSeconds: 10
                  timeoutSeconds: 5
                  failureThreshold: 3
                volumeMounts:
                  - name: model-cache
                    mountPath: /data
                  - name: dshm
                    mountPath: /dev/shm
            volumes:
              - name: dshm
                emptyDir:
                  medium: Memory
                  sizeLimit: 32Gi
              - name: model-cache
                ephemeral:
                  volumeClaimTemplate:
                    spec:
                      accessModes: ["ReadWriteOnce"]
                      storageClassName: hyperdisk-balanced-tpu
                      resources:
                        requests:
                          storage: 700Gi
        workerTemplate:
          metadata:
            labels:
              app: sglang-prefill
              role: worker
          spec: *prefill-pod-spec
    ---
    # ==============================================================================
    # DECODE POOL: Internal ClusterIP Service exposing SGLang Decode Endpoint
    # ==============================================================================
    apiVersion: v1
    kind: Service
    metadata:
      name: sglang-decode-service
      labels:
        app: sglang-decode
    spec:
      type: ClusterIP
      selector:
        app: sglang-decode
        role: leader
      ports:
        - name: http
          protocol: TCP
          port: 8000
          targetPort: 8000
        - name: bootstrap
          protocol: TCP
          port: 8998
          targetPort: 8998
    ---
    # ==============================================================================
    # DECODE POOL: LeaderWorkerSet (TPU v7x, 1 slice = 2 nodes, 8 chips / 16 TensorCores)
    # ==============================================================================
    apiVersion: leaderworkerset.x-k8s.io/v1
    kind: LeaderWorkerSet
    metadata:
      name: sglang-tpu-decode
      annotations:
        leaderworkerset.sigs.k8s.io/exclusive-topology: "cloud.google.com/gke-nodepool"
      labels:
        app: sglang-decode
    spec:
      replicas: 1
      rolloutStrategy:
        type: RollingUpdate
      leaderWorkerTemplate:
        size: 2
        restartPolicy: RecreateGroupOnPodRestart
        leaderTemplate:
          metadata:
            labels:
              app: sglang-decode
              role: leader
          spec: &decode-pod-spec
            securityContext:
              fsGroup: 65534
            nodeSelector:
              cloud.google.com/gke-tpu-accelerator: tpu7x
              cloud.google.com/gke-tpu-topology: 2x2x2
            tolerations:
              - key: "google.com/tpu"
                operator: "Exists"
                effect: "NoSchedule"
            containers:
              - name: sglang
                image: $SGLANG_IMAGE
                command:
                  - /bin/bash
                  - -c
                  - |
                    set -ex
                    ulimit -c 0
                    LEADER_POD="${LWS_LEADER_ADDRESS%%.*}"
                    DNS_SUFFIX="${LWS_LEADER_ADDRESS#*.}"
                    HOSTS="${LWS_LEADER_ADDRESS}"
                    for ((i = 1; i < LWS_GROUP_SIZE; i++)); do
                      HOSTS="${HOSTS},${LEADER_POD}-${i}.${DNS_SUFFIX}"
                    done
                    export TPU_WORKER_HOSTNAMES="${HOSTS}"
                    export TPU_WORKER_ID="${LWS_WORKER_INDEX}"
                    export TPU_TOPOLOGY="${TPU_TOPOLOGY:-2x2x2}"
                    echo "=== [decode rank ${LWS_WORKER_INDEX}/${LWS_GROUP_SIZE}] hosts=${TPU_WORKER_HOSTNAMES} ==="
                    exec python3 -m sglang.launch_server \
                      --device=tpu \
                      --host=0.0.0.0 \
                      --port=8000 \
                      --model-path=Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 \
                      --tensor-parallel-size=16 \
                      --ep-size=16 \
                      --nnodes="${LWS_GROUP_SIZE}" \
                      --node-rank="${LWS_WORKER_INDEX}" \
                      --dist-init-addr="${LWS_LEADER_ADDRESS}:20000" \
                      --attention-backend=pallas_tpu_rpa_batched \
                      --mem-fraction-static=0.8 \
                      --max-running-requests=256 \
                      --chunked-prefill-size=8192 \
                      --max-total-tokens=786432 \
                      --context-length=9216 \
                      --enable-torch-compile \
                      --torch-compile-max-bs=256 \
                      --tpu-compile-tracer=direct \
                      --sampling-defaults=openai \
                      --enable-fused-qk-norm-rope \
                      --disaggregation-mode=decode \
                      --disaggregation-transfer-backend=tpu \
                      --download-dir=/data
                env:
                  - name: HF_TOKEN
                    valueFrom:
                      secretKeyRef:
                        name: hf-secret
                        key: hf_api_token
                        optional: true
                resources:
                  requests:
                    cpu: "16"
                    memory: 128Gi
                    google.com/tpu: "4"
                  limits:
                    memory: 768Gi
                    google.com/tpu: "4"
                ports:
                  - containerPort: 8000
                    name: http
                  - containerPort: 8998
                    name: bootstrap
                startupProbe:
                  httpGet:
                    path: /health
                    port: 8000
                  initialDelaySeconds: 60
                  periodSeconds: 15
                  timeoutSeconds: 10
                  failureThreshold: 236
                readinessProbe:
                  httpGet:
                    path: /health
                    port: 8000
                  periodSeconds: 10
                  timeoutSeconds: 5
                  failureThreshold: 3
                volumeMounts:
                  - name: model-cache
                    mountPath: /data
                  - name: dshm
                    mountPath: /dev/shm
            volumes:
              - name: dshm
                emptyDir:
                  medium: Memory
                  sizeLimit: 32Gi
              - name: model-cache
                ephemeral:
                  volumeClaimTemplate:
                    spec:
                      accessModes: ["ReadWriteOnce"]
                      storageClassName: hyperdisk-balanced-tpu
                      resources:
                        requests:
                          storage: 700Gi
        workerTemplate:
          metadata:
            labels:
              app: sglang-decode
              role: worker
          spec: *decode-pod-spec
    ---
    # ==============================================================================
    # SGLANG DISAGGREGATION ROUTER / PROXY DEPLOYMENT
    # ==============================================================================
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: sglang-router
      labels:
        app: sglang-router
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: sglang-router
      template:
        metadata:
          labels:
            app: sglang-router
        spec:
          containers:
            - name: router
              image: $SGLANG_IMAGE
              command:
                - python3
                - -m
                - sglang_router.launch_router
              args:
                - --pd-disaggregation
                - --prefill
                - http://sglang-prefill-service:8000
                - "8998"
                - --decode
                - http://sglang-decode-service:8000
                - --host
                - 0.0.0.0
                - --port
                - "8000"
                - --model-path
                - Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8
                - --request-timeout-secs
                - "3600"
                - --worker-startup-timeout-secs
                - "5400"
              env:
                - name: HF_TOKEN
                  valueFrom:
                    secretKeyRef:
                      name: hf-secret
                      key: hf_api_token
                      optional: true
              ports:
                - containerPort: 8000
                  name: http
              readinessProbe:
                httpGet:
                  path: /health_generate
                  port: 8000
                initialDelaySeconds: 15
                periodSeconds: 30
                timeoutSeconds: 10
                failureThreshold: 3
              resources:
                requests:
                  cpu: "1"
                  memory: 4Gi
                limits:
                  cpu: "4"
                  memory: 12Gi
    ---
    # ==============================================================================
    # SGLANG EXTERNAL SERVICE: Public / Client Endpoint for OpenAI-compatible Serving
    # ==============================================================================
    apiVersion: v1
    kind: Service
    metadata:
      name: sglang-service
      labels:
        app: sglang-router
    spec:
      type: ClusterIP
      selector:
        app: sglang-router
      ports:
        - name: http
          protocol: TCP
          port: 8000
          targetPort: 8000
  2. 워크로드 매니페스트를 클러스터에 적용합니다.

    envsubst '$SGLANG_IMAGE' < qwen3-coder-480b-multihost-disagg-v7x.yaml | kubectl apply -f -
  3. 라우터, 프리필, 디코딩 포드가 생성되었는지 확인합니다.

    kubectl get pods

    모든 포드가 준비되면 출력이 다음과 비슷하게 표시됩니다.

    NAME                             READY   STATUS    RESTARTS   AGE
    sglang-router-6d87cb8c7d-gv4wr   1/1     Running   0          39m
    sglang-tpu-decode-0              1/1     Running   0          40m
    sglang-tpu-decode-0-1            1/1     Running   0          40m
    sglang-tpu-prefill-0             1/1     Running   0          40m
    sglang-tpu-prefill-0-1           1/1     Running   0          40m
    
  4. 시작이 완료될 때까지 프리필 및 디코딩 리더 포드 로그를 모니터링합니다.

    kubectl logs -f -l role=leader --prefix=true &
    LOG_PID=$!
    kubectl wait --for=condition=Ready --timeout=3600s pod -l role=leader
    kill "${LOG_PID}" 2>/dev/null || true

    초기 모델 다운로드, 가중치 로드, 컴파일은 처음 실행할 때 약 15~30분 정도 걸립니다.

    스팟 용량 또는 Flex-start VM과 같은 선점형 용량을 사용하는 경우 노드 풀 생성 후 TPU 슬라이스 노드가 생성되는 데 시간이 더 오래 걸릴 수 있으며 워크로드 실행 중에 선점될 수 있습니다. 자세한 내용은 선점형 VM 인스턴스를 참조하세요. TPU 슬라이스 노드가 생성되었는지 확인하려면 노드 상태 및 조건 확인을 참조하세요.

    프리필 및 디코딩 리더 포드 로그 모두에 다음 메시지가 출력되고 kubectl get pods이 모든 포드의 READY 열에 1/1을 표시할 때까지 기다립니다.

    The server is fired up and ready to roll!
    
  5. 두 번째 터미널 세션을 열고 로컬 포트 8000를 sglang-service에 전달합니다.

    kubectl port-forward service/sglang-service 8000:8000

    이 터미널 세션에서 포트 전달을 실행 중인 상태로 둡니다.

추론 요청 전송

첫 번째 터미널 세션으로 돌아가서 SGLang 라우터에서 노출하는 /v1/chat/completions 엔드포인트에 채팅 완료 요청을 전송합니다.

curl -s -X POST http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8",
    "messages": [
      {"role": "user", "content": "Explain quicksort in two sentences."}
    ],
    "max_tokens": 60,
    "temperature": 0.0
  }'

벤치마크 워크로드 실행

sglang-router 포드에서 1,024 토큰 입력 및 1,024 토큰 출력 (1k/1k) 워크로드를 사용하여 배포된 Qwen3-Coder 모델을 벤치마킹하려면 다음 명령어를 실행합니다.

kubectl exec deployment/sglang-router -- python3 -m sglang.bench_serving \
  --backend sglang \
  --base-url http://localhost:8000 \
  --model Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 \
  --dataset-name random \
  --random-input-len 1024 \
  --random-output-len 1024 \
  --random-range-ratio 1.0 \
  --num-prompts 256 \
  --max-concurrency 64

삭제

이 튜토리얼에서 사용한 리소스에 대한 추가 지속 요금이 Google Cloud 계정에 청구되지 않도록 하려면 다음 리소스를 삭제합니다.

  1. SGLang 프리필, 디코딩, 라우터 워크로드를 삭제합니다.

    kubectl delete -f qwen3-coder-480b-multihost-disagg-v7x.yaml --ignore-not-found
  2. TPU7x 노드 풀, 워크로드 배치 정책, GKE 클러스터를 삭제합니다.

    gcloud container node-pools delete tpu7x-pool-0 \
        --cluster=${CLUSTER_NAME} \
        --location=${REGION} \
        --project=${PROJECT_ID} \
        --quiet
    
    gcloud container node-pools delete tpu7x-pool-1 \
        --cluster=${CLUSTER_NAME} \
        --location=${REGION} \
        --project=${PROJECT_ID} \
        --quiet
    
    gcloud container clusters delete ${CLUSTER_NAME} \
        --location=${REGION} \
        --project=${PROJECT_ID} \
        --quiet
    
    gcloud compute resource-policies delete tpu7x-2x2x2-workload-policy \
        --region=${REGION} \
        --project=${PROJECT_ID} \
        --quiet

다음 단계