Gestisci Qwen3-Coder-30B-A3B su GKE con SGLang TorchTPU multi-host e disaggregazione DP su acceleratori TPU v6e

Questo tutorial mostra come eseguire il deployment e l'erogazione di Qwen3-Coder-30B-A3B (Qwen/Qwen3-Coder-30B-A3B-Instruct-FP8), un modello di programmazione Mixture of Experts, su Google Kubernetes Engine (GKE). Il deployment utilizza acceleratori TPU v6e (Trillium) multi-host e il framework di distribuzione SGLang con TorchTPU (torchtpu) e disaggregazione prefill-decode (DP).

Nella configurazione del deployment descritta in questo tutorial, esegui il provisioning di due node pool ct6e-standard-4t multi-host, ciascuno disposto in una topologia 2x4 (otto chip TPU v6e su due nodi per pool di nodi, per un totale di 16 chip TPU v6e). Utilizza l'API LeaderWorkerSet di Kubernetes per:

  • Esegui server di pre-riempimento e decodifica multi-host disaggregati su sezioni TPU v6e separate con parallelismo dei tensori e parallelismo degli esperti abilitati.
  • Instrada le richieste tramite un router di disaggregazione SGLang.

Per eseguire un workflow di gestione SGLang TorchTPU multi-host simile su acceleratori TPU7x (Ironwood) con il modello Qwen3-Coder-480B più grande, consulta Gestisci Qwen3-Coder-480B su GKE con SGLang TorchTPU multi-host e disaggregazione DP su acceleratori TPU7x.

Obiettivi

  • Configura il cluster GKE e i node pool TPU v6e multi-host.
  • Configura le credenziali e installa il controller LeaderWorkerSet.
  • Esegui il deployment dei workload di prefill, decodifica e router SGLang multi-host.
  • Invia una richiesta di inferenza al servizio router SGLang.
  • Esegui un workload di benchmark di gestione.
  • Eseguire la pulizia.

Costi

In questo documento vengono utilizzati i seguenti componenti fatturabili diGoogle Cloud:

Per generare una stima dei costi in base all'utilizzo previsto, utilizza il calcolatore prezzi.

Al termine delle attività descritte in questo documento, puoi evitare l'addebito di ulteriori costi eliminando le risorse che hai creato. Per saperne di più, consulta Esegui la pulizia.

Prima di iniziare

Configura l'ambiente per il tutorial:

  • Installa e inizializza Google Cloud CLI e installa kubectl. Se utilizzi Cloud Shell, entrambi gli strumenti sono preinstallati.

  • Abilita l'API Kubernetes Engine, l'API Compute Engine, l'API Cloud TPU e l'API Artifact Registry nel tuo progetto Google Cloud :

    Abilita le API

  • Assicurati che il tuo progetto disponga di una quota disco e di una quota di indirizzi IP esterni sufficienti nella regione in cui si trova la capacità TPU:

    1. Nella console Google Cloud , vai alla pagina Quote e limiti di sistema.

      Vai a Quote e Limiti di sistema

    2. Verifica di avere almeno 400 GiB di quota Google Cloud Hyperdisk Balanced disponibile nei quattro volumi temporanei da 100 GiB utilizzati per la memorizzazione nella cache del modello dai pod di pre-riempimento e decodifica.

    3. Filtra in base a Indirizzi IPv4 esterni regionali in uso e verifica di avere una quota di indirizzi IP sufficiente nella regione di destinazione.

Ottieni un token di accesso a Hugging Face

Per utilizzare questo tutorial, devi disporre di un token di accesso a Hugging Face. Puoi registrarti per un account senza costi su Hugging Face. Dopo aver creato un account, genera un token di accesso:

  1. Nella pagina Welcome to Hugging Face, fai clic sull'avatar del tuo account e seleziona Token di accesso.
  2. Nella pagina Token di accesso, fai clic su Crea nuovo token.
  3. Seleziona il tipo di token Read (Lettura) e inserisci un nome per il token.
  4. Viene visualizzato il token di accesso. Salva il token in un luogo sicuro.

Ruoli obbligatori

Per ottenere le autorizzazioni necessarie per completare questo tutorial, chiedi all'amministratore di concederti i seguenti ruoli IAM nel progetto:

