Diffuser un LLM à l'aide de TPU Trillium sur GKE avec vLLM

Ce tutoriel explique comment diffuser des grands modèles de langage (LLM) à l'aide des TPU (Tensor Processing Units) sur Google Kubernetes Engine (GKE) avec le framework de diffusion vLLM. Dans ce tutoriel, vous allez diffuser Llama 3.1 70b, utiliser TPU Trillium et configurer l'autoscaling horizontal des pods à l'aide des métriques du serveur vLLM.

Ce document est un bon point de départ si vous avez besoin du contrôle précis, de l'évolutivité, de la résilience, de la portabilité et de la rentabilité des services Kubernetes gérés lors du déploiement et de la diffusion de vos charges de travail d'IA/de ML.

Arrière-plan

En utilisant TPU Trillium sur GKE, vous pouvez mettre en œuvre une solution de diffusion robuste et prête pour la production avec tous les avantages de Kubernetes géré, y compris une évolutivité efficace et une meilleure disponibilité. Cette section décrit les principales technologies utilisées dans ce guide.

TPU Trillium

Les TPU sont des circuits intégrés propres à une application (ASIC) développés spécifiquement par Google. Les TPU permettent d'accélérer le machine learning et les modèles d'IA créés à l'aide de frameworks tels que TensorFlow, PyTorch et JAX. Ce tutoriel utilise le TPU Trillium, qui est la sixième génération de TPU de Google.

Avant d'utiliser des TPU dans GKE, nous vous recommandons de suivre le parcours de formation suivant :

  1. Découvrez l'architecture système de TPU Trillium.
  2. Apprenez-en plus sur les TPU dans GKE.

vLLM

vLLM est un framework Open Source hautement optimisé pour la diffusion de LLM. vLLM peut augmenter le débit de diffusion sur les TPU, avec des fonctionnalités telles que les suivantes :

  • Implémentation optimisée du transformateur avec PagedAttention.
  • Traitement par lots continu pour améliorer le débit global de diffusion.
  • Parallélisme Tensor et diffusion distribuée sur plusieurs TPU.

Pour en savoir plus, consultez la documentation vLLM.

Cloud Storage FUSE

Cloud Storage FUSE permet à votre cluster GKE d'accéder à Cloud Storage pour les pondérations de modèle qui résident dans des buckets de stockage d'objets. Dans ce tutoriel, le bucket Cloud Storage créé sera initialement vide. Lorsque vLLM démarre, GKE télécharge le modèle depuis Hugging Face et met en cache les pondérations dans le bucket Cloud Storage. Lors du redémarrage du pod ou de l'augmentation de l'échelle du déploiement, les chargements de modèles suivants téléchargeront les données mises en cache à partir du bucket Cloud Storage, en tirant parti des téléchargements parallèles pour des performances optimales.

Pour en savoir plus, consultez la documentation du pilote CSI Cloud Storage FUSE.

Créer un cluster GKE

Vous pouvez diffuser les LLM sur des TPU dans un cluster GKE Autopilot ou GKE Standard. Nous vous recommandons d'utiliser un cluster GKE Autopilot pour une expérience Kubernetes entièrement gérée. Pour choisir le mode de fonctionnement GKE le mieux adapté à vos charges de travail, consultez Choisir un mode de fonctionnement GKE.

Autopilot

  1. Créez un cluster GKE Autopilot :

    gcloud container clusters create-auto ${CLUSTER_NAME} \
        --cluster-version=${CLUSTER_VERSION} \
        --location=${CONTROL_PLANE_LOCATION}
    

Standard

  1. Créez un cluster GKE Standard :

    gcloud container clusters create ${CLUSTER_NAME} \
        --project=${PROJECT_ID} \
        --location=${CONTROL_PLANE_LOCATION} \
        --node-locations=${ZONE} \
        --cluster-version=${CLUSTER_VERSION} \
        --workload-pool=${PROJECT_ID}.svc.id.goog \
        --addons GcsFuseCsiDriver
    
  2. Créez un pool de nœuds de tranche TPU :

    gcloud container node-pools create tpunodepool \
        --location=${CONTROL_PLANE_LOCATION} \
        --node-locations=${ZONE} \
        --num-nodes=1 \
        --machine-type=ct6e-standard-8t \
        --cluster=${CLUSTER_NAME} \
        --enable-autoscaling --total-min-nodes=1 --total-max-nodes=2
    

    GKE crée les ressources suivantes pour le LLM :

