Qwen3-30B-A3B in der GKE mit vLLM TorchTPU auf TPU v6e bereitstellen

In dieser Anleitung erfahren Sie, wie Sie das Mixture of Experts-Modell Qwen3-30B-A3B (Qwen/Qwen3-30B-A3B-FP8) in der Google Kubernetes Engine (GKE) mit einem TPU v6e (Trillium)-Beschleuniger mit einem einzelnen Host und dem vLLM TPU-Bereitstellungs-Framework mit TorchTPU (torchtpu) bereitstellen und ausführen. Sie führen die Arbeitslast auf einem einzelnen ct6e-standard-4t-Knoten aus, der vier TPU v6e-Chips in einer 2x2-Topologie enthält, wobei Datenparallelität und Expertenparallelität aktiviert sind.

Informationen zum Ausführen eines ähnlichen vLLM-TorchTPU-Bereitstellungs-Workflows mit einem einzelnen Host auf TPU7x (Ironwood)-Beschleunigern mit dem größeren Qwen3.5-397B-Modell finden Sie unter Qwen3.5-397B in der GKE mit vLLM-TorchTPU auf TPU7x-Beschleunigern bereitstellen.

Ziele

  • Richten Sie Ihren GKE-Cluster und den TPU v6e-Knotenpool mit einem einzelnen Host ein.
  • vLLM-TorchTPU-Server mit einem einzelnen Host in GKE bereitstellen
  • Senden Sie eine Inferenzanfrage an den vLLM-TorchTPU-Server.
  • Führen Sie eine Benchmark-Arbeitslast für die Bereitstellung aus.
  • bereinigen.

Kosten

In diesem Dokument verwenden Sie die folgenden kostenpflichtigen Komponenten vonGoogle Cloud:

Mit dem Preisrechner können Sie eine Kostenschätzung Ihrer voraussichtlichen Nutzung vornehmen.

Nach Abschluss der in diesem Dokument beschriebenen Aufgaben können Sie weitere Kosten vermeiden, indem Sie die erstellten Ressourcen löschen. Weitere Informationen finden Sie unter Bereinigen.

Hinweis

Richten Sie die Umgebung für die Anleitung ein:

Hugging Face-Zugriffstoken abrufen

Für diese Anleitung benötigen Sie ein Hugging Face-Zugriffstoken. Sie können sich bei Hugging Face für ein kostenloses Konto registrieren. Nachdem Sie ein Konto erstellt haben, generieren Sie ein Zugriffstoken:

  1. Klicken Sie auf der Seite Willkommen bei Hugging Face auf Ihren Konto-Avatar und wählen Sie Zugriffstokens aus.
  2. Klicken Sie auf der Seite Zugriffstokens auf Neues Token erstellen.
  3. Wählen Sie den Tokentyp Lesen aus und geben Sie einen Namen für das Token ein.
  4. Ihr Zugriffstoken wird angezeigt. Speichern Sie das Token an einem sicheren Ort.

Erforderliche Rollen

Bitten Sie Ihren Administrator, Ihnen die folgenden IAM-Rollen für Ihr Projekt zuzuweisen, um die Berechtigungen zu erhalten, die Sie zum Ausführen dieser Anleitung benötigen:

Weitere Informationen zum Zuweisen von Rollen finden Sie unter Zugriff auf Projekte, Ordner und Organisationen verwalten.

Sie können die erforderlichen Berechtigungen auch über benutzerdefinierte Rollen oder andere vordefinierte Rollen erhalten.

Umgebungsvariablen einrichten

Richten Sie die Authentifizierung ein:

gcloud auth login
gcloud auth application-default login

Richten Sie die Umgebungsvariablen ein:

export PROJECT_ID="YOUR_PROJECT_ID"
export REGION="YOUR_REGION"
export ZONE="YOUR_ZONE"
export CLUSTER_NAME="YOUR_CLUSTER_NAME"
export MACHINE_TYPE="tpu7x-standard-4t"
export NODE_POOL_NAME="np-0"
export RESERVATION_NAME="YOUR_RESERVATION_NAME"
export HF_TOKEN="YOUR_HF_TOKEN"
export NETWORK="YOUR_NETWORK_NAME"
export SUBNETWORK="YOUR_SUBNETWORK_NAME"
export VLLM_IMAGE="YOUR_VLLM_TPU_IMAGE"

