Почему полнотекстовый поиск пасует перед фотографией стула
Представьте: вы ищете на Авито «деревянный стул с вензелями». Вбиваете запрос — и получаете кучу пластиковых табуреток, потому что продавец написал «стул, дерево, резной» в описании, а картинку прилепил от дивана. Классика. Полнотекстовый поиск слеп к визуалу. Он цепляется за слова, а не за то, что реально изображено.
А теперь представьте, что модель видит фотографию и понимает: «Ага, тут стул, дерево, резьба, вензеля — это то, что нужно». Именно это и сделали ребята из Авито, прикрутив мультимодальную модель Qwen2.5-VL. И нет, они не стали затачивать гигантскую LLM под миллиарды объявлений. Они пошли по пути LoRA + vLLM. Давайте разберем, как это работает, где они наступили на грабли и что получилось в итоге.
Дисклеймер: все данные основаны на открытых докладах команды Авито и моем опыте внедрения похожих решений. Никакой секретной информации — только инженерная логика.
Архитектура: Qwen2.5-VL как поводырь для поиска
Выбор пал на Qwen2.5-VL-7B (апрель 2026, последняя стабильная версия на момент внедрения). Почему не 72B? Стоимость инференса. 7B — золотая середина: достаточно понимания картинок, чтобы отличить рюкзак от чемодана, и достаточно быстрый для продакшена.
Модель работает в пайплайне:
- Пользователь вводит текстовый запрос.
- К каждому объявлению (их миллионы) уже заранее извлечены визуальные эмбеддинги через Qwen2.5-VL — они кешируются.
- vLLM сервит модель, которая налету сравнивает эмбеддинги запроса и эмбеддинги фото, дообученные под текстовые описания.
- Результаты ранжируются по релевантности и подмешиваются в выдачу.
Звучит логично. Но есть нюанс: базовая модель Qwen2.5-VL не заточена под задачу классификации «запрос-изображение». Её учили на совсем других данных. Тут и пригодился LoRA.
LoRA: лёгкий тюнинг без тяжёлой артиллерии
Авито не стали фулл-файнтюнить модель. Это дорого, долго и рискованно (забудет, как видеть текст). Вместо этого нацепили LoRA-адаптеры ранга 16 на все линейные слои — и текстового энкодера, и визуального. Обучали на парах «запрос-изображение», собранных вручную: 50 000 примеров, где изображение точно соответствует запросу, и 50 000 негативных (не соответствует).
Главная боль, с которой столкнулись — петли повторений в LoRA. Если обучать слишком долго или с высокой скоростью, модель начинает «зацикливаться» на паттернах — выдавать один и тот же эмбеддинг на любую картинку. Я подробно разбирал эту проблему в статье Петли повторений в LoRA: как я ломал Qwen2.5-VL-3B и что из этого вышло. Там же описан способ детекта — ловить на валидации, когда loss перестаёт падать, а эмбеддинги становятся одинаковыми.
В Авито пошли дальше: они использовали гибридный метод QAT+LoRA, о котором я писал в Гибридный метод QAT+LoRA: скрытая альтернатива QLoRA. Это позволило одновременно квантовать модель до INT8 и дообучать LoRA без потери качества. Итоговый размер адаптера — всего 14 МБ, а модель после квантования занимает ~4 ГБ вместо 14 ГБ FP16.
vLLM: инференс на стероидах
Выкатить модель на продакшен — полдела. Нужно, чтобы она отвечала за миллисекунды. vLLM — оптимальный выбор для обслуживания LLM: continuous batching, PagedAttention, поддержка LoRA адаптеров на лету (можно подгружать разные адаптеры под разные категории товаров).
Архитектура развёртывания на Авито:
- Кластер из 8 GPU A100 80GB (NVLink внутри ноды).
- Модель Qwen2.5-VL-7B квантизированная INT8 + LoRA адаптер (14 MB) загружается в vLLM.
- Для инференса используется batching до 64 запросов на один GPU.
- Параллельно запущен сервис препроцессинга изображений (OpenCV + кроп лиц/объектов, если нужно).
Важный нюанс: vLLM по умолчанию не умеет обрабатывать изображения в мультимодальных моделях. Для Qwen2.5-VL используется специальный multimodal plugin (отдельный PR в vLLM, вкоммиченный в феврале 2026). Без него пришлось бы извлекать эмбеддинги отдельно, что добавляет latency. На Авито форкнули vLLM и добавили поддержку vision encoder в основную pipeline — теперь изображения обрабатываются прямо в батче с текстом.
Результаты и метрики
Замеряли NDCG@10 на тестовой выборке из 10 000 запросов. Без мультимодальной модели — 0.32. С моделью (только LoRA) — 0.38. С моделью + QAT+LoRA — 0.37 (потеря 1% из-за квантования, но скорость выросла вдвое). В Авито выбрали второй вариант — профит в скорости перевесил микроскопическое падение метрики.
Ещё один замер — user engagement: количество кликов по объявлениям, поднявшимся в топ за счёт визуальной релевантности, выросло на 12%. Неплохо для пары адаптеров и одного дополнительного GPU.
Грабли, на которые наступили (и вы наступите)
- Дисбаланс классов в обучающей выборке. Позитивных пар много, негативных — ещё больше. Брали ratio 1:1, но модель стала слишком «пессимистичной» — часто говорила «не релевантно» даже для хороших совпадений. Решили: добавили hard negative mining — искали объявления, которые текстовый поиск считает релевантными, а визуально нет.
- Проблема с несколькими объектами на фото. Qwen2.5-VL иногда «путалась»: на кучу хлама модель могла найти и стол, и стул, и велосипед. Пришлось добавить детекцию объектов (YOLOv8) и передавать в промпт только координаты основного объекта.
- Медленный препроцессинг. Конвертация всех изображений в эмбеддинги для базы объявлений заняла 3 дня на 100 GPU. Решение: распараллелили через Spark + Kafka, и теперь инкрементально обновляем только новые/изменённые объявления.
Как не надо делать: типичные ошибки
Покажу на примере. Допустим, вы решили просто взять Qwen2.5-VL и использовать её как ранкер: подавать на вход запрос + текст описания + изображение, получать score релевантности. Проблема: latency — один вызов модели 500 мс, а нужно ранжировать топ-100 объявлений — 50 секунд на запрос. Неприемлемо.
Правильно: заранее извлекать визуальные эмбеддинги (один раз на объявление), а на запросе считать косинусную близость между эмбеддингом запроса (закодированным через текстовый энкодер) и эмбеддингами фото. vLLM здесь не нужна? Нужна — для быстрой генерации эмбеддингов запросов и для реранкинга в сложных случаях. В Авито сделали двухстадийную схему: быстрый косинус (50 мс) + уточнение vLLM для Top-10 (ещё 100 мс). Итого ~150 мс — приемлемо.
Ошибка: пытаться скормить модели целиком изображение на каждый запрос. Препроцессинг и кеширование — ваши лучшие друзья. Иначе убьёте и GPU, и пользователей ожиданием.
Часто задаваемые вопросы
Почему не взяли CLIP?
CLIP (OpenAI) отлично понимает картинки, но плохо — специфичные категории (запчасти, специфические товары). Qwen2.5-VL, дообученная на данных Авито, показала на 15% выше NDCG. К тому же CLIP не поддерживает LoRA адаптацию под новые категории без переобучения всего encoder.
Сколько стоило обучение?
Обучение LoRA на 100k пар заняло 8 часов на 4 GPU A100. Квантование QAT+LoRA добавило ещё 4 часа. Стоимость ~$500 в облачных GPU (по тарифам апреля 2026). Деплой — 8 GPU A100 постоянная аренда ~$20/час.
А что с Qwen3-VL? Она же вышла?
Да, Qwen3-VL доступна с января 2026, но её архитектура изменилась — LoRA адаптеры требуют переделки. Авито решили не переписывать пайплайн, пока текущее решение даёт бизнесу деньги. Миграция запланирована на Q3 2026.
Неочевидный совет: смотрите на пользовательское поведение, а не только на NDCG
Авито заметили, что после внедрения мультимодального поиска пользователи стали реже уточнять запросы. Логи: раньше на запрос «диван кожаный» человек открывал 5 объявлений, потом возвращался и писал «диван кожаный угловой». Теперь — открывает одно-два и покупает. Это значит, что модель понимает контент, а не просто ранжирует по ключевым словам.
Прогноз: через год все крупные e-commerce платформы будут использовать мультимодальные эмбеддинги как базовый слой поиска. Текстовый поиск уйдёт на роль fallback’а — для cases, когда фото нет или оно неинформативно. И LoRA с vLLM — идеальный тандем, чтобы внедрить это без смены стека.