Mettre en service Qwen3.8-2.4T sur GKE avec désagrégation de disques persistants multi-hôtes à l'aide de Dynamo et SGLang sur des VM A4

Ce tutoriel vous explique comment déployer et diffuser Qwen3.8-2.4T (nvidia/Qwen3.8-2.4T-A95B-NVFP4), un modèle Mixture of Experts de 2,4 billions de paramètres avec 95 milliards de paramètres actifs quantifiés en NVFP4, sur Google Kubernetes Engine (GKE) surGoogle Cloud. Le déploiement utilise Cluster Toolkit, quatre instances de machines virtuelles (VM) A4 (a4-highgpu-8g) multi-hôtes avec un total de 32 GPU NVIDIA B200, NVIDIA Dynamo et le framework de diffusion SGLang avec désagrégation préremplissage-décodage (PD).

Dans la configuration de déploiement décrite dans ce tutoriel, vous allez utiliser Cluster Toolkit pour provisionner un cluster GKE en mode Standard. Le cluster contient quatre nœuds a4-highgpu-8g connectés via un réseau GPUDirect accès direct à la mémoire à distance (RDMA). Vous installez ensuite le planificateur KAI et la plate-forme NVIDIA Dynamo avec la planification de groupe Grove, puis vous préparez les pondérations du modèle dans Cloud Storage. Ensuite, vous déployez une ressource personnalisée DynamoGraphDeployment qui installe le bucket avec Cloud Storage FUSE et sépare le préremplissage et le décodage sur deux pools de nœuds de calcul multi-hôtes de 16 GPU avec parallélisme de tenseur, parallélisme d'expert et un cache KV FP8.

Objectifs

  • Installez Cluster Toolkit et provisionnez un cluster GKE A4 multi-hôte en mode Standard.
  • Installez le planificateur KAI et la plate-forme NVIDIA Dynamo avec la planification de groupe Grove sur votre cluster.
  • Préparez les pondérations du modèle Qwen3.8-2.4T dans un bucket Cloud Storage.
  • Déployez un serveur d'inférence SGLang multi-hôte et désagrégé par disque persistant sur quatre nœuds A4, avec un total de 32 GPU NVIDIA B200.
  • Envoyez une requête de complétion de chat au routeur d'interface Dynamo.
  • Exécutez une charge de travail de benchmark de diffusion.
  • Nettoyez les ressources déployées.

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.

Avant de commencer

Configurez votre environnement pour le tutoriel :

  1. Installez et initialisez la Google Cloud CLI, puis installez kubectl et Helm (version 3.10 ou ultérieure). Si vous utilisez Cloud Shell, ces outils sont préinstallés.
  2. Si vous suivez ce tutoriel depuis un poste de travail Linux ou macOS plutôt que depuis Cloud Shell, installez les dépendances Cluster Toolkit requises, telles que Terraform et Git.
  3. Activez l'API Kubernetes Engine, l'API Compute Engine, l'API Cloud Storage, l'API Identity and Access Management, l'API Cloud Resource Manager et l'API Service Usage dans votre projet Google Cloud  :

    Activer les API

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

Exportez les variables d'environnement que vous utiliserez tout au long de ce tutoriel :

export PROJECT_ID="YOUR_PROJECT_ID"
export REGION="YOUR_REGION"
export ZONE="YOUR_ZONE"
export CLUSTER_NAME="YOUR_CLUSTER_NAME"
export RESERVATION_NAME="YOUR_RESERVATION_NAME"
export GCS_BUCKET="YOUR_BUCKET_NAME"
export HF_TOKEN="YOUR_HF_TOKEN"
export CLUSTER_TOOLKIT_VERSION="v1.104.0"
export DYNAMO_RELEASE_VERSION="1.5.0"

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 où se trouve votre capacité A4 et où vous souhaitez déployer votre cluster, par exemple us-central1.
  • YOUR_ZONE : zone où se trouve votre capacité A4, par exemple us-central1-b.
  • YOUR_CLUSTER_NAME : nom de votre cluster GKE et de votre déploiement Cluster Toolkit. Utilisez un nom de 20 caractères ou moins pour que l'ID de compte de service du pool de nœuds généré (YOUR_CLUSTER_NAME-gke-np-sa) ne dépasse pas la limite de 30 caractères d'Identity and Access Management (IAM).
  • YOUR_RESERVATION_NAME : nom de votre réservation A4 ou chemin d'accès au bloc de réservation étendu au format RESERVATION_NAME/reservationBlocks/RESERVATION_BLOCK_NAME.
  • YOUR_BUCKET_NAME : nom unique du bucket Cloud Storage qui stocke l'état Terraform pour Cluster Toolkit et les pondérations du modèle Qwen3.8-2.4T préparé.
  • YOUR_HF_TOKEN : votre jeton d'accès Hugging Face.

