Если ты хоть раз пытался построить систему поиска по корпоративным документам (RAG), то наверняка сталкивался с тихим ужасом: пользователь задает точный вопрос по финансовому отчету («Какова операционная маржа в третьем квартале?»), а умная языковая модель либо разводит руками, либо берет цифру с потолка.
Почему так происходит? Корень проблемы спрятан в традиционных алгоритмах векторного поиска (Dense Retrieval). Вчера мы вскользь упоминали мультимодальные эмбеддинги Perplexity pplx-embed-v2-late, а сегодня давай подробно разберем, как устроен этот механизм и почему он ломает привычный подход к извлечению данных.
Классический энкодер берет страницу сложного PDF-документа — со сносками, графиками, многоуровневыми таблицами и балансами — и пытается утрамбовать весь этот массив в один-единственный плоский вектор (обычно на 768 или 1536 чисел). В этой «усушке» неизбежно теряется геометрия: строки таблиц перемешиваются с заголовками, а мелкие цифры в сносках просто растворяются в общем шуме.
Команда Perplexity AI пошла другим путем и выложила в открытый доступ семейство моделей pplx-embed-v2-late, построенное на архитектуре позднего взаимодействия (Late-Interaction).
Как работает позднее взаимодействие и оператор MaxSim
Вместо того чтобы сплющивать документ в один вектор, pplx-embed-v2-late сохраняет для каждого токена страницы свой собственный компактный 128-мерный вектор. Если на странице 500 токенов, модель формирует матрицу из 500 векторов.
Когда ты отправляешь поисковый запрос, магия происходит на этапе расчета сходства. Модель использует оператор максимального сходства (MaxSim):
- Каждое отдельное слово твоего запроса сравнивается со всеми токенами документа;
- Для каждого слова запроса находится токен документа с максимальным совпадением;
- Полученные максимумы суммируются в итоговый скор релевантности.
Если в запросе есть слово «маржа», алгоритм найдет в документе точный вектор ячейки таблицы, где написано «маржа», даже если весь остальной текст вокруг посвящен логистике или кадровым перестановкам. В независимых тестах на сложных финансовых и юридических таблицах эта архитектура обходит традиционные плотные эмбеддинги, превосходящие ее по весу в 5 раз.
При этом модель нативно мультимодальна: страницу можно подавать не только как текст, но и как прямое изображение скана (PNG/JPEG). Модель сама выравнивает визуальные токены таблицы с текстом запроса, избавляя тебя от капризных OCR-библиотек.
Семейство моделей и асимметричная индексация
Линейка pplx-embed-v2-late включает две основные версии:
- pplx-embed-v2-late-0.6b (~340M активных параметров) — сверхлегкая модель, которую можно запускать локально даже на слабом GPU или CPU;
- pplx-embed-v2-late-9b (~7.4B активных параметров) — флагман для индексации документов экстремальной сложности.
Поскольку обе модели проецируют данные в единое 128-мерное пространство, доступен элегантный трюк: асимметричный деплой. Ты можешь предварительно проиндексировать корпоративный архив тяжелой 9B-моделью, а пользовательские запросы на лету кодировать легковесной 0.6B-версией с задержкой менее 15 миллисекунд.
Практический запуск на Python через Sentence Transformers
Благодаря поддержке в библиотеке sentence-transformers >= 6.0.0 запустить локальный инференс можно буквально в десяток строк кода:
import torch
from PIL import Image
from sentence_transformers import MultiVectorEncoder
# Загрузка легковесной модели 0.6B на локальный GPU
device = "cuda" if torch.cuda.is_available() else "cpu"
model = MultiVectorEncoder(
"perplexity-ai/pplx-embed-v2-late-0.6b",
device=device,
torch_dtype=torch.float16 if device == "cuda" else torch.float32,
)
# 1. Индексация текстового фрагмента и изображения таблицы
document_text = (
"Отчет Q3 2026: Выручка облачного сегмента составила $4.2B, "
"показав прирост 28% YoY. Операционная маржа выросла до 31%."
)
document_image = Image.open("balance_sheet_q3.png").convert("RGB")
doc_embeddings = model.encode_document([document_text, document_image])
# 2. Кодирование пользовательского запроса
query = "Какова выручка облачного направления за третий квартал 2026 года?"
query_embedding = model.encode_query([query])
# 3. Расчет релевантности через оператор MaxSim
similarity_scores = model.similarity(query_embedding, doc_embeddings)
print(f"Релевантность текста: {similarity_scores[0][0]:.4f}")
print(f"Релевантность скана таблицы: {similarity_scores[0][1]:.4f}")
А вот как организовать потоковую конвертацию и индексацию многостраничного PDF:
from pdf2image import convert_from_path
def index_pdf_document(pdf_path: str, model: MultiVectorEncoder):
pages = convert_from_path(pdf_path, dpi=150)
indexed_pages = []
for idx, page in enumerate(pages):
page_rgb = page.convert("RGB")
embedding = model.encode_document([page_rgb])
indexed_pages.append({"page_number": idx + 1, "embedding": embedding})
return indexed_pages
Если база документов насчитывает миллионы страниц, не пытайся считать MaxSim в лоб по всему массиву: это потребует колоссального объема вычислений. Настрой двухэтапный поиск (Two-Stage Retrieval): сначала быстрый фильтр (BM25 или одиночный плотный вектор) отбирает 100 наиболее похожих кандидатов, а затем pplx-embed-v2-late проводит точный попиксельный и пословный скоринг первой сотни.
Обратная сторона медали: хранение индексов
Где подвох? В объеме дискового пространства. Сохранение матрицы 128-мерных векторов на каждый токен страницы раздувает векторный индекс в 20–50 раз по сравнению с привычными одиночными эмбеддингами. Для боевой эксплуатации тебе потребуется современная векторная база данных с поддержкой мультивекторного квантования (например, Qdrant или Milvus). Но если твоя задача — находить точные цифры в балансах без галлюцинаций, эта плата за надежность полностью оправдана.
