Disponibilizar o Qwen3.8-2.4T no GKE com desagregação de DP de vários hosts usando o Dynamo e o SGLang em VMs A4

Neste tutorial, mostramos como implantar e disponibilizar o Qwen3.8-2.4T (nvidia/Qwen3.8-2.4T-A95B-NVFP4), um modelo de Mix de Especialistas de 2,4 trilhões de parâmetros com 95 bilhões de parâmetros ativos quantizados para NVFP4, no Google Kubernetes Engine (GKE) em Google Cloud. A implantação usa o Cluster Toolkit, quatro instâncias de máquina virtual (VM) A4 (a4-highgpu-8g) com vários hosts e um total de 32 GPUs NVIDIA B200, NVIDIA Dynamo e o framework de exibição SGLang com desagregação de pré-preenchimento e decodificação (DP).

Na configuração de implantação descrita neste tutorial, use o Cluster Toolkit para provisionar um cluster do GKE no modo Standard. O cluster contém quatro nós a4-highgpu-8g conectados pela rede de acesso direto à memória remota (RDMA, na sigla em inglês) do GPUDirect. Em seguida, instale o programador KAI e a plataforma NVIDIA Dynamo com a programação de gangues do Grove e prepare os pesos do modelo no Cloud Storage. Em seguida, implante um recurso personalizado DynamoGraphDeployment que monta o bucket com o Cloud Storage FUSE e separa o preenchimento prévio e a decodificação em dois pools de workers de vários hosts de 16 GPUs com paralelismo de tensor, paralelismo de especialista e um cache KV FP8.

Objetivos

  • Instale o Cluster Toolkit e provisione um cluster do GKE A4 de vários hosts no modo padrão.
  • Instale o programador KAI e a plataforma NVIDIA Dynamo com a programação de gangues do Grove no cluster.
  • Armazene os pesos do modelo Qwen3.8-2.4T em um bucket do Cloud Storage.
  • Implantar um servidor de inferência SGLang com vários hosts e DP desagregado em quatro nós A4, com um total de 32 GPUs NVIDIA B200.
  • Envie uma solicitação de conclusão de chat para o roteador de front-end do Dynamo.
  • Executar uma carga de trabalho de comparativo de mercado de exibição.
  • Limpe os recursos implantados.

Custos

Neste documento, você usará os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços.

Antes de começar

Configure o ambiente para o tutorial:

  1. Instale e inicialize a Google Cloud CLI e instale kubectl e Helm (versão 3.10 ou mais recente). Se você usa o Cloud Shell, essas ferramentas já estão pré-instaladas.
  2. Se você estiver executando este tutorial em uma estação de trabalho Linux ou macOS em vez do Cloud Shell, instale as dependências necessárias do Cluster Toolkit, como o Terraform e o Git.
  3. Ative as API Kubernetes Engine, Compute Engine, Cloud Storage, Identity and Access Management, Cloud Resource Manager e Service Usage no projeto do Google Cloud :

    Ativar APIs

Receber um token de acesso do Hugging Face

Você precisa de um token de acesso do Hugging Face para usar este tutorial. Você pode se inscrever para uma conta sem custo financeiro no Hugging Face. Depois de criar uma conta, gere um token de acesso:

  1. Na página Welcome to Hugging Face, clique no avatar da sua conta e selecione Tokens de acesso.
  2. Na página Tokens de acesso, clique em Criar novo token.
  3. Selecione o tipo de token Read e insira um nome para ele.
  4. Seu token de acesso será exibido. Salve o token em um local seguro.

Funções exigidas

Para conseguir as permissões necessárias para concluir o tutorial, peça ao administrador para conceder a você os seguintes papéis do IAM no seu projeto:

Para mais informações sobre a concessão de papéis, consulte Gerenciar o acesso a projetos, pastas e organizações.

Também é possível conseguir as permissões necessárias usando papéis personalizados ou outros papéis predefinidos.

Configurar as variáveis de ambiente

Exporte as variáveis de ambiente que você vai usar neste 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}"

Substitua:

  • YOUR_PROJECT_ID: ID do seu projeto do Google Cloud.
  • YOUR_REGION: a região em que a capacidade do A4 está localizada e em que você quer implantar o cluster, como us-central1.
  • YOUR_ZONE: a zona em que sua capacidade de A4 está localizada, como us-central1-b.
  • YOUR_CLUSTER_NAME: o nome do cluster do GKE e da implantação do Cluster Toolkit. Use um nome com até 20 caracteres para que o ID da conta de serviço do pool de nós gerado (YOUR_CLUSTER_NAME-gke-np-sa) permaneça dentro do limite de 30 caracteres do Identity and Access Management (IAM).
  • YOUR_RESERVATION_NAME: o nome da sua reserva de A4 ou o caminho do bloco de reserva estendido no formato RESERVATION_NAME/reservationBlocks/RESERVATION_BLOCK_NAME.
  • YOUR_BUCKET_NAME: um nome globalmente exclusivo para o bucket do Cloud Storage que armazena o estado do Terraform para o Cluster Toolkit e os pesos do modelo Qwen3.8-2.4T em etapas.
  • YOUR_HF_TOKEN: seu token de acesso do Hugging Face.