gcloud config set project "${PROJECT_ID}"
gcloud config set billing/quota_project "${PROJECT_ID}"

Ersetzen Sie Folgendes:

  • YOUR_PROJECT_ID: die ID Ihres Projekts in Google Cloud.
  • YOUR_REGION: die Region, in der Sie Ihren Cluster bereitstellen möchten, z. B. us-central1.
  • YOUR_ZONE: die Zone, in der Sie Ihren TPU v6e-Knotenpool bereitstellen möchten, z. B. us-central1-c.
  • YOUR_CLUSTER_NAME: der Name Ihres GKE-Cluster, z. B. tpu-v6e-tutorial-cluster.
  • YOUR_RESERVATION_NAME: Wenn Sie eine Reservierung verwenden, der Name Ihrer TPU v6e-Reservierung.
  • YOUR_HF_TOKEN: Ihr Hugging Face-Zugriffstoken.
  • YOUR_NETWORK_NAME: der Name IhresGoogle Cloud -Netzwerks. Wenn Sie ein benutzerdefiniertes Netzwerk erstellt haben, geben Sie dieses Netzwerk an. Geben Sie andernfalls default an.
  • YOUR_SUBNETWORK_NAME: der Name IhresGoogle Cloud -Subnetzwerks. Wenn Sie ein benutzerdefiniertes Subnetzwerk erstellt haben, geben Sie dieses Subnetzwerk an. Geben Sie andernfalls default an.
  • YOUR_VLLM_TPU_IMAGE: das zu verwendende vLLM-TorchTPU-Image. Informationen zu öffentlichen Images finden Sie im vLLM TorchTPU-Repository.

GKE-Cluster erstellen

Führen Sie die folgenden Schritte aus, um einen GKE-Cluster im Standardmodus zu erstellen und einen TPU v6e-Knotenpool mit einem einzelnen Host anzuhängen:

  1. Erstellen Sie einen regionalen GKE-Cluster:

    gcloud container clusters create "${CLUSTER_NAME}" \
        --project="${PROJECT_ID}" \
        --location="${REGION}" \
        --node-locations="${ZONE}" \
        --network="${NETWORK}" \
        --subnetwork="${SUBNETWORK}"

    Der Standardmaschinentyp für GKE-Cluster (e2-medium) hat nicht genügend CPU-Kapazität für Arbeitslast-Controller. Daher wird in diesem Befehl e2-standard-4 angegeben.

    Die Ausgabe sieht etwa so aus:

    NAME                      LOCATION     MASTER_VERSION      MASTER_IP      MACHINE_TYPE   NODE_VERSION        NUM_NODES  STATUS
    tpu-v6e-tutorial-cluster  us-central1  1.35.7-gke.1027000  34.63.222.246  e2-standard-4  1.35.7-gke.1027000  3          RUNNING
    
  2. Erstellen Sie einen TPU v6e-Knotenpool mit einem einzelnen Host und einem ct6e-standard-4t-Knoten (vier TPU v6e-Chips in einer 2x2-Topologie) mithilfe Ihrer Reservierung:

    gcloud container node-pools create "${NODE_POOL_NAME}" \
        --cluster="${CLUSTER_NAME}" \
        --machine-type="${MACHINE_TYPE}" \
        --location="${REGION}" \
        --node-locations="${ZONE}" \
        --project="${PROJECT_ID}" \
        --num-nodes=1 \
        --reservation-affinity=specific \
        --reservation="${RESERVATION_NAME}"

    Anstatt eine bestimmte Reservierung zu verwenden, können Sie den Knotenpool auch mit Spot-Kapazität oder Flex-Start-VMs erstellen. Übergeben Sie dazu die folgenden Flags:

    • Spot-VMs: Ersetzen Sie die Flags --reservation-affinity und --reservation durch --spot, um Spot-Kapazität zu verwenden. Es kann einige Zeit dauern, bis Instanzen mit Spot-Kapazität nach dem Erstellen des Knotenpools erstellt werden. Außerdem können sie während der Ausführung von Arbeitslasten vorzeitig beendet werden. Wenn die Erstellung Ihrer Pods einige Zeit dauert oder Arbeitslasten während der Ausführung beendet werden, kann dies die Ursache sein.
    • Flex-Start-VMs: Wenn Sie den Knotenpool mit der Verbrauchsoption Flex-Start bereitstellen möchten, übergeben Sie das Flag --flex-start. Weitere Informationen finden Sie unter Kleine Batcharbeitslast mit TPUs und Flex-Start-VMs ausführen.

    Die Ausgabe sieht etwa so aus:

    NAME  MACHINE_TYPE      DISK_SIZE_GB  NODE_VERSION
    np-0  ct6e-standard-4t  100           1.35.7-gke.1027000
    
  3. Konfigurieren Sie kubectl-Anmeldedaten für die Kommunikation mit Ihrem Cluster:

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