Créer le cluster GKE

Téléchargez Cluster Toolkit et provisionnez un cluster GKE en mode Standard avec quatre nœuds a4-highgpu-8g, une mise en réseau GPUDirect RDMA et la compatibilité DRA :

  1. Connectez-vous à la Google Cloud CLI et générez des identifiants par défaut de l'application pour Terraform :

    gcloud auth login
    gcloud auth application-default login
  2. Téléchargez et extrayez le bundle Cluster Toolkit prédéfini pour la version spécifiée dans CLUSTER_TOOLKIT_VERSION (v1.104.0) :

    TAG="${CLUSTER_TOOLKIT_VERSION:-v1.104.0}"
    OS="linux"
    ARCH="amd64"
    mkdir -p cluster-toolkit && curl -L "https://github.com/GoogleCloudPlatform/cluster-toolkit/releases/download/${TAG}/gcluster_bundle_${OS}_${ARCH}.tgz" | tar -xz -C cluster-toolkit

    Si vous exécutez cette commande sur macOS ou sur un poste de travail arm64, définissez OS="mac" ou ARCH="arm64" pour qu'il corresponde à votre plate-forme.

  3. Pour stocker l'état Terraform de votre déploiement Cluster Toolkit et les pondérations du modèle préparé, créez un bucket Cloud Storage dans la même région que votre réservation A4, avec l'accès uniforme au niveau du bucket et la gestion des versions d'objets activés :

    gcloud storage buckets create "gs://${GCS_BUCKET}" \
        --default-storage-class=STANDARD \
        --project="${PROJECT_ID}" \
        --location="${REGION}" \
        --uniform-bucket-level-access
    gcloud storage buckets update "gs://${GCS_BUCKET}" --versioning
  4. Créez le fichier de configuration de déploiement cluster-toolkit/examples/gke-a4/gke-a4-deployment.yaml pour quatre nœuds a4-highgpu-8g :

    cat <<EOF > cluster-toolkit/examples/gke-a4/gke-a4-deployment.yaml
    terraform_backend_defaults:
      type: gcs
      configuration:
        bucket: ${GCS_BUCKET}
    
    vars:
      project_id: ${PROJECT_ID}
      deployment_name: ${CLUSTER_NAME}
      region: ${REGION}
      zone: ${ZONE}
      static_node_count: 4
      authorized_cidr: 0.0.0.0/0
      reservation_affinity:
        consume_reservation_type: SPECIFIC_RESERVATION
        specific_reservations:
        - name: ${RESERVATION_NAME}
    EOF

    Par défaut, le fichier de déploiement est configuré pour utiliser une réservation spécifique. Si vous n'avez pas de réservation spécifique, vous pouvez mettre à jour cluster-toolkit/examples/gke-a4/gke-a4-deployment.yaml avant de déployer le cluster pour utiliser une autre option de consommation, telle que les VM Spot ou les VM Flex-start. Pour obtenir des instructions de configuration, consultez Options de consommation GKE.

  5. Accédez au répertoire cluster-toolkit, déployez le cluster GKE à l'aide de la commande gcluster deploy, puis revenez à votre répertoire de travail :

    cd cluster-toolkit
    ./gcluster deploy \
        -d examples/gke-a4/gke-a4-deployment.yaml \
        examples/gke-a4/gke-a4.yaml \
        --auto-approve -w
    cd ..

    La création du cluster prend environ 20 à 30 minutes. Si vous exécutez ./gcluster deploy sans l'option --auto-approve, saisissez a lorsque vous y êtes invité pour appliquer les modifications proposées.

  6. Configurez les identifiants kubectl pour communiquer avec votre cluster :

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

Installer le planificateur KAI et NVIDIA Dynamo

