Best Practices für die Batchinferenz in GKE

In diesem Dokument werden Best Practices für das Ausführen von Batch-Inferenz-Arbeitslasten in Google Kubernetes Engine (GKE) beschrieben. Die Batch-Inferenz ist der Prozess, bei dem ein Machine-Learning-Modell verwendet wird, um Vorhersagen für große Datasets zu generieren. Dabei werden hoher Durchsatz und Kosteneffizienz gegenüber sofortigen Antworten mit niedriger Latenz priorisiert.

In diesem Leitfaden wird zwischen Batch-Inferenz und Batching von Anfragen (oder dynamischem Batching) unterschieden. Das Batching von Anfragen ist eine serverseitige Technik in Engines wie vLLM oder SGLang, bei der gleichzeitige Echtzeitanfragen gruppiert werden, um die Effizienz von Beschleunigern zu optimieren. Sie können Batching von Anfragen auf Batchinferenz-Arbeitslasten anwenden.

Die Best Practices in diesem Leitfaden beziehen sich auf zwei gängige Arten von Batchinferenzmustern:

  • Asynchrone Inferenz: Daten werden kurz nach der Erstellung in Blöcken verarbeitet. Bei einer typischen Latenz von Sekunden bis Minuten wird bei diesem Ansatz der Bedarf an aktuellen Daten mit der Effizienz der gleichzeitigen Verarbeitung mehrerer Elemente in Einklang gebracht. Die asynchrone Inferenz wird manchmal auch als Near-Realtime-Inferenz bezeichnet.
  • Bei der Batchinferenz werden große Mengen an gesammelten Daten in geplanten Intervallen (z. B. täglich oder wöchentlich) verarbeitet. Die Latenz liegt in der Regel zwischen Stunden und Tagen, da diese Jobs oft in Nebenzeiten geplant werden, um die Ressourcenverfügbarkeit zu maximieren.

Diese Empfehlungen sind eine spezielle Optimierungsebene, die auf den Grundlagen basiert, die im Überblick über Best Practices für Inferenz in GKE beschrieben werden. Bevor Sie Batcharbeitslasten optimieren, sollten Sie die wichtigsten Best Practices für die Modellauswahl, Quantisierung und Beschleunigerauswahl befolgt haben.

Architekturmuster für die Batchinferenzverarbeitung auswählen

Die Auswahl des richtigen Architekturmusters ist die wichtigste Entscheidung für die Bereitstellung Ihrer Batch-Inferenz-Arbeitslasten, da sie sich auf das Verhältnis zwischen Latenz, Durchsatz und Kosten auswirkt. Um die Effizienz aufrechtzuerhalten, muss der Inferenzdurchsatz in den Nebenzeiten höher sein als die Rate eingehender Anfragen, damit die Warteschlangen nicht unendlich lang werden.

Asynchrone Inferenz für burstartige Arbeitslasten verwenden

Die asynchrone Inferenz eignet sich gut für Anwendungsfälle, die häufige, inkrementelle Aktualisierungen erfordern, z. B.:

  • Nutzerempfehlungsprofile werden alle paar Minuten auf Grundlage der letzten Interaktionen aktualisiert.
  • Verarbeitung von Erwähnungen in sozialen Medien in Abständen von einer Minute für die Echtzeitüberwachung.
  • Marktbewegende Signale aus hochfrequenten Finanzdatenstreams erkennen
  • Sentimentanalyse von eingehendem Kundenfeedback oder Newsfeeds

Wählen Sie dieses Muster aus, wenn Ihre Arbeitslast Latenzen von einigen Sekunden bis zu einigen Minuten tolerieren kann.

Berücksichtigen Sie bei der Implementierung der asynchronen Inferenz die folgenden Merkmale:

  • Latenz: Die Zeit bis zum ersten Token kann zwischen einigen Sekunden und mehreren Minuten liegen.
  • Datenquellen: In der Regel verarbeiten Sie Datasets mit einer Größe von Megabyte bis Gigabyte, z. B. Nachrichten aus Pub/Sub oder Dateien aus Cloud Storage, die über einen kurzen Zeitraum hinweg gesammelt wurden.
  • Compute-Muster: Ihre Infrastruktur sollte einen kontinuierlichen Dienst unterstützen, der häufige Arbeitsspitzen bewältigt.
  • Kostenoptimierung: Dieses Muster bietet ein ausgewogenes Verhältnis zwischen Echtzeitinferenz mit niedriger Latenz und Batchverarbeitung mit hohem Durchsatz.

Batchinferenz für große Datasets verwenden

