Diffuser Qwen3-30B-A3B sur GKE avec vLLM TorchTPU sur TPU v6e

Ce tutoriel vous explique comment déployer et diffuser le modèle Mixture of Experts Qwen3-30B-A3B (Qwen/Qwen3-30B-A3B-FP8) sur Google Kubernetes Engine (GKE) à l'aide d'un accélérateur TPU v6e (Trillium) à hôte unique et du framework de diffusion TPU vLLM avec TorchTPU (torchtpu). Vous exécutez la charge de travail sur un seul nœud ct6e-standard-4t, qui contient quatre puces TPU v6e dans une topologie 2x2, avec le parallélisme des données et le parallélisme des experts activés.

Pour exécuter un workflow de diffusion vLLM TorchTPU à hôte unique similaire sur des accélérateurs TPU7x (Ironwood) avec le modèle Qwen3.5-397B plus volumineux, consultez Diffuser Qwen3.5-397B sur GKE avec vLLM TorchTPU sur des accélérateurs TPU7x.

Objectifs

  • Configurez votre cluster GKE et votre pool de nœuds TPU v6e à hôte unique.
  • Déployez le serveur TorchTPU vLLM à hôte unique sur GKE.
  • Envoyez une requête d'inférence au serveur vLLM TorchTPU.
  • Exécutez une charge de travail de benchmark de diffusion.
  • Effectuer un nettoyage.

Coûts

Dans ce document, vous utilisez les composants facturables suivants deGoogle Cloud :

Obtenez une estimation des coûts en fonction de votre utilisation prévue à l'aide du simulateur de coût.

Une fois que vous avez terminé les tâches décrites dans ce document, vous pouvez éviter de continuer à payer des frais en supprimant les ressources que vous avez créées. Pour en savoir plus, consultez la section Effectuer un nettoyage.

Avant de commencer

Configurez votre environnement pour le tutoriel :

Obtenir un jeton d'accès Hugging Face