Préparez votre cluster en créant un secret Hugging Face et en installant le planificateur KAI et la plate-forme NVIDIA Dynamo avec la planification de groupe Grove :

  1. 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 -
  2. Téléchargez le chart Helm de la plate-forme NVIDIA Dynamo (v1.5.0), installez le planificateur KAI, puis installez la plate-forme Dynamo avec Grove activé :

    RELEASE_VERSION="${DYNAMO_RELEASE_VERSION:-1.5.0}"
    helm pull "https://helm.ngc.nvidia.com/nvidia/ai-dynamo/charts/dynamo-platform-${RELEASE_VERSION}.tgz"
    tar -zxf "dynamo-platform-${RELEASE_VERSION}.tgz"
    
    helm upgrade --install kai-scheduler ./dynamo-platform/charts/kai-scheduler \
        --set scheduler.args.default-staleness-grace-period="-1s" \
        --set topologyMigration.enabled=false
    
    kubectl wait --for=condition=Available deployment/kai-operator --timeout=120s
    until kubectl get deployment/queue-controller >/dev/null 2>&1; do sleep 2; done
    kubectl wait --for=condition=Available deployment/queue-controller --timeout=120s
    
    helm upgrade --install dynamo-platform ./dynamo-platform \
        --set global.grove.install=true \
        --set global.grove.enabled=true \
        --set global.kai-scheduler.enabled=true

Stocker les pondérations du modèle dans Cloud Storage