Die Batchinferenz eignet sich ideal für umfangreiche, episodische Jobs, bei denen Verzögerungen von Stunden oder Tagen toleriert werden können, z. B. für:

  • Erstellung nächtlicher Risikobewertungsberichte auf Grundlage der Finanztransaktionen des Vortags.
  • Produkteinbettungen für einen gesamten Katalog erstellen, um nachgelagerte Such- und Empfehlungssysteme zu unterstützen.
  • Große Datasets mit Bildern für das Modelltraining oder die Archivierung kategorisieren.

Wählen Sie dieses Muster aus, wenn Sie große Datenmengen verarbeiten und Latenzen von Stunden bis zu mehreren Tagen tolerieren können.

Berücksichtigen Sie bei der Implementierung der Batchinferenz die folgenden Merkmale:

  • Latenz: Die Startlatenz für Arbeitslasten liegt in der Regel zwischen Minuten und Tagen, da Jobs oft außerhalb der Spitzenzeiten geplant werden.
  • Datenquellen: Sie verarbeiten große Datasets von Gigabytes bis Petabytes, die in der Regel in Cloud Storage- oder BigQuery-Tabellen gespeichert sind.
  • Berechnungsmuster: Sie verwenden episodische, burstartige Jobs, die initialisiert werden, die Daten verarbeiten und dann beendet werden.
  • Kostenoptimierung: Dieses Muster lässt sich mit einem nutzungsbasierten Modell sehr gut optimieren. Da Batchjobs flexible Abschlusszeiträume haben, empfehlen wir, Spot-VMs zu verwenden, um die Kosten zu senken.

Durchsatz und Kosteneffizienz optimieren

Batch-Inferenz-Arbeitslasten eignen sich besonders für kostensparende Infrastruktur, die Unterbrechungen beinhalten kann.

Spot-VMs zur Reduzierung der Computing-Kosten verwenden

Nutzen Sie die Rabatte von Spot-VMs für Batch-Jobs. Da Batch-Inferenz-Arbeitslasten in der Regel Latenz und Unterbrechungen tolerieren, eignen sie sich gut für die reduzierten Preise von Spot-Kapazität.

Achten Sie darauf, dass in Ihrem Code für die Batchinferenz Checkpointing implementiert ist, um potenzielle Unterbrechungen zu verarbeiten. Wenn eine Spot-VM vorzeitig beendet wird, können Sie einen neuen Knoten erstellen und Ihre Arbeitslast mit dem letzten verarbeiteten Batch fortsetzen, anstatt von vorn zu beginnen.

Batchgröße für Arbeitslast und Anfragebatchgröße optimieren

Um Ressourcenkonflikte und Job-Timeouts zu vermeiden, sollte die Anzahl der Elemente, die an Ihre Engine gesendet werden (Arbeitslast-Batch), mindestens so groß sein wie die Anzahl der gleichzeitigen Anfragen, die der Server verarbeiten kann (Anfrage-Batch), um eine Unterauslastung von Beschleunigern zu vermeiden.

Batchgröße der Arbeitslast optimieren

Die Batchgröße der Arbeitslast ist die Gesamtzahl der Elemente, die in einer einzelnen Arbeitseinheit an die Inferenz-Engine gesendet werden. Sie konfigurieren dies in Ihrer Client-Übermittlungslogik oder Kubernetes-Jobkonfiguration, indem Sie Ihre Daten aufteilen oder mehrere Elemente in einer einzelnen Anfrage gruppieren.

Verwenden Sie die folgenden Grenzwerte, um die optimale Batchgröße für die Arbeitslast zu ermitteln:

  • Mindest-Batchgröße berechnen: Die Batchgröße Ihrer Arbeitslast muss mindestens so groß sein wie die Batchgröße Ihrer Anfrage. Wenn Sie beispielsweise ein einzelnes Element an einen Server senden, der 256 Elemente gleichzeitig verarbeiten kann, wird der Server nicht ausreichend genutzt. Die Mindestgröße finden Sie in der Konfiguration Ihres Inferenzservers, z. B. im Argument max_num_seqs in vLLM. Sie können Ihre Clientlogik so konfigurieren, dass mehrere Elemente in einer einzelnen Anfrage gruppiert werden, oder Ihre Daten so aufteilen, dass jeder Job eine Mindestmenge an Daten erhält, die der Batchgröße der Anfrage entspricht oder diese überschreitet.
  • Maximale Batchgröße berechnen: Achten Sie darauf, dass die Batchgröße Ihrer Arbeitslast es dem Pod ermöglicht, die Ausführung vor Erreichen des in Ihrem Kubernetes-Job definierten activeDeadlineSeconds-Time-outs abzuschließen. Schätzen Sie die Zeit, die für die Verarbeitung eines Anfrage-Batch erforderlich ist, und legen Sie die Größe der Arbeitslast so fest, dass der Pod die Frist deutlich einhält. Wenn Ihr activeDeadlineSeconds beispielsweise 3.600 Sekunden und Ihr Start-Overhead 600 Sekunden beträgt, muss die maximale Ausführungszeit so festgelegt sein, dass der Pod in weniger als 3.000 Sekunden abgeschlossen werden kann.

