← Лендинг Hermes Agent

Опубликовано

Поиск по скриншотам документации на одной видеокарте: EmbeddingGemma 2, 53 картинки и 8 секунд

Автор: Гусев Николай [портфолио]

Обновлено

Темы: embeddinggemma, rag, embeddings, multimodal

Google выкатила EmbeddingGemma 2, открыта модель эмбеддингов, которая за раз ест текст, код, картинки и аудио и выдает всё это в общее векторное пространство. 740M параметров, Apache 2.0. Я проверил её на живой задаче, где она, как выяснилось, решает проблему, которую мой собственный стек закрывает криво: поиск по скриншотам в технической документации.

Проблема: скриншоты не находятся никак

У меня работает локальный RAG поверх корпоративной документации (Astra Linux, ALD Pro). Пайп стандартный: PDF превращается в текст (pdftotext), текст режется на чанки, чанки уходят в векторную базу с bge-m3. По тексту ищет отлично.

Только вот документация на 700 страниц, в ней 150 скриншотов интерфейса, и когда вопрос звучит как "где в мастере выбрать лес доверие", текстовый поиск мимо. Не потому, что поиск плохой, а потому, что ответ лежит на картинке: окно мастера, галочка на пункте, кнопка. Текст страницы вокруг скриншота нужных слов может не содержать.

OCR это лечит наполовину: он вытащит надписи с картинки, но не поймет, что перед ним мастер настройки, а не случайная строка меню. И он ничего не даст на схемах и диаграммах, где текста нет вообще.

Что я проверил

Взял свежий llama.cpp, который идет с LM Studio (билд 2.53.0), и веса unsloth в GGUF: текстовая часть 0.31 ГБ в Q8, мультимодальный проектор 0.55 ГБ. Пересобирать llama.cpp не пришлось, поддержка уже в рантайме. Поднял llama-server в режиме --embedding на свободном порту, :1234 LM Studio не трогал.

Первый сюрприз: картинку в /v1/embeddings нельзя передать как image_url в явном виде, сервер отвечает 400. Рабочий формат - content parts внутри chat-сообщения, как в chat completions:

{"input": [{"role": "user", "content": [
  {"type": "text", "text": "task: search result | "},
  {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
]}], "model": "m"}

Второй сюрприз: без task-префиксов модель почти не отличает релевант от мусора, 0.53 против 0.65 на любом запросе. С префиксами "task: search result |" для документов и "task: search result | query:" для запросов картина становится чистой: релевант 0.99, мусор около 0.50. Это не опция, это обязательный пункт.

Живой замер на документации

Взял реальный документ из корпоративной поставки, инструкцию по доверительным отношениям между ALD Pro и MS AD, 148 страниц. Из него pymupdf вытащил 53 скриншота за 3.6 секунды. Проиндексировал все через модель: 8 секунд на GPU, 0.15 секунды на картинку. Весь индекс - 0.9 МБ.

Потом погонял текстовые запросы против этого индекса и сверил попадания глазами по документу.

"общий доступ к папке security sharing windows"   -> с.118, окно свойств папки, 0.807
"подключение сетевого ресурса smb"                -> с.120, подключение папки в Samba, 0.771
"мастер создания доверия forest"                  -> с.33, мастер New Trust Wizard, 0.781
"kerberos билеты TGT TGS KDC"                     -> с.46, схема билетов, 0.757
мусорный запрос ("погода")                        -> пол 0.62

Четыре запроса, четыре точных попадания в конкретное окно на конкретной странице. Замечу, что я специально проверял попадания по тексту PDF и по содержимому скриншотов, а не верил цифре косинуса: первые два "промаха" в моем тесте оказались моими же ошибками в ожиданиях, картинка лежала на соседней странице.

Контрольный вопрос: а не дублирует ли это OCR? Для скриншотов с текстом частично да, OCR вытащил бы надписи. Но разница всплывает там, где текста нет: нарисованная мной схема сети без единого слова дала 0.65 к запросу про архитектуру против 0.54 у мусорного запроса. Слабее, чем у скриншотов UI, но разделение есть. Бар-чарт к запросу про графики - 0.766. OCR тут бессилен полностью.

Аудио, о котором в анонсе кричат громче всего

Аудио-ветка работает, но по-другому, чем я ожидал. Голосовое сообщение на 82 секунды, реальное, по-русски, конвертируется в wav и отправляется тем же content parts, тип input_audio. Вектор получается за 1.5 секунды, без единого слова Whisper.

Качество ниже, чем у картинок: 82 секунды речи про настройку Яндекс.Директа дали 0.718 к своему запросу и 0.663 к чужому. Разделение всего 0.06, это не поиск, это грубый фильтр. Две голосовые из одной переписки дали 0.825 между собой, на разные темы - 0.736, дельта 0.09.

Практический вывод: аудио-эмбеддинги годятся как первая стадия поиска по архиву голосовых (взять топ-20 кандидатов), а финальный отбор все равно требует транскрипта. Заменить Whisper они не заменяют, у разработчиков llama.cpp эта ветка честно помечена experimental.

Что со всем этим делать

Схема встраивания в существующий пайп: после pdftotext добавить стадию, которая через pymupdf вытаскивает картинки и гонит их в эмбеддинг-индекс с привязкой к странице. Текстовый индекс живет как жил, новый индекс ищется параллельно. Ответ на запрос - "скриншот на странице N документа X", дальше текст страницы тянется обычным поиском.

Цена вопроса: 0.86 ГБ весов на диске, 0.15 секунды индексации на картинку, пара сотен строк кода. По сравнению с OCR-пайплайном это не замена, а второй слой: OCR ловит текст на картинках, эмбеддинги ловят саму картинку.

Ограничение, о котором стоит знать сразу: для текстовых векторов bge-m3 в моих замерах нерелевантные пары дают косинус 0.28, у EmbeddingGemma 0.50. Рабочее окно между шумом и темой у неё уже, порог отсечки держать труднее. Заменять боевой текстовый эмбеддер смысла не увидел. Ровно одно новое место, где эта модель даёт то, чего не было: индекс изображений поверх текстового поиска.

Готовый скрипт индексации (достаёт картинки из PDF, строит индекс, ищет по нему) приложен к материалам статьи: github.com/NikolayGusev-astra/channel-materials, каталог 2026-10-07-embeddinggemma2-image-search/code. Я прогнал его на том же документе перед публикацией: 53 картинки за 8 секунд, запрос про мастер доверия вернул страницы 33-35, где этот мастер и живёт.

Ссылки