Préparez le point de contrôle nvidia/Qwen3.8-2.4T-A95B-NVFP4 dans votre bucket Cloud Storage régional (gs://${GCS_BUCKET}) afin que les pods de nœuds de calcul d'inférence puissent installer les pondérations avec Cloud Storage FUSE au lieu de les télécharger depuis Hugging Face à chaque démarrage à froid :

  1. Attribuez le rôle Utilisateur d'objets Storage (roles/storage.objectUser) sur le bucket gs://${GCS_BUCKET} que vous avez créé dans Créer le cluster GKE au compte de service Google lié au compte de service Kubernetes workload-identity-k8s-sa :

    GSA=$(kubectl get sa workload-identity-k8s-sa -o jsonpath='{.metadata.annotations.iam\.gke\.io/gcp-service-account}')
    gcloud storage buckets add-iam-policy-binding "gs://${GCS_BUCKET}" \
        --member="serviceAccount:${GSA}" \
        --role="roles/storage.objectUser"
  2. Téléchargez les fichiers manifestes qwen3.8-2.4t-nvfp4-stage-weights-job.yaml et qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml :

    curl -O https://raw.githubusercontent.com/GoogleCloudSamples/AIHypercomputerSamples/refs/heads/main/gpu/inference/qwen38-2t/qwen3.8-2.4t-nvfp4-stage-weights-job.yaml
    curl -O https://raw.githubusercontent.com/GoogleCloudSamples/AIHypercomputerSamples/refs/heads/main/gpu/inference/qwen38-2t/qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml

    Vous pouvez également créer un fichier nommé qwen3.8-2.4t-nvfp4-stage-weights-job.yaml avec le contenu suivant. Ce job indexé exécute quatre pods parallèles sur vos quatre nœuds A4. Chaque pod télécharge une tranche de taille équilibrée du point de contrôle nvidia/Qwen3.8-2.4T-A95B-NVFP4 sur le disque SSD NVMe local du nœud et l'importe dans gs://${GCS_BUCKET}/hf_models/models--nvidia--qwen3_8-2_4t-a95b-nvfp4 à l'aide de gcloud storage rsync :

    # ==============================================================================
    # Stage nvidia/Qwen3.8-2.4T-A95B-NVFP4 (~1.44 TB, 210 files) from Hugging Face Hub
    # into an in-region Cloud Storage bucket for qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml
    #
    # Each Indexed Job pod downloads a size-balanced slice of the repo onto the node's
    # local NVMe SSD, then uploads that slice with `gcloud storage rsync`.
    # ==============================================================================
    apiVersion: batch/v1
    kind: Job
    metadata:
      name: qwen3-8-2-4t-stage-weights
    spec:
      completionMode: Indexed
      completions: 4
      parallelism: 4
      backoffLimitPerIndex: 2
      template:
        metadata:
          labels:
            app: qwen3-8-2-4t-stage-weights
        spec:
          restartPolicy: Never
          serviceAccountName: workload-identity-k8s-sa
          nodeSelector:
            cloud.google.com/gke-accelerator: nvidia-b200
          tolerations:
            - key: "nvidia.com/gpu"
              operator: "Exists"
              effect: "NoSchedule"
          topologySpreadConstraints:
            - maxSkew: 1
              topologyKey: kubernetes.io/hostname
              whenUnsatisfiable: DoNotSchedule
              labelSelector:
                matchLabels:
                  app: qwen3-8-2-4t-stage-weights
          initContainers:
            - name: download
              image: nvcr.io/nvidia/ai-dynamo/sglang-runtime:1.5.0
              command: ["python3", "-c"]
              args:
                - |
                  import os, time
                  from huggingface_hub import HfApi, snapshot_download
                  repo = os.environ["REPO_ID"]
                  n = int(os.environ["NUM_SHARDS"])
                  i = int(os.environ["JOB_COMPLETION_INDEX"])
                  info = HfApi().model_info(repo, files_metadata=True)
                  files = sorted(((s.size or 0, s.rfilename) for s in info.siblings), reverse=True)
                  bins = [[0, []] for _ in range(n)]
                  for size, name in files:
                      b = min(bins, key=lambda x: x[0])
                      b[0] += size
                      b[1].append(name)
                  size, mine = bins[i]
                  print(f"shard {i}/{n}: {len(mine)} files, {size / 1e9:.1f} GB", flush=True)
                  for attempt in range(1, 7):
                      try:
                          snapshot_download(repo, local_dir="/ssd/model", allow_patterns=mine, max_workers=16)
                          break
                      except Exception as e:
                          print(f"shard {i}/{n}: attempt {attempt} failed: {e!r}", flush=True)
                          if attempt == 6:
                              raise
                          time.sleep(30)
                  print(f"shard {i}/{n}: download complete", flush=True)
              env:
                - name: REPO_ID
                  value: "nvidia/Qwen3.8-2.4T-A95B-NVFP4"
                - name: NUM_SHARDS
                  value: "4"
                - name: HF_HOME
                  value: "/ssd/hf"
                - name: HF_HUB_DISABLE_XET
                  value: "1"
                - name: HF_TOKEN
                  valueFrom:
                    secretKeyRef:
                      name: hf-secret
                      key: hf_api_token
                      optional: true
              resources:
                requests:
                  cpu: "16"
                  memory: "64Gi"
                  ephemeral-storage: "450Gi"
                limits:
                  cpu: "16"
                  memory: "64Gi"
                  ephemeral-storage: "700Gi"
              volumeMounts:
                - name: ssd
                  mountPath: /ssd
          containers:
            - name: upload
              image: gcr.io/google.com/cloudsdktool/google-cloud-cli:slim
              command: ["/bin/bash", "-c"]
              args:
                - |
                  set -euo pipefail
                  gcloud storage rsync --recursive --exclude='^\.cache/.*' /ssd/model "${DEST}"
                  echo "shard ${JOB_COMPLETION_INDEX}: upload complete"
              env:
                - name: DEST
                  value: "gs://${GCS_BUCKET}/hf_models/models--nvidia--qwen3_8-2_4t-a95b-nvfp4"
              resources:
                requests:
                  cpu: "16"
                  memory: "32Gi"
                limits:
                  cpu: "16"
                  memory: "32Gi"
              volumeMounts:
                - name: ssd
                  mountPath: /ssd
          volumes:
            - name: ssd
              emptyDir: {}
  3. Remplacez qwen3.8-2.4t-nvfp4-stage-weights-job.yaml par le nom de votre bucket Cloud Storage, démarrez le job de préproduction, attendez que les quatre pods aient terminé d'importer les pondérations, puis supprimez le job terminé :

    envsubst '${GCS_BUCKET}' < qwen3.8-2.4t-nvfp4-stage-weights-job.yaml | kubectl apply -f -
    
    kubectl wait --for=condition=complete job/qwen3-8-2-4t-stage-weights --timeout=2h
    
    kubectl delete job qwen3-8-2-4t-stage-weights

    Le téléchargement des pondérations du modèle sur le disque SSD NVMe local et leur importation dans Cloud Storage prennent environ 25 minutes.

  4. Facultatif : Pendant l'exécution du job de préproduction, diffusez les journaux d'un pod de préproduction dans une deuxième session de terminal pour surveiller la progression du téléchargement :

    POD_NAME="YOUR_POD_NAME"
    kubectl logs -f "${POD_NAME}"

    Remplacez YOUR_POD_NAME par le nom de l'un des pods qwen3-8-2-4t-stage-weights de kubectl get pods.

Déployer le serveur SGLang désagrégé multi-hôte

Le fichier manifeste de la charge de travail (qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml) définit un Kubernetes ResourceClaimTemplate (all-mrdma), qui alloue les appareils RDMA multi-NIC du nœud (mrdma.google.com) via DRA, les ressources Role et RoleBinding pour le compte de service workload-identity-k8s-sa, et un NVIDIA Dynamo DynamoGraphDeployment (qwen3-8-2-4t-a4-disagg) avec la planification de groupe Grove activée. Le déploiement comprend les éléments suivants :

  • Interface (Frontend) : exécute une instance répliquée de routeur qui accepte les requêtes des clients, découvre les nœuds de calcul de préremplissage et de décodage enregistrés, et coordonne le routage des requêtes désagrégées.

  • Nœud de calcul de préremplissage (Prefill) : exécute un pool de nœuds de calcul à deux nœuds sur deux nœuds a4-highgpu-8g avec un total de 16 GPU NVIDIA B200. Chaque pod installe les pondérations nvidia/Qwen3.8-2.4T-A95B-NVFP4 préparées à partir de votre bucket Cloud Storage sur /model-weights à l'aide du pilote CSI Cloud Storage FUSE (gcsfuse.csi.storage.gke.io) avec la mise en cache des fichiers SSD NVMe locaux, source l'environnement NCCL/gIB et lance python3 -m dynamo.sglang dans --disaggregation-mode prefill avec les options suivantes :

    • --served-model-name "nvidia/Qwen3.8-2.4T-A95B-NVFP4"
    • --tensor-parallel-size 16
    • --ep-size 16
    • --nnodes 2
    • --moe-runner-backend flashinfer_trtllm
    • --attention-backend triton
    • --kv-cache-dtype fp8_e4m3
    • --disable-flashinfer-autotune
  • Nœud de calcul de décodage (Decode) : exécute un pool de nœuds de calcul à deux nœuds sur les deux nœuds a4-highgpu-8g restants, avec un total de 16 GPU NVIDIA B200. Chaque pod installe les pondérations du modèle préparé à partir de Cloud Storage sur /model-weights via le pilote CSI Cloud Storage FUSE et lance python3 -m dynamo.sglang dans --disaggregation-mode decode avec les options suivantes :

    • --served-model-name "nvidia/Qwen3.8-2.4T-A95B-NVFP4"
    • --tensor-parallel-size 16
    • --ep-size 16
    • --nnodes 2
    • --moe-runner-backend flashinfer_trtllm
    • --attention-backend triton
    • --kv-cache-dtype fp8_e4m3

Déployez et surveillez le serveur d'inférence désagrégé :

  1. Examinez le fichier manifeste de charge de travail qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml que vous avez téléchargé précédemment ou créez un fichier nommé qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml avec le contenu suivant :

    # ==============================================================================
    # Multihost Disaggregated Serving of Qwen3.8-2.4T-A95B (NVFP4 MoE) on GKE
    # using NVIDIA Dynamo Platform (v1.5.0) and SGLang on GPU A4 (4 x a4-highgpu-8g)
    # ==============================================================================
    apiVersion: resource.k8s.io/v1
    kind: ResourceClaimTemplate
    metadata:
      name: all-mrdma
    spec:
      spec:
        devices:
          requests:
            - name: req-mrdma
              exactly:
                deviceClassName: mrdma.google.com
                allocationMode: All
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: dynamo-workload-identity-discovery-role
      namespace: default
    rules:
      - apiGroups: [""]
        resources: ["endpoints", "pods"]
        verbs: ["get", "list", "watch"]
      - apiGroups: ["discovery.k8s.io"]
        resources: ["endpointslices"]
        verbs: ["get", "list", "watch"]
      - apiGroups: ["nvidia.com"]
        resources: ["dynamoworkermetadatas"]
        verbs: ["create", "get", "list", "watch", "update", "patch", "delete"]
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: dynamo-workload-identity-discovery-binding
      namespace: default
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: Role
      name: dynamo-workload-identity-discovery-role
    subjects:
      - kind: ServiceAccount
        name: workload-identity-k8s-sa
        namespace: default
    ---
    apiVersion: nvidia.com/v1alpha1
    kind: DynamoGraphDeployment
    metadata:
      name: qwen3-8-2-4t-a4-disagg
      annotations:
        nvidia.com/enable-grove: "true"
    spec:
      backendFramework: sglang
      services:
        # --------------------------------------------------------------------------
        # FRONTEND: Dynamo OpenAI-Compatible Router (`python3 -m dynamo.frontend`)
        # --------------------------------------------------------------------------
        Frontend:
          componentType: frontend
          replicas: 1
          resources:
            requests:
              cpu: "8"
              memory: "32Gi"
            limits:
              cpu: "8"
              memory: "32Gi"
          extraPodSpec:
            tolerations:
              - key: "nvidia.com/gpu"
                operator: "Exists"
                effect: "NoSchedule"
            mainContainer:
              image: nvcr.io/nvidia/ai-dynamo/sglang-runtime:1.5.0
              command: ["/bin/bash", "-c"]
              args:
                - |
                  set -ex
                  exec python3 -m dynamo.frontend --http-port 8000
    
        # --------------------------------------------------------------------------
        # PREFILL WORKER: 2 x GPU A4 (`a4-highgpu-8g`, 16 x B200 GPUs, TP=16, EP=16)
        # --------------------------------------------------------------------------
        Prefill:
          componentType: worker
          subComponentType: prefill
          replicas: 1
          multinode:
            nodeCount: 2
          extraPodMetadata:
            annotations:
              gke-gcsfuse/volumes: "true"
          livenessProbe:
            httpGet:
              path: /live
              port: system
            periodSeconds: 60
            timeoutSeconds: 30
            failureThreshold: 60
          readinessProbe:
            httpGet:
              path: /health
              port: system
            initialDelaySeconds: 120
            periodSeconds: 30
            timeoutSeconds: 30
            failureThreshold: 60
          sharedMemory:
            size: 128Gi
          resources:
            requests:
              gpu: "8"
              cpu: "128"
              memory: "1500Gi"
            limits:
              gpu: "8"
              cpu: "128"
              memory: "2000Gi"
          envs:
            - name: MODEL_PATH
              value: "/model-weights/hf_models/models--nvidia--qwen3_8-2_4t-a95b-nvfp4"
            - name: DYN_SYSTEM_PORT
              value: "9090"
            - name: LD_LIBRARY_PATH
              value: "/usr/local/gib/lib64:/usr/local/nvidia/lib64"
            - name: GLOO_SOCKET_IFNAME
              value: "eth0"
            - name: NCCL_ALGO
              value: "Ring,Tree"
            - name: WITH_NVIDIA_PEERMEM
              value: "0"
          extraPodSpec:
            serviceAccountName: workload-identity-k8s-sa
            automountServiceAccountToken: true
            resourceClaims:
              - name: rdma
                resourceClaimTemplateName: all-mrdma
            nodeSelector:
              cloud.google.com/gke-accelerator: nvidia-b200
            tolerations:
              - key: "nvidia.com/gpu"
                operator: "Exists"
                effect: "NoSchedule"
            mainContainer:
              image: nvcr.io/nvidia/ai-dynamo/sglang-runtime:1.5.0
              resources:
                claims:
                  - name: rdma
              startupProbe:
                httpGet:
                  path: /live
                  port: system
                initialDelaySeconds: 120
                periodSeconds: 30
                timeoutSeconds: 10
                failureThreshold: 120
              command: ["/bin/bash", "-c"]
              args:
                - |
                  set -ex
                  if [ -f /usr/local/gib/scripts/set_nccl_env.sh ]; then
                    . /usr/local/gib/scripts/set_nccl_env.sh
                  fi
    
                  echo "=== [Dynamo Prefill Worker] Launching dynamo.sglang ==="
                  exec python3 -m dynamo.sglang \
                    --dist-init-addr "${GROVE_PCSG_NAME}-${GROVE_PCSG_INDEX}-prefill-ldr-0.${GROVE_HEADLESS_SERVICE}.:29500" \
                    --model-path "${MODEL_PATH}" \
                    --served-model-name "nvidia/Qwen3.8-2.4T-A95B-NVFP4" \
                    --host 0.0.0.0 \
                    --disaggregation-mode prefill \
                    --disaggregation-bootstrap-port 30001 \
                    --tensor-parallel-size 16 \
                    --ep-size 16 \
                    --moe-runner-backend flashinfer_trtllm \
                    --attention-backend triton \
                    --disable-flashinfer-autotune \
                    --nnodes 2 \
                    --kv-cache-dtype fp8_e4m3 \
                    --context-length 16384 \
                    --mem-fraction-static 0.85 \
                    --enable-metrics
              volumeMounts:
                - name: library-dir-host
                  mountPath: /usr/local/nvidia
                - name: gib
                  mountPath: /usr/local/gib
                - name: gcs-model-dir
                  mountPath: /model-weights
                  readOnly: true
            volumes:
              - name: library-dir-host
                hostPath:
                  path: /home/kubernetes/bin/nvidia
              - name: gib
                hostPath:
                  path: /home/kubernetes/bin/gib
              - name: gcs-model-dir
                csi:
                  driver: gcsfuse.csi.storage.gke.io
                  readOnly: true
                  volumeAttributes:
                    bucketName: ${GCS_BUCKET}
                    mountOptions: "implicit-dirs,file-cache:max-size-mb:-1"
    
        # --------------------------------------------------------------------------
        # DECODE WORKER: 2 x GPU A4 (`a4-highgpu-8g`, 16 x B200 GPUs, TP=16, EP=16)
        # --------------------------------------------------------------------------
        Decode:
          componentType: worker
          subComponentType: decode
          replicas: 1
          multinode:
            nodeCount: 2
          extraPodMetadata:
            annotations:
              gke-gcsfuse/volumes: "true"
          livenessProbe:
            httpGet:
              path: /live
              port: system
            periodSeconds: 60
            timeoutSeconds: 30
            failureThreshold: 60
          readinessProbe:
            httpGet:
              path: /health
              port: system
            initialDelaySeconds: 120
            periodSeconds: 30
            timeoutSeconds: 30
            failureThreshold: 60
          sharedMemory:
            size: 128Gi
          resources:
            requests:
              gpu: "8"
              cpu: "128"
              memory: "1500Gi"
            limits:
              gpu: "8"
              cpu: "128"
              memory: "2000Gi"
          envs:
            - name: MODEL_PATH
              value: "/model-weights/hf_models/models--nvidia--qwen3_8-2_4t-a95b-nvfp4"
            - name: DYN_SYSTEM_PORT
              value: "9090"
            - name: LD_LIBRARY_PATH
              value: "/usr/local/gib/lib64:/usr/local/nvidia/lib64"
            - name: GLOO_SOCKET_IFNAME
              value: "eth0"
            - name: NCCL_ALGO
              value: "Ring,Tree"
            - name: WITH_NVIDIA_PEERMEM
              value: "0"
          extraPodSpec:
            serviceAccountName: workload-identity-k8s-sa
            automountServiceAccountToken: true
            resourceClaims:
              - name: rdma
                resourceClaimTemplateName: all-mrdma
            nodeSelector:
              cloud.google.com/gke-accelerator: nvidia-b200
            tolerations:
              - key: "nvidia.com/gpu"
                operator: "Exists"
                effect: "NoSchedule"
            mainContainer:
              image: nvcr.io/nvidia/ai-dynamo/sglang-runtime:1.5.0
              resources:
                claims:
                  - name: rdma
              startupProbe:
                httpGet:
                  path: /live
                  port: system
                initialDelaySeconds: 120
                periodSeconds: 30
                timeoutSeconds: 10
                failureThreshold: 120
              command: ["/bin/bash", "-c"]
              args:
                - |
                  set -ex
                  if [ -f /usr/local/gib/scripts/set_nccl_env.sh ]; then
                    . /usr/local/gib/scripts/set_nccl_env.sh
                  fi
    
                  echo "=== [Dynamo Decode Worker] Launching dynamo.sglang ==="
                  exec python3 -m dynamo.sglang \
                    --dist-init-addr "${GROVE_PCSG_NAME}-${GROVE_PCSG_INDEX}-decode-ldr-0.${GROVE_HEADLESS_SERVICE}.:29500" \
                    --model-path "${MODEL_PATH}" \
                    --served-model-name "nvidia/Qwen3.8-2.4T-A95B-NVFP4" \
                    --host 0.0.0.0 \
                    --disaggregation-mode decode \
                    --disaggregation-bootstrap-port 30001 \
                    --tensor-parallel-size 16 \
                    --ep-size 16 \
                    --moe-runner-backend flashinfer_trtllm \
                    --attention-backend triton \
                    --nnodes 2 \
                    --kv-cache-dtype fp8_e4m3 \
                    --context-length 16384 \
                    --mem-fraction-static 0.85 \
                    --enable-metrics
              volumeMounts:
                - name: library-dir-host
                  mountPath: /usr/local/nvidia
                - name: gib
                  mountPath: /usr/local/gib
                - name: gcs-model-dir
                  mountPath: /model-weights
                  readOnly: true
            volumes:
              - name: library-dir-host
                hostPath:
                  path: /home/kubernetes/bin/nvidia
              - name: gib
                hostPath:
                  path: /home/kubernetes/bin/gib
              - name: gcs-model-dir
                csi:
                  driver: gcsfuse.csi.storage.gke.io
                  readOnly: true
                  volumeAttributes:
                    bucketName: ${GCS_BUCKET}
                    mountOptions: "implicit-dirs,file-cache:max-size-mb:-1"
  2. Remplacez qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml par le nom de votre bucket Cloud Storage et appliquez le fichier manifeste de la charge de travail à votre cluster :

    envsubst '${GCS_BUCKET}' < qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml | kubectl apply -f -
  3. Pour surveiller le chargement des pondérations et l'initialisation du serveur, listez les pods de votre cluster, puis diffusez les journaux du pod leader de préremplissage et du pod leader de décodage :

    kubectl get pods
    POD_NAME="YOUR_POD_NAME"
    kubectl logs -f "${POD_NAME}"

    Remplacez YOUR_POD_NAME par le nom du pod leader de préremplissage ou du pod leader de décodage de la sortie kubectl get pods.

    Lors d'un démarrage à froid, il faut environ 4 à 5 minutes pour diffuser les 2 400 000 000 000 poids du modèle NVFP4 (~1,44 To sur 200 segments) depuis Cloud Storage via Cloud Storage FUSE et initialiser les serveurs SGLang multi-hôtes. Avant d'envoyer des requêtes, attendez que les pods d'interface affichent 1/1 et que les pods de préremplissage et de décodage affichent 2/2 dans la colonne READY de kubectl get pods :

    NAME                                                     READY   STATUS    RESTARTS   AGE
    qwen3-8-2-4t-a4-disagg-0-decode-0-decode-ldr-wz92h       2/2     Running   0          5m
    qwen3-8-2-4t-a4-disagg-0-decode-0-decode-wkr-fb8g6       2/2     Running   0          5m
    qwen3-8-2-4t-a4-disagg-0-frontend-jrx4s                  1/1     Running   0          5m
    qwen3-8-2-4t-a4-disagg-0-prefill-0-prefill-ldr-7nz25     2/2     Running   0          5m
    qwen3-8-2-4t-a4-disagg-0-prefill-0-prefill-wkr-7d5cw     2/2     Running   0          5m
    

Envoyer des requêtes d'inférence

Envoyez une requête de complétion de chat au service Qwen3.8-2.4T déployé :

  1. Transférez le port local 8000 vers le service qwen3-8-2-4t-a4-disagg-frontend :

    kubectl port-forward svc/qwen3-8-2-4t-a4-disagg-frontend 8000:8000

    Laissez le transfert de port s'exécuter dans cette session de terminal ou exécutez la commande en arrière-plan.

  2. Dans une deuxième session de terminal, envoyez une requête de complétion de chat au point de terminaison /v1/chat/completions :

    curl -s http://127.0.0.1:8000/v1/chat/completions \
      -H "Content-Type: application/json" \
      -d '{
        "model": "nvidia/Qwen3.8-2.4T-A95B-NVFP4",
        "messages": [{"role": "user", "content": "Explain KV-cache disaggregation in one sentence."}],
        "max_tokens": 64
      }' | jq .

