Menyajikan Qwen3-Coder-30B-A3B di GKE dengan SGLang TorchTPU multi-host dan disagregasi PD di akselerator TPU v6e

Tutorial ini menunjukkan cara men-deploy dan menyajikan Qwen3-Coder-30B-A3B (Qwen/Qwen3-Coder-30B-A3B-Instruct-FP8), model coding mixture of experts, di Google Kubernetes Engine (GKE). Deployment ini menggunakan akselerator TPU v6e (Trillium) multi-host dan framework penyajian SGLang dengan TorchTPU (torchtpu) dan disagregasi prefill-decode (PD).

Dalam konfigurasi deployment yang dijelaskan dalam tutorial ini, Anda menyediakan dua node pool ct6e-standard-4t multi-host, masing-masing disusun dalam topologi 2x4 (delapan chip TPU v6e di dua node per node pool, dengan total 16 chip TPU v6e). Anda menggunakan Kubernetes LeaderWorkerSet API untuk melakukan hal berikut:

  • Jalankan server pra-pengisian dan dekode multi-host yang dipisahkan pada slice TPU v6e terpisah dengan paralelisme tensor dan paralelisme pakar diaktifkan.
  • Merutekan permintaan melalui router disagregasi SGLang.

Untuk menjalankan alur kerja penyajian TorchTPU SGLang multi-host serupa di akselerator TPU7x (Ironwood) dengan model Qwen3-Coder-480B yang lebih besar, lihat Menyajikan Qwen3-Coder-480B di GKE dengan TorchTPU SGLang multi-host dan disagregasi PD di akselerator TPU7x.

Tujuan

  • Siapkan cluster GKE dan node pool TPU v6e multi-host.
  • Konfigurasikan kredensial dan instal pengontrol LeaderWorkerSet.
  • Men-deploy workload prefill, dekode, dan router SGLang multi-host.
  • Kirim permintaan inferensi ke layanan router SGLang.
  • Menjalankan workload benchmark inferensi.
  • Jalankan pembersihan.

Biaya

Dalam dokumen ini, Anda akan menggunakan komponenGoogle Cloudyang dapat ditagih berikut:

Untuk membuat perkiraan biaya berdasarkan proyeksi penggunaan Anda, gunakan kalkulator harga.

Setelah menyelesaikan tugas yang dijelaskan dalam dokumen ini, Anda dapat menghindari penagihan berkelanjutan dengan menghapus resource yang Anda buat. Untuk mengetahui informasi selengkapnya, lihat Pembersihan.

Sebelum memulai

Siapkan lingkungan Anda untuk tutorial:

  • Instal dan lakukan inisialisasi Google Cloud CLI, dan instal kubectl. Jika Anda menggunakan Cloud Shell, kedua alat tersebut sudah diinstal sebelumnya.

  • Aktifkan Kubernetes Engine API, Compute Engine API, Cloud TPU API, dan Artifact Registry API di project Google Cloud Anda:

    Aktifkan API

  • Pastikan project Anda memiliki kuota disk dan kuota alamat IP eksternal yang memadai di region tempat kapasitas TPU Anda berada:

    1. Di konsol Google Cloud , buka halaman Quotas & System Limits.

      Buka Quotas & System Limits

    2. Pastikan Anda memiliki setidaknya 400 GiB kuota Google Cloud Hyperdisk Balanced yang tersedia di empat volume efemeral 100 GiB yang digunakan untuk caching model oleh Pod prefill dan decode.

    3. Filter menurut In-use regional external IPv4 addresses dan pastikan Anda memiliki kuota alamat IP yang cukup di region target.

Mendapatkan token akses Hugging Face

Anda memerlukan token akses Hugging Face untuk menggunakan tutorial ini. Anda dapat mendaftar untuk mendapatkan akun gratis di Hugging Face. Setelah Anda memiliki akun, buat token akses:

  1. Di halaman Welcome to Hugging Face, klik avatar akun Anda, lalu pilih Token akses.
  2. Di halaman Token akses, klik Create new token.
  3. Pilih jenis token Read dan masukkan nama untuk token Anda.
  4. Token akses Anda akan ditampilkan. Simpan token di tempat yang aman.

Peran yang diperlukan

Untuk mendapatkan izin yang Anda perlukan untuk menyelesaikan tutorial ini, minta administrator untuk memberi Anda peran IAM berikut di project Anda:

Untuk mengetahui informasi selengkapnya tentang pemberian peran, lihat Mengelola akses ke project, folder, dan organisasi.

Anda mungkin juga bisa mendapatkan izin yang diperlukan melalui peran khusus atau peran bawaan lainnya.

Pastikan akun layanan default Compute Engine Anda memiliki peran Identity and Access Management (IAM) berikut di project Anda:

Menyiapkan variabel lingkungan Anda

Ekspor variabel lingkungan yang Anda gunakan di sepanjang tutorial ini:

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"

Ganti kode berikut:

  • YOUR_CLUSTER_NAME: nama cluster GKE Anda.
  • YOUR_PROJECT_ID: ID project Google Cloud Anda.
  • YOUR_REGION: region tempat Anda ingin men-deploy cluster, seperti us-central1.
  • YOUR_ZONE: zona tempat Anda ingin men-deploy node pool TPU v6e, seperti us-central1-b.
  • YOUR_RESERVATION_NAME: jika Anda menggunakan pemesanan, nama pemesanan TPU v6e Anda.
  • YOUR_HF_TOKEN: token akses Hugging Face Anda.

Jika Anda menggunakan image container TorchTPU SGLang kustom, ganti lmsysorg/sglang:v0.1.0-tpu dalam variabel SGLANG_IMAGE dengan URI image Anda.

Membuat cluster GKE

Untuk membuat cluster GKE dalam mode Standard dan memasang dua node pool TPU v6e multi-host untuk disaggregasi prefill dan decode, selesaikan langkah-langkah berikut:

  1. Buat cluster GKE regional yang menggunakan node e2-standard-4 untuk node pool default, yang menyediakan resource CPU dan memori yang memadai untuk pengontrol LeaderWorkerSet dan router disagregasi SGLang:

    gcloud container clusters create "${CLUSTER_NAME}" \
      --project="${PROJECT_ID}" \
      --location="${REGION}" \
      --machine-type=e2-standard-4
  2. Buat dua node pool TPU v6e multi-host, masing-masing berisi dua node ct6e-standard-4t (delapan chip TPU v6e dalam topologi 2x4 per node pool, dengan total 16 chip 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}"

    Selain menggunakan pemesanan tertentu, Anda juga dapat membuat node pool menggunakan kapasitas Spot atau VM Flex-start:

    Spot VM

    Sediakan node pool menggunakan kapasitas Spot dengan mengganti flag --reservation-affinity=specific dan --reservation="${RESERVATION}" dengan flag --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

    Instance yang menggunakan kapasitas Spot mungkin memerlukan waktu untuk dibuat setelah node pool dibuat, dan mungkin di-preempt selama workload berjalan.

    Flex-start VM

    Sediakan node pool menggunakan opsi konsumsi 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

    Untuk mengetahui informasi selengkapnya, lihat Menjalankan workload batch kecil dengan TPU dan Flex-start VM.

  3. Konfigurasi kredensial kubectl untuk berkomunikasi dengan cluster Anda:

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

Mengonfigurasi kredensial dan menginstal LeaderWorkerSet

Untuk menyiapkan cluster Anda untuk deployment SGLang multi-host, selesaikan langkah-langkah berikut:

  1. Buat Secret Kubernetes bernama hf-secret untuk menyimpan token akses Hugging Face Anda:

    kubectl create secret generic hf-secret \
        --from-literal=hf_api_token="${HF_TOKEN}"
  2. Instal CustomResourceDefinition dan pengontrol LeaderWorkerSet di cluster Anda:

    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

Men-deploy server SGLang multi-host