Configurer kubectl pour communiquer avec votre cluster

Pour configurer kubectl de manière à communiquer avec votre cluster, exécutez la commande suivante :

  gcloud container clusters get-credentials ${CLUSTER_NAME} --location=${CONTROL_PLANE_LOCATION}

Créer un secret Kubernetes pour les identifiants Hugging Face

  1. Créez un espace de noms. Vous pouvez ignorer cette étape si vous utilisez l'espace de noms default :

    kubectl create namespace ${NAMESPACE}
    
  2. Créez un secret Kubernetes contenant le jeton Hugging Face en exécutant la commande suivante :

    kubectl create secret generic hf-secret \
        --from-literal=hf_api_token=${HF_TOKEN} \
        --namespace ${NAMESPACE}
    

Créer un bucket Cloud Storage

Dans Cloud Shell, exécutez la commande suivante :

gcloud storage buckets create gs://${GSBUCKET} \
    --uniform-bucket-level-access

Cette opération crée un bucket Cloud Storage pour stocker les fichiers de modèle que vous téléchargez depuis Hugging Face.

Configurer un compte de service Kubernetes pour accéder au bucket

  1. Créez le compte de service Kubernetes :

    kubectl create serviceaccount ${KSA_NAME} --namespace ${NAMESPACE}
    
  2. Accordez l'accès en lecture et en écriture au compte de service Kubernetes pour accéder au bucket Cloud Storage :

    gcloud storage buckets add-iam-policy-binding gs://${GSBUCKET} \
      --member "principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${PROJECT_ID}.svc.id.goog/subject/ns/${NAMESPACE}/sa/${KSA_NAME}" \
      --role "roles/storage.objectUser"
    
  3. Vous pouvez également accorder un accès en lecture et en écriture à tous les buckets Cloud Storage du projet :

    gcloud projects add-iam-policy-binding ${PROJECT_ID} \
    --member "principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${PROJECT_ID}.svc.id.goog/subject/ns/${NAMESPACE}/sa/${KSA_NAME}" \
    --role "roles/storage.objectUser"
    

    GKE crée les ressources suivantes pour le LLM :

    1. Un bucket Cloud Storage pour stocker le modèle téléchargé et le cache de compilation. Un pilote CSI Cloud Storage FUSE lit le contenu du bucket.
    2. Volumes avec la mise en cache des fichiers activée et la fonctionnalité de téléchargement parallèle de Cloud Storage FUSE.
    Bonne pratique :

    Utilisez un cache de fichiers soutenu par tmpfs ou Hyperdisk / Persistent Disk en fonction de la taille attendue du contenu du modèle (fichiers de poids, par exemple). Dans ce tutoriel, vous allez utiliser le cache de fichiers Cloud Storage FUSE basé sur la RAM.

Déployer un serveur de modèles vLLM

