Суть
Векторный поиск (bi-encoder) быстрый, но грубый: он кодирует вопрос и документ независимо и сравнивает косинусом. Cross-encoder подаёт пару «вопрос + документ» в модель вместе, поэтому ранжирует точнее — но медленнее, и его нельзя гонять по всей базе.
Зачем это нужно
Если запустить cross-encoder на всех документах базы, поиск простого запроса может занять ~2 минуты. Поэтому его применяют только к уже отфильтрованному топу: даёт стабильный +5-8% accuracy при контролируемой latency.
Как работает (воронка)
- Recall-этап — гибридный поиск (Hybrid Search, BM25 + vector) отбирает топ-50…100 кандидатов.
- Precision-этап — cross-encoder переранжирует топ-20.
- Финал — топ-5 наиболее релевантных чанков уходят в промпт LLM.
- Multi-stage Reranking — каскад: быстрый bi-encoder → cross-encoder → (опционально) LLM-фильтр. Каждая ступень дороже и точнее предыдущей.
- Trade-off: «быстрый, но неточный RAG бесполезен» — реранкинг и есть способ докупить точность за приемлемую задержку (целевые P95 < 3 c, см. RAG Metrics).
- Реранкинг — самая дешёвая по усилиям техника с стабильным приростом, поэтому входит в Production Baseline 2026 (вместе с Hybrid Search).
- Multilingual reranker (на момент источников): jina-reranker-v3 — 0.6B, listwise, «last but not late» (causal attention по запросу и всем кандидатам в одном окне, без late-interaction как у ColBERT), BEIR 61.94 nDCG@10; покрывает и русский. ⚠️ Конкретные SOTA-модели быстро устаревают.
- Top-k как production-эвристика (пример, не универсальный стандарт): top-50 (BM25) → top-20 (vector) → top-5 (после reranker); целевые latency P95 < 3 c, TTFT < 500 мс.
Пример
Двухступенчатая воронка: bi-encoder быстро отбирает top-10 кандидатов, cross-encoder (принимает пару «вопрос+документ» вместе) точно переранжирует их в top-k.
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
def reranking_retrieve(question, k=3):
candidates = vectorstore.similarity_search(question, k=10) # bi-encoder: грубо, top-10
pairs = [(question, c.page_content) for c in candidates]
scores = reranker.predict(pairs) # cross-encoder: точно
ranked = sorted(zip(scores, candidates), key=lambda x: x[0], reverse=True)
return [d.metadata["doc_id"] for _, d in ranked][:k]
Связано с
- RAG — реранкинг = precision-этап retrieval
- Hybrid Search — поставляет кандидатов на реранкинг
- Embeddings — bi-encoder (быстрый отбор) работает на эмбеддингах
Открытые вопросы
- RU-специфичный выбор/бенчмарк reranker'а (multilingual jina-reranker-v3 — текущий ориентир; прямого RU-сравнения reranker'ов нет)
- tensor-based reranking (RAGFlow) vs классический cross-encoder