Per saperne di più sulla concessione dei ruoli, consulta Gestisci l'accesso a progetti, cartelle e organizzazioni.

Potresti anche riuscire a ottenere le autorizzazioni richieste tramite i ruoli personalizzati o altri ruoli predefiniti.

Assicurati che il tuo service account predefinito di Compute Engine disponga dei seguenti ruoli IAM (Identity and Access Management) nel tuo progetto:

Imposta le variabili di ambiente

Esporta le variabili di ambiente che utilizzi in questo tutorial:

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"

Sostituisci quanto segue:

  • YOUR_CLUSTER_NAME: il nome del tuo cluster GKE.
  • YOUR_PROJECT_ID: l'ID del tuo progetto Google Cloud.
  • YOUR_REGION: la regione in cui vuoi eseguire il deployment del cluster, ad esempio us-central1.
  • YOUR_ZONE: la zona in cui vuoi eseguire il deployment dei pool di nodi TPU v6e, ad esempio us-central1-b.
  • YOUR_RESERVATION_NAME: se utilizzi una prenotazione, il nome della prenotazione TPU v6e.
  • YOUR_HF_TOKEN: il tuo token di accesso a Hugging Face.

Se utilizzi un'immagine container TorchTPU SGLang personalizzata, sostituisci lmsysorg/sglang:v0.1.0-tpu nella variabile SGLANG_IMAGE con l'URI della tua immagine.

Crea il cluster GKE

Per creare un cluster GKE in modalità Standard e collegare due pool di nodi TPU v6e multi-host per la disaggregazione di precompilazione e decodifica, completa i seguenti passaggi:

  1. Crea un cluster GKE a livello di regione che utilizza nodi e2-standard-4 per il pool di nodi predefinito, che fornisce risorse di CPU e memoria sufficienti per il controller LeaderWorkerSet e il router di disaggregazione SGLang:

    gcloud container clusters create "${CLUSTER_NAME}" \
      --project="${PROJECT_ID}" \
      --location="${REGION}" \
      --machine-type=e2-standard-4
  2. Crea due pool di nodi TPU v6e multi-host, ciascuno contenente due nodi ct6e-standard-4t (otto chip TPU v6e in una topologia 2x4 per pool di nodi, per un totale di 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}"

    Anziché utilizzare una prenotazione specifica, puoi anche creare i node pool utilizzando la capacità spot o le VM con avvio flessibile:

    VM spot

    Esegui il provisioning dei node pool utilizzando la capacità Spot sostituendo i flag --reservation-affinity=specific e --reservation="${RESERVATION}" con il 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

    La creazione delle istanze che utilizzano la capacità spot potrebbe richiedere del tempo dopo la creazione del pool di nodi e potrebbero essere prerilasciate durante l'esecuzione dei workload.

    Flex-start VM

    Esegui il provisioning dei node pool utilizzando l'opzione di consumo Avvio flessibile:

    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

    Per saperne di più, consulta Esegui un workload batch di piccole dimensioni con TPU e VM con avvio flessibile.

  3. Configura le credenziali kubectl per comunicare con il tuo cluster:

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

Configura le credenziali e installa LeaderWorkerSet

Per preparare il cluster per il deployment SGLang multi-host, completa i seguenti passaggi:

  1. Crea un secret di Kubernetes denominato hf-secret per archiviare il tuo token di accesso Hugging Face:

    kubectl create secret generic hf-secret \
        --from-literal=hf_api_token="${HF_TOKEN}"
  2. Installa CustomResourceDefinition e il controller LeaderWorkerSet sul tuo cluster:

    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

Esegui il deployment del server SGLang multi-host