Pour déployer le serveur de modèle vLLM, ce tutoriel utilise un déploiement Kubernetes. Un déploiement est un objet de l'API Kubernetes qui vous permet d'exécuter plusieurs instances dupliquées de pods répartis entre les nœuds d'un cluster.

  1. Examinez le fichier manifeste de déploiement suivant enregistré sous le nom vllm-llama3-70b.yaml, qui utilise une seule réplique :

    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: vllm-tpu
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: vllm-tpu
      template:
        metadata:
          labels:
            app: vllm-tpu
          annotations:
            gke-gcsfuse/volumes: "true"
            gke-gcsfuse/cpu-limit: "0"
            gke-gcsfuse/memory-limit: "0"
            gke-gcsfuse/ephemeral-storage-limit: "0"
        spec:
          serviceAccountName: KSA_NAME
          nodeSelector:
            cloud.google.com/gke-tpu-topology: 2x4
            cloud.google.com/gke-tpu-accelerator: tpu-v6e-slice
          containers:
          - name: vllm-tpu
            image: vllm/vllm-tpu:latest
            command: ["python3", "-m", "vllm.entrypoints.openai.api_server"]
            args:
            - --host=0.0.0.0
            - --port=8000
            - --tensor-parallel-size=8
            - --max-model-len=4096
            - --model=meta-llama/Llama-3.1-70B
            - --download-dir=/data
            - --max-num-batched-tokens=512
            - --max-num-seqs=128
            env: 
            - name: HUGGING_FACE_HUB_TOKEN
              valueFrom:
                secretKeyRef:
                  name: hf-secret
                  key: hf_api_token
            - name: VLLM_XLA_CACHE_PATH
              value: "/data"
            - name: VLLM_USE_V1
              value: "1"
            ports:
            - containerPort: 8000
            resources:
              limits:
                google.com/tpu: 8
            readinessProbe:
              tcpSocket:
                port: 8000
              initialDelaySeconds: 15
              periodSeconds: 10
            volumeMounts:
            - name: gcs-fuse-csi-ephemeral
              mountPath: /data
            - name: dshm
              mountPath: /dev/shm
          volumes:
          - name: gke-gcsfuse-cache
            emptyDir:
              medium: Memory
          - name: dshm
            emptyDir:
              medium: Memory
          - name: gcs-fuse-csi-ephemeral
            csi:
              driver: gcsfuse.csi.storage.gke.io
              volumeAttributes:
                bucketName: GSBUCKET
                mountOptions: "implicit-dirs,file-cache:enable-parallel-downloads:true,file-cache:parallel-downloads-per-file:100,file-cache:max-parallel-downloads:-1,file-cache:download-chunk-size-mb:10,file-cache:max-size-mb:-1"
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: vllm-service
    spec:
      selector:
        app: vllm-tpu
      type: LoadBalancer	
      ports:
        - name: http
          protocol: TCP
          port: 8000  
          targetPort: 8000
    

    Si vous augmentez le nombre de répliques du déploiement, les écritures simultanées dans VLLM_XLA_CACHE_PATH provoqueront l'erreur RuntimeError: filesystem error: cannot create directories. Pour éviter cette erreur, deux options s'offrent à vous :

    1. Supprimez l'emplacement du cache XLA en supprimant le bloc suivant du fichier YAML de déploiement. Cela signifie que toutes les répliques recompileront le cache.

      - name: VLLM_XLA_CACHE_PATH
        value: "/data"
      
    2. Mettez à l'échelle le déploiement sur 1 et attendez que le premier réplica soit prêt et écrive dans le cache XLA. Effectuez ensuite un scaling vers des répliques supplémentaires. Cela permet au reste des répliques de lire le cache sans tenter de l'écrire.

  2. Appliquez le fichier manifeste en exécutant la commande suivante :

    kubectl apply -f vllm-llama3-70b.yaml -n ${NAMESPACE}
    
  3. Affichez les journaux du serveur de modèles en cours d'exécution :

    kubectl logs -f -l app=vllm-tpu -n ${NAMESPACE}
    

    Le résultat doit ressembler à ce qui suit :

    INFO:     Started server process [1]
    INFO:     Waiting for application startup.
    INFO:     Application startup complete.
    INFO:     Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)
    

Diffuser le modèle

  1. Pour obtenir l'adresse IP externe du service VLLM, exécutez la commande suivante :

    export vllm_service=$(kubectl get service vllm-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}' -n ${NAMESPACE})
    
  2. Interagissez avec le modèle à l'aide de curl :

    curl http://$vllm_service:8000/v1/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "meta-llama/Llama-3.1-70B",
        "prompt": "San Francisco is a",
        "max_tokens": 7,
        "temperature": 0
    }'
    

    La sortie devrait ressembler à ce qui suit :

    {"id":"cmpl-6b4bb29482494ab88408d537da1e608f","object":"text_completion","created":1727822657,"model":"meta-llama/Llama-3-8B","choices":[{"index":0,"text":" top holiday destination featuring scenic beauty and","logprobs":null,"finish_reason":"length","stop_reason":null,"prompt_logprobs":null}],"usage":{"prompt_tokens":5,"total_tokens":12,"completion_tokens":7}}
    

