Gestisci Qwen3.8-2.4T su GKE con disaggregazione dei DP multi-host utilizzando Dynamo e SGLang su VM A4

Questo tutorial mostra come eseguire il deployment e l'addestramento di Qwen3.8-2.4T (nvidia/Qwen3.8-2.4T-A95B-NVFP4), un modello Mixture of Experts con 2400 miliardi di parametri e 95 miliardi di parametri attivi quantizzati in NVFP4, su Google Kubernetes Engine (GKE) suGoogle Cloud. Il deployment utilizza Cluster Toolkit, quattro istanze di macchine virtuali (VM) A4 (a4-highgpu-8g) multi-host con un totale di 32 GPU NVIDIA B200, NVIDIA Dynamo e il framework di distribuzione SGLang con disaggregazione prefill-decode (DP).

Nella configurazione del deployment descritta in questo tutorial, utilizzi Cluster Toolkit per eseguire il provisioning di un cluster GKE in modalità Standard. Il cluster contiene quattro nodi a4-highgpu-8g connessi tramite rete GPUDirect accesso diretto alla memoria da remoto (RDMA). Poi, installi la piattaforma KAI Scheduler e NVIDIA Dynamo con la pianificazione di gruppo Grove e metti in gestione temporanea i pesi del modello in Cloud Storage. Successivamente, esegui il deployment di una risorsa personalizzata DynamoGraphDeployment che monta il bucket con Cloud Storage FUSE e separa il pre-riempimento e la decodifica in due pool di worker multi-host con 16 GPU con parallelismo dei tensori, parallelismo degli esperti e una cache KV FP8.

Obiettivi

  • Installa Cluster Toolkit ed esegui il provisioning di un cluster GKE A4 multi-host in modalità Standard.
  • Installa KAI Scheduler e la piattaforma NVIDIA Dynamo con lo scheduling di gruppo Grove sul tuo cluster.
  • Esegui lo stage dei pesi del modello Qwen3.8-2.4T in un bucket Cloud Storage.
  • Esegui il deployment di un server di inferenza SGLang multi-host con disaggregazione DP su quattro nodi A4, con un totale di 32 GPU NVIDIA B200.
  • Invia una richiesta di completamento della chat al router di frontend Dynamo.
  • Esegui un workload di benchmark di gestione.
  • Esegui la pulizia delle risorse di cui hai eseguito il deployment.

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.

Prima di iniziare

Configura l'ambiente per il tutorial:

  1. Installa e inizializza Google Cloud CLI e installa kubectl e Helm (versione 3.10 o successive). Se utilizzi Cloud Shell, questi strumenti sono preinstallati.
  2. Se esegui questo tutorial da una workstation Linux o macOS anziché da Cloud Shell, installa le dipendenze di Cluster Toolkit richieste, come Terraform e Git.
  3. Abilita l'API Kubernetes Engine, l'API Compute Engine, l'API Cloud Storage, l'API Identity and Access Management, l'API Cloud Resource Manager e l'API Service Usage nel tuo progetto Google Cloud :

    Abilita le API

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 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.

Imposta le variabili di ambiente

Esporta le variabili di ambiente che utilizzi in questo tutorial:

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

Sostituisci quanto segue:

  • YOUR_PROJECT_ID: l'ID del tuo progetto Google Cloud.
  • YOUR_REGION: la regione in cui si trova la tua capacità A4 e in cui vuoi eseguire il deployment del cluster, ad esempio us-central1.
  • YOUR_ZONE: la zona in cui si trova la tua capacità A4, ad esempio us-central1-b.
  • YOUR_CLUSTER_NAME: il nome del tuo cluster GKE e del deployment di Cluster Toolkit. Utilizza un nome di massimo 20 caratteri in modo che l'ID account del service account del pool di nodi generato (YOUR_CLUSTER_NAME-gke-np-sa) rimanga entro il limite di 30 caratteri di Identity and Access Management (IAM).
  • YOUR_RESERVATION_NAME: il nome della prenotazione A4 o il percorso del blocco di prenotazione esteso nel formato RESERVATION_NAME/reservationBlocks/RESERVATION_BLOCK_NAME.
  • YOUR_BUCKET_NAME: un nome univoco globale per il bucket Cloud Storage che archivia lo stato Terraform per Cluster Toolkit e i pesi del modello Qwen3.8-2.4T di cui è stato eseguito lo stage.
  • YOUR_HF_TOKEN: il tuo token di accesso a Hugging Face.

Crea il cluster GKE

