TPU7x アクセラレータでマルチホスト SGLang TorchTPU を使用して GKE で Qwen3-Coder-480B をサービングする

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

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

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

目標

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

費用

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

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

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

始める前に

  • Google Cloud CLI をインストールして初期化し、kubectl をインストールします。Cloud Shell を使用する場合、どちらのツールもプリインストールされています。

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

    1. [Welcome to Hugging Face] ページで、アカウントのアバターをクリックし、[アクセス トークン] を選択します。
    2. [アクセス トークン] ページで、[新しいトークンを作成] をクリックします。
    3. [Read] トークン タイプを選択し、トークンの名前を入力します。
    4. アクセス トークンが表示されます。トークンを安全な場所に保存します。

  • Google Cloud プロジェクトで Kubernetes Engine API、Compute Engine API、Cloud TPU API、Artifact Registry API を有効にします。

    API の有効化

  • TPU 容量が配置されているリージョンに、プロジェクトのディスク割り当てと外部 IP アドレス割り当てが十分にあることを確認します。

    1. Google Cloud コンソールで、[割り当てとシステム上限] ページに移動します。

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

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

    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 クラスタを作成する

Standard モードで GKE クラスタを作成し、プリフィルとデコードの分離用に 2 つのマルチホスト TPU7x ノードプールを接続するには、次の操作を行います。

  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. マルチホスト 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 ノードプール(tpu7x-pool-0 と tpu7x-pool-1)を作成します。各ノードプールには 2 つの tpu7x-standard-4t ノードが含まれます(ノードプールごとに 2x2x2 トポロジの 8 つの TPU7x チップ、合計 16 個の TPU7x チップ)。

    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

    特定の予約を使用する代わりに、次のフラグを渡して、Spot 容量または Flex Start VM を使用してノードプールを作成することもできます。

    • Spot VM: --reservation-affinity=specific と --reservation=${RESERVATION} を --spot に置き換えて、Spot 容量を使用します。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 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-480b-multihost-disagg-v7x.yaml)は、2 つの 2x2x2 TPU7x ノードプール間で分離されたプレフィルとデコードのサービングを調整するために、次のリソースを定義します。

  • StorageClass(hyperdisk-balanced-tpu): Google Cloud Hyperdisk Balanced ストレージを基盤とするエフェメラル 700 GiB ReadWriteOnce ボリュームをプロビジョニングします。4 つのプリフィル Pod とデコード Pod はそれぞれ、ダウンロードした Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 チェックポイントをローカルに保存するために、/data に独自のボリュームをマウントします。
  • プレフィル プール(sglang-prefill-service と sglang-tpu-prefill): 1 つの 2x2x2 TPU7x ノードプール(16 個の TensorCore を提供する 8 個の TPU チップ)に排他的に固定された size: 2 の LeaderWorkerSet(1 つのリーダー Pod と 1 つのワーカー Pod)を実行します。LeaderWorkerSet は、"cloud.google.com/gke-nodepool" に設定された leaderworkerset.sigs.k8s.io/exclusive-topology アノテーションを使用します。Pod は、--disaggregation-mode=prefill、--tensor-parallel-size=16、--ep-size=16 フラグを指定して sglang.launch_server を起動し、HTTP ポート 8000 とブートストラップ ポート 8998 を公開します。
  • デコードプール(sglang-decode-service と sglang-tpu-decode): 2 番目の 2x2x2 TPU7x ノードプール(16 個の TensorCore を提供する 8 個の TPU チップ)に固定された 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 Deployment を実行し、ポート 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-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. ルーター、プリフィル、デコードの 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

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

    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-480B-A35B-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 モデルのベンチマークを行うには、次のコマンドを実行します。

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

次のステップ