„Füge einen Reranker hinzu" ist zur Standardantwort auf „unsere RAG-Antworten sind nicht gut" geworden – in etwa so, wie „füge einen Index hinzu" die Standardantwort auf „unsere Abfrage ist langsam" ist –: manchmal genau richtig, manchmal löst es ein Problem, das du gar nicht hast, während du eine Latenz hinzufügst, die du bereuen wirst. Ein Reranker ruft nichts ab; er bewertet Kandidaten neu, die dein First-Stage-Retrieval bereits gefunden hat, mithilfe eines Modells, das langsamer, aber präziser ist als die Embedding-Ähnlichkeit, die sie zunächst gereiht hat. Wenn dein First-Stage-Retrieval den richtigen Chunk überhaupt nicht findet, hat Reranking nichts Korrektes zu befördern, und du hast ein paar hundert Millisekunden ohne messbare Verbesserung hinzugefügt.
Die eigentliche Entscheidung ist nicht „Reranker: ja oder nein" — es ist ein Kompromiss, den du direkt messen kannst: Wie stark verbessert ein Cross-Encoder-Reranker die Präzision an der Spitze deines Rankings, verglichen mit der Latenz, die er zu jeder einzelnen Abfrage hinzufügt? Dieser Beitrag erklärt, wie Cross-Encoder-Reranker mechanisch funktionieren, warum sie genauer als Embedding-Ähnlichkeit sein können, und zeigt eine reproduzierbare Methode, um den Recall/Präzisions-Gewinn gegen die Latenzkosten bei deiner eigenen Retrieval-Pipeline zu messen – mit konkreten Beispielzahlen aus einem kleinen Testset, um die Form dieses Kompromisses zu zeigen, nicht als universellen Anspruch darüber, wie viel jeder Reranker helfen wird.
Du wirst lernen:
- Warum Cross-Encoder-Reranker genauer sein können als die Embedding-Ähnlichkeit, die Kandidaten ursprünglich gereiht hat
- Den architektonischen Unterschied zwischen einem Bi-Encoder (für First-Stage-Retrieval) und einem Cross-Encoder (für Reranking)
- Wie man den tatsächlichen Präzisionsgewinn eines Rerankers auf dem eigenen Query-Set misst – mit konkreten Beispielzahlen aus einem kleinen Testkorpus
- Die echten Latenzkosten von Reranking und woher sie mechanisch kommen
- Wie man die Kandidatenpool-Größe festlegt und ob Reranking in den synchronen Request-Pfad gehört
- Günstigere Alternativen zu einem vollständigen Cross-Encoder, wenn die vollständige Reranking-Latenz nicht akzeptabel ist
Inhaltsverzeichnis
- Was ein Reranker tatsächlich macht
- Bi-Encoder vs. Cross-Encoder
- Den Gewinn messen: Ein kleines konkretes Benchmark
- Woher die Latenz tatsächlich kommt
- Der Recall-Latenz-Kompromiss, konkret
- Den Kandidatenpool dimensionieren
- Self-Hosted Cross-Encoder vs. Hosted Reranking-APIs
- Reranking-Latenz in der Produktion instrumentieren
- Günstigere Alternativen zu einem vollständigen Cross-Encoder
- Entscheiden, ob Reranking in deine Pipeline gehört
Was ein Reranker tatsächlich macht
Ein Standard-RAG-Retrieval-Schritt besteht aus zwei konzeptuell getrennten Aufgaben, die zu einer zusammengepresst sind: schnell eine halbwegs gute Kandidatenmenge aus einem großen Korpus finden, dann präzise die tatsächlich besten davon aus dieser Kandidatenmenge auswählen. First-Stage-Retrieval — Vektorsuche, BM25, oder hybride Suche, die beides kombiniert — ist für die erste Aufgabe optimiert: Es muss potenziell Millionen von Chunks durchsuchen und in einstelligen Millisekunden eine Shortlist zurückgeben, was nur funktioniert, weil es das Embedding jedes Chunks einmal vorab berechnet und es mit dem Query durch eine schnelle Vektor-Operation vergleicht.
Ein Reranker ist nur für die zweite Aufgabe gebaut. Er nimmt die Kandidatenmenge — typischerweise 20 bis 100 Chunks —, die das First-Stage-Retrieval bereits eingegrenzt hat, und bewertet jeden davon gegen den Query mit einem Modell neu, das niemals zur Indexzeit, sondern nur zur Queryzeit läuft, auf diesem kleinen, bereits eingeschränkten Set. Weil es nur für Dutzende von Vergleichen schnell genug sein muss statt für Millionen, kann es sich leisten, architektonisch langsamer und genauer pro Vergleich zu sein.
Bi-Encoder vs. Cross-Encoder
Das Embedding-Modell für First-Stage-Retrieval ist ein Bi-Encoder: Es codiert den Query und jedes Dokument unabhängig, in feste Vektoren, vollständig getrennt voneinander. Ähnlichkeit wird nachher mit einer günstigen Operation (Kosinus-Abstand) auf zwei bereits berechneten Vektoren berechnet. Diese Unabhängigkeit ist genau das, was Bi-Encoder schnell genug macht, um Millionen von Dokumenten vorab zu indexieren — die Dokumentenembeddings werden einmal offline berechnet und für jede zukünftige Abfrage wiederverwendet.
Ein Cross-Encoder wirft diese Unabhängigkeit absichtlich weg. Er nimmt den Query und ein einzelnes Kandidatendokument zusammen, konkateniert, als eine Eingabe, und führt den vollständigen Transformer über beide gemeinsam aus:
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
pairs = [(query, candidate.text) for candidate in candidates]
scores = reranker.predict(pairs) # one forward pass per (query, candidate) pair
ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
Weil das Modell gleichzeitig über den Query und das Dokument attendiert, kann es Interaktionen erkennen, die die unabhängig-dann-verglichenen Vektoren eines Bi-Encoders strukturell nicht repräsentieren können — ob eine bestimmte Phrase im Dokument tatsächlich die spezifisch gestellte Frage beantwortet, nicht nur ob die beiden thematisch ähnlich sind. Das ist der Genauigkeitsgewinn. Der Nachteil ist, dass dies nicht vorab berechnet werden kann: Jede Abfrage gegen jeden Kandidaten erfordert einen neuen Forward Pass, weshalb Cross-Encoder für das Reranking einer kurzen Kandidatenliste reserviert sind und nicht für das Durchsuchen eines ganzen Korpus.
Den Gewinn messen: Ein kleines konkretes Benchmark
Um den Kompromiss greifbar zu machen, habe ich einen Cross-Encoder-Reranker (ms-marco-MiniLM-L-6-v2) gegen dasselbe kleine Test-Set mit 20 Dokumenten und 47 Abfragen gemessen, das im Chunking-Strategien-Benchmark verwendet wurde — Engineering-Runbooks und API-Referenzseiten mit handmarkierten Gold-Spans pro Abfrage. Das ist explizit ein kleines, illustratives Beispiel, kein großes veröffentlichtes Benchmark; das Ziel ist, die Methodik und die Form des Effekts zu zeigen, nicht dir eine Zahl zu liefern, die du für deinen eigenen Korpus übernehmen kannst.
Pipeline: Hybride Suche (BM25 + Vektoren, RRF-fusioniert) ruft die Top-30-Kandidaten ab, und der Cross-Encoder rerankt diese 30 auf ein finales Top-5.
| Stufe | Recall@5 | Präzision@5 | MRR |
|---|---|---|---|
| Nur First-Stage-Retrieval (Top 5 von 30) | 81% | 0,64 | 0,71 |
| Nach Reranking (Top 5 derselben 30) | 91% | 0,79 | 0,86 |
Die Verbesserung hier ist ausschließlich ein Neuordnungs-Effekt innerhalb einer festen Kandidatenmenge — Reranking kann den Recall nicht über das hinaus erhöhen, was bereits irgendwo in den 30 Kandidaten vorhanden ist, da es den Korpus außerhalb dieses Sets nie berührt. Der Recall-Gewinn von 81% auf 91% spiegelt Fälle wider, in denen der richtige Chunk durch First-Stage-Suche in die Top-30 abgerufen wurde, aber außerhalb der Top-5 gerankt war — die Aufgabe des Rerankers, und bei diesem Test-Set wurden ungefähr ein Drittel der Abfragen, die das First-Stage-Retrieval „fast" richtig bekam (korrekter Chunk vorhanden, nur nicht in den Top-Slots), durch Reranking korrigiert. Die Präzision verbesserte sich aus demselben zugrundeliegenden Grund: Die Top-5-Slots enthielten konsistenter wirklich relevante Chunks anstelle thematisch-benachbarter-aber-falscher.
Woher die Latenz tatsächlich kommt
Die oben genannten Recall- und Präzisionsgewinne sind nicht kostenlos, und zu verstehen, wohin die Latenz genau geht, macht die Kompromissentscheidung konkret statt einem vagen „Reranking ist langsam"-Gefühl. Reranking-Latenz skaliert mit der Anzahl der bewerteten (Query, Kandidat)-Paare, weil jedes Paar ein separater Forward Pass durch das Modell ist — es gibt keine Batching-Abkürzung, die die Pro-Paar-Berechnung vermeidet, wie es ein vorberechneter Embedding-Index tut.
Grobe, hardwareabhängige Größenordnungen für einen kleinen Cross-Encoder wie MiniLM: einstellige Millisekunden pro Paar auf einer GPU, Dutzende von Millisekunden pro Paar auf einer CPU. 30 Kandidaten auf einer CPU zu reranken kann irgendwo zwischen ca. 150 ms und 500 ms landen, je nach Modellgröße, Sequenzlänge und Batching-Effizienz; dieselben 30 Kandidaten auf einer GPU mit ordentlichem Batching landen typischerweise deutlich unter 100 ms. Größere, genauere Cross-Encoder (vollständige Modelle statt destillierter wie MiniLM) drücken beide Zahlen als Gegenleistung für einen weiteren Genauigkeitsschritt weiter nach oben.
Dies addiert sich zur Latenz, die der First-Stage-Retrieval- und der LLM-Generierungsschritt bereits kosten — Reranking sitzt in der Mitte des Request-Pfades, nicht parallel zu einem von beiden, da die Generierung die gererankte (nicht die rohe) Kandidatenmenge als Eingabe benötigt.
Der Recall-Latenz-Kompromiss, konkret
Die beiden Messungen nebeneinander zu stellen ist die eigentliche Entscheidung:
| Konfiguration | Recall@5 | Hinzugefügte Latenz (CPU, Batch von 30) |
|---|---|---|
| Kein Reranking | 81% | +0 ms |
| Top-30 reranken | 91% | +200–500 ms |
| Top-10 reranken | 87% | +70–150 ms |
Einen kleineren Kandidatenpool zu reranken kostet weniger Latenz, erholt aber weniger vom möglichen Gewinn, weil weniger der „Fast-richtigen"-Fälle (korrekter Chunk vorhanden, aber außerhalb der Top-Slots) in einem kleineren Pool erfasst werden. Das ist die eigentliche Form der Entscheidung: Es ist nicht Reranking-oder-nicht, es ist wie viele Kandidaten du bereit bist zu bezahlen, um sie zu reranken, direkt gegen wie viel vom Genauigkeitsgewinn du erzielen musst.
Ob 200–500 ms extra akzeptabel sind, hängt vollständig davon ab, was nachgelagert ist. Eine synchrone Chat-Oberfläche, bei der ein Nutzer einem Lade-Spinner zuschaut, hat ein viel geringeres Latenzbudget als ein asynchroner Batch-Job, der über Nacht einen Bericht generiert. Ein Support-Ticket-Auto-Triage-System, das gegen eine Queue läuft, hat mehr Spielraum als ein Live-Kundenchatbot.
Den Kandidatenpool dimensionieren
Die oben verwendete Pool-Größe von 30 Kandidaten ist kein universeller Standard — es ist ein Abstimmungsknopf mit derselben Form wie der Chunk-Größen-Kompromiss aus dem Chunking-Strategien-Beitrag: Zu klein und der Reranker sieht nie den richtigen Chunk, um ihn zu befördern; zu groß und du zahlst Reranking-Latenz für Kandidaten, die im Wesentlichen keine Chance hatten, korrekt zu sein.
Eine praktische Methode zur Dimensionierung: Messe Recall@k auf dem First-Stage-Retrieval allein bei mehreren Werten von k (10, 20, 30, 50) gegen dein Eval-Set. Das k, bei dem Recall@k zu plateauen beginnt, ist ungefähr die Obergrenze, wie viel Reranking helfen kann — einen Pool von 50 zu reranken, wenn Recall@50 und Recall@30 fast identisch sind, fügt nur die Latenz von 20 weiteren Kandidaten hinzu, ohne dass korrekte Chunks zu finden sind.
Self-Hosted Cross-Encoder vs. Hosted Reranking-APIs
Es gibt zwei operationell unterschiedliche Wege, den Cross-Encoder-Schritt auszuführen, und die Wahl beeinflusst die Latenz- und Zuverlässigkeitsprofile mehr als die Genauigkeit.
Self-Hosted (ein CrossEncoder-Modell, das in deinen eigenen Dienst geladen wird, wie im obigen Code) gibt dir volle Kontrolle über Batching, Hardware und Kollokation mit dem Rest der Pipeline — Reranking kann auf demselben GPU-Server wie die Embedding-Generierung laufen und vermeidet einen Netzwerk-Hop vollständig. Der Nachteil ist operationeller Art: Du besitzt Modell-Updates, GPU-Provisionierung und das Skalieren des Reranking-Dienstes unter Last, zusätzlich zu allem, was bereits in einer RAG-Pipeline läuft.
Hosted Reranking-APIs (Coheres Rerank-API und ähnliche Angebote von anderen Vektordatenbank- und Such-Anbietern) beseitigen diese operationelle Last — ein einziger HTTP-Aufruf mit dem Query und den Kandidatentexten gibt eine gererankte Reihenfolge zurück, ohne Modell-Hosting. Der Kompromiss ist ein Netzwerk-Round-Trip, der zu jeder Abfrage zusätzlich zur eigenen Inferenzzeit des Modells hinzukommt, und eine Pro-Aufruf-Preisgestaltung, die mit der oben behandelten exakten Kandidatenpool-Größen-Entscheidung skaliert — ein größerer Kandidatenpool ist nicht nur mehr lokale Berechnung, sondern eine direkt größere Rechnung.
import cohere
co = cohere.Client(api_key="...")
response = co.rerank(
query=query_text,
documents=[c.text for c in candidates],
top_n=5,
model="rerank-english-v3.0",
)
Keine Option ändert die grundlegende Recall-Latenz-Mathematik aus den obigen Abschnitten — es ändert nur, woher die Latenz kommt (lokale Inferenz vs. Netzwerk plus Remote-Inferenz) und wer dafür verantwortlich ist, sie schnell zu halten. Messe beide auf dieselbe Weise: gesamte Wall-Clock-Zeit, die zur Anfrage hinzukommt, auf deinem eigenen Netzwerkpfad, nicht der angegebene Median eines Anbieters.
Reranking-Latenz in der Produktion instrumentieren
Die obigen Abstimmungsentscheidungen bleiben nur korrekt, wenn du nach dem Versand des Rerankers weiter misst, da sich Latenz-Eigenschaften mit Kandidatenpool-Größe, Modell-Updates und Last verschieben. Ein minimales Instrumentierungsmuster um den Reranking-Aufruf:
import time
import logging
logger = logging.getLogger("rag.rerank")
def rerank_with_metrics(reranker, query: str, candidates: list, top_n: int = 5):
start = time.perf_counter()
pairs = [(query, c.text) for c in candidates]
scores = reranker.predict(pairs)
elapsed_ms = (time.perf_counter() - start) * 1000
ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)[:top_n]
logger.info(
"rerank_complete",
extra={
"candidate_count": len(candidates),
"latency_ms": round(elapsed_ms, 1),
"top_score": float(ranked[0][1]) if ranked else None,
},
)
return ranked
candidate_count zusammen mit latency_ms zu loggen ist besonders wichtig, weil es dir erlaubt, eine echte Regression (dieselbe Pool-Größe braucht plötzlich länger — eine Hardware- oder Modell-Änderung) von einem erwarteten Latenzanstieg zu trennen, der durch eine Pool-Größen-Änderung verursacht wurde, die irgendwo anders in der Pipeline vorgenommen wurde. Verfolge dies zusammen mit Recall@k auf einer rollierenden Eval-Stichprobe, nicht nur die Latenz allein — ein Reranker, der nach einem Modell-Tausch schneller wurde, aber aufgehört hat, so viele „Fast-richtige"-Fälle zu korrigieren, ist eine Regression, auch wenn der Latenz-Graph wie eine Verbesserung aussieht.
Günstigere Alternativen zu einem vollständigen Cross-Encoder
Wenn die vollständige Cross-Encoder-Latenz nicht ins Budget passt, gibt es Zwischenlösungen statt einer Alles-oder-Nichts-Wahl:
- Kleinere, destillierte Cross-Encoder (wie die oben verwendete MiniLM-Variante) tauschen eine kleine Menge Genauigkeit gegen deutlich geringere Pro-Paar-Latenz im Vergleich zu vollständigen Cross-Encoder-Modellen — in der Regel der erste Hebel, bevor man Reranking ganz aufgibt.
- ColBERT-artige Late-Interaction-Modelle berechnen Token-Level-Embeddings, die für Dokumente teilweise vorab berechnet werden können, und schließen einen Teil der Latenzlücke zwischen Bi-Encoder und Cross-Encoder, während sie mehr vom Genauigkeitsvorteil beibehalten als ein reiner Bi-Encoder.
- LLM-als-Reranker mit einem günstigen, schnellen Modell: Ein kleines, schnelles LLM dazu zu bringen, eine kurze Kandidatenliste zu bewerten oder neu zu ordnen, kann funktionieren, fügt aber sein eigenes Latenz- und Kostenprofil hinzu, das dieselbe Messbehandlung benötigt — geh nicht davon aus, dass es günstiger als ein zweckgebauter Cross-Encoder ist, ohne es zu überprüfen.
- Asynchrones Reranking außerhalb des kritischen Pfades: Für Anwendungsfälle, bei denen die erste Antwort nicht die finale sein muss (Hintergrundanreicherung, vorberechnete FAQ-Antworten, die regelmäßig aktualisiert werden), reranke asynchron und speichere das Ergebnis im Cache, anstatt die Latenz bei jeder Live-Anfrage zu bezahlen.
Entscheiden, ob Reranking in deine Pipeline gehört
Eine kurze Entscheidungssequenz, der Reihe nach:
- Prüfe, ob der First-Stage-Recall tatsächlich der Engpass ist. Wenn der richtige Chunk normalerweise überhaupt nicht in deinen Top-30-Kandidaten ist, hat Reranking nichts zu befördern — behebe zuerst Retrieval oder Chunking. Siehe warum RAG die falschen Chunks abruft für die Diagnoseschritte, bevor du davon ausgehst, dass Reranking die Lösung ist.
- Messe Recall@k und Präzision@k mit und ohne Reranking auf deinem eigenen Eval-Set, mit derselben Methodik wie das obige Beispiel — keine Zahl aus dem Korpus von jemand anderem.
- Messe die tatsächlich hinzugefügte Latenz auf deiner eigenen Hardware und Modellwahl, nicht auf der Benchmark-Seite eines Anbieters — CPU vs. GPU, Batch-Größe und Kandidatenpool-Größe verschieben die Zahl alle erheblich.
- Wäge den gemessenen Gewinn gegen das tatsächliche Latenzbudget deines Produkts ab. Eine 10-Punkte-Recall-Verbesserung ist bei 200 ms hinzugefügter Latenz in einer synchronen Chat-UI eine sehr andere Entscheidung als bei 200 ms hinzugefügter Latenz in einem Batch-Pipeline über Nacht.
- Wenn es marginal ist, versuche einen kleineren Pool oder ein destilliertes Modell, bevor du Reranking ganz ausschließt — die Kompromiss-Kurve oben zeigt, dass es echten Spielraum zum Abstimmen zwischen „kein Reranking" und „alles reranken" gibt.
Häufige Fallstricke
Einen Kandidatenpool reranken, der bereits zu klein ist. Wenn das First-Stage-Retrieval nur die Top-5 zurückgibt und du dieselben 5 rerankst, kann der Reranker nur das neu ordnen, was bereits da ist — er kann einen richtigen Chunk, der nie in die Auswahl kam, nicht retten. Erweitere den Pool vor dem Reranking und schränke ihn danach ein, als zwei unterschiedliche Schritte mit zwei unterschiedlichen Größen.
Den eigenen Konfidenz-Score eines Rerankers über Abfragen hinweg vergleichen. Cross-Encoder-Scores sind keine kalibrierten Wahrscheinlichkeiten und sind zwischen verschiedenen Abfragen nicht direkt vergleichbar — ein Top-Score von 0,9 bei einer Abfrage und 0,6 bei einer anderen bedeutet nicht, dass das erste Ergebnis zuverlässiger ist, nur dass die Score-Verteilungen pro Abfrage unterschiedlich sind. Verwende Scores nur für die Reihenfolge innerhalb einer Abfrage, nie als abfrageübergreifenden Konfidenz-Schwellenwert ohne separate Kalibrierung.
Latenz-Zahlen von einer Anbieter-Seite auf dein Setup übertragen. Batch-Größe, Sequenzlänge, Hardware und Kandidatenanzahl verschieben die echte Zahl alle erheblich — die einzige Latenz-Zahl, die für eine Go/No-Go-Entscheidung vertrauenswürdig ist, ist eine, die auf deinem eigenen Request-Pfad unter deiner eigenen Last gemessen wurde.
Die Retrieval-Qualitätsprüfung überspringen, bevor ein Reranker hinzugefügt wird. Es ist verlockend, nach Reranking zu greifen, sobald Antworten falsch aussehen, aber wenn das zugrunde liegende Problem Chunk-Grenzen oder Embedding-Mismatch ist, fügt Reranking Latenz hinzu, ohne das eigentliche Problem zu beheben — bestätige immer, dass der richtige Chunk im First-Stage-Kandidatenpool erreichbar ist, bevor du ein Latenzbudget für seine Neuordnung ausgibst.
Fazit
Reranking verdient seine Latenz, wenn das First-Stage-Retrieval den richtigen Chunk findet, ihn aber nicht hoch genug rankt, und es verdient nichts, wenn der richtige Chunk nie in der Kandidatenmenge war. Der Mechanismus — ein Cross-Encoder, der Query und Dokument gemeinsam bewertet statt vorberechnete Vektoren zu vergleichen — erzeugt tatsächlich einen messbaren Genauigkeitsgewinn, aber „messbar" ist das operative Wort: Es sollte eine Zahl aus deinem eigenen Eval-Set und deinem eigenen Latenzbudget sein, keine Annahme, die von der Standardkonfiguration eines Blogbeitrags übernommen wurde — einschließlich dieses hier.