Scarica Cluster Toolkit ed esegui il provisioning di un cluster GKE in modalità Standard con quattro nodi a4-highgpu-8g, networking GPUDirect RDMA e supporto DRA:

  1. Accedi a Google Cloud CLI e genera le Credenziali predefinite dell'applicazione per Terraform:

    gcloud auth login
    gcloud auth application-default login
  2. Scarica ed estrai il bundle Cluster Toolkit predefinito per la versione specificata in 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

    Se esegui questo comando su macOS o su una workstation arm64, imposta OS="mac" o ARCH="arm64" in modo che corrisponda alla tua piattaforma.

  3. Per archiviare lo stato di Terraform per il deployment di Cluster Toolkit e i pesi del modello di cui è stato eseguito lo stage, crea un bucket Cloud Storage nella stessa regione della prenotazione A4 con accesso uniforme a livello di bucket e controllo delle versioni degli oggetti abilitato:

    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. Crea il file di configurazione del deployment cluster-toolkit/examples/gke-a4/gke-a4-deployment.yaml per quattro nodi 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

    Per impostazione predefinita, il file di deployment è configurato per utilizzare una prenotazione specifica. Se non hai una prenotazione specifica, prima di eseguire il deployment del cluster, puoi aggiornare cluster-toolkit/examples/gke-a4/gke-a4-deployment.yaml in modo che utilizzi un'opzione di consumo alternativa, ad esempio VM spot o Flex-start VM. Per istruzioni sulla configurazione, consulta Opzioni di consumo di GKE.

  5. Passa alla directory cluster-toolkit, esegui il deployment del cluster GKE utilizzando il comando gcluster deploy, quindi torna alla directory di lavoro:

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

    La creazione del cluster richiede circa 20-30 minuti. Se esegui ./gcluster deploy senza il flag --auto-approve, per applicare le modifiche proposte, inserisci a quando richiesto.

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

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

Installa KAI Scheduler e NVIDIA Dynamo

Prepara il cluster creando un secret di Hugging Face e installando lo scheduler KAI e la piattaforma NVIDIA Dynamo con lo scheduling di gruppo Grove:

  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}" \
        --dry-run=client -o yaml | kubectl apply -f -
  2. Scarica il grafico Helm della piattaforma NVIDIA Dynamo (v1.5.0), installa lo scheduler KAI e installa la piattaforma Dynamo con Grove abilitato:

    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

Esegui lo stage dei pesi del modello in Cloud Storage