Wenn die Batchgröße Ihrer Arbeitslast zu klein ist, verschwendet Ihr Job Zeit mit dem Pod-Start-Overhead (Gewichte herunterladen, bereitstellen, Beschleuniger initialisieren). Wenn sie zu groß ist, besteht das Risiko, dass der Job von GKE aufgrund des Zeitlimits für activeDeadlineSeconds beendet wird. In diesem Fall schlägt der Job fehl und der Fortschritt geht verloren.

Batchgröße für Anfragen anpassen

Die Batchgröße für Anfragen ist die Anzahl der gleichzeitigen Anfragen, die der Inferenzserver gleichzeitig auf dem Accelerator verarbeitet. Sie optimieren diesen Parameter, indem Sie serverspezifische Flags in der Konfiguration des Inferenzservers anpassen, z. B. das Flag --max-num-seqs für vLLM.

Ihr Ziel ist es, die GPU-Auslastung zu maximieren, ohne Fehler aufgrund mangelnden Arbeitsspeichers (Out-of-memory, OOM) auszulösen. Wenn die Batchgröße der Anfrage nicht kalibriert ist, wird der Beschleuniger in Ihrem System entweder nicht ausreichend genutzt oder der Modellserver stürzt ab. Für vLLM können Sie Tools wie das vLLM-Script „auto_tune“ verwenden, um die besten Werte für die Einstellungen max_num_seqs und max_num_batched_tokens für Ihre spezifische Hardware zu finden. Weitere Informationen finden Sie im Leitfaden Best Practices für Inferenz in GKE unter Konfiguration des Inferenzservers optimieren.

Asynchrone Komponenten für die asynchrone Inferenz implementieren

Für die asynchrone Inferenz empfehlen wir, Messaging-Puffer zu verwenden, um die Erfassungsebene von der Inferenzebene zu entkoppeln.

Das folgende Architekturdiagramm zeigt ein Beispiel für eine Plattform für asynchrone Inferenz. Diese Architektur schützt Inferenzserver vor Trafficspitzen, verwaltet Arbeitsrückstände und sorgt für eine hohe Auslastung der Beschleuniger.

Das Diagramm zeigt den Fluss von Pub/Sub zu Abonnenten, einem Inferenz-Gateway und einem Inferenzserver. Die Ergebnisse werden in AlloyDB gespeichert und fehlgeschlagene Nachrichten werden an ein Dead-Letter-Thema gesendet.

Plattform für asynchrone Inferenz in GKE

Die Architektur besteht aus den folgenden Komponenten:

  • Pub/Sub-Thema:Dient als dauerhafter Puffer für eingehende Clientnachrichten mit einer Aufbewahrungsdauer von 7 bis 31 Tagen.
  • Abonnent:Eine Komponente, die Nachrichtenbatches liest, Anfragen an den Inferenzserver sendet und die Verarbeitung bestätigt.
  • Subscriber-HPA:Das Subscriber-Deployment wird anhand des Messwerts num_undelivered_messages (Anzahl der unbestätigten Nachrichten) skaliert.
  • Speicher:Inferenz-Ergebnisse mithilfe einer Datenbank (z. B. AlloyDB) oder eines Objektspeichers (z. B. Cloud Storage) speichern.
  • Inference Gateway:Stellt die Inferenzarbeitslasten für den Abonnenten bereit.
  • Inferenzserver:Verarbeitet die Batch-Inferenzanfragen (z. B. vLLM).
  • Server-HPA:Skaliert die Inferenz-Engine anhand von enginespezifischen Messwerten wie vllm:num_requests_waiting.
  • Thema für unzustellbare Nachrichten:Erfasst Nachrichten, deren Verarbeitung nach einer festgelegten Anzahl von Wiederholungen mit exponentiellem Backoff fehlschlägt.

Weitere Informationen finden Sie in der Referenzimplementierung auf GitHub.

Anfragen puffern und zusammenfassen

