1. Introdução
Neste codelab, você vai aprender a implantar serviços de inferência de vLLM (modelo de linguagem grande virtual) de alto desempenho e vários hosts no Google Kubernetes Engine (GKE) usando as TPUs do Google Cloud. Você vai configurar a inferência distribuída usando o Ray e gerenciar a carga de trabalho nativamente no GKE usando o LeaderWorkerSets.
Este tutorial simula uma configuração de produção para disponibilizar modelos grandes, como o Qwen 30B.
Atividades deste laboratório
- Criar uma rede VPC personalizada para tráfego de acelerador.
- Provisionar um cluster do GKE com o operador Ray e o driver CSI do GCS Fuse.
- Inicializar um Rapid Cache do GCS para carregamento acelerado de modelos.
- Provisionar um pool de nós de TPU v6e de vários hosts com capacidade reservada.
- Configurar a Identidade da carga de trabalho para acesso seguro aos pesos do modelo.
- Implantar e testar o mecanismo vLLM que disponibiliza um modelo de parâmetro 30B.

O que é necessário
- Ter um projeto do Google Cloud com o faturamento ativado.
- Uma reserva do Google Cloud para recursos de TPU v6e (32 chips,
ct6e-standard-4t). - Acesso para copiar pesos de modelo de um bucket de origem.
- Cloud Shell ou um terminal local com
gcloud,kubectlehelminstalados.
- Duração estimada:60 minutos
- Custo estimado:menos de US $60 (supondo que a desmontagem seja feita imediatamente).
2. Antes de começar
Criar ou selecionar um projeto do Google Cloud
- No Console do Google Cloud, selecione ou crie um projeto na nuvem do Google Cloud.
- Verifique se o faturamento está ativado no seu projeto na nuvem.
Iniciar o Cloud Shell
- Clique em Ativar o Cloud Shell na parte de cima do console do Google Cloud.
- Verifique a autenticação:
gcloud auth list
- Confirme seu projeto:
gcloud config get project
- Defina-o, se necessário:
export PROJECT_ID=<YOUR_PROJECT_ID>
gcloud config set project $PROJECT_ID
Definir variáveis de ambiente
Para facilitar a execução de comandos, defina as seguintes variáveis no shell. Substitua <YOUR_ZONE> pela zona de TPU alocada e <YOUR_RESERVATION_NAME> pelo ID da reserva. Você precisará criar um token de acesso de usuário do Hugging Face para baixar pesos de modelo restritos. Depois de criar, substitua <YOUR_HUGGING_FACE_TOKEN> pelo token recém-criado.
export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format="value(projectNumber)")
export ZONE="<YOUR_ZONE>" # e.g., us-east5-a
export REGION=${ZONE%-*}
export CLUSTER_NAME="qwen-serving-cluster"
export GVNIC_NETWORK_PREFIX="qwen-serving"
export BUCKET_NAME="inf-demo-model-storage-${PROJECT_NUMBER}"
export RESERVATION_NAME="<YOUR_RESERVATION_NAME>"
export NODE_POOL_NAME="tpu-v6e-32-resvd-pool"
export MULTIHOST_COLLECTION_NAME="tpu-6-collection"
export HF_TOKEN="<YOUR_HUGGING_FACE_TOKEN>" # Token with access to Qwen model if restricted
Ativar APIs
Ative os serviços necessários do Google Cloud:
gcloud services enable \
container.googleapis.com \
compute.googleapis.com \
iam.googleapis.com \
cloudresourcemanager.googleapis.com
3. Criar rede personalizada
As cargas de trabalho de TPU de vários hosts exigem configurações de rede específicas, incluindo tamanhos de MTU maiores para comunicação eficiente do acelerador. Crie uma rede VPC personalizada para o cluster.
- Crie a rede VPC com um MTU grande (8896):
gcloud compute --project=${PROJECT_ID} \ networks create ${GVNIC_NETWORK_PREFIX}-main \ --subnet-mode=custom \ --mtu=8896 - Crie a sub-rede para o cluster:
gcloud compute --project=${PROJECT_ID} \ networks subnets create ${GVNIC_NETWORK_PREFIX}-tpu \ --network=${GVNIC_NETWORK_PREFIX}-main \ --region=${REGION} \ --range=192.168.100.0/24 - Crie regras de firewall que permitam o tráfego interno para que os workers possam se comunicar:
gcloud compute --project=${PROJECT_ID} firewall-rules create ${GVNIC_NETWORK_PREFIX}-allow-internal \ --network=${GVNIC_NETWORK_PREFIX}-main \ --allow=all \ --source-ranges=172.16.0.0/12,192.168.0.0/16,10.0.0.0/8 \ --description="Allow all internal traffic within the network."
4. Provisionar cluster do GKE
Crie uma configuração de cluster do GKE padrão configurada para oferecer suporte a montagens do GCS Fuse e cargas de trabalho do operador Ray.
- Crie o cluster:
gcloud container clusters create ${CLUSTER_NAME} \ --project=${PROJECT_ID} \ --location=${REGION} \ --release-channel=rapid \ --machine-type=e2-standard-4 \ --network=${GVNIC_NETWORK_PREFIX}-main \ --subnetwork=${GVNIC_NETWORK_PREFIX}-tpu \ --num-nodes=1 \ --gateway-api=standard \ --enable-managed-prometheus \ --enable-dataplane-v2 \ --enable-dataplane-v2-metrics \ --workload-pool=${PROJECT_ID}.svc.id.goog \ --addons=GcsFuseCsiDriver,RayOperator \ --enable-ip-alias - Recupere as credenciais do cluster:
gcloud container clusters get-credentials ${CLUSTER_NAME} --region=${REGION} - Crie o secret do Hugging Face: salve seu token com segurança para downloads de acesso ao contêiner:
kubectl create secret generic hf-secret \ --from-literal=hf_api_token=${HF_TOKEN} \ --dry-run=client -o yaml | kubectl apply -f - - Instale o LeaderWorkerSet (LWS) pelo Helm. O LWS gerencia grupos de pods que precisam ser programados juntos:
helm install lws oci://registry.k8s.io/lws/charts/lws \ --version=0.7.0 \ --namespace lws-system \ --create-namespace \ --wait
5. Ativar o Rapid Cache do GCS
Para acelerar a leitura de dezenas de GBs de pesos do Cloud Storage durante a disponibilização, crie um bucket do GCS e ative o Rapid Cache do GCS na sua zona.
- Crie o bucket:
gcloud storage buckets create gs://$BUCKET_NAME \ --location=$REGION \ --uniform-bucket-level-access - Inicialize o Rapid Cache na sua zona de TPU:
gcloud storage buckets anywhere-caches create gs://$BUCKET_NAME $ZONE \ --ttl=1d \ --admission-policy=ADMIT_ON_FIRST_MISS
6. Configurar a Identidade da carga de trabalho e as permissões de armazenamento
Configure links de identidade para montar o bucket de peso com segurança nos pods do GKE sem incorporar chaves de longa duração.
- Crie uma conta de serviço do IAM dedicada:
gcloud iam service-accounts create tpu-reader-sa - Conceda permissões de leitura do bucket:
gcloud storage buckets add-iam-policy-binding gs://${BUCKET_NAME} \ --member="serviceAccount:tpu-reader-sa@${PROJECT_ID}.iam.gserviceaccount.com" \ --role="roles/storage.objectAdmin" - Crie a vinculação da Identidade da carga de trabalho para a conta de serviço do Kubernetes do namespace
default:gcloud iam service-accounts add-iam-policy-binding tpu-reader-sa@${PROJECT_ID}.iam.gserviceaccount.com \ --role="roles/iam.workloadIdentityUser" \ --member="serviceAccount:${PROJECT_ID}.svc.id.goog[default/default]" - Anote a SA do Kubernetes:
kubectl annotate serviceaccount default iam.gke.io/gcp-service-account=tpu-reader-sa@${PROJECT_ID}.iam.gserviceaccount.com
7. Configuração de pesos do modelo
Para disponibilizar um modelo de parâmetro 30B, é necessário baixar pesos do Hugging Face para o bucket do GCS. Para ignorar o limite de cota de disco do Cloud Shell (5 GB), use um job do Kubernetes padrão para fazer o download diretamente no cluster e gravar no volume montado do GCS Fuse com segurança.
- Implante o job do downloader de modelos: crie e aplique o seguinte manifesto para iniciar o download:
cat <<EOF | kubectl apply -f - apiVersion: batch/v1 kind: Job metadata: name: model-downloader spec: ttlSecondsAfterFinished: 60 template: metadata: annotations: gke-gcsfuse/volumes: "true" gke-gcsfuse/memory-limit: "0" spec: serviceAccountName: default restartPolicy: OnFailure containers: - name: downloader image: python:3.10-slim command: ["/bin/sh", "-c"] args: - | pip install -U "huggingface_hub[hf_transfer]" filelock export HF_HUB_ENABLE_HF_TRANSFER=1 python -c ' import filelock class DummyLock: def __init__(self, *args, **kwargs): pass def __enter__(self): return self def __exit__(self, *args): pass def acquire(self, *args, **kwargs): pass def release(self, *args, **kwargs): pass filelock.FileLock = DummyLock from huggingface_hub import snapshot_download snapshot_download( repo_id="Qwen/Qwen3-30B-A3B", local_dir="/models/qwen3-30b-weights", local_dir_use_symlinks=False ) ' env: - name: HF_TOKEN valueFrom: secretKeyRef: name: hf-secret key: hf_api_token volumeMounts: - name: model-weights mountPath: /models volumes: - name: model-weights csi: driver: gcsfuse.csi.storage.gke.io volumeAttributes: bucketName: ${BUCKET_NAME} mountOptions: "implicit-dirs" EOF - Monitore o download: verifique os registros do pod do downloader para acompanhar o progresso:
Aguarde até que o job seja concluído com o status de sucesso.kubectl logs -f job/model-downloader
8. Criar pool de nós de TPU reservados
Provisione a fração de TPU de vários hosts usando a reserva de capacidade atual.
- Execute o comando de criação:
gcloud beta container node-pools create ${NODE_POOL_NAME} \ --project=${PROJECT_ID} \ --cluster=${CLUSTER_NAME} \ --region=${REGION} \ --node-locations=${ZONE} \ --machine-type=ct6e-standard-4t \ --tpu-topology=4x8 \ --num-nodes=8 \ --scopes=https://www.googleapis.com/auth/cloud-platform \ --reservation-affinity=specific \ --reservation=${RESERVATION_NAME} \ --accelerator-network-profile=auto \ --node-labels=cloud.google.com/gke-nodepool-group-name=${MULTIHOST_COLLECTION_NAME} \ --node-labels=cloud.google.com/gke-workload-type=HIGH_AVAILABILITY \ --node-labels=cloud.google.com/gke-networking-dra-driver=true - Aguarde os nós entrarem: é possível observar o escalonamento da agregação de nós diretamente. Aguarde até que oito nós que contenham
ct6eentrem emkubectl get nodes.
9. Implantar o serviço vLLM
- Crie declarações de rede: é necessário solicitar o ambiente de rede:
cat <<EOF | kubectl apply -f - apiVersion: resource.k8s.io/v1 kind: ResourceClaimTemplate metadata: name: all-netdev spec: spec: devices: requests: - name: req-netdev exactly: deviceClassName: netdev.google.com allocationMode: All EOF - Implante o endpoint da API do balanceador de carga:
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: vllm-tpu-service spec: type: LoadBalancer selector: leaderworkerset.sigs.k8s.io/name: vllm-tpu-qwen leaderworkerset.sigs.k8s.io/worker-index: "0" ports: - protocol: TCP port: 8000 targetPort: 8000 EOF - Implante a carga de trabalho do LeaderWorkerSet: esse manifesto inicia a agregação de head/worker do Ray de forma dinâmica nos oito hosts de fração.
cat <<EOF | kubectl apply -f - apiVersion: leaderworkerset.x-k8s.io/v1 kind: LeaderWorkerSet metadata: name: vllm-tpu-qwen spec: replicas: 1 leaderWorkerTemplate: size: 8 restartPolicy: RecreateGroupOnPodRestart workerTemplate: metadata: annotations: gke-gcsfuse/volumes: "true" gke-gcsfuse/memory-limit: "0" labels: leaderworkerset.sigs.k8s.io/name: vllm-tpu-qwen gke-gcsfuse/volumes: "true" spec: hostname: vllm-tpu-qwen serviceAccountName: default containers: - name: vllm-tpu image: vllm/vllm-tpu:nightly command: ["sh", "-c"] args: - | MY_TPU_IP=\$(hostname -I | awk '{print \$1}') echo "My TPU Network IP is: \$MY_TPU_IP" LEADER_DNS="vllm-tpu-qwen-0.vllm-tpu-qwen" until getent hosts \$LEADER_DNS; do echo "DNS not ready. Sleeping 5s..." sleep 5 done LEADER_IP=\$(getent hosts \$LEADER_DNS | awk '{print \$1}') export JAX_PLATFORMS='' export SCAN_TPU_CHIPS=True export TPU_MULTIHOST_BACKEND=ray export JAX_DISTRIBUTED_INITIALIZATION_TIMEOUT=300 export LD_LIBRARY_PATH=\$LD_LIBRARY_PATH:/usr/local/lib export VLLM_HOST_IP=\$MY_TPU_IP if [ "\$LWS_WORKER_INDEX" = "0" ]; then echo "Starting Ray Head..." ray start --head --port=6379 --node-ip-address=\$MY_TPU_IP --resources='{"TPU": 4}' --block & sleep 20 until ray status; do sleep 5; done echo "Starting vLLM API Server..." python3 -m vllm.entrypoints.openai.api_server \ --model=/models/qwen3-30b-weights \ --tensor-parallel-size=32 \ --pipeline-parallel-size=1 \ --distributed-executor-backend=ray \ --host=0.0.0.0 --port=8000 \ --enforce-eager \ --gpu-memory-utilization=0.90 else ray start --address=\${LEADER_IP}:6379 --node-ip-address=\$MY_TPU_IP --resources='{"TPU": 4}' --block fi ports: - containerPort: 8000 - containerPort: 6379 volumeMounts: - name: model-weights mountPath: /models readOnly: true - name: dshm mountPath: /dev/shm resources: claims: - name: net-resources limits: google.com/tpu: 4 memory: "100Gi" requests: google.com/tpu: 4 memory: "100Gi" nodeSelector: cloud.google.com/gke-tpu-accelerator: tpu-v6e-slice cloud.google.com/gke-tpu-topology: 4x8 gke.networks.io/accelerator-network-profile: auto resourceClaims: - name: net-resources resourceClaimTemplateName: all-netdev volumes: - name: model-weights csi: driver: gcsfuse.csi.storage.gke.io volumeAttributes: bucketName: ${BUCKET_NAME} mountOptions: "implicit-dirs" - name: dshm emptyDir: medium: Memory EOF
10. Resposta do teste de implantação
Pode levar de 5 a 10 minutos para que todos os pods no LeaderWorkerSet extraiam imagens de contêiner, inicializem o Ray e fiquem totalmente Ready. É possível acompanhar o status observando a inicialização do pod:
kubectl get pods -l leaderworkerset.sigs.k8s.io/name=vllm-tpu-qwen -w
Aguarde até que todos os oito pods vllm-tpu-qwen- mostrem STATUS como Running e READY como 2/2 e verifique se o balanceador de carga recebeu um IP externo antes de continuar. Isso pode levar de 7 a 10 minutos.
- Recupere o IP externo:
export EXTERNAL_IP=$(kubectl get svc vllm-tpu-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}') echo $EXTERNAL_IP
Atenção: em um serviço de produção, esse endpoint precisa ser protegido com algo como o Identity-Aware Proxy (IAP).
- Envie a solicitação de inferência usando
curl: Você verá uma saída semelhante a uma resposta JSON contendo o texto que representa sua inferência gerada.curl -N -s http://$EXTERNAL_IP:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "/models/qwen3-30b-weights", "messages": [{"role": "user", "content": "Write a haiku about high-performance computing on TPUs."}], "temperature": 0.7, "max_tokens": 100, "stream": true }' | sed 's/^data: //' | grep -v '\[DONE\]' | grep -v '^$' | jq -rj '.choices[0].delta.content // empty' ; echo ""
11. Limpar
Para evitar cobranças contínuas na sua conta do Google Cloud, exclua os recursos criados durante este codelab.
- Excluir pool de nós:
gcloud container node-pools delete "${NODE_POOL_NAME}" \ --cluster="${CLUSTER_NAME}" \ --region="${REGION}" \ --project="${PROJECT_ID}" --quiet - Excluir cluster:
gcloud container clusters delete "${CLUSTER_NAME}" \ --region="${REGION}" \ --project="${PROJECT_ID}" --quiet - Excluir configurações de rede e firewall:
gcloud compute firewall-rules delete \ "${GVNIC_NETWORK_PREFIX}-allow-internal" \ --project="${PROJECT_ID}" --quiet gcloud compute networks subnets delete "${GVNIC_NETWORK_PREFIX}-tpu" \ --region="${REGION}" --quiet gcloud compute networks delete "${GVNIC_NETWORK_PREFIX}-main" --quiet - Desvincular e excluir conta de serviço:
# 1. Create the cleanup script cat << 'EOF' > clean_up_sa.sh #!/bin/bash # Validate that PROJECT_ID is available if [ -z "$PROJECT_ID" ]; then echo "Error: PROJECT_ID environment variable is not set." exit 1 fi SA_EMAIL="tpu-reader-sa@${PROJECT_ID}.iam.gserviceaccount.com" SA_MEMBER="serviceAccount:${SA_EMAIL}" echo "Gathering IAM policy for ${SA_EMAIL}..." # Fetch roles assigned to this specific SA ROLES=$(gcloud projects get-iam-policy ${PROJECT_ID} \ --flatten="bindings[].members" \ --filter="bindings.members:${SA_MEMBER}" \ --format="value(bindings.role)") if [ -z "$ROLES" ]; then echo "No IAM bindings found for this service account." else for ROLE in $ROLES; do echo "Removing binding for: ${ROLE}..." gcloud projects remove-iam-policy-binding ${PROJECT_ID} \ --member="${SA_MEMBER}" \ --role="${ROLE}" --quiet > /dev/null done echo "Successfully unbound all roles." fi # 2. Delete the service account itself echo "Deleting service account..." gcloud iam service-accounts delete ${SA_EMAIL} --project=${PROJECT_ID} --quiet echo "Cleanup complete." EOF # 2. Make the script executable and run it chmod +x clean_up_sa.sh ./clean_up_sa.sh - Excluir bucket do GCS : acesse o console do Cloud, selecione Cloud Storage > Buckets, selecione inf-demo-model-storage e escolha "Excluir".
12. Parabéns
Parabéns! Você implantou com sucesso uma pilha de vLLM de alta taxa de inferência de TPU de vários hosts usando o Ray nativamente no Google Kubernetes Engine.
O que você aprendeu
- Provisionamento de caminhos personalizados para tráfego de TPU de alta velocidade.
- Montagem de pesos usando o GCS Fuse e caches rápidos regionais.
- Orquestração de frações de carga de trabalho de vários hosts sincronizadas nativamente pelo LeaderWorkerSets.
- Para saber mais, consulte o guia do usuário do vLLM e os guias de implantação do llm-d.