Criar o cluster do GKE

Faça o download do Cluster Toolkit e provisione um cluster do GKE no modo Standard com quatro nós a4-highgpu-8g, rede GPUDirect RDMA e suporte a DRA:

  1. Faça login na Google Cloud CLI e gere o Application Default Credentials para o Terraform:

    gcloud auth login
    gcloud auth application-default login
  2. Faça o download e extraia o pacote pré-criado do Cluster Toolkit para a versão especificada em 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 você executar esse comando no macOS ou em uma estação de trabalho arm64, defina OS="mac" ou ARCH="arm64" para corresponder à sua plataforma.

  3. Para armazenar o estado do Terraform para a implantação do Cluster Toolkit e os pesos do modelo em etapas, crie um bucket do Cloud Storage na mesma região da reserva A4 com acesso uniforme no nível do bucket e controle de versões de objetos ativado:

    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. Crie o arquivo de configuração de implantação cluster-toolkit/examples/gke-a4/gke-a4-deployment.yaml para quatro nós 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

    Por padrão, o arquivo de implantação é configurado para consumir uma reserva específica. Se você não tiver uma reserva específica, antes de implantar o cluster, atualize cluster-toolkit/examples/gke-a4/gke-a4-deployment.yaml para usar uma opção de consumo alternativa, como VMs spot ou VMs flex-start. Para instruções de configuração, consulte Opções de consumo do GKE.

  5. Mude para o diretório cluster-toolkit, implante o cluster do GKE usando o comando gcluster deploy e volte para o diretório de trabalho:

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

    A criação do cluster leva aproximadamente de 20 a 30 minutos. Se você executar ./gcluster deploy sem a flag --auto-approve, para aplicar as mudanças propostas, insira a quando solicitado.

  6. Configure as credenciais de kubectl para se comunicar com o cluster:

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

Instalar o KAI Scheduler e o NVIDIA Dynamo

Prepare o cluster criando um Secret do Hugging Face e instalando o programador do KAI e a plataforma NVIDIA Dynamo com a programação de gangues do Grove:

  1. Crie um Secret do Kubernetes chamado hf-secret para armazenar o token de acesso do Hugging Face:

    kubectl create secret generic hf-secret \
        --from-literal=hf_api_token="${HF_TOKEN}" \
        --dry-run=client -o yaml | kubectl apply -f -
  2. Faça o download do gráfico do Helm da plataforma NVIDIA Dynamo (v1.5.0), instale o programador KAI e instale a plataforma Dynamo com o Grove ativado:

    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

Organizar os pesos do modelo no Cloud Storage

