LLM mithilfe von TPU Trillium in GKE mit vLLM bereitstellen

In dieser Anleitung wird gezeigt, wie Sie Large Language Models (LLMs) mit Tensor Processing Units (TPUs) in Google Kubernetes Engine (GKE) mit dem vLLM-Bereitstellungs-Framework bereitstellen. In diesem Tutorial stellen Sie Llama 3.1 70b bereit, verwenden TPU Trillium und richten horizontales Pod-Autoscaling mit vLLM-Servermesswerten ein.

Dieses Dokument ist ein guter Ausgangspunkt, wenn Sie bei der Bereitstellung und Zugänglichmachung Ihrer KI/ML-Arbeitslasten die detaillierte Kontrolle, Skalierbarkeit, Robustheit, Übertragbarkeit und Kosteneffizienz von verwaltetem Kubernetes benötigen.

Hintergrund

Wenn Sie TPU Trillium in GKE verwenden, können Sie eine robuste, produktionsbereite Bereitstellungslösung mit allen Vorteilen von verwaltetem Kubernetes implementieren, darunter effiziente Skalierbarkeit und höhere Verfügbarkeit. In diesem Abschnitt werden die in diesem Leitfaden verwendeten Schlüsseltechnologien beschrieben.

TPU Trillium

TPUs sind von Google speziell entwickelte anwendungsspezifische integrierte Schaltkreise (ASICs). TPUs werden verwendet, um das maschinelle Lernen und die KI-Modelle zu beschleunigen, die mit Frameworks wie TensorFlow, PyTorch und JAX erstellt wurden. In dieser Anleitung wird TPU Trillium verwendet, die sechste Generation der TPUs von Google.

Bevor Sie TPUs in GKE verwenden, sollten Sie den folgenden Lernpfad durcharbeiten:

  1. Informationen zur Systemarchitektur von TPU Trillium
  2. TPUs in GKE

vLLM

vLLM ist ein hoch optimiertes Open-Source-Framework für die Bereitstellung von LLMs. vLLM kann den Bereitstellungsdurchsatz auf TPUs über Funktionen wie die Folgenden beschleunigen:

  • Optimierte Transformer-Implementierung mit PagedAttention.
  • Kontinuierliche Batchverarbeitung zur Verbesserung des allgemeinen Bereitstellungsdurchsatzes.
  • Tensor-Parallelität und verteilte Bereitstellung auf mehreren TPUs.

Weitere Informationen finden Sie in der vLLM-Dokumentation.

Cloud Storage FUSE

Cloud Storage FUSE bietet Zugriff von Ihrem GKE-Cluster auf Cloud Storage für Modellgewichte, die sich in Object Storage-Buckets befinden. In dieser Anleitung ist der erstellte Cloud Storage-Bucket anfangs leer. Wenn vLLM gestartet wird, lädt GKE das Modell von Hugging Face herunter und speichert die Gewichte im Cloud Storage-Bucket. Beim Neustart des Pods oder beim Hochskalieren der Bereitstellung werden bei nachfolgenden Modellladevorgängen zwischengespeicherte Daten aus dem Cloud Storage-Bucket heruntergeladen. Dabei werden parallele Downloads für eine optimale Leistung genutzt.

Weitere Informationen finden Sie in der Dokumentation zum CSI-Treiber für Cloud Storage FUSE.

GKE-Cluster erstellen

Sie können LLMs auf TPUs in einem GKE-Cluster im Autopilot- oder Standardmodus bereitstellen. Für eine vollständig verwaltete Kubernetes-Umgebung empfehlen wir die Verwendung eines Autopilot-Clusters. Informationen zum Auswählen des GKE-Betriebsmodus, der für Ihre Arbeitslasten am besten geeignet ist, finden Sie unter GKE-Betriebsmodus auswählen.

Autopilot

  1. Erstellen Sie einen GKE Autopilot-Cluster:

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

Standard

  1. GKE-Standardcluster erstellen:

    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. So erstellen Sie einen TPU-Slice-Knotenpool:

    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 erstellt die folgenden Ressourcen für das LLM:

kubectl für die Kommunikation mit Ihrem Cluster konfigurieren

Führen Sie den folgenden Befehl aus, um kubectl für die Kommunikation mit Ihrem Cluster zu konfigurieren:

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

Kubernetes-Secret für Hugging Face-Anmeldedaten erstellen

  1. Namespace erstellen Wenn Sie den Namespace default verwenden, können Sie diesen Schritt überspringen:

    kubectl create namespace ${NAMESPACE}
    
  2. Erstellen Sie mit dem folgenden Befehl ein Kubernetes-Secret, das das Hugging Face-Token enthält:

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

Cloud Storage-Bucket erstellen

Führen Sie in Cloud Shell den folgenden Befehl aus:

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

Dadurch wird ein Cloud Storage-Bucket zum Speichern der Modelldateien erstellt, die Sie von Hugging Face herunterladen.