Configurer l'autoscaler personnalisé

Dans cette section, vous allez configurer l'autoscaling horizontal des pods à l'aide de métriques Prometheus personnalisées. Vous utilisez les métriques Google Cloud Managed Service pour Prometheus du serveur vLLM.

Pour en savoir plus, consultez Google Cloud Managed Service pour Prometheus. Cette option doit être activée par défaut sur le cluster GKE.

  1. Configurez l'adaptateur de métriques personnalisées Stackdriver sur votre cluster :

    kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/k8s-stackdriver/master/custom-metrics-stackdriver-adapter/deploy/production/adapter_new_resource_model.yaml
    
  2. Ajoutez le rôle Lecteur Monitoring au compte de service utilisé par l'adaptateur de métriques personnalisées Stackdriver :

    gcloud projects add-iam-policy-binding projects/${PROJECT_ID} \
        --role roles/monitoring.viewer \
        --member=principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${PROJECT_ID}.svc.id.goog/subject/ns/custom-metrics/sa/custom-metrics-stackdriver-adapter
    
  3. Enregistrez le manifeste suivant sous le nom vllm_pod_monitor.yaml :

    
    apiVersion: monitoring.googleapis.com/v1
    kind: PodMonitoring
    metadata:
     name: vllm-pod-monitoring
    spec:
     selector:
       matchLabels:
         app: vllm-tpu
     endpoints:
     - path: /metrics
       port: 8000
       interval: 15s
    
  4. Appliquez-le au cluster :

    kubectl apply -f vllm_pod_monitor.yaml -n ${NAMESPACE}
    

Générer une charge sur le point de terminaison vLLM

Générez de la charge sur le serveur vLLM pour tester la façon dont GKE effectue l'autoscaling avec une métrique vLLM personnalisée.

  1. Exécutez un script bash (load.sh) pour envoyer N requêtes parallèles au point de terminaison vLLM :

    #!/bin/bash
    N=PARALLEL_PROCESSES
    export vllm_service=$(kubectl get service vllm-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}' -n ${NAMESPACE})
    for i in $(seq 1 $N); do
      while true; do
        curl http://$vllm_service:8000/v1/completions -H "Content-Type: application/json" -d '{"model": "meta-llama/Llama-3.1-70B", "prompt": "Write a story about san francisco", "max_tokens": 1000, "temperature": 0}'
      done &  # Run in the background
    done
    wait
    

    Remplacez PARALLEL_PROCESSES par le nombre de processus parallèles que vous souhaitez exécuter.

  2. Exécutez le script bash :

    chmod +x load.sh
    nohup ./load.sh &
    

Vérifier que Google Cloud Managed Service pour Prometheus ingère les métriques

Une fois que Google Cloud Managed Service pour Prometheus a récupéré les métriques et que vous ajoutez de la charge au point de terminaison vLLM, vous pouvez afficher les métriques dans Cloud Monitoring.

  1. Dans la console Google Cloud , accédez à la page Explorateur de métriques.

    Accéder à l'explorateur de métriques

  2. Cliquez sur < > PromQL.

  3. Saisissez la requête suivante pour observer les métriques de trafic :

    vllm:num_requests_waiting{cluster='CLUSTER_NAME'}
    

Un graphique en courbes affiche votre métrique vLLM (num_requests_waiting) mesurée au fil du temps. La métrique vLLM passe de 0 (préchargement) à une valeur (post-chargement). Ce graphique confirme que vos métriques vLLM sont ingérées dans Google Cloud Managed Service pour Prometheus. L'exemple de graphique suivant montre une valeur de préchargement initiale de 0, qui atteint une valeur de post-chargement maximale de près de 400 en une minute.

vllm:num_requests_waiting

Déployer la configuration de l'autoscaler horizontal de pods