Prepare o checkpoint nvidia/Qwen3.8-2.4T-A95B-NVFP4 no bucket do Cloud Storage na região (gs://${GCS_BUCKET}) para que os pods de worker de inferência possam montar os pesos com o Cloud Storage FUSE em vez de fazer o download deles do Hugging Face em cada inicialização a frio:

  1. Conceda o papel de Usuário de objetos do Storage (roles/storage.objectUser) no bucket gs://${GCS_BUCKET} que você criou em Criar o cluster do GKE à conta de serviço do Google vinculada à conta de serviço do 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. Faça o download dos manifestos 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

    Como alternativa, crie um arquivo chamado qwen3.8-2.4t-nvfp4-stage-weights-job.yaml com o conteúdo a seguir. Esse job indexado executa quatro pods paralelos nos quatro nós A4. Cada pod faz o download de uma fração de tamanho equilibrado do checkpoint nvidia/Qwen3.8-2.4T-A95B-NVFP4 para o SSD NVMe local do nó e o envia para gs://${GCS_BUCKET}/hf_models/models--nvidia--qwen3_8-2_4t-a95b-nvfp4 usando 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. Substitua o nome do bucket do Cloud Storage em qwen3.8-2.4t-nvfp4-stage-weights-job.yaml, inicie o job de preparo, aguarde todos os quatro pods terminarem de fazer upload dos pesos e exclua o job concluído:

    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

    O download dos pesos do modelo para o SSD NVMe local e o upload deles para o Cloud Storage levam aproximadamente 25 minutos.

  4. Opcional: enquanto o job de preparo estiver em execução, transmita os registros de um pod de preparo em uma segunda sessão de terminal para monitorar o progresso do download:

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

    Substitua YOUR_POD_NAME pelo nome de um dos pods qwen3-8-2-4t-stage-weights de kubectl get pods.

Implantar o servidor SGLang desagregado de vários hosts

O manifesto da carga de trabalho (qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml) define um ResourceClaimTemplate do Kubernetes (all-mrdma), que aloca os dispositivos RDMA com várias NICs do nó (mrdma.google.com) por meio de recursos de DRA, papel e RoleBinding para a conta de serviço workload-identity-k8s-sa, e um DynamoGraphDeployment da NVIDIA (qwen3-8-2-4t-a4-disagg) com a programação de gangues do Grove ativada. A implantação consiste no seguinte:

  • Front-end (Frontend): executa uma réplica de roteador que aceita solicitações de clientes, descobre workers de preenchimento prévio e decodificação registrados e coordena o roteamento de solicitações desagregadas.

  • Worker de preenchimento prévio (Prefill): executa um pool de workers de dois nós em dois nós a4-highgpu-8g com um total de 16 GPUs NVIDIA B200. Cada pod monta os pesos nvidia/Qwen3.8-2.4T-A95B-NVFP4 preparados do seu bucket do Cloud Storage em /model-weights usando o driver CSI do Cloud Storage FUSE (gcsfuse.csi.storage.gke.io) com armazenamento em cache de arquivos SSD NVMe local, origina o ambiente NCCL/gIB e inicia python3 -m dynamo.sglang em --disaggregation-mode prefill com as seguintes flags:

    • --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
  • Worker de decodificação (Decode): executa um pool de workers de dois nós nos dois nós a4-highgpu-8g restantes, com um total de 16 GPUs NVIDIA B200. Cada pod monta os pesos do modelo em etapas do Cloud Storage em /model-weights por meio do driver CSI do Cloud Storage FUSE e inicia python3 -m dynamo.sglang em --disaggregation-mode decode com as seguintes flags:

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

Implante e monitore o servidor de inferência desagregado:

  1. Revise o manifesto de carga de trabalho qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml que você salvou anteriormente ou crie um arquivo chamado qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml com o seguinte conteúdo:

    # ==============================================================================
    # 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. Substitua o nome do bucket do Cloud Storage em qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml e aplique o manifesto da carga de trabalho ao cluster:

    envsubst '${GCS_BUCKET}' < qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml | kubectl apply -f -
  3. Para monitorar o carregamento de pesos e a inicialização do servidor, liste os pods no cluster e transmita os registros do pod líder de preenchimento prévio e do pod líder de decodificação:

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

    Substitua YOUR_POD_NAME pelo nome do pod líder de preenchimento prévio ou do pod líder de decodificação da saída kubectl get pods.

    Em uma inicialização a frio, leva aproximadamente de 4 a 5 minutos para transmitir os 2.400.000.000.000 de pesos do modelo NVFP4 (~1,44 TB em 200 fragmentos) do Cloud Storage pelo Cloud Storage FUSE e inicializar os servidores SGLang de vários hosts. Antes de enviar solicitações, aguarde até que os pods de front-end mostrem 1/1 e os pods de preenchimento prévio e decodificação mostrem 2/2 na coluna 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
    

Enviar solicitações de inferência

Envie uma solicitação de conclusão de chat para o serviço Qwen3.8-2.4T implantado:

  1. Encaminhe a porta local 8000 para o serviço qwen3-8-2-4t-a4-disagg-frontend:

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

    Deixe o encaminhamento de portas em execução nesta sessão do terminal ou execute o comando em segundo plano.

  2. Em uma segunda sessão de terminal, envie uma solicitação de conclusão de chat para o 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 .

Executar uma carga de trabalho de comparativo de mercado

Faça um comparativo do servidor Qwen3.8-2.4T implantado no pod de front-end do Dynamo usando sglang.bench_serving com uma configuração de 1.024 tokens de entrada, 1.024 tokens de saída (1k/1k), 256 comandos e uma simultaneidade máxima 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

Depois que o comparativo de mercado for concluído, procure uma saída que contenha métricas como as seguintes:

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

Limpar

Para evitar cobranças contínuas adicionais na sua conta do Google Cloud pelos recursos usados neste tutorial, exclua as cargas de trabalho do Kubernetes, o cluster do GKE e o bucket do Cloud Storage:

  1. Exclua os recursos DynamoGraphDeployment e ResourceClaimTemplate:

    kubectl delete -f qwen3.8-2.4t-nvfp4-multihost-a4-gcsfuse.yaml --ignore-not-found || true
  2. Desinstale as versões do Helm da plataforma NVIDIA Dynamo e do KAI Scheduler:

    helm uninstall dynamo-platform kai-scheduler || true
  3. Exclua o Secret do Kubernetes do Hugging Face:

    kubectl delete secret hf-secret --ignore-not-found || true
  4. No diretório cluster-toolkit, destrua o cluster do GKE e a infraestrutura associada usando o comando gcluster destroy:

    cd cluster-toolkit
    ./gcluster destroy "${CLUSTER_NAME}" --auto-approve || true
    cd ..
  5. Exclua o bucket do Cloud Storage que armazena os pesos do modelo em etapas e o estado do Terraform:

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

A seguir