在 GKE 上使用多主機 SGLang TorchTPU 和 TPU v6e 加速器上的 PD 解構,提供 Qwen3-Coder-30B-A3B

本教學課程說明如何在 Google Kubernetes Engine (GKE) 部署及提供 Qwen3-Coder-30B-A3B (Qwen/Qwen3-Coder-30B-A3B-Instruct-FP8),這是一種混合專家編碼模型。部署作業會使用多主機 TPU v6e (Trillium) 加速器和 SGLang 服務架構,並搭配 TorchTPU (torchtpu) 和預先填入解碼 (PD) 分解。

在本教學課程說明的部署設定中,您會佈建兩個多主機 ct6e-standard-4t 節點集區,每個集區都以 2x4 拓撲排列 (每個節點集區有兩個節點,共八個 TPU v6e 晶片,總計 16 個 TPU v6e 晶片)。您可以使用 Kubernetes LeaderWorkerSet API 執行下列操作:

  • 在不同的 TPU v6e 配量上,執行分散式多主機預填和解碼伺服器,並啟用張量平行處理和專家平行處理。
  • 透過 SGLang 解構路由器轉送要求。

如要在 TPU7x (Ironwood) 加速器上,使用較大的 Qwen3-Coder-480B 模型執行類似的多主機 SGLang TorchTPU 服務工作流程,請參閱「在 GKE 上使用多主機 SGLang TorchTPU 和 TPU7x 加速器上的 PD 分解功能提供 Qwen3-Coder-480B」。

目標

  • 設定 GKE 叢集和多主機 TPU v6e 節點集區。
  • 設定憑證並安裝 LeaderWorkerSet 控制器。
  • 部署多主機 SGLang 預先填入、解碼和路由器工作負載。
  • 向 SGLang 路由器服務傳送推論要求。
  • 執行基準測試工作負載。
  • 清除所用資源。

費用

在本文件中,您會使用下列Google Cloud計費元件:

您可以使用 Pricing Calculator 根據預測用量估算費用。

完成本文所述工作後,您可以刪除建立的資源,避免繼續計費,詳情請參閱「清除所用資源」一節。

事前準備

為教學課程設定環境:

  • 安裝並初始化 Google Cloud CLI,然後安裝 kubectl。如果使用 Cloud Shell,這兩項工具都已預先安裝。

  • 在 Google Cloud 專案中啟用 Kubernetes Engine API、Compute Engine API、Cloud TPU API 和 Artifact Registry API:

    啟用 API

  • 確認專案在 TPU 容量所在區域有足夠的磁碟配額和外部 IP 位址配額:

    1. 在 Google Cloud 控制台,前往「Quotas & System Limits」(配額與系統限制) 頁面。

      前往「Quotas & System Limits」(配額與系統限制) 頁面

    2. 確認您至少有 400 GiB 的可用 Google Cloud Hyperdisk Balanced 配額,可供預先填入和解碼 Pod 用於模型快取的四個 100 GiB 臨時磁碟區使用。

    3. 依「使用中的區域性外部 IPv4 位址」篩選,確認目標區域有足夠的 IP 位址配額。

取得 Hugging Face 存取權杖

您需要 Hugging Face 存取權杖,才能使用本教學課程。您可以在 Hugging Face 註冊免費帳戶。擁有帳戶後,請產生存取權杖:

  1. 在「Welcome to Hugging Face」頁面,點選帳戶顯示圖片,然後選取「存取權杖」。
  2. 在「存取權杖」頁面,點選「建立新權杖」。
  3. 選取「Read」權杖類型,並輸入權杖名稱。
  4. 系統會顯示存取權杖。將權杖儲存在安全的地方。

必要的角色

如要取得完成本教學課程所需的權限,請要求管理員在專案中授予您下列 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:要部署 TPU v6e 節點集區的可用區,例如 us-central1-b。
  • YOUR_RESERVATION_NAME:如果使用預留項目,請提供 TPU v6e 預留項目的名稱。
  • YOUR_HF_TOKEN:您的 Hugging Face 存取權杖。

如果您使用自訂 SGLang TorchTPU 容器映像檔,請將 SGLANG_IMAGE 變數中的 lmsysorg/sglang:v0.1.0-tpu 替換為映像檔的 URI。

建立 GKE 叢集

如要以 Standard 模式建立 GKE 叢集,並連結兩個多主機 TPU v6e 節點集區,用於預先填入和解碼拆解,請完成下列步驟:

  1. 建立區域性 GKE 叢集,並在預設節點集區使用 e2-standard-4 節點,為 LeaderWorkerSet 控制器和 SGLang 分解路由器提供充足的 CPU 和記憶體資源:

    gcloud container clusters create "${CLUSTER_NAME}" \
      --project="${PROJECT_ID}" \
      --location="${REGION}" \
      --machine-type=e2-standard-4
  2. 建立兩個多主機 TPU v6e 節點集區,每個集區包含兩個 ct6e-standard-4t 節點 (每個節點集區有八個 TPU v6e 晶片,採用 2x4 拓撲,共 16 個 TPU v6e 晶片):

    gcloud container node-pools create tpu-v6e-pool-0 \
        --cluster="${CLUSTER_NAME}" \
        --location="${REGION}" \
        --node-locations="${ZONE}" \
        --machine-type=ct6e-standard-4t \
        --tpu-topology=2x4 \
        --num-nodes=2 \
        --reservation-affinity=specific \
        --reservation="${RESERVATION}" \
        --project="${PROJECT_ID}"
    
    gcloud container node-pools create tpu-v6e-pool-1 \
        --cluster="${CLUSTER_NAME}" \
        --location="${REGION}" \
        --node-locations="${ZONE}" \
        --machine-type=ct6e-standard-4t \
        --tpu-topology=2x4 \
        --num-nodes=2 \
        --reservation-affinity=specific \
        --reservation="${RESERVATION}" \
        --project="${PROJECT_ID}"

    除了使用特定預留項目,您也可以使用 Spot 運算資源或彈性啟動型 VM 建立節點集區:

    Spot VM

    將 --reservation-affinity=specific 和 --reservation="${RESERVATION}" 旗標替換為 --spot 旗標,使用 Spot 容量佈建節點集區:

    gcloud container node-pools create tpu-v6e-pool-0 \
        --cluster="${CLUSTER_NAME}" \
        --location="${REGION}" \
        --node-locations="${ZONE}" \
        --machine-type=ct6e-standard-4t \
        --tpu-topology=2x4 \
        --num-nodes=2 \
        --project="${PROJECT_ID}" \
        --spot
    
    gcloud container node-pools create tpu-v6e-pool-1 \
        --cluster="${CLUSTER_NAME}" \
        --location="${REGION}" \
        --node-locations="${ZONE}" \
        --machine-type=ct6e-standard-4t \
        --tpu-topology=2x4 \
        --num-nodes=2 \
        --project="${PROJECT_ID}" \
        --spot

    節點集區建立後,使用 Spot 容量的執行個體可能需要一段時間才能建立,且在工作負載執行期間可能會遭到先占。

    彈性啟動型 VM

    使用彈性啟動用量方案佈建節點集區:

    gcloud container node-pools create tpu-v6e-pool-0 \
        --cluster="${CLUSTER_NAME}" \
        --location="${REGION}" \
        --node-locations="${ZONE}" \
        --machine-type=ct6e-standard-4t \
        --tpu-topology=2x4 \
        --project="${PROJECT_ID}" \
        --reservation-affinity=none \
        --enable-autoscaling \
        --num-nodes=0 \
        --min-nodes=0 \
        --max-nodes=2 \
        --flex-start
    
    gcloud container node-pools create tpu-v6e-pool-1 \
        --cluster="${CLUSTER_NAME}" \
        --location="${REGION}" \
        --node-locations="${ZONE}" \
        --machine-type=ct6e-standard-4t \
        --tpu-topology=2x4 \
        --project="${PROJECT_ID}" \
        --reservation-affinity=none \
        --enable-autoscaling \
        --num-nodes=0 \
        --min-nodes=0 \
        --max-nodes=2 \
        --flex-start

    詳情請參閱「使用 TPU 和彈性啟動 VM 執行小型批次工作負載」。

  3. 設定 kubectl 憑證,以便與叢集通訊:

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

設定憑證並安裝 LeaderWorkerSet

如要準備叢集,以便部署多主機 SGLang,請完成下列步驟:

  1. 建立名為 hf-secret 的 Kubernetes Secret,用於儲存 Hugging Face 存取權杖:

    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-30b-multihost-disagg-v6e-2x4.yaml) 定義下列資源,用於協調兩個 2x4 TPU v6e 節點集區中,分散式預先填入和解碼服務:

  • StorageClass (hyperdisk-balanced-tpu):佈建由 Google Cloud Hyperdisk Balanced 儲存空間支援的臨時 100 GiB ReadWriteOnce 磁碟區。四個預先填入和解碼 Pod 各自會在 /data 掛接磁碟區,將下載的 Qwen/Qwen3-Coder-30B-A3B-Instruct-FP8 檢查點儲存在本機。

  • 預先填入集區 (sglang-prefill-service 和 sglang-tpu-prefill):執行 LeaderWorkerSet,其中包含一個 leader Pod 和一個 worker Pod,專門固定至一個 2x4 TPU v6e 節點集區。這個節點集區有八個 TPU 晶片,可提供八個 TensorCore。LeaderWorkerSet 會使用設為 "cloud.google.com/gke-nodepool" 的 leaderworkerset.sigs.k8s.io/exclusive-topology 註解。Pod 會使用下列旗標啟動 sglang.launch_server,並公開通訊埠 8000 和啟動程序通訊埠 8998:

    • --disaggregation-mode=prefill
    • --tensor-parallel-size=8
    • --ep-size=8
  • 解碼集區 (sglang-decode-service 和 sglang-tpu-decode):執行 size: 2 的 LeaderWorkerSet,固定至第二個 2x4 TPU v6e 節點集區 (八個 TPU 晶片,提供八個 TensorCore)。LeaderWorkerSet 會使用下列旗標啟動 sglang.launch_server:

    • --disaggregation-mode=decode
    • --tensor-parallel-size=8
    • --ep-size=8
  • 分解路由器 (sglang-router 和 sglang-service):執行 CPU 部署作業,以 --pd-disaggregation 旗標執行 sglang_router.launch_router 模組,並在通訊埠 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-30b-multihost-disagg-v6e-2x4.yaml

    或者,您也可以建立名為 qwen3-coder-30b-multihost-disagg-v6e-2x4.yaml 的檔案,並加入下列內容:

    # ==============================================================================
    # Serve Qwen/Qwen3-Coder-30B-A3B-Instruct-FP8 on GKE / Trillium (TPU v6e)
    # using SGLang TorchTPU with Prefill/Decode Disaggregation, multi-host (2x4)
    # ==============================================================================
    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 v6e, 1 slice = 2 nodes, 8 chips / 8 TensorCores, 2x4)
    # ==============================================================================
    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: tpu-v6e-slice
              cloud.google.com/gke-tpu-topology: 2x4
            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 TORCH_TPU_DEVICES_PER_HOST=4
                    export TPU_TOPOLOGY="2x4x1"
                    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-30B-A3B-Instruct-FP8 \
                      --tensor-parallel-size=8 \
                      --ep-size=8 \
                      --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=524288 \
                      --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: 280Gi
                    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: 100Gi
        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 v6e, 1 slice = 2 nodes, 8 chips / 8 TensorCores, 2x4)
    # ==============================================================================
    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: tpu-v6e-slice
              cloud.google.com/gke-tpu-topology: 2x4
            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 TORCH_TPU_DEVICES_PER_HOST=4
                    export TPU_TOPOLOGY="2x4x1"
                    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-30B-A3B-Instruct-FP8 \
                      --tensor-parallel-size=8 \
                      --ep-size=8 \
                      --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=524288 \
                      --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: 280Gi
                    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: 100Gi
        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-30B-A3B-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-30b-multihost-disagg-v6e-2x4.yaml | kubectl apply -f -
  3. 確認路由器、預填和解碼 Pod 已建立:

    kubectl get pods

    所有 Pod 都準備就緒後,輸出內容會類似如下:

    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. 監控預先填入和解碼 Leader Pod 記錄,直到啟動完成:

    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

    首次執行時,初始模型下載、權重載入和編譯作業約需七到十分鐘。

    如果您使用 Spot 容量或彈性啟動型 VM 等先占容量,建立節點集區後,可能需要較長時間才能建立 TPU 磁碟區節點,而且這些節點可能會在工作負載執行期間遭到先占。詳情請參閱「先占 VM 執行個體」。如要確認 TPU 切片節點是否已建立,請參閱「查看節點的狀態和條件」。

    等待預先填入和解碼 Leader Pod 記錄輸出下列訊息,且 kubectl get pods 在所有 Pod 的 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-30B-A3B-Instruct-FP8",
    "messages": [
      {"role": "user", "content": "Explain quicksort in two sentences."}
    ],
    "max_tokens": 60,
    "temperature": 0.0
  }'

執行基準工作負載

如要使用 sglang-router Pod 的 1,024 個詞元輸入和 1,024 個詞元輸出 (1k/1k) 工作負載,對已部署的 Qwen3-Coder-30B-A3B 模型進行基準測試,請執行下列指令:

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

基準測試完成後,請尋找包含下列指標的輸出內容:

  • Output token throughput
  • Total token throughput
  • TTFT
  • TPOT

清除所用資源

為避免系統持續向您的 Google Cloud 帳戶收取本教學課程所用資源的額外費用,請刪除下列資源:

  1. 刪除 SGLang 預先填入、解碼和路由器工作負載:

    kubectl delete -f qwen3-coder-30b-multihost-disagg-v6e-2x4.yaml --ignore-not-found
  2. 刪除 TPU v6e 節點集區和 GKE 叢集:

    gcloud container node-pools delete tpu-v6e-pool-0 \
        --cluster="${CLUSTER_NAME}" \
        --location="${REGION}" \
        --project="${PROJECT_ID}" \
        --quiet
    
    gcloud container node-pools delete tpu-v6e-pool-1 \
        --cluster="${CLUSTER_NAME}" \
        --location="${REGION}" \
        --project="${PROJECT_ID}" \
        --quiet
    
    gcloud container clusters delete "${CLUSTER_NAME}" \
        --location="${REGION}" \
        --project="${PROJECT_ID}" \
        --quiet

後續步驟