So verwalten Sie den Fluss von Anfragen:

  • Pub/Sub als dauerhaften Puffer verwenden:Implementieren Sie Pub/Sub, um Inferencing-Anfragen dauerhaft zu speichern. Diese Einrichtung fungiert als FIFO-Puffer, in dem Anfragen gespeichert werden, bis ein Consumer sie verarbeiten kann. So wird eine Serverüberlastung bei Traffic-Spitzen verhindert.
  • Pull-Abos mit clientseitiger Ablaufsteuerung verwenden:Konfigurieren Sie ein Pull-Abomodell. So kann Ihre Abonnentenanwendung Nachrichten explizit nur dann anfordern, wenn sie die Kapazität hat, sie zu verarbeiten. Sie haben also die volle Kontrolle über die Aufnahmerate.
  • Nachrichten aggregieren, um die Server-Batchgröße zu erreichen:Senden Sie nicht eine Pub/Sub-Nachricht als eine Inferenzanfrage. Stattdessen sollte der Abonnent mehrere Nachrichten in einer einzelnen Batchanfrage bündeln, die der optimalen Batchgröße des Inferenzservers entspricht (z. B. passend zu den max_num_seqs-Einstellungen in vLLM). So wird sichergestellt, dass die Beschleuniger vollständig ausgelastet sind, und der Durchsatz wird maximiert. Konfigurieren Sie die max_messages-Pull-Einstellung Ihres Abonnenten auf ein Vielfaches von max_num_seqs, damit jeder Forward-Pass des Modells vollständig gesättigt ist.

Abonnenten und Server automatisch skalieren

Für eine effektive Batch-Inferenz müssen die Abonnenten (CPU-gebunden) anders skaliert werden als die Inferenzserver (GPU- oder TPU-gebunden).

  • Abonnenten basierend auf dem Arbeitsrückstand skalieren:Konfigurieren Sie den HorizontalPodAutoscaler (HPA) für Ihr Abonnentendeployment basierend auf dem Messwert num_undelivered_messages aus Pub/Sub. Weitere Informationen finden Sie im Pub/Sub-HPA-Beispiel. Berechnen Sie die Anzahl der Replikate, die Sie verwenden möchten, mit der folgenden Gleichung:

    \[ desiredReplicas = \frac{num\_undelivered\_messages}{target\_latency\_seconds \times throughput\_per\_replica} \]

  • Infrastrukturkontingente berücksichtigen:Beschränken Sie die maximale Anzahl von Replikaten Ihrer Abonnenten explizit, indem Sie die Einstellung maxReplicas in Ihrem HPA konfigurieren. Skalieren Sie die Anzahl der Abonnenten nicht über das hinaus, was das GPU- oder TPU-Kontingent Ihrer Inferenzserver unterstützt. Wenn Sie Abonnenten überdimensionieren, wird der Engpass auf den Inferenzserver verlagert, wodurch die Ressourcenkonkurrenz ohne Steigerung des Durchsatzes zunimmt.

  • Inferenzserver anhand von Messwerten der Engine skalieren:Sie können das Deployment Ihres Inferenzservers anhand von Messwerten skalieren, die direkt von der Inferenz-Engine exportiert werden (nicht nur über CPU/Arbeitsspeicher). Verwenden Sie beispielsweise die Einstellung vllm:num_requests_waiting für vLLM, mit der der Verarbeitungsrückstand direkt auf der Ebene des Modellservers gemessen wird. Weitere Informationen finden Sie unter Pods automatisch skalieren.

Fehler und Zeitüberschreitungen behandeln

So gehen Sie mit Fehlern und Zeitüberschreitungen um:

  • Bestätigungsfristen proaktiv verlängern:Konfigurieren Sie Ihren Abonnenten so, dass die Pub/Sub-Bestätigungsfrist (ack) für Nachrichten, die verarbeitet werden, proaktiv verlängert wird, um Wiederholungs- und Doppelverarbeitung zu vermeiden. Dieser Ansatz ist erforderlich, da Inferenzaufgaben oft länger dauern als die standardmäßigen Zeitüberschreitungszeiträume. In der Regel sollte der Verlängerungszeitraum länger als die Batchinferenzzeit im Worst-Case-Szenario sein.
  • Fehler mit einem Thema für unzustellbare Nachrichten isolieren:Aktivieren Sie ein Thema für unzustellbare Nachrichten, um automatisch fehlerhafte Nachrichten zu isolieren, bei denen die Zustellung wiederholt fehlgeschlagen ist. So wird verhindert, dass „Poison Pill“-Nachrichten die Warteschlange blockieren und die gesamte Pipeline zum Stillstand bringen.
  • Backoff-Strategien implementieren:Wenn der Inferenzserver Fehler vom Typ 429 (Too Many Requests) oder 503 (Service Unavailable) zurückgibt, muss der Abonnent diese abfangen und eine exponentielle Backoff-Strategie implementieren, um die Nutzung von Pub/Sub vorübergehend zu pausieren, bis der Server wieder verfügbar ist.