Anmeldedaten konfigurieren

Erstellen Sie ein Kubernetes-Secret mit dem Namen hf-secret, um Ihr Hugging Face-Zugriffstoken zu speichern:

kubectl create secret generic hf-secret \
    --from-literal=hf_api_token="${HF_TOKEN}" \
    --dry-run=client -o yaml | kubectl apply -f -

vLLM-TorchTPU-Server bereitstellen

Im Arbeitslastmanifest (qwen3_5_server.yaml) werden die folgenden Ressourcen definiert:

  • Eine StorageClass (hyperdisk-balanced-qwen3) und ein PersistentVolumeClaim (hd-claim-qwen3) mit 500 GiB, die von Google Cloud Hyperdisk Balanced-Speicher unterstützt werden, um die heruntergeladenen Modellgewichtungen, den XLA-Kompilierungs-Cache und den PyTorch-Kompilierungs-Cache zu speichern.

  • Ein Deployment (vllm-qwen3), das auf einem einzelnen TPU v6e-Knoten mit einer 2x2-Topologie (google.com/tpu: '4') geplant ist. Der Container führt vllm serve für Qwen/Qwen3-30B-A3B-FP8 mit den folgenden Flags aus:

    • --data-parallel-size=4
    • --tensor-parallel-size=1
    • --enable-expert-parallel
    • --kv-cache-dtype=fp8
    • --max-model-len=9236
    • --max-num-seqs=64.
  • Einen ClusterIP-Dienst (vllm-qwen3-service), der Port 8000 verfügbar macht.