Kubernetes-Dienstkonto für den Zugriff auf den Bucket einrichten

  1. Erstellen Sie das Kubernetes-Dienstkonto:

    kubectl create serviceaccount ${KSA_NAME} --namespace ${NAMESPACE}
    
  2. Gewähren Sie dem Kubernetes-Dienstkonto Lese-/Schreibzugriff, um auf den Cloud Storage-Bucket zuzugreifen:

    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. Alternativ können Sie Lese- und Schreibzugriff auf alle Cloud Storage-Buckets im Projekt gewähren:

    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 erstellt die folgenden Ressourcen für das LLM:

    1. Ein Cloud Storage-Bucket zum Speichern des heruntergeladenen Modells und des Kompilierungscache. Ein Cloud Storage FUSE-CSI-Treiber liest den Inhalt des Buckets.
    2. Volumes mit aktiviertem Datei-Caching und die Funktion paralleler Download von Cloud Storage FUSE.
    Best Practice:

    Verwenden Sie einen Dateicache, der von tmpfs oder Hyperdisk / Persistent Disk unterstützt wird, je nach erwarteter Größe der Modellinhalte, z. B. Gewichtsdateien. In dieser Anleitung verwenden Sie einen durch RAM unterstützten Cloud Storage FUSE-Dateicache.

vLLM-Modellserver bereitstellen

In dieser Anleitung wird eine Kubernetes-Bereitstellung verwendet, um den vLLM-Modellserver bereitzustellen. Ein Deployment ist ein Kubernetes-API-Objekt, mit dem Sie mehrere Replikate von Pods ausführen können, die auf die Knoten in einem Cluster verteilt sind.

  1. Sehen Sie sich das folgende Deployment-Manifest an, das als vllm-llama3-70b.yaml gespeichert ist und ein einzelnes Replikat verwendet:

    
    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
    

    Wenn Sie das Deployment auf mehrere Replikate hochskalieren, führen die gleichzeitigen Schreibvorgänge in VLLM_XLA_CACHE_PATH zum Fehler RuntimeError: filesystem error: cannot create directories. Sie haben zwei Möglichkeiten, diesen Fehler zu vermeiden:

    1. Entfernen Sie den XLA-Cache-Speicherort, indem Sie den folgenden Block aus dem Deployment-YAML entfernen. Das bedeutet, dass alle Replikate den Cache neu kompilieren.

      - name: VLLM_XLA_CACHE_PATH
        value: "/data"
      
    2. Skalieren Sie die Bereitstellung auf 1 und warten Sie, bis das erste Replikat bereit ist und in den XLA-Cache schreibt. Skalieren Sie dann auf zusätzliche Replikate. So können die verbleibenden Replikate den Cache lesen, ohne zu versuchen, ihn zu schreiben.

  2. Wenden Sie das Manifest mit dem folgenden Befehl an:

    kubectl apply -f vllm-llama3-70b.yaml -n ${NAMESPACE}
    
  3. So rufen Sie die Logs des laufenden Modellservers auf:

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

    Die Ausgabe sollte in etwa so aussehen:

    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)
    