Manifes workload (qwen3-coder-30b-multihost-disagg-v6e-2x4.yaml) menentukan resource berikut untuk mengoordinasikan prefill dan inferensi decode yang dipisahkan di dua node pool TPU v6e 2x4:

  • StorageClass (hyperdisk-balanced-tpu): menyediakan volume ReadWriteOnce 100 GiB efemeral yang didukung oleh penyimpanan Google Cloud Hyperdisk Balanced. Setiap dari empat Pod prefill dan dekode memasang volumenya sendiri di /data untuk menyimpan checkpoint Qwen/Qwen3-Coder-30B-A3B-Instruct-FP8 yang didownload secara lokal.

  • Prefill pool (sglang-prefill-service dan sglang-tpu-prefill): menjalankan LeaderWorkerSet, yang berisi satu Pod leader dan satu Pod worker, yang disematkan secara eksklusif ke satu node pool TPU v6e 2x4. Node pool ini memiliki delapan chip TPU, yang menyediakan delapan TensorCore. LeaderWorkerSet menggunakan anotasi leaderworkerset.sigs.k8s.io/exclusive-topology yang ditetapkan ke "cloud.google.com/gke-nodepool". Pod meluncurkan sglang.launch_server dengan flag berikut, yang mengekspos port 8000 dan port bootstrap 8998:

    • --disaggregation-mode=prefill
    • --tensor-parallel-size=8
    • --ep-size=8
  • Decode pool (sglang-decode-service dan sglang-tpu-decode): menjalankan LeaderWorkerSet size: 2 yang disematkan ke node pool TPU v6e 2x4 kedua (delapan chip TPU, yang menyediakan delapan TensorCore). LeaderWorkerSet meluncurkan sglang.launch_server dengan flag berikut:

    • --disaggregation-mode=decode
    • --tensor-parallel-size=8
    • --ep-size=8
  • Router disagregasi (sglang-router dan sglang-service): menjalankan deployment CPU yang mengeksekusi modul sglang_router.launch_router dengan flag --pd-disaggregation dan mengekspos Layanan ClusterIP di port 8000. Router mengarahkan perintah dan mengoordinasikan transfer cache KV antara layanan sglang-prefill-service dan sglang-decode-service.

Untuk men-deploy dan memantau server SGLang multi-host, selesaikan langkah-langkah berikut:

  1. Download manifes workload:

    curl -LO https://raw.githubusercontent.com/GoogleCloudSamples/AIHypercomputerSamples/main/tpu/inference/qwen3-coder/qwen3-coder-30b-multihost-disagg-v6e-2x4.yaml

    Atau, buat file bernama qwen3-coder-30b-multihost-disagg-v6e-2x4.yaml dengan konten berikut:

    # ==============================================================================
    # 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. Terapkan manifes workload ke cluster Anda:

    envsubst '$SGLANG_IMAGE' < qwen3-coder-30b-multihost-disagg-v6e-2x4.yaml | kubectl apply -f -
  3. Pastikan Pod router, prefill, dan decode telah dibuat:

    kubectl get pods

    Jika semua Pod sudah siap, output-nya akan mirip dengan berikut ini:

    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. Pantau log Pod leader prefill dan decode hingga startup selesai:

    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

    Download model awal, pemuatan bobot, dan kompilasi memerlukan waktu sekitar tujuh hingga sepuluh menit pada operasi pertama.

    Jika Anda menggunakan kapasitas preemptible seperti kapasitas Spot atau VM Flex-start, mungkin perlu waktu lebih lama setelah pembuatan node pool untuk membuat node slice TPU, dan node tersebut mungkin di-preempt selama eksekusi workload. Untuk mengetahui informasi selengkapnya, lihat Instance preemptible VM. Untuk memeriksa apakah node slice TPU Anda telah dibuat, lihat Memeriksa status dan kondisi node.

    Tunggu hingga log Pod leader prefill dan decode menampilkan pesan berikut dan kubectl get pods menampilkan 1/1 di kolom READY untuk semua Pod:

    The server is fired up and ready to roll!
    
  5. Buka sesi terminal kedua dan teruskan port lokal 8000 ke sglang-service:

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

    Biarkan penerusan port berjalan di sesi terminal ini.

Mengirim permintaan inferensi

Kembali ke sesi terminal pertama Anda dan kirim permintaan penyelesaian chat ke endpoint /v1/chat/completions yang diekspos oleh router SGLang:








  
  














  
  
  
  



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
  }'

Menjalankan workload benchmark

Untuk menjalankan benchmark model Qwen3-Coder-30B-A3B yang di-deploy menggunakan workload input 1.024 token dan output 1.024 token (1k/1k) dari Pod sglang-router, jalankan perintah berikut:

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

Setelah benchmark selesai, cari output yang berisi metrik seperti berikut:

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

Pembersihan

Agar tidak menimbulkan biaya berkelanjutan tambahan pada akun Google Cloud Anda untuk resource yang digunakan dalam tutorial ini, hapus resource berikut:

  1. Hapus workload prefill, dekode, dan router SGLang:

    kubectl delete -f qwen3-coder-30b-multihost-disagg-v6e-2x4.yaml --ignore-not-found
  2. Hapus node pool TPU v6e dan cluster 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

Langkah berikutnya