1. Introducción
En este codelab, aprenderás a implementar servicios de inferencia de vLLM (modelo de lenguaje grande virtual) de alto rendimiento y varios hosts en Google Kubernetes Engine (GKE) con las TPU de Google Cloud. Configurarás la inferencia distribuida con Ray y administrarás la carga de trabajo de forma nativa en GKE con LeaderWorkerSets.
Este recorrido simula una configuración de producción para entregar modelos grandes, como Qwen 30B.
Actividades
- Crea una red de VPC personalizada para el tráfico del acelerador.
- Aprovisiona un clúster de GKE con el operador de Ray y el controlador de CSI de GCS FUSE.
- Inicializa una caché rápida de GCS para acelerar la carga del modelo.
- Aprovisiona un grupo de nodos TPU v6e de varios hosts con capacidad reservada.
- Configura Workload Identity para acceder de forma segura a los pesos del modelo.
- Implementa y prueba el motor de vLLM que entrega un modelo de 30 mil millones de parámetros.

Requisitos
- Un proyecto de Google Cloud con facturación habilitada.
- Una reservación de Google Cloud para recursos de TPU v6e (32 chips,
ct6e-standard-4t). - Acceso para copiar los pesos del modelo desde un bucket de origen
- Cloud Shell o una terminal local con
gcloud,kubectlyhelminstalados
- Duración estimada: 60 minutos
- Costo estimado: Menos de USD 60 (suponiendo que el desmontaje se realice de inmediato).
2. Antes de comenzar
Crea o selecciona un proyecto de Google Cloud
- En la consola de Google Cloud, selecciona o crea un proyecto de Google Cloud.
- Confirma que la facturación está habilitada para tu proyecto de Cloud.
Inicie Cloud Shell
- Haz clic en Activar Cloud Shell en la parte superior de la consola de Google Cloud.
- Verifica la autenticación:
gcloud auth list
- Confirma tu proyecto:
gcloud config get project
- Establécela si es necesario:
export PROJECT_ID=<YOUR_PROJECT_ID>
gcloud config set project $PROJECT_ID
Configura variables de entorno
Para facilitar la ejecución de comandos, define las siguientes variables en tu shell. Reemplaza <YOUR_ZONE> por la zona de TPU asignada y <YOUR_RESERVATION_NAME> por el ID de tu reserva. Deberás crear un token de acceso de usuario de Hugging Face para descargar los pesos del modelo restringido. Una vez que lo hayas creado, reemplaza <YOUR_HUGGING_FACE_TOKEN> por el token que acabas de crear.
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
Habilita las APIs
Habilita los servicios de Google Cloud necesarios:
gcloud services enable \
container.googleapis.com \
compute.googleapis.com \
iam.googleapis.com \
cloudresourcemanager.googleapis.com
3. Crea redes personalizadas
Las cargas de trabajo de TPU de varios hosts requieren configuraciones de red específicas, incluidos tamaños de MTU más altos para una comunicación eficiente del acelerador. Crea una red de VPC personalizada para tu clúster.
- Crea la red de VPC con una MTU grande (8896):
gcloud compute --project=${PROJECT_ID} \ networks create ${GVNIC_NETWORK_PREFIX}-main \ --subnet-mode=custom \ --mtu=8896 - Crea la subred para el clúster:
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 - Crea reglas de firewall que permitan el tráfico interno para que los trabajadores puedan comunicarse:
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. Aprovisiona el clúster de GKE
Crea una configuración de clúster de GKE Standard configurada para admitir Ray Operator y las activaciones de GCS Fuse.
- Crea el clúster:
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 - Retrieve Cluster Credentials:
gcloud container clusters get-credentials ${CLUSTER_NAME} --region=${REGION} - Crea un secreto de Hugging Face: Guarda tu token de forma segura para las descargas de acceso al contenedor:
kubectl create secret generic hf-secret \ --from-literal=hf_api_token=${HF_TOKEN} \ --dry-run=client -o yaml | kubectl apply -f - - Instala LeaderWorkerSet (LWS) a través de Helm. LWS administra grupos de pods que se deben programar juntos:
helm install lws oci://registry.k8s.io/lws/charts/lws \ --version=0.7.0 \ --namespace lws-system \ --create-namespace \ --wait
5. Habilita Rapid Cache de GCS
Para acelerar la lectura de decenas de GB de pesos de Cloud Storage durante la entrega, crea un bucket de GCS y habilita la caché rápida de GCS en tu zona.
- Crea el bucket:
gcloud storage buckets create gs://$BUCKET_NAME \ --location=$REGION \ --uniform-bucket-level-access - Inicializa Rapid Cache en tu zona de TPU:
gcloud storage buckets anywhere-caches create gs://$BUCKET_NAME $ZONE \ --ttl=1d \ --admission-policy=ADMIT_ON_FIRST_MISS
6. Configura Workload Identity y los permisos de almacenamiento
Configura vínculos de identidad para activar de forma segura el bucket de peso en tus Pods de GKE sin incorporar claves de larga duración.
- Crea una cuenta de servicio de IAM dedicada:
gcloud iam service-accounts create tpu-reader-sa - Otorga permisos de lectura del 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" - Crea una vinculación de Workload Identity para la cuenta de servicio de Kubernetes del espacio de nombres
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]" - Anota la SA de Kubernetes:
kubectl annotate serviceaccount default iam.gke.io/gcp-service-account=tpu-reader-sa@${PROJECT_ID}.iam.gserviceaccount.com
7. Configuración de los pesos del modelo
Para entregar un modelo de 30 mil millones de parámetros, debes descargar los pesos de Hugging Face en tu bucket de GCS. Para omitir el límite de cuota de disco de Cloud Shell (5 GB), usa un trabajo estándar de Kubernetes para descargar directamente dentro del clúster y escribir de forma segura en el volumen de GCS FUSE que se haya activado.
- Implementa el trabajo de Model Downloader: Crea y aplica el siguiente manifiesto para iniciar la descarga:
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 - Supervisa la descarga: Verifica los registros del pod del descargador para seguir el progreso:
Espera hasta que el trabajo se complete con el estado de éxito.kubectl logs -f job/model-downloader
8. Crea un grupo de nodos TPU reservado
Aprovisiona la porción de TPU de varios hosts real con tu reserva de capacidad existente.
- Ejecuta el comando de creación:
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 - Espera a que se unan los nodos: Puedes observar el ajuste de escala de la agregación de nodos directamente. Espera hasta que 8 nodos que contengan
ct6ese unan akubectl get nodes.
9. Implementa el servicio de vLLM
- Crear reclamos de red: Debes solicitar el entorno de red:
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 - Implementa el extremo de la API del balanceador de cargas:
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 - Deploy LeaderWorkerSet workload: Este manifiesto inicia la agregación de líder y trabajador de Ray de forma dinámica en los 8 hosts de segmentación.
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. Respuesta de prueba de implementación
Todos los Pods del LeaderWorkerSet pueden tardar entre 5 y 10 minutos en extraer imágenes de contenedores, inicializar Ray y estar completamente Ready. Puedes hacer un seguimiento del estado observando la inicialización del pod:
kubectl get pods -l leaderworkerset.sigs.k8s.io/name=vllm-tpu-qwen -w
Espera hasta que los 8 Pods vllm-tpu-qwen- muestren STATUS como Running y READY como 2/2, y asegúrate de que el balanceador de cargas haya recibido una IP externa antes de continuar. Este proceso puede tardar entre 7 y 10 minutos.
- Recuperar IP externa:
export EXTERNAL_IP=$(kubectl get svc vllm-tpu-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}') echo $EXTERNAL_IP
Precaución: En un servicio de producción, este extremo debe protegerse con algo como Identity-Aware Proxy (IAP).
- Envía la solicitud de inferencia con
curl: Deberías ver un resultado similar a una respuesta JSON que contiene el texto que representa tu inferencia generada.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. Limpia
Para evitar que se apliquen cargos a tu cuenta de Google Cloud, borra los recursos que creaste durante este codelab.
- Borrar grupo de nodos:
gcloud container node-pools delete "${NODE_POOL_NAME}" \ --cluster="${CLUSTER_NAME}" \ --region="${REGION}" \ --project="${PROJECT_ID}" --quiet - Borrar clúster:
gcloud container clusters delete "${CLUSTER_NAME}" \ --region="${REGION}" \ --project="${PROJECT_ID}" --quiet - Borra la configuración de red y 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 - Desvincula y borra la cuenta de servicio:
# 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 - Borra el bucket de GCS. Navega a la consola de Cloud, selecciona Cloud Storage -> Buckets, selecciona inf-demo-model-storage y, luego, elige "Borrar".
12. ¡Felicitaciones!
¡Felicitaciones! Implementaste correctamente una pila de vLLM con una alta tasa de inferencia de TPU de varios hosts que utiliza Ray de forma nativa en Google Kubernetes Engine.
Qué aprendiste
- Aprovisionamiento de rutas personalizadas diseñadas para el tráfico de TPU de alta velocidad
- Se ajustan los pesos con GCS Fuse y cachés rápidas regionales.
- Organizar porciones de cargas de trabajo de varios hosts sincronizadas de forma nativa a través de LeaderWorkerSets
- Para obtener más información, consulta la Guía del usuario de vLLM y las Guías de implementación de llm-d.