Disponibilizar o Qwen3-30B-A3B no GKE com o vLLM TorchTPU na TPU v6e

Neste tutorial, mostramos como implantar e disponibilizar o modelo de mix de especialistas Qwen3-30B-A3B (Qwen/Qwen3-30B-A3B-FP8) no Google Kubernetes Engine (GKE) usando um acelerador TPU v6e (Trillium) de host único e o framework de disponibilização de TPU vLLM com o TorchTPU (torchtpu). Você executa a carga de trabalho em um único nó ct6e-standard-4t, que contém quatro chips de TPU v6e em uma topologia 2x2, com paralelismo de dados e paralelismo de especialistas ativados.

Para executar um fluxo de trabalho de disponibilização de TorchTPU vLLM de host único semelhante em aceleradores TPU7x (Ironwood) com o modelo Qwen3.5-397B maior, consulte Disponibilizar o Qwen3.5-397B no GKE com o vLLM TorchTPU em aceleradores TPU7x.

Objetivos

  • Configure o cluster do GKE e o pool de nós da TPU v6e de host único.
  • Implantar o servidor TorchTPU do vLLM de host único no GKE.
  • Envie uma solicitação de inferência ao servidor vLLM TorchTPU.
  • Executar uma carga de trabalho de comparativo de mercado de exibição.
  • Fazer a limpeza.

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.

Ao concluir as tarefas descritas neste documento, é possível evitar o faturamento contínuo excluindo os recursos criados. Para mais informações, consulte Limpeza.

Antes de começar

Configure o ambiente para o tutorial:

  • Ative a API Kubernetes Engine no projeto do Google Cloud :

    Ativar a API Kubernetes Engine

  • Ative a API Artifact Registry no projeto do Google Cloud :

    Ative a API Artifact Registry

  • Instale e inicialize a Google Cloud CLI e instale kubectl. Se você usa o Cloud Shell, as duas ferramentas já estão pré-instaladas.

  • Verifique se o projeto tem cota de SSD e cota de endereço IP externo suficientes na região em que a capacidade da TPU está localizada:

    1. No console do Google Cloud , acesse a página Cotas e limites do sistema.

      Acesse Cotas e limites do sistema

    2. Filtre por SSD de disco permanente (GB) e verifique se você tem pelo menos 500 GB de cota disponível para o volume de cache do modelo.

    3. Filtre por Endereços IPv4 externos regionais em uso e verifique se você tem cota de endereço IP suficiente na região de destino.

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

Configure sua autenticação:

gcloud auth login
gcloud auth application-default login

Configure as variáveis de ambiente:

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

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

Substitua:

  • YOUR_PROJECT_ID: ID do seu projeto do Google Cloud.
  • YOUR_REGION: a região em que você quer implantar o cluster, como us-central1.
  • YOUR_ZONE: a zona em que você quer implantar o pool de nós da TPU v6e, como us-central1-c.
  • YOUR_CLUSTER_NAME: o nome do cluster do GKE, como tpu-v6e-tutorial-cluster.
  • YOUR_RESERVATION_NAME: se você estiver usando uma reserva, o nome da sua reserva de TPU v6e.
  • YOUR_HF_TOKEN: seu token de acesso do Hugging Face.
  • YOUR_NETWORK_NAME: o nome da rede Google Cloud . Se você criou uma rede personalizada, especifique essa rede. Caso contrário, especifique default.
  • YOUR_SUBNETWORK_NAME: o nome da sub-rede Google Cloud . Se você criou uma sub-rede personalizada, especifique-a. Caso contrário, especifique default.
  • YOUR_VLLM_TPU_IMAGE: a imagem do TorchTPU do vLLM a ser usada. Para imagens públicas, consulte o repositório vLLM TorchTPU.

Criar o cluster do GKE

Para criar um cluster do GKE no modo padrão e anexar um pool de nós de TPU v6e de host único, siga estas etapas:

  1. Crie um cluster regional do GKE:

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

    O tipo de máquina padrão para clusters do GKE (e2-medium) não tem capacidade de CPU suficiente para controladores de carga de trabalho. Por isso, esse comando especifica e2-standard-4.

    O resultado será o seguinte:

    NAME                      LOCATION     MASTER_VERSION      MASTER_IP      MACHINE_TYPE   NODE_VERSION        NUM_NODES  STATUS
    tpu-v6e-tutorial-cluster  us-central1  1.35.7-gke.1027000  34.63.222.246  e2-standard-4  1.35.7-gke.1027000  3          RUNNING
    
  2. Crie um pool de nós de TPU v6e de host único com um nó ct6e-standard-4t (quatro chips de TPU v6e em uma topologia 2x2) usando sua reserva:

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

    Em vez de usar uma reserva específica, também é possível criar o pool de nós usando a capacidade spot ou as VMs flex-start transmitindo as seguintes flags:

    • VMs spot: substitua as flags --reservation-affinity e --reservation por --spot para usar a capacidade de spot. As instâncias que usam a capacidade spot podem levar algum tempo para serem criadas após a criação do pool de nós e podem ser interrompidas durante as execuções da carga de trabalho. Se os pods demorarem para ser criados ou as cargas de trabalho pararem no meio da execução, essa pode ser a causa.
    • VMs flex-start: para provisionar o pool de nós usando a opção de consumo de início flexível, transmita a flag --flex-start. Para mais informações, consulte Executar uma carga de trabalho em lote pequena com TPUs e VMs de início flexível.

    O resultado será o seguinte:

    NAME  MACHINE_TYPE      DISK_SIZE_GB  NODE_VERSION
    np-0  ct6e-standard-4t  100           1.35.7-gke.1027000
    
  3. Configure as credenciais de kubectl para se comunicar com o cluster:

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

