Двухуровневый допрос: почему векторному поиску нужен кросс-энкодер
Пайплайн автоматической сертификации проверяет документацию поставщиков на кондитерскую глазурь для европейского знака V-Label.
Система отправляет поисковый запрос по базе из тридцати тысяч технических спецификаций: «Подтвердить, что в натуральном ванильном ароматизаторе используются растворители исключительно растительного происхождения».
Гибридный поиск из пятой главы (BM25 плюс векторный поиск через Reciprocal Rank Fusion) находит пятьдесят кандидатов за 18 миллисекунд.
На первом месте с оценкой 0.94 оказывается следующий фрагмент:
«Технический паспорт №9104: Натуральный ароматизатор ванили (мадагаскарский экстракт). В качестве матрицы-носителя применен пищевой глицерин для кондитерских изделий».
А документ №38, затерявшийся внизу выборки, содержит ключевую деталь:
«Дополнение к паспорту №9104: Растворитель-носитель глицерин (CAS 56-81-5) синтезирован методом переэтерификации жирных кислот говяжьего жира под давлением».
Если передать все пятьдесят найденных фрагментов напрямую в контекст большой языковой модели, возникнет перегрузка контекста. Модель зафиксирует совпадение формулировок на первом месте («натуральный ароматизатор», «пищевой глицерин»), пропустит скрытое примечание на 38-й позиции и сделает вывод, что ингредиент соответствует требованиям. Партия уходит в производство, проваливает лабораторную проверку при инспекции ритейла и попадает под отзыв.
Причина этой ошибки кроется в архитектуре би-энкодеров.
Узкое горлышко би-энкодеров
Векторный поиск по умолчанию работает на би-энкодерах (bi-encoders). В этой схеме модель обрабатывает запрос и документ раздельно:
Запрос ───► [Трансформер-энкодер] ───► Вектор u (1536 чисел)
│
Косинусное сходство
│
Документ ───► [Трансформер-энкодер] ───► Вектор v (1536 чисел)
Поскольку документы кодируются независимо от запроса, векторы для миллионов спецификаций можно рассчитать заранее, сохранить в векторном индексе (HNSW или IVFFlat) и опрашивать за считанные миллисекунды.
За эту скорость приходится платить потерей контекста.
Когда модель сжимает страницу химического паспорта в один массив чисел, она теряет связи между отдельными токенами. В би-энкодере токены запроса физически не взаимодействуют с токенами документа.
В запросе написано: «растворители растительного происхождения». В документе написано: «пищевой глицерин для кондитерских изделий».
В векторном пространстве слова растворитель, носитель, глицерин, ароматизатор и ваниль находятся рядом. Векторный индекс фиксирует близость и выдает сходство 0.94. Проверить, согласуется ли требование «растительного происхождения» с происхождением конкретной партии, би-энкодер не может: токены запроса и документа не встречаются в механизме внимания.
Кросс-энкодер: сквозное попарное внимание
Кросс-энкодер (cross-encoder) убирает эту изолированность. Вместо раздельных векторов он объединяет запрос и проверяемый документ в одну последовательность:
[CLS] Токены запроса [SEP] Токены документа [SEP]
│
[12-24 слоев сквозного self-attention]
│
Каждый токен взаимодействует
с каждым токеном
│
[Выход CLS]
│
Оценка релевантности (от 0 до 1)
В каждом слое трансформера токены запроса напрямую взаимодействуют со всеми токенами документа.
Когда кросс-энкодер доходит до словосочетания «растительного происхождения», веса внимания направляются на слова «глицерин», «переэтерификация» и «говяжий жир». Сеть фиксирует логическое и фактическое противоречие между запросом и описанием сырья.
Результат работы кросс-энкодера — не абстрактный угол между векторами, а прямая оценка соответствия: отвечает ли этот конкретный фрагмент на поставленный вопрос.
При оценке кросс-энкодером:
- Фрагмент №1 (размытое упоминание пищевого глицерина) падает с 0.94 до 0.12.
- Фрагмент №38 (указание на животный жир) получает максимальный балл по проверке сырья и сразу попадает в отчет инспектора.
Архитектура воронки
Если кросс-энкодер работает точнее, почему не проверять им всю базу?
Из-за вычислительной сложности.
Для поиска по базе в сто тысяч документов би-энкодер кодирует запрос один раз (5 миллисекунд) и сканирует индекс (10 миллисекунд). Суммарное время — 15 миллисекунд.
Кросс-энкодер ничего не может рассчитать заранее. Поскольку запрос и текст склеиваются перед подачей в сеть, для ста тысяч документов потребуется провести сто тысяч прямых проходов через трансформер. При времени 20 миллисекунд на документ на графическом процессоре один запрос выполнялся бы больше получаса.
Поэтому в инженерной практике используют воронку:
- Этап 1: Быстрый отбор кандидатов (Би-энкодер + BM25) Гибридный поиск отбирает топ-50 кандидатов из 100 000 документов за 15 мс через Reciprocal Rank Fusion.
- Этап 2: Точечная проверка (Кросс-энкодер)
Только эти 50 пар отправляются в кросс-энкодер (
bge-reranker-largeилиms-marco-MiniLM-L-6-v2) одним батчем на GPU (32 мс). - Этап 3: Генерация ответа в LLM В контекст языковой модели передаются только 5 лучших фрагментов с подтвержденным соответствием.
from sentence_transformers import CrossEncoder
class ComplianceReranker:
def __init__(self, model_name: str = "BAAI/bge-reranker-large"):
# Загружаем кросс-энкодер для попарного анализа запрос-документ
self.model = CrossEncoder(model_name)
def rerank(self, query: str, candidates: list[dict], top_k: int = 5) -> list[dict]:
"""
Переранжирование кандидатов первичного поиска через механизм cross-attention.
candidates: список словарей с ключами 'doc_id', 'text', 'retrieval_score'
"""
if not candidates:
return []
# Формируем пары [запрос, текст документа] для совместной подачи в сеть
pairs = [[query, doc["text"]] for doc in candidates]
# Батчевый проход через слои трансформера
cross_scores = self.model.predict(pairs)
# Прикрепляем полученные оценки к метаданным
reranked = []
for doc, score in zip(candidates, cross_scores):
reranked.append({
"doc_id": doc["doc_id"],
"text": doc["text"],
"initial_rank_score": doc.get("retrieval_score", 0.0),
"cross_encoder_score": float(score)
})
# Сортируем документы строго по оценке попарного внимания
reranked.sort(key=lambda item: item["cross_encoder_score"], reverse=True)
return reranked[:top_k]
Практический эффект
При анализе спецификаций на пищевое сырье добавление кросс-энкодера меняет структуру выдачи:
- Пул кандидатов: Топ-50 фрагментов из гибридного поиска (BM25 + Dense).
- Время ответа: Первичный поиск занимает 18 мс, переранжирование 50 фрагментов — 32 мс. Общее время пайплайна остается в пределах 55 мс.
- Точность топ-3: Доля релевантных фактов в первых трех позициях вырастает на 34% по сравнению с прямой передачей кандидатов из векторной базы в LLM.
Отсекая внешне похожие, но фактологически пустые формулировки, система подает в языковую модель очищенный контекст, исключая галлюцинации на этапе аудита.
В среду, в седьмой главе, мы разберем Двухэтапный RAG (сначала стандарты, затем лабораторные факты): как на первом этапе извлечь требования стандарта (ISO 23662), а на втором — использовать эти критерии для проверки паспортов качества (CoA) и лабораторных тестов.
Используете ли вы кросс-энкодеры в своих поисковых пайплайнах, и какой лимит задержки заложен в вашей архитектуре на этап переранжирования?