Il manifest del workload (qwen3-coder-30b-multihost-disagg-v6e-2x4.yaml) definisce le seguenti risorse per coordinare la precompilazione e la decodifica disaggregate per la distribuzione tra i due pool di nodi TPU v6e 2x4:

  • StorageClass (hyperdisk-balanced-tpu): esegue il provisioning di volumi ReadWriteOnce temporanei da 100 GiB supportati dallo spazio di archiviazione Google Cloud Hyperdisk Balanced. Ognuno dei quattro pod di precompilazione e decodifica monta il proprio volume in /data per archiviare localmente i checkpoint Qwen/Qwen3-Coder-30B-A3B-Instruct-FP8 scaricati.

  • Pool di precompilazione (sglang-prefill-service e sglang-tpu-prefill): esegue un LeaderWorkerSet, che contiene un pod leader e un pod worker, assegnati esclusivamente a un pool di nodi TPU v6e 2x4. Questo pool di nodi ha otto chip TPU, che forniscono otto TensorCore. LeaderWorkerSet utilizza l'annotazione leaderworkerset.sigs.k8s.io/exclusive-topology impostata su "cloud.google.com/gke-nodepool". I pod avviano sglang.launch_server con i seguenti flag, esponendo la porta 8000 e la porta di bootstrap 8998:

    • --disaggregation-mode=prefill
    • --tensor-parallel-size=8
    • --ep-size=8
  • Pool di decodifica (sglang-decode-service e sglang-tpu-decode): esegue un LeaderWorkerSet di size: 2 assegnato al secondo pool di nodi TPU v6e 2x4 (otto chip TPU, che forniscono otto TensorCore). LeaderWorkerSet avvia sglang.launch_server con i seguenti flag:

    • --disaggregation-mode=decode
    • --tensor-parallel-size=8
    • --ep-size=8
  • Router di disaggregazione (sglang-router e sglang-service): esegue un deployment della CPU che esegue il modulo sglang_router.launch_router con il flag --pd-disaggregation ed espone un servizio ClusterIP sulla porta 8000. Il router indirizza i prompt e coordina il trasferimento della cache KV tra i servizi sglang-prefill-service e sglang-decode-service.

Per eseguire il deployment e monitorare i server SGLang multi-host, completa i seguenti passaggi:

  1. Scarica il manifest del workload:

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

    In alternativa, crea un file denominato qwen3-coder-30b-multihost-disagg-v6e-2x4.yaml con i seguenti contenuti:

    # ==============================================================================
    # 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. Applica il manifest del workload al cluster:

    envsubst '$SGLANG_IMAGE' < qwen3-coder-30b-multihost-disagg-v6e-2x4.yaml | kubectl apply -f -
  3. Verifica che i pod di router, precompilazione e decodifica siano stati creati:

    kubectl get pods

    Quando tutti i pod sono pronti, l'output è simile al seguente:

    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. Monitora i log dei pod leader di pre-compilazione e decodifica fino al completamento dell'avvio:

    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

    Il download iniziale del modello, il caricamento dei pesi e la compilazione richiedono circa 7-10 minuti alla prima esecuzione.

    Se utilizzi una capacità preemptible, ad esempio una capacità spot o VM con avvio flessibile, la creazione dei nodi di sezioni TPU potrebbe richiedere più tempo dopo la creazione del pool di nodi e questi nodi potrebbero essere prerilasciati durante l'esecuzione dei workload. Per saperne di più, consulta Istanze VM preemptible. Per verificare se i nodi della sezione TPU sono stati creati, consulta Controlla lo stato e le condizioni del nodo.

    Attendi che i log dei pod leader di precompilazione e decodifica restituiscano il seguente messaggio e che kubectl get pods mostri 1/1 nella colonna READY per tutti i pod:

    The server is fired up and ready to roll!
    
  5. Apri una seconda sessione del terminale e inoltra la porta locale 8000 a sglang-service:

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

    Lascia il port forwarding in esecuzione in questa sessione del terminale.

Invia richieste di inferenza

Torna alla prima sessione del terminale e invia una richiesta di completamento della chat all'endpoint /v1/chat/completions esposto dal 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
  }'

Esegui un workload di benchmark

Per eseguire il benchmark del modello Qwen3-Coder-30B-A3B di cui è stato eseguito il deployment utilizzando un workload di input da 1024 token e di output da 1024 token (1k/1k) dal pod sglang-router, esegui il seguente comando:

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

Al termine del benchmark, cerca un output contenente metriche come le seguenti:

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

Esegui la pulizia

Per evitare che al tuo account Google Cloud vengano addebitati costi aggiuntivi relativi alle risorse utilizzate in questo tutorial, elimina le seguenti risorse:

  1. Elimina i workload di pre-compilazione, decodifica e router SGLang:

    kubectl delete -f qwen3-coder-30b-multihost-disagg-v6e-2x4.yaml --ignore-not-found
  2. Elimina i pool di nodi TPU v6e e il 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

Passaggi successivi