Führen Sie die folgenden Schritte aus, um die Arbeitslast bereitzustellen und zu überwachen:

  1. Rufen Sie das Servermanifest ab:

    curl -O https://raw.githubusercontent.com/GoogleCloudSamples/AIHypercomputerSamples/refs/heads/main/tpu/inference/qwen3-5-397b/qwen3_5_server.yaml

    Alternativ können Sie eine Datei mit dem Namen qwen3_5_server.yaml und folgendem Inhalt erstellen:

    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: hyperdisk-balanced-qwen3-5
    provisioner: pd.csi.storage.gke.io
    parameters:
      type: hyperdisk-balanced
    reclaimPolicy: Delete
    volumeBindingMode: WaitForFirstConsumer
    allowVolumeExpansion: true
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: hd-claim-qwen3-5
      namespace: default
    spec:
      storageClassName: hyperdisk-balanced-qwen3-5
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 1500Gi
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: vllm-qwen3-5
      namespace: default
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: vllm-qwen3-5
      template:
        metadata:
          labels:
            app: vllm-qwen3-5
        spec:
          nodeSelector:
            cloud.google.com/gke-tpu-accelerator: tpu7x
            cloud.google.com/gke-tpu-topology: 2x2x1
          tolerations:
          - key: "cloud.google.com/gke-spot"
            operator: "Equal"
            value: "true"
            effect: "NoSchedule"
          containers:
          - name: vllm-tpu
            image: $VLLM_IMAGE
            command: ["vllm", "serve"]
            args:
            - --host=0.0.0.0
            - --port=8000
            - --model=Qwen/Qwen3.5-397B-A17B-FP8
            - --tokenizer=Qwen/Qwen3.5-397B-A17B-FP8
            - --max-model-len=9236
            - --max-num-batched-tokens=1024
            - --max-num-seqs=64
            - --no-enable-prefix-caching
            - --gpu-memory-utilization=0.9
            - --data-parallel-size=8
            - --tensor-parallel-size=1
            - --async-scheduling
            - --language-model-only
            - --enable-auto-tool-choice
            - --tool-call-parser=qwen3_coder
            - --reasoning-parser=qwen3
            - '--limit-mm-per-prompt={"image": 0, "video": 0}'
            - --kv-cache-dtype=fp8
            - --enable-expert-parallel
            - --download-dir=/data
            - --block-size=128
            env:
            - name: MODEL_IMPL_TYPE
              value: "vllm"
            - name: DP_SCHED_ENABLED
              value: "1"
            - name: ONEHOT_MOE_PERMUTE_THRESHOLD
              value: "32768"
            - name: VLLM_ENGINE_READY_TIMEOUT_S
              value: "9600"
            - name: VLLM_ENGINE_ITERATION_TIMEOUT_S
              value: "1800"
            - name: USE_MOE_SPARSE_CORE
              value: "1"
            - name: TPU_MOE_SKIP_PADDED_TOKENS
              value: "1"
            - name: VLLM_CACHE_ROOT
              value: "/data/vllm_cache"
            - name: VLLM_TORCH_COMPILE_CACHE_DIR
              value: "/data/torch_compile_cache"
            - name: XLA_CACHE_DIR
              value: "/data/xla_cache"
            - name: HF_HOME
              value: /data
            - name: HF_TOKEN
              valueFrom:
                secretKeyRef:
                  name: hf-secret
                  key: hf_api_token
            ports:
            - containerPort: 8000
            resources:
              limits:
                google.com/tpu: '4'
              requests:
                google.com/tpu: '4'
            readinessProbe:
              tcpSocket:
                port: 8000
              initialDelaySeconds: 15
              periodSeconds: 10
            volumeMounts:
            - mountPath: "/data"
              name: data-volume
            - mountPath: /dev/shm
              name: dshm
          volumes:
          - emptyDir:
              medium: Memory
            name: dshm
          - name: data-volume
            persistentVolumeClaim:
              claimName: hd-claim-qwen3-5
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: vllm-qwen3-5-service
      namespace: default
    spec:
      selector:
        app: vllm-qwen3-5
      type: ClusterIP
      ports:
      - name: http
        protocol: TCP
        port: 8000
        targetPort: 8000
  2. Wenden Sie das vLLM-TorchTPU-Servermanifest auf Ihren Cluster an:

    envsubst '$VLLM_IMAGE' < "qwen3_5_server.yaml" | kubectl apply -f -
  3. Beobachten Sie die Serverbereitstellungslogs, bis der Anwendungsstart abgeschlossen ist:

    kubectl wait --for=jsonpath='{.status.phase}'=Running pod -l app=vllm-qwen3-5 --timeout=900s
    kubectl logs deployment/vllm-qwen3-5 -c vllm-tpu -f

    Der erste Dienststart kann etwa 15 bis 30 Minuten dauern, während der Pod die Modellgewichtungen herunterlädt und das Modell kompiliert.

    Wenn Sie eine vorzeitige Kapazität wie Spot oder Flex-Start verwenden, kann es nach dem Erstellen des Knotenpools länger dauern, bis Ihre TPU-Slice-Knoten erstellt werden. Außerdem können sie während der Ausführung von Arbeitslasten vorzeitig beendet werden. Wenn das Starten Ihrer Pods länger als erwartet dauert oder Ihre Arbeitslasten mittendrin beendet werden, könnte dies die Ursache sein. Weitere Informationen finden Sie unter Preemption von Spot-VMs. Informationen zum Prüfen, ob Ihre TPU-Slice-Knoten erstellt wurden, finden Sie unter Status und Bedingungen des Knotens prüfen.

    Warten Sie, bis in den Logs Application startup complete angezeigt wird, bevor Sie Traffic senden:

    (APIServer pid=1) INFO:     Started server process [1]
    (APIServer pid=1) INFO:     Waiting for application startup.
    (APIServer pid=1) INFO:     Application startup complete.
    
  4. Öffnen Sie eine zweite Terminalsitzung.

  5. Leiten Sie in Ihrer zweiten Terminalsitzung den lokalen Port 8000 an vllm-qwen3-service weiter:

    kubectl port-forward service/vllm-qwen3-5-service 8000:8000

    Lassen Sie die Portweiterleitung in dieser Terminalsitzung laufen.

Inferenzanfragen senden

Kehren Sie zur ersten Terminalsitzung zurück und senden Sie eine Anfrage zur Chat-Vervollständigung an den Endpunkt /v1/chat/completions:

curl http://localhost:8000/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "Qwen/Qwen3.5-397B-A17B-FP8",
        "messages": [
            {"role": "user", "content": "Hello! Tell me a joke."}
        ],
        "max_tokens": 512,
        "temperature": 0.7
    }' | jq .

Benchmark-Arbeitslast ausführen

Führen Sie die folgenden Schritte aus, um das bereitgestellte Qwen3-30B-A3B-Modell mit einer Arbeitslast von 8.192 Token Eingabe und 1.024 Token Ausgabe (8k/1k) mit der InferenceX-Benchmark-Suite zu benchmarken:

  1. Rufen Sie das Benchmark-Manifest ab:

    curl -O https://raw.githubusercontent.com/GoogleCloudSamples/AIHypercomputerSamples/refs/heads/main/tpu/inference/qwen3-5-397b/qwen3_5-benchmark-8k1k.yaml

    Alternativ können Sie eine Datei mit dem Namen qwen3_5-benchmark-8k1k.yaml und folgendem Inhalt erstellen:

    apiVersion: v1
    kind: Pod
    metadata:
      name: qwen3-5-bench-8k1k
      namespace: default
    spec:
      restartPolicy: OnFailure
      terminationGracePeriodSeconds: 60
      containers:
      - name: vllm-bench
        image: $VLLM_IMAGE
        command: ["/bin/bash", "-c"]
        args:
        - |
          while ! curl http://vllm-qwen3-5-service:8000/ping; do sleep 30 && echo 'Waiting for server...'; done
          apt-get update && apt-get install -y git && \
          git clone https://github.com/SemiAnalysisAI/InferenceX.git /ubench/inferencex && \
          cd /ubench/inferencex && \
          git checkout 89ce6098ef2bc4576a735c43f39c7d972b091cfc && \
          python3 /ubench/inferencex/utils/bench_serving/benchmark_serving.py \
            --backend=vllm \
            --percentile-metrics='ttft,tpot,itl,e2el' \
            --host=vllm-qwen3-5-service \
            --port=8000 \
            --model=Qwen/Qwen3.5-397B-A17B-FP8 \
            --tokenizer=Qwen/Qwen3.5-397B-A17B-FP8 \
            --dataset-name=random \
            --random-input-len=8192 \
            --random-output-len=1024 \
            --random-range-ratio=1.0 \
            --num-prompts=2560 \
            --num-warmups=512 \
            --max-concurrency=256 \
            --ignore-eos
        env:
        - name: HUGGING_FACE_HUB_TOKEN
          valueFrom:
            secretKeyRef:
              key: hf_api_token
              name: hf-secret
  2. Wenden Sie das Benchmark-Pod-Manifest an:

    envsubst '$VLLM_IMAGE' < "qwen3_5-benchmark-8k1k.yaml" | kubectl apply -f -
  3. Behalten Sie die Benchmark-Logs im Blick:

    kubectl wait --for=jsonpath='{.status.phase}'=Running pod --field-selector=metadata.name=qwen3-5-bench-8k1k --timeout=600s
    kubectl logs -f qwen3-5-bench-8k1k

    Der Benchmark dauert etwa 25 bis 30 Minuten.

  4. Suchen Sie nach Abschluss des Benchmarks nach einer Ausgabe, die Messwerte wie die folgenden enthält:

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

Bereinigen

Damit Ihrem Google Cloud Konto die in dieser Anleitung verwendeten Ressourcen nicht laufend in Rechnung gestellt werden, löschen Sie die Kubernetes-Arbeitslasten, den TPU-Knotenpool und den GKE-Cluster:

  1. Löschen Sie das Deployment, den Dienst, den PersistentVolumeClaim und das Hugging Face-Secret des vLLM-TorchTPU-Servers:

    kubectl delete -f qwen3_5_server.yaml --ignore-not-found || true
    kubectl delete secret hf-secret --ignore-not-found || true
  2. Löschen Sie den TPU v6e-Knotenpool und den GKE-Cluster:

    gcloud container node-pools delete "${NODE_POOL_NAME}" \
        --cluster="${CLUSTER_NAME}" \
        --location="${REGION}" \
        --project="${PROJECT_ID}" \
        --quiet || true
    
    gcloud container clusters delete "${CLUSTER_NAME}" \
        --location="${REGION}" \
        --project="${PROJECT_ID}" \
        --quiet || true

Nächste Schritte