Batchjobs in großem Umfang orchestrieren

Wenn Sie umfangreiche Datasets verarbeiten, sollten Sie die folgenden Best Practices beachten, um den Durchsatz zu maximieren, die Kosteneffizienz zu gewährleisten, eine umfassende Rückverfolgbarkeit für Audits zu implementieren und ein erweitertes Kontingentmanagement und eine erweiterte Jobpriorisierung anzuwenden.

JobSet für die verteilte Inferenz mit mehreren Knoten verwenden

Wir empfehlen, die Kubernetes-Ressource JobSet zu verwenden, um verteilte Inferenzarbeitslasten zu orchestrieren, für die mehrere Knoten zusammenarbeiten müssen, z. B. große Modelle, die auf TPU-Pods oder GPU-Clustern mit mehreren Knoten ausgeführt werden. Bei Standard-Kubernetes-Jobs kann nicht garantiert werden, dass alle erforderlichen Pods gleichzeitig gestartet werden. Dies kann bei verteilten Arbeitslasten zu Deadlocks führen.

JobSet ist eine Kubernetes-native API, mit der Gruppen von Jobs als Einheit verwaltet werden. Sie bietet die folgenden Vorteile für die Batchinferenz:

  • Gang-Scheduling:Damit wird sichergestellt, dass alle erforderlichen Ressourcen wie TPU-Slices oder GPU-Knoten verfügbar sind, bevor die Arbeitslast gestartet wird, um Deadlocks zu vermeiden.
  • Exklusive Platzierung:Damit wird sichergestellt, dass ein einzelner JobSet exklusiven Zugriff auf die Netzwerk-Topologie (z. B. einen TPU-Slice) hat, um die Interconnect-Leistung zu maximieren.
  • Fehlerbehebung:Damit können Sie bestimmte replizierte Jobs oder den gesamten Satz neu starten, wenn ein Worker ausfällt. Das hängt von Ihrer Konfiguration ab.

Indexierte Jobs für das Sharding von Daten verwenden

Wenn Sie JobSet verwenden, konfigurieren Sie ReplicatedJob so, dass die Einstellung completionMode: Indexed verwendet wird. Bei dieser Einstellung wird automatisch eine Umgebungsvariable JOB_COMPLETION_INDEX in jeden Pod eingefügt. Ihr Inferenzcode kann diesen Index verwenden, um deterministisch einen eindeutigen Datenshard für die Verarbeitung auszuwählen.

Wenn Sie beispielsweise einen Cloud Storage-Bucket mit 100.000 Bildern haben und ein JobSet mit einem Parallelismus von 10 bereitstellen, liest jeder der 10 Pods beim Start seinen Index (0–9). Pod 0 kann dann berechnen, dass er die Bilder 0 bis 9.999 verarbeiten soll, während Pod 1 die Bilder 10.000 bis 19.999 verarbeitet. Dadurch ist kein separater Dienst für die Aufgabenwarteschlange erforderlich.

Sidecar-Muster für die Serversättigung verwenden

Um die Beschleunigernutzung zu maximieren, konfigurieren Sie Ihre JobSet-Pods mit zwei Containern, die das Sidecar-Muster verwenden:

  • Inferenzserver:Ein optimierter Server (z. B. vLLM), der sich ausschließlich auf GPU- oder TPU-Berechnungen konzentriert.
  • Client-Treiber:Ein Logikcontainer, der asynchron eine große Anzahl von Anfragen an den Server auf localhost sendet.

Durch diese Entkopplung wird dafür gesorgt, dass die GPU oder TPU immer ausgelastet ist und nie inaktiv ist, während auf Netzwerk-E/A oder die Datenvorverarbeitung gewartet wird. Ohne diesen Ansatz kann es bei Modellen, die Daten sequenziell laden, dazu kommen, dass der Beschleuniger auf den Abschluss von E/A-Vorgängen wartet, was zu einer Unterauslastung führt. Anstatt darauf zu warten, dass die Daten verarbeitet werden, kann der Clienttreiber beispielsweise Daten vorab abrufen und kontinuierlich asynchrone Anfragen an den Inferenzserver senden. So wird sichergestellt, dass die Anfrage-Warteschlange des Beschleunigers immer gefüllt ist.

Zusammenfassung der Checkliste

Kategorie Best Practice
Architekturmuster
Kosten und Durchsatz
Messaging und Skalierung
Orchestrierung