Zum Inhalt springen
tecminds

Embedding Batch Size: Wie eine einzige zu grosse Anfrage unser RAG-Retrieval still lahmlegte

Ein Embedding-Batch-Size-Bug lieferte bei grossen Dokumenten null Vektoren und liess deren Embeddings still verschwinden — die Retrieval-Qualität halbierte sich. Fix, Backfill und Lektion.

TTobias LüscherCo‑Founder · TecMinds2026-06-01 · 8 Min Lesezeit

Embedding Batch Size: Wie eine einzige zu grosse Anfrage unser RAG-Retrieval still lahmlegte

Als ein Zitat-Verifizierungs-Job "keine stützende Passage" zurückgab für eine Aussage, von der wir wussten, dass sie auf Seite 14 des Quell-PDFs steht, war der offensichtliche Verdächtige der LLM-Judge. Die eigentliche Ursache lag drei Schichten tiefer in der Embeddings-Pipeline und hatte das Retrieval auf jedem grossen Dokument wochenlang still verschlechtert. Die Lektion — Embedding Batch Size ist wichtiger, als das übliche Cookbook-Beispiel vermuten lässt — kam letzte Woche während eines Retrieval-Quality-Sprints auf Acurio ans Licht, unserem Produkt zur KI-Zitat-Verifizierung. Der Bug war klein. Die Auswirkungen waren es nicht. Das ist die Aufzeichnung.

Das Symptom: 19 Prozentpunkte Verdict-Lücke bei grossen Quellen

Wir betreiben eine hybride Retrieval-Pipeline — semantische Embeddings oben, BM25 darunter, dazwischen ein Reranker — um KI-Zitat-Verifizierung zu ermöglichen. Auf den meisten Dokumenten stimmen die drei Schichten überein, und der Judge sieht die stärkste Passage für jede zitierte Aussage. Auf einer bestimmten Teilmenge von Dokumenten brach die Übereinstimmung zusammen. Der Judge lieferte "unbekannt" oder "nicht gestützt" für Zitate, die ein menschlicher Reviewer in fünfzehn Sekunden gefunden hätte.

Das Muster war der Hinweis. Die Fehler häuften sich bei Dokumenten mit mehr als etwa 600 Chunks — lange Richtlinien, mehrkapitelige PDFs, Quellen in Diplomarbeit-Länge. Alles, was bequem in einen einzelnen kleinen Embeddings-Call passte, funktionierte. Alles andere nicht.

Wir liessen einen reinen Recall-Harness über die zehn schwierigsten Produktionszitate laufen (kein LLM-Judge, nur "hat der richtige Chunk die Top-K erreicht?") und der Abstand war eklatant. Auf den betroffenen Dokumenten war die richtige Passage nirgends im Kandidaten-Pool. Das Retrieval rankte sie nicht schlecht — es hatte überhaupt keine semantische Repräsentation dieser Chunks. Die Verdict-Accuracy-Lücke, gemessen über Produktions-Logs der betroffenen Quellen, lag bei rund 19 Prozentpunkten unter dem Rest des Korpus.

Das ist eine stille, partielle Verschlechterung: Das Produkt lief weiter, die UI lieferte Antworten, und kein Fehler erreichte je den Nutzer. Die schlimmste Bug-Klasse, die ein Retrieval-System haben kann.

Die Ursache: Eine zu grosse Anfrage, ein Alles-oder-nichts-Guard

Unsere embedTexts-Funktion schickte alle Chunks eines Dokuments in einer einzigen Anfrage an einen OpenAI-kompatiblen Embeddings-Endpoint — mit fixiertem dimensions-Parameter, damit eine Änderung des Provider-Defaults nicht still die Spaltenbreite der Datenbank verletzen konnte.

Das funktionierte gut, bis ein Dokument mit 752 Chunks ankam. Der Endpoint, konfrontiert mit einem so grossen Input-Array, antwortete mit HTTP 200 und data: []. Kein Fehler, kein Rate-Limit-Signal, keine "too many inputs"-Meldung — einfach eine leere Liste, wo wir 752 Vektoren erwartet hatten.