Modell bereitstellen

  1. Führen Sie den folgenden Befehl aus, um die externe IP-Adresse des VLLM-Dienstes abzurufen:

    export vllm_service=$(kubectl get service vllm-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}' -n ${NAMESPACE})
    
  2. Mit dem Modell über curl interagieren:

    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
    }'
    

    Die Ausgabe sollte in etwa so aussehen:

    {"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}}
    

Benutzerdefiniertes Autoscaling einrichten

In diesem Abschnitt richten Sie das horizontale Pod-Autoscaling mit benutzerdefinierten Prometheus-Messwerten ein. Sie verwenden die Google Cloud Managed Service for Prometheus-Messwerte vom vLLM-Server.

Weitere Informationen finden Sie unter Google Cloud Managed Service for Prometheus. Diese Option sollte im GKE-Cluster standardmäßig aktiviert sein.

  1. Richten Sie den Stackdriver-Adapter für benutzerdefinierte Messwerte in Ihrem Cluster ein:

    kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/k8s-stackdriver/master/custom-metrics-stackdriver-adapter/deploy/production/adapter_new_resource_model.yaml
    
  2. Fügen Sie dem Dienstkonto, das vom Stackdriver-Adapter für benutzerdefinierte Messwerte verwendet wird, die Rolle „Monitoring-Betrachter“ hinzu:

    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. Speichern Sie das folgende Manifest als 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. Wenden Sie es auf den Cluster an:

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

Last auf dem vLLM-Endpunkt erzeugen

Erstellen Sie Last für den vLLM-Server, um zu testen, wie GKE mit einem benutzerdefinierten vLLM-Messwert skaliert.

  1. Führen Sie ein Bash-Skript (load.sh) aus, um N parallele Anfragen an den vLLM-Endpunkt zu senden:

    #!/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
    

    Ersetzen Sie PARALLEL_PROCESSES durch die Anzahl der parallelen Prozesse, die Sie ausführen möchten.

  2. Führen Sie das Bash-Skript aus:

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

Prüfen, ob Google Cloud Managed Service for Prometheus die Messwerte aufnimmt

Nachdem Google Cloud Managed Service for Prometheus die Messwerte erfasst hat und Sie den vLLM-Endpunkt belasten, können Sie die Messwerte in Cloud Monitoring ansehen.

  1. Rufen Sie in der Google Cloud Console die Seite Metrics Explorer auf.

    Zu Metrics Explorer

  2. Klicken Sie auf < > PromQL.

  3. Geben Sie die folgende Abfrage ein, um Traffic-Messwerte zu beobachten:

    vllm:num_requests_waiting{cluster='CLUSTER_NAME'}
    

Ein Liniendiagramm zeigt den vLLM-Messwert (num_requests_waiting) im Zeitverlauf. Der vLLM-Messwert wird von 0 (vor dem Laden) auf einen Wert (nach dem Laden) skaliert. Dieses Diagramm bestätigt, dass Ihre vLLM-Messwerte in Google Cloud Managed Service for Prometheus aufgenommen werden. Das folgende Beispiel zeigt einen Startwert für das Vorladen von 0, der innerhalb einer Minute einen maximalen Wert nach dem Laden von fast 400 erreicht.

vllm:num_requests_waiting

Konfiguration für horizontales Pod-Autoscaling bereitstellen

Bei der Entscheidung, auf welchen Messwert das Autoscaling erfolgen soll, empfehlen wir die folgenden Messwerte für vLLM-TPUs:

  • num_requests_waiting: Dieser Messwert bezieht sich auf die Anzahl der Anfragen, die in der Warteschlange des Modellservers warten. Diese Zahl beginnt merklich zu steigen, wenn der KV-Cache voll ist.

  • gpu_cache_usage_perc: Dieser Messwert bezieht sich auf die Nutzung des KV-Cache, die direkt mit der Anzahl der Anfragen korreliert, die für einen bestimmten Inferenzzyklus auf dem Modellserver verarbeitet werden. Dieser Messwert funktioniert auf GPUs und TPUs gleich, ist aber an das GPU-Benennungsschema gebunden.

Wir empfehlen die Verwendung von num_requests_waiting, wenn Sie den Durchsatz und die Kosten optimieren möchten und Ihre Latenzziele mit dem maximalen Durchsatz Ihres Modellservers erreicht werden können.

Wir empfehlen die Verwendung von gpu_cache_usage_perc für latenzempfindliche Arbeitslasten, bei denen die warteschlangenbasierte Skalierung nicht schnell genug ist, um Ihre Anforderungen zu erfüllen.

Weitere Informationen finden Sie unter Best Practices für das Autoscaling von Inferenzen für LLM-Arbeitslasten (Large Language Model) mit TPUs.

Wenn Sie ein averageValue-Ziel für Ihre HPA-Konfiguration auswählen, müssen Sie es experimentell ermitteln. Im Blogpost Save on GPUs: Smarter autoscaling for your GKE inferencing workloads (GPUs sparen: Intelligenteres Autoscaling für Ihre GKE-Inferenzarbeitslasten) finden Sie weitere Ideen zur Optimierung dieses Teils. Der in diesem Blogpost verwendete profile-generator funktioniert auch für vLLM TPU.

In der folgenden Anleitung stellen Sie Ihre HPA-Konfiguration mit dem Messwert num_requests_waiting bereit. Zu Demonstrationszwecken legen Sie den Messwert auf einen niedrigen Wert fest, damit die HPA-Konfiguration Ihre vLLM-Replikate auf zwei skaliert. So stellen Sie die Konfiguration des horizontalen Pod-Autoscalings mit num_requests_waiting bereit:

  1. Speichern Sie das folgende Manifest als 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
    

    Die vLLM-Messwerte in Google Cloud Managed Service for Prometheus folgen dem Format vllm:metric_name.

    Best Practice:

    Verwenden Sie num_requests_waiting zum Skalieren des Durchsatzes. Verwenden Sie gpu_cache_usage_perc für latenzempfindliche TPU-Anwendungsfälle.

  2. Stellen Sie die Konfiguration für das horizontale Pod-Autoscaling bereit:

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

    GKE plant die Bereitstellung eines weiteren Pods, wodurch der Autoscaler für Knotenpools einen zweiten Knoten hinzufügt, bevor die zweite vLLM-Replica bereitgestellt wird.

  3. Beobachten Sie den Fortschritt des Pod-Autoscalings:

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

    Die Ausgabe sieht etwa so aus:

    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. Warten Sie 10 Minuten und wiederholen Sie die Schritte im Abschnitt Prüfen, ob Google Cloud Managed Service for Prometheus die Messwerte aufnimmt. Google Cloud Managed Service for Prometheus erfasst jetzt die Messwerte von beiden vLLM-Endpunkten.