Exécuter une charge de travail de benchmark

Effectuez une analyse comparative du serveur Qwen3.8-2.4T déployé à partir du pod d'interface Dynamo à l'aide de sglang.bench_serving avec une configuration de 1 024 jetons d'entrée, 1 024 jetons de sortie (1k/1k), 256 requêtes et une simultanéité maximale de 128 :

ROUTER_POD=$(kubectl get pod -l 'nvidia.com/dynamo-component-type=frontend' -o jsonpath='{.items[0].metadata.name}')

kubectl exec "${ROUTER_POD}" -- python3 -m sglang.bench_serving \
  --backend sglang-oai-chat \
  --base-url http://localhost:8000 \
  --model nvidia/Qwen3.8-2.4T-A95B-NVFP4 \
  --dataset-name random \
  --num-prompts 256 \
  --random-input-len 1024 \
  --random-output-len 1024 \
  --random-range-ratio 1.0 \
  --max-concurrency 128 | tee benchmark.log

Une fois l'analyse comparative terminée, 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 cluster GKE et votre bucket Cloud Storage :

  1. Supprimez les ressources DynamoGraphDeployment et ResourceClaimTemplate :

    kubectl delete -f qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml --ignore-not-found || true
  2. Désinstallez les versions Helm de la plate-forme NVIDIA Dynamo et du planificateur KAI :

    helm uninstall dynamo-platform kai-scheduler || true
  3. Supprimez le secret Kubernetes Hugging Face :

    kubectl delete secret hf-secret --ignore-not-found || true
  4. À partir du répertoire cluster-toolkit, détruisez le cluster GKE et l'infrastructure associée à l'aide de la commande gcluster destroy :

    cd cluster-toolkit
    ./gcluster destroy "${CLUSTER_NAME}" --auto-approve || true
    cd ..
  5. Supprimez le bucket Cloud Storage qui stocke les pondérations du modèle préparé et l'état Terraform :

    gcloud storage rm --recursive "gs://${GCS_BUCKET}"
    

Étapes suivantes