Esegui lo stage del checkpoint nvidia/Qwen3.8-2.4T-A95B-NVFP4 nel tuo bucket Cloud Storage (gs://${GCS_BUCKET}) nella regione, in modo che i pod worker di inferenza possano montare i pesi con Cloud Storage FUSE anziché scaricarli da Hugging Face a ogni avvio a freddo:

  1. Concedi il ruolo Storage Object User (roles/storage.objectUser) sul bucket gs://${GCS_BUCKET} che hai creato in Crea il cluster GKE al account di servizio Google associato al service account 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. Scarica i manifest qwen3.8-2.4t-nvfp4-stage-weights-job.yaml e 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

    In alternativa, crea un file denominato qwen3.8-2.4t-nvfp4-stage-weights-job.yaml con i seguenti contenuti. Questo job indicizzato esegue quattro pod paralleli sui quattro nodi A4. Ogni pod scarica una sezione di dimensioni bilanciate del checkpoint nvidia/Qwen3.8-2.4T-A95B-NVFP4 nell'SSD NVMe locale del nodo e la carica su gs://${GCS_BUCKET}/hf_models/models--nvidia--qwen3_8-2_4t-a95b-nvfp4 utilizzando 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. Sostituisci il nome del bucket Cloud Storage in qwen3.8-2.4t-nvfp4-stage-weights-job.yaml, avvia il job di gestione temporanea, attendi che tutti e quattro i pod abbiano completato il caricamento dei pesi, quindi elimina il job completato:

    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

    Il download dei pesi del modello sull'SSD NVMe locale e il caricamento su Cloud Storage richiedono circa 25 minuti.

  4. (Facoltativo) Mentre il job di gestione temporanea è in esecuzione, trasmetti in streaming i log di un pod di gestione temporanea in una seconda sessione del terminale per monitorare l'avanzamento del download:

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

    Sostituisci YOUR_POD_NAME con il nome di uno dei pod qwen3-8-2-4t-stage-weights di kubectl get pods.

Esegui il deployment del server SGLang disaggregato multi-host

Il manifest del workload (qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml) definisce un ResourceClaimTemplate di Kubernetes (all-mrdma), che alloca i dispositivi RDMA multi-NIC del nodo (mrdma.google.com) tramite DRA, risorse Role e RoleBinding per il account di servizio workload-identity-k8s-sa e un NVIDIA Dynamo DynamoGraphDeployment (qwen3-8-2-4t-a4-disagg) con la pianificazione di gruppo Grove abilitata. Il deployment è costituito da quanto segue:

  • Frontend (Frontend): esegue una replica del router che accetta le richieste dei client, rileva i worker di precompilazione e decodifica registrati e coordina il routing delle richieste disaggregate.

  • Worker di precompilazione (Prefill): esegue un pool di worker a due nodi su due nodi a4-highgpu-8g con un totale di 16 GPU NVIDIA B200. Ogni pod monta i pesi nvidia/Qwen3.8-2.4T-A95B-NVFP4 di cui è stato eseguito lo stage dal tuo bucket Cloud Storage in /model-weights utilizzando il driver CSI Cloud Storage FUSE (gcsfuse.csi.storage.gke.io) con la memorizzazione nella cache dei file degli SSD NVMe locali, genera l'ambiente NCCL/gIB e avvia python3 -m dynamo.sglang in --disaggregation-mode prefill con i seguenti flag:

    • --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
  • Decode worker (Decode): esegue un pool di worker a due nodi nei restanti due nodi a4-highgpu-8g, con un totale di 16 GPU NVIDIA B200. Ogni pod monta i pesi del modello preparato da Cloud Storage in /model-weights tramite il driver CSI di Cloud Storage FUSE e avvia python3 -m dynamo.sglang in --disaggregation-mode decode con i seguenti flag:

    • --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

Esegui il deployment e il monitoraggio del server di inferenza disaggregato:

  1. Esamina il manifest del workload qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml che hai scaricato in precedenza o crea un file denominato qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml con i seguenti contenuti:

    # ==============================================================================
    # 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. Sostituisci il nome del tuo bucket Cloud Storage in qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml e applica il manifest del workload al tuo cluster:

    envsubst '${GCS_BUCKET}' < qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml | kubectl apply -f -
  3. Per monitorare il caricamento dei pesi e l'inizializzazione del server, elenca i pod nel tuo cluster e trasmetti in streaming i log del pod leader di pre-compilazione e del pod leader di decodifica:

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

    Sostituisci YOUR_POD_NAME con il nome del pod leader di precompilazione o del pod leader di decodifica dall'output kubectl get pods.

    Con un avvio a freddo, sono necessari circa 4-5 minuti per eseguire lo streaming dei 2.400.000.000.000 di pesi del modello NVFP4 (~1,44 TB su 200 shard) da Cloud Storage tramite Cloud Storage FUSE e inizializzare i server SGLang multi-host. Prima di inviare qualsiasi richiesta, attendi che i pod di frontend mostrino 1/1 e i pod di precompilazione e decodifica mostrino 2/2 nella colonna READY di 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
    

Invia richieste di inferenza

Invia una richiesta di completamento della chat al servizio Qwen3.8-2.4T di cui è stato eseguito il deployment:

  1. Inoltra la porta locale 8000 al servizio qwen3-8-2-4t-a4-disagg-frontend:

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

    Lascia il port forwarding in esecuzione in questa sessione del terminale o esegui il comando in background.

  2. In una seconda sessione del terminale, invia una richiesta di completamento della chat all'endpoint /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 .

Esegui un workload di benchmark

Esegui il benchmark del server Qwen3.8-2.4T di cui è stato eseguito il deployment dal pod frontend Dynamo utilizzando sglang.bench_serving con una configurazione di 1024 token di input, 1024 token di output (1k/1k), 256 prompt e una contemporaneità massima di 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

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 per le risorse utilizzate in questo tutorial, elimina i workload Kubernetes, il cluster GKE e il bucket Cloud Storage:

  1. Elimina le risorse DynamoGraphDeployment e ResourceClaimTemplate:

    kubectl delete -f qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml --ignore-not-found || true
  2. Disinstalla le release Helm della piattaforma NVIDIA Dynamo e di KAI Scheduler:

    helm uninstall dynamo-platform kai-scheduler || true
  3. Elimina il secret Kubernetes di Hugging Face:

    kubectl delete secret hf-secret --ignore-not-found || true
  4. Dalla directory cluster-toolkit, elimina il cluster GKE e l'infrastruttura associata utilizzando il comando gcluster destroy:

    cd cluster-toolkit
    ./gcluster destroy "${CLUSTER_NAME}" --auto-approve || true
    cd ..
  5. Elimina il bucket Cloud Storage che archivia i pesi del modello in fase di preparazione e lo stato di Terraform:

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

Passaggi successivi