Die nächste Schicht machte das Problem schlimmer. Der Caller weigert sich — sinnvollerweise — eine teilweise Embeddings-Charge einzufügen: Wenn die Länge des Vektor-Arrays nicht zur Länge des Chunk-Arrays passt, ist etwas schiefgelaufen, und man will kein halb beschriebenes Dokument in pgvector committen, wo Downstream-Queries Chunk-Indizes zu Embeddings versetzt zuordnen würden. Der Guard schlug zu, die Embeddings des gesamten Dokuments wurden verworfen, und die Chunks landeten mit leeren embedding-Spalten in der Datenbank.

Jetzt der stille Teil. Unser Retrieval-Pfad ist hybrid: Hat ein Chunk einen Vektor, trägt die semantische Ähnlichkeit bei; hat er keinen, trägt ihn BM25 allein per lexikalischer Überlappung. Grosse Dokumente tauchten also weiter in den Suchergebnissen auf — nur war die semantische Hälfte der Engine für sie abgeschaltet. BM25 ist gut darin, "finde einen Chunk, der diese Wörter benutzt" zu beantworten. Es ist nicht gut darin, "finde einen Chunk, der diese Aussage stützt" zu beantworten. Die Verdict-Qualität brach ein, ohne dass ein einziger Fehler in den Logs erschien.

Sechs Produktionsdokumente standen bei null Embeddings, als wir das Problem fanden.

Der Fix: Batchen, längenerhaltend bleiben, backfillen

Die Reparatur ist eine 60-Zeilen-Änderung im Embeddings-Modul, und das Muster verallgemeinert sich auf jede Pipeline, die eine entfernte Embeddings-API aufruft.

Die Anfrage batchen. Eine neue EMBEDDING_BATCH_SIZE (Default 96) deckelt die Anfragegrösse unter jeder Provider-Obergrenze. Das 752-Chunk-Dokument wird jetzt als acht Anfragen à 96 statt als eine à 752 verschickt. Kosten: ein zusätzlicher Round-Trip pro Batch; bei Dokumenten, die in einen einzelnen Call passen, keine Änderung.

Die Funktion längenerhaltend machen. Das ist der Teil, den Lehrbuchbeispiele auslassen. Das interne Ergebnis-Array wird auf input.length vorallokiert und nach absoluter Position gefüllt. Ein Batch, das fehlschlägt, lässt seine Slots als leere Arrays zurück — es schiebt den Rest der Antwort nicht nach links, und es bricht den ganzen Call nicht ab. Der "Vektorzahl muss gleich Chunkzahl sein"-Guard des Callers gilt weiterhin, weil die Längen weiterhin übereinstimmen, und der Insert-Pfad überspringt die leeren Zeilen. Ein schlechtes Batch verliert 96 Embeddings statt 752, und die anderen 656 Chunks landen korrekt.

Hartes Versagen von Teilerfolg unterscheiden. Die Funktion gibt nur dann null zurück, wenn kein einziges Batch erfolgreich war. Jedes erfolgreiche Batch reicht, um das Ergebnis von "Fallback auf reines BM25" auf "Nutze, was wir haben" zu kippen. Die Alles-oder-nichts-Semantik des alten Codes — ein Fehler war gleichbedeutend mit Totalausfall — ist Geschichte.

Ein dauerhaftes Backfill schreiben. Ein backfill-source-embeddings-Skript durchsucht die Datenbank nach Chunks mit Embeddings der Länge null, embettet sie neu und schreibt die Vektoren an Ort und Stelle. Dry-Run ist Default; --write committet. Dasselbe Skript reparierte die sechs beschädigten Produktionsdokumente am Morgen, an dem der Fix ausgerollt wurde.

Der Retrieval-Recall-Test, der auf den betroffenen Dokumenten zuvor null Treffer gezeigt hatte, lieferte nun Top-1- / Top-3- / Top-5-Werte auf Höhe des restlichen Korpus. Die Verdict-Accuracy-Lücke schloss sich.

Was wir gemessen und verworfen haben

Ein Retrieval-Sprint ist auch eine Gelegenheit, die Techniken zu testen, die man immer schon mal ausprobieren wollte. Die meisten halfen auf unseren Daten nicht, und die Disziplin, das auch laut zu sagen, ist wichtiger, als jeden Paper-Trick auszuliefern.