Lorsque vous choisissez la métrique sur laquelle effectuer l'autoscaling, nous vous recommandons les métriques suivantes pour vLLM TPU :

  • num_requests_waiting : cette métrique concerne le nombre de requêtes en attente dans la file d'attente du serveur de modèle. Ce nombre commence à augmenter de manière notable lorsque le cache KV est plein.

  • gpu_cache_usage_perc : cette métrique est liée à l'utilisation du cache kv, qui est directement corrélée au nombre de requêtes traitées pour un cycle d'inférence donné sur le serveur de modèle. Notez que cette métrique fonctionne de la même manière sur les GPU et les TPU, bien qu'elle soit liée au schéma de dénomination des GPU.

Nous vous recommandons d'utiliser num_requests_waiting lorsque vous optimisez le débit et les coûts, et lorsque vos objectifs de latence sont réalisables avec le débit maximal de votre serveur de modèles.

Nous vous recommandons d'utiliser gpu_cache_usage_perc si vous avez des charges de travail sensibles à la latence où le scaling basé sur la file d'attente n'est pas assez rapide pour répondre à vos besoins.

Pour en savoir plus, consultez Bonnes pratiques pour l'autoscaling des charges de travail d'inférence de grands modèles de langage (LLM) avec des TPU.

Lorsque vous sélectionnez une averageValue cible pour votre configuration AHP, vous devez la déterminer de manière expérimentale. Consultez l'article de blog Économisez sur les GPU : autoscaling plus intelligent pour vos charges de travail d'inférence GKE pour obtenir d'autres idées sur la façon d'optimiser cette partie. Le générateur de profils utilisé dans cet article de blog fonctionne également pour vLLM TPU.

Dans les instructions suivantes, vous déployez votre configuration AHP à l'aide de la métrique num_requests_waiting. À des fins de démonstration, vous définissez la métrique sur une valeur faible afin que la configuration AHP ajuste vos répliques vLLM à deux. Pour déployer la configuration de l'autoscaler horizontal de pods à l'aide de num_requests_waiting, procédez comme suit :

  1. Enregistrez le manifeste suivant sous le nom vllm-hpa.yaml :

    
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
     name: vllm-hpa
    spec:
     scaleTargetRef:
       apiVersion: apps/v1
       kind: Deployment
       name: vllm-tpu
     minReplicas: 1
     maxReplicas: 2
     metrics:
       - type: Pods
         pods:
           metric:
             name: prometheus.googleapis.com|vllm:num_requests_waiting|gauge
           target:
             type: AverageValue
             averageValue: 10
    

    Les métriques vLLM dans Google Cloud Managed Service pour Prometheus suivent le format vllm:metric_name.

    Bonne pratique :

    Utilisez num_requests_waiting pour faire évoluer le débit. Utilisez gpu_cache_usage_perc pour les cas d'utilisation de TPU sensibles à la latence.

  2. Déployez la configuration de l'autoscaler horizontal de pods :

    kubectl apply -f vllm-hpa.yaml -n ${NAMESPACE}
    

    GKE planifie le déploiement d'un autre pod, ce qui déclenche l'autoscaler du pool de nœuds pour ajouter un deuxième nœud avant de déployer la deuxième réplique vLLM.

  3. Observez la progression de l'autoscaling des pods :

    kubectl get hpa --watch -n ${NAMESPACE}
    

    Le résultat ressemble à ce qui suit :

    NAME       REFERENCE             TARGETS       MINPODS   MAXPODS   REPLICAS   AGE
    vllm-hpa   Deployment/vllm-tpu   <unknown>/10   1         2         0          6s
    vllm-hpa   Deployment/vllm-tpu   34972m/10      1         2         1          16s
    vllm-hpa   Deployment/vllm-tpu   25112m/10      1         2         2          31s
    vllm-hpa   Deployment/vllm-tpu   35301m/10      1         2         2          46s
    vllm-hpa   Deployment/vllm-tpu   25098m/10      1         2         2          62s
    vllm-hpa   Deployment/vllm-tpu   35348m/10      1         2         2          77s
    
  4. Patientez 10 minutes, puis répétez les étapes de la section Vérifier que Google Cloud Managed Service pour Prometheus ingère les métriques. Google Cloud Managed Service pour Prometheus ingère désormais les métriques des deux points de terminaison vLLM.