Configurar credenciais

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 -

Implantar o servidor TorchTPU do vLLM

O manifesto da carga de trabalho (qwen3_5_server.yaml) define os seguintes recursos:

  • Um StorageClass (hyperdisk-balanced-qwen3) e um PersistentVolumeClaim de 500 GiB (hd-claim-qwen3) com suporte do armazenamento do Google Cloud Hyperdisk Balanced para armazenar os pesos do modelo baixado, o cache de compilação do XLA e o cache de compilação do PyTorch.

  • Uma implantação (vllm-qwen3) programada em um único nó de TPU v6e com uma topologia 2x2 (google.com/tpu: '4'). O contêiner executa vllm serve para Qwen/Qwen3-30B-A3B-FP8 com as seguintes flags:

    • --data-parallel-size=4
    • --tensor-parallel-size=1
    • --enable-expert-parallel
    • --kv-cache-dtype=fp8
    • --max-model-len=9236
    • --max-num-seqs=64.
  • Um serviço ClusterIP (vllm-qwen3-service) que expõe a porta 8000.

Para implantar e monitorar a carga de trabalho, siga estas etapas:

  1. Busque o manifesto do servidor:

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

    Como alternativa, você pode criar um arquivo chamado qwen3_5_server.yaml com o seguinte conteúdo:

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

    envsubst '$VLLM_IMAGE' < "qwen3_5_server.yaml" | kubectl apply -f -
  3. Monitore os registros de implantação do servidor até que a inicialização do aplicativo seja concluída:

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

    A inicialização do serviço pode levar de 15 a 30 minutos enquanto o pod faz o download dos pesos do modelo e o compila.

    Se você estiver usando capacidade preemptiva, como Spot ou início flexível, pode levar mais tempo após a criação do pool de nós para que os nós de fração de TPU sejam criados, e eles podem ser forçados a interrupção durante as execuções de carga de trabalho. Se os pods estiverem demorando mais do que o esperado para iniciar ou se as cargas de trabalho pararem no meio, essa pode ser a causa. Para mais informações, consulte Preempção de VMs spot. Para verificar se os nós de fração de TPU foram criados, consulte Verificar o status e as condições do nó.

    Aguarde até que os registros informem Application startup complete antes de enviar tráfego:

    (APIServer pid=1) INFO:     Started server process [1]
    (APIServer pid=1) INFO:     Waiting for application startup.
    (APIServer pid=1) INFO:     Application startup complete.
    
  4. Abra uma segunda sessão de terminal.

  5. Na segunda sessão do terminal, encaminhe a porta local 8000 para vllm-qwen3-service:

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

    Deixe o encaminhamento de portas em execução nessa sessão do terminal.

Enviar solicitações de inferência

Volte para a primeira sessão do terminal e envie uma solicitação de conclusão de chat para o endpoint /v1/chat/completions:

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

Executar uma carga de trabalho de comparativo de mercado

Para comparar o modelo Qwen3-30B-A3B implantado usando uma carga de trabalho de entrada de 8.192 tokens e saída de 1.024 tokens (8k/1k) com o conjunto de comparativos de mercado InferenceX, siga estas etapas:

  1. Busque o manifesto de comparativo de mercado:

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

    Como alternativa, crie um arquivo chamado qwen3_5-benchmark-8k1k.yaml com o seguinte conteúdo:

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

    envsubst '$VLLM_IMAGE' < "qwen3_5-benchmark-8k1k.yaml" | kubectl apply -f -
  3. Monitore os registros de comparativo de mercado:

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

    O comparativo de mercado leva aproximadamente de 25 a 30 minutos para ser concluído.

  4. 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 extras na sua conta do Google Cloud pelos recursos usados neste tutorial, exclua as cargas de trabalho do Kubernetes, o pool de nós da TPU e o cluster do GKE:

  1. Exclua a implantação, o serviço, o PersistentVolumeClaim e o secret do Hugging Face do servidor TorchTPU do vLLM:

    kubectl delete -f qwen3_5_server.yaml --ignore-not-found || true
    kubectl delete secret hf-secret --ignore-not-found || true
  2. Exclua o pool de nós da TPU v6e e o cluster do GKE:

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

A seguir