Wir massen Reciprocal Rank Fusion zwischen den semantischen und den BM25-Kandidaten — kein signifikanter Vorteil gegenüber der bestehenden gewichteten Mischung. Wir massen Pool-Widening (Top-50 auf Top-100 vor dem Rerank) — keine messbare Genauigkeitssteigerung, nur längere Rerank-Latenz. Wir testeten eine Parent-Child-Embed-Small-Strategie — das kleinere Embedding-Modell verlor Boden auf den schwierigsten rein semantischen Zitaten und kam bestenfalls auf null heraus. Alle drei sind nun im Engineering-Log als "ausprobiert, verworfen" festgehalten, damit die nächste Person, die danach greift, das vorherige Ergebnis sieht.

Eine Technik landete dann doch, hinter einem Flag: Neighbor Expansion. Bei einem Treffer werden die ±1 angrenzenden Chunks in den Judge-Kontext gespleisst. Auf den zehn schwierigsten Produktionszitaten hob das die Verdict-Übereinstimmung von 5/9 auf 7/9, ohne Mehrheits-Regressionen. Wir lieferten es defaultmässig deaktiviert aus — die Validierungsstichprobe ist auf False Negatives schief, sodass wir das gegenläufige Risiko (zusätzlicher Kontext bewertet ein teilweise gestütztes Zitat fälschlich als voll gestützt) noch nicht messen können. Sobald wir ein ausbalanciertes Eval-Set haben, kippt das Flag.

Derselbe Instinkt — die stillen Fehlermodi messen, bevor man die lauten Features skaliert — durchzieht den Aufbau unseres KI-Zitat-Verifizierungs-Workflows selbst und die Agent-Approval-Gates, über die wir letzten Monat geschrieben haben. Produktions-Retrieval lebt oder stirbt an den Rändern, für die niemand einen Test geschrieben hat.

Was das für alle bedeutet, die eine RAG-Pipeline betreiben

Drei Erkenntnisse, in der Reihenfolge, in der sie am meisten Zeit sparen.

Deckle deine Embedding Batch Size unter jeder Provider-Obergrenze und behandle eine Längen-Diskrepanz als "dieses Batch fehlgeschlagen", nicht als "dieses Dokument fehlgeschlagen". Provider lassen zu grosse Embeddings-Anfragen tendenziell als leere Arrays scheitern, nicht als 4xx-Fehler, und die meisten Referenz-Implementierungen reichen diese Leere bis in die Datenbank durch. Allokiere das Ergebnis vor, fülle nach Index, und lass ein schlechtes Batch ein schlechtes Batch sein.

Ergänze deine Retrieval-Logs um einen "Sind wir still in den Fallback gerutscht?"-Detektor. Unser eigentliches Versäumnis war nicht der Bug — es war die Abwesenheit einer Metrik für "Dokumente, die mit null semantischen Vektoren indexiert wurden." Jeder Chunk, der mit leerer Embedding-Spalte im Store landet, sollte ein Zähler sein, den du grafisch verfolgen kannst. Dieselbe Form — stille partielle Verschlechterung unter einem try/catch — tauchte Anfang Monat in unserem FastAPI-Rate-Limit-Header-Vorfall auf, und die Lektion ist dieselbe: Eine Library, die ihr Verhalten je nach Form ihrer Eingaben tauscht, findet irgendwann eine Form, die du nicht getestet hast.

Lege neben jede "diese Spalte darf leer sein"-Pipeline ein Backfill-Skript. Wenn der Bug ausgeliefert wird — und das tun gelegentlich alle —, ist der Unterschied zwischen "wir haben ein Skript erneut laufen lassen und die Produktionsdaten heilten sich" und "wir mussten unter Druck ein Recovery-Tool schreiben" die Frage, ob das Skript schon am ersten Tag im Repo lag. Default auf Dry-Run, Writes gaten, neben der Funktion ausliefern, die es backfillt.

Wenn du Retrieval in Produktion betreibst und vor dem Ausliefern ein zweites Augenpaar auf die Batching-Grenze, den längenerhaltenden Guard und die Backfill-Story haben willst, buche einen kostenlosen AI Potenzial-Check — oder lies, wie wir über KI-Agenten für Schweizer KMU den breiteren Architekturkontext denken.

acurio · Halluzinierte Zitate? Nicht in deinem Manuskript.

Citation‑Checker für Zotero. Findet halluzinierte oder nur teilweise gestützte Quellen in KI‑geschriebenen Texten. Thesis‑Pakete ab CHF 19, Schweizer Datenverarbeitung.

NÄCHSTER SCHRITTHat dich das interessiert?