Vous avez besoin d'un jeton d'accès Hugging Face pour suivre ce tutoriel. Vous pouvez créer un compte sans frais sur Hugging Face. Une fois que vous disposez d'un compte, générez un jeton d'accès :

  1. Sur la page Welcome to Hugging Face (Bienvenue sur Hugging Face), cliquez sur l'avatar de votre compte, puis sélectionnez Access tokens (Jetons d'accès).
  2. Sur la page Access tokens (Jetons d'accès), cliquez sur Create new token (Créer un jeton).
  3. Sélectionnez le type de jeton Read (Lecture) et saisissez un nom pour votre jeton.
  4. Votre jeton d'accès s'affiche. Enregistrez le jeton dans un endroit sûr.

Rôles requis

Pour obtenir les autorisations nécessaires pour suivre ce tutoriel, demandez à votre administrateur de vous accorder les rôles IAM suivants sur votre projet :

Pour en savoir plus sur l'attribution de rôles, consultez la page Gérer l'accès aux projets, aux dossiers et aux organisations.

Vous pouvez également obtenir les autorisations requises avec des rôles personnalisés ou d'autres rôles prédéfinis.

Configurer vos variables d'environnement

Configurez votre authentification :

gcloud auth login
gcloud auth application-default login

Configurez vos variables d'environnement :

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

Remplacez les éléments suivants :

  • YOUR_PROJECT_ID : ID de votre projet Google Cloud.
  • YOUR_REGION : région dans laquelle vous souhaitez déployer votre cluster, par exemple us-central1.
  • YOUR_ZONE : zone dans laquelle vous souhaitez déployer votre pool de nœuds TPU v6e, par exemple us-central1-c.
  • YOUR_CLUSTER_NAME : nom de votre cluster GKE, par exemple tpu-v6e-tutorial-cluster.
  • YOUR_RESERVATION_NAME : si vous utilisez une réservation, nom de votre réservation de TPU v6e.
  • YOUR_HF_TOKEN : votre jeton d'accès Hugging Face.
  • YOUR_NETWORK_NAME : nom de votre réseauGoogle Cloud . Si vous avez créé un réseau personnalisé, spécifiez ce réseau. Sinon, spécifiez default.
  • YOUR_SUBNETWORK_NAME : nom de votre sous-réseauGoogle Cloud . Si vous avez créé un sous-réseau personnalisé, spécifiez-le. Sinon, spécifiez default.
  • YOUR_VLLM_TPU_IMAGE : image vLLM TorchTPU à utiliser. Pour les images publiques, consultez le dépôt vLLM TorchTPU.

Créer le cluster GKE

Pour créer un cluster GKE en mode Standard et associer un pool de nœuds TPU v6e à hôte unique, procédez comme suit :

  1. Créez un cluster GKE régional :

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

    Le type de machine par défaut pour les clusters GKE (e2-medium) ne dispose pas d'une capacité de processeur suffisante pour les contrôleurs de charge de travail. Cette commande spécifie donc e2-standard-4.

    Le résultat ressemble à ce qui suit :

    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. Créez un pool de nœuds TPU v6e à hôte unique avec un nœud ct6e-standard-4t (quatre puces TPU v6e dans une topologie 2x2) à l'aide de votre réservation :

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

    Au lieu d'utiliser une réservation spécifique, vous pouvez également créer le pool de nœuds à l'aide de la capacité Spot ou de VM à démarrage flexible en transmettant les options suivantes :

    • VM Spot : remplacez les options --reservation-affinity et --reservation par --spot pour utiliser la capacité Spot. La création des instances utilisant la capacité Spot peut prendre du temps après la création du pool de nœuds, et elles peuvent être préemptées pendant l'exécution de la charge de travail. Si vos pods mettent du temps à être créés ou si les charges de travail s'arrêtent en cours d'exécution, cela peut en être la cause.
    • VM à démarrage flexible : pour provisionner le pool de nœuds à l'aide de l'option de consommation Démarrage flexible, transmettez l'option --flex-start. Pour en savoir plus, consultez Exécuter une petite charge de travail par lot avec des TPU et des VM à démarrage flexible.

    Le résultat ressemble à ce qui suit :

    NAME  MACHINE_TYPE      DISK_SIZE_GB  NODE_VERSION
    np-0  ct6e-standard-4t  100           1.35.7-gke.1027000
    
  3. Configurez les identifiants kubectl pour communiquer avec votre cluster :

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

Configurer les identifiants

Créez un secret Kubernetes nommé hf-secret pour stocker votre jeton d'accès Hugging Face :

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

Déployer le serveur vLLM TorchTPU

Le fichier manifeste de la charge de travail (qwen3_5_server.yaml) définit les ressources suivantes :

  • Une StorageClass (hyperdisk-balanced-qwen3) et un PersistentVolumeClaim de 500 Gio (hd-claim-qwen3) reposant sur un stockage Google Cloud Hyperdisk Balanced pour stocker les pondérations du modèle téléchargé, le cache de compilation XLA et le cache de compilation PyTorch.

  • Un déploiement (vllm-qwen3) planifié sur un seul nœud TPU v6e avec une topologie 2x2 (google.com/tpu: '4'). Le conteneur exécute vllm serve pour Qwen/Qwen3-30B-A3B-FP8 avec les options suivantes :

    • --data-parallel-size=4
    • --tensor-parallel-size=1
    • --enable-expert-parallel
    • --kv-cache-dtype=fp8
    • --max-model-len=9236
    • --max-num-seqs=64.
  • Un service ClusterIP (vllm-qwen3-service) qui expose le port 8000.

Pour déployer et surveiller la charge de travail, procédez comme suit :

  1. Récupérez le fichier manifeste du serveur :

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

    Vous pouvez également créer un fichier nommé qwen3_5_server.yaml avec le contenu suivant :

    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. Appliquez le fichier manifeste du serveur vLLM TorchTPU à votre cluster :

    envsubst '$VLLM_IMAGE' < "qwen3_5_server.yaml" | kubectl apply -f -
  3. Surveillez les journaux de déploiement du serveur jusqu'à ce que le démarrage de l'application soit terminé :

    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

    Le démarrage initial du service peut prendre environ 15 à 30 minutes, pendant que le pod télécharge les pondérations du modèle et le compile.

    Si vous utilisez une capacité préemptive comme Spot ou démarrage flexible, la création de vos nœuds de tranche TPU peut prendre plus de temps après la création du pool de nœuds, et ils peuvent être préemptés pendant l'exécution des charges de travail. Si vos pods mettent plus de temps que prévu à démarrer ou si vos charges de travail s'arrêtent au milieu, cela peut en être la cause. Pour en savoir plus, consultez Préemption des VM Spot. Pour vérifier si vos nœuds de tranche TPU ont été créés, consultez Vérifier l'état et les conditions du nœud.

    Attendez que les journaux signalent Application startup complete avant d'envoyer du trafic :

    (APIServer pid=1) INFO:     Started server process [1]
    (APIServer pid=1) INFO:     Waiting for application startup.
    (APIServer pid=1) INFO:     Application startup complete.
    
  4. Ouvrez une deuxième session de terminal.

  5. Dans votre deuxième session de terminal, transférez le port local 8000 vers vllm-qwen3-service :

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

    Laissez le transfert de port s'exécuter dans cette session de terminal.

Envoyer des requêtes d'inférence

Revenez à votre première session de terminal et envoyez une requête de complétion de chat au point de terminaison /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 .

Exécuter une charge de travail de benchmark

Pour évaluer le modèle Qwen3-30B-A3B déployé à l'aide d'une charge de travail d'entrée de 8 192 jetons et de sortie de 1 024 jetons (8k/1k) avec la suite d'analyse comparative InferenceX, procédez comme suit :

  1. Récupérez le fichier manifeste du benchmark :

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

    Vous pouvez également créer un fichier nommé qwen3_5-benchmark-8k1k.yaml avec le contenu suivant :

    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. Appliquez le fichier manifeste du pod de benchmark :

    envsubst '$VLLM_IMAGE' < "qwen3_5-benchmark-8k1k.yaml" | kubectl apply -f -
  3. Surveillez les journaux de benchmark :

    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

    L'exécution du benchmark prend environ 25 à 30 minutes.

  4. Une fois le benchmark terminé, recherchez un résultat contenant des métriques telles que les suivantes :

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

Effectuer un nettoyage

Pour éviter que des frais supplémentaires ne soient facturés sur votre compte Google Cloud pour les ressources utilisées dans ce tutoriel, supprimez les charges de travail Kubernetes, le pool de nœuds TPU et le cluster GKE :

  1. Supprimez le déploiement, le service, le PersistentVolumeClaim et le secret Hugging Face du serveur vLLM TorchTPU :

    kubectl delete -f qwen3_5_server.yaml --ignore-not-found || true
    kubectl delete secret hf-secret --ignore-not-found || true
  2. Supprimez le pool de nœuds TPU v6e et le cluster GKE :

    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

Étapes suivantes