TPU v6e アクセラレータでマルチホスト SGLang TorchTPU と PD 分離を使用して GKE で Qwen3-Coder-30B-A3B をサービングする

このチュートリアルでは、Google Kubernetes Engine(GKE)に Mixture of Experts コーディング モデルである Qwen3-Coder-30B-A3B(Qwen/Qwen3-Coder-30B-A3B-Instruct-FP8)をデプロイして提供する方法について説明します。このデプロイでは、マルチホスト TPU v6e(Trillium)アクセラレータと、TorchTPU(torchtpu)およびプレフィル デコード(PD)分離を備えた SGLang サービング フレームワークを使用します。

このチュートリアルで説明するデプロイ構成では、2 つのマルチホスト ct6e-standard-4t ノードプールをプロビジョニングします。各ノードプールは 2x4 トポロジで配置されます(ノードプールごとに 2 つのノードに 8 つの TPU v6e チップ、合計 16 個の TPU v6e チップ)。Kubernetes LeaderWorkerSet API を使用して、次のことを行います。

  • テンソル並列処理とエキスパート並列処理を有効にして、分離されたマルチホスト プリフィル サーバーとデコード サーバーを別々の TPU v6e スライスで実行します。
  • SGLang 分離ルーターを介してリクエストをルーティングします。

より大規模な Qwen3-Coder-480B モデルを使用して、TPU7x(Ironwood)アクセラレータで同様のマルチホスト SGLang TorchTPU サービング ワークフローを実行するには、TPU7x アクセラレータでマルチホスト SGLang TorchTPU と PD 分離を使用して GKE で Qwen3-Coder-480B をサービングするをご覧ください。

目標

  • GKE クラスタとマルチホスト TPU v6e ノードプールを設定します。
  • 認証情報を構成し、LeaderWorkerSet コントローラをインストールします。
  • マルチホストの SGLang プレフィル、デコード、ルーターのワークロードをデプロイします。
  • SGLang ルーター サービスに推論リクエストを送信します。
  • サービング ベンチマーク ワークロードを実行します。
  • クリーンアップする。

費用

このドキュメントでは、課金対象である次のGoogle Cloudコンポーネントを使用します。

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。

このドキュメントに記載されているタスクの完了後、作成したリソースを削除すると、それ以上の請求は発生しません。詳細については、クリーンアップをご覧ください。

始める前に

チュートリアルの環境を設定します。

  • 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 コンソールで、[割り当てとシステム上限] ページに移動します。

      [割り当てとシステム上限] に移動

    2. プリフィル Pod とデコード Pod によるモデル キャッシュに使用される 4 つの 100 GiB エフェメラル ボリューム全体で、少なくとも 400 GiB の Google Cloud Hyperdisk Balanced 割り当てが利用可能であることを確認します。

    3. [使用中のリージョン外部 IPv4 アドレス] でフィルタし、ターゲット リージョンに十分な IP アドレス割り当てがあることを確認します。

Hugging Face アクセス トークンを取得する

このチュートリアルを使用するには、Hugging Face アクセス トークンが必要です。Hugging Face で無料アカウントに登録できます。アカウントを作成したら、アクセス トークンを生成します。

  1. [Welcome to Hugging Face] ページで、アカウントのアバターをクリックし、[アクセス トークン] を選択します。
  2. [アクセス トークン] ページで、[Create new token] をクリックします。
  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 クラスタを作成し、プリフィルとデコードの分離用に 2 つのマルチホスト TPU v6e ノードプールを接続するには、次の操作を行います。

  1. デフォルトのノードプールに e2-standard-4 ノードを使用するリージョン GKE クラスタを作成します。これにより、LeaderWorkerSet コントローラと SGLang 分離ルーターに十分な CPU リソースとメモリリソースが提供されます。

    gcloud container clusters create "${CLUSTER_NAME}" \
      --project="${PROJECT_ID}" \
      --location="${REGION}" \
      --machine-type=e2-standard-4
  2. 2 つのマルチホスト TPU v6e ノードプールを作成します。各ノードプールには 2 つの ct6e-standard-4t ノードが含まれます(ノードプールごとに 2x4 トポロジの 8 つの TPU v6e チップ、合計 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 容量または Flex Start 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 容量を使用するインスタンスは、ノードプールの作成後に作成されるまでに時間がかかることがあり、ワークロードの実行中にプリエンプトされる可能性があります。

    Flex Start VM

    Flex Start 消費オプションを使用してノードプールをプロビジョニングします。

    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 と Flex Start VM で小規模なバッチ ワークロードを実行するをご覧ください。

  3. クラスタと通信するように kubectl 認証情報を構成します。

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

認証情報を構成して LeaderWorkerSet をインストールする

マルチホスト SGLang デプロイ用にクラスタを準備する手順は次のとおりです。

  1. Hugging Face アクセス トークンを保存する hf-secret という名前の Kubernetes Secret を作成します。

    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)は、2 つの 2x4 TPU v6e ノードプール間で分離されたプレフィルとデコードのサービングを調整するために、次のリソースを定義します。

  • StorageClass(hyperdisk-balanced-tpu): Google Cloud Hyperdisk Balanced ストレージを基盤とするエフェメラル 100 GiB ReadWriteOnce ボリュームをプロビジョニングします。4 つのプリフィル Pod とデコード Pod はそれぞれ、ダウンロードした Qwen/Qwen3-Coder-30B-A3B-Instruct-FP8 チェックポイントをローカルに保存するために、独自のボリュームを /data にマウントします。

  • プリフィル プール(sglang-prefill-service と sglang-tpu-prefill): 1 つのリーダー Pod と 1 つのワーカー Pod を含む LeaderWorkerSet を実行します。これは、1 つの 2x4 TPU v6e ノードプールに排他的に固定されます。このノードプールには 8 個の TPU チップがあり、8 個の 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): 2 番目の 2x4 TPU v6e ノードプール(8 個の TPU チップ、8 個の TensorCore を提供)に固定された size: 2 の LeaderWorkerSet を実行します。LeaderWorkerSet は、次のフラグを指定して sglang.launch_server を起動します。

    • --disaggregation-mode=decode
    • --tensor-parallel-size=8
    • --ep-size=8
  • 分離ルーター(sglang-router と sglang-service): --pd-disaggregation フラグを指定して sglang_router.launch_router モジュールを実行する CPU デプロイを実行し、ポート 8000 で ClusterIP Service を公開します。ルーターはプロンプトを転送し、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. 起動が完了するまで、プリフィルとデコードのリーダー 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

    最初の実行では、モデルの初期ダウンロード、重みの読み込み、コンパイルに約 7 ~ 10 分かかります。

    Spot 容量や Flex Start VM などのプリエンプティブル容量を使用している場合、ノードプールの作成後に TPU スライスノードが作成されるまでに時間がかかることがあります。また、ワークロードの実行中にプリエンプトされる可能性があります。詳細については、プリエンプティブル VM インスタンスをご覧ください。TPU スライスノードが作成されているかどうかを確認するには、ノードのステータスと条件を確認するをご覧ください。

    プリフィルとデコードのリーダー Pod のログに次のメッセージが出力され、kubectl get pods のすべての Pod の READY 列に 1/1 が表示されるまで待ちます。

    The server is fired up and ready to roll!
    
  5. 2 つ目のターミナル セッションを開き、ローカルポート 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

次のステップ