Привет, Хабр! Меня зовут Иван Попов, я руковожу командой компьютерного зрения CV Hub в дирекции искусственного интеллекта X5 Tech. А ещё у нас в команде есть Иваныч. Так пользователи «Пятёрочки» ласково называют нашу CV-систему, которая модерирует фотографии в «Клубе тайных покупателей». Когда анкета закрывается за час, в комментариях пишут: «Иваныч сегодня хорошо работает». Имя дали сами пользователи, совпадение с моим именем чистая случайность, опыт Amazon Go не повторяем))
За последние восемь месяцев Иваныч из MVP вырос в сервис, который проверяет около 10 миллионов фотографий в месяц по всем «Пятёрочкам» и «Перекрёсткам» (порядка 27 000 магазинов). Под капотом у нас: Triton Inference Server, 26 моделей, 62 типа проверок, гибрид специализированных CNN и VLM-моделей.
Эта статья не про то, какие классные модели у нас обучены. А про то, что значит продуктивизация CV на практике. О том, как мы выжимали из инженерии каждый процент, чтобы 10 миллионов фото в месяц обрабатывались без падений, каждый VLM-вызов был не зря потрачен, а новые типы проверок добавлялись максимально быстро.
Вкратце для тех, кто торопится:
Бизнес-задача: снять с модераторов «Клуба тайных покупателей» «Перекрёстка» и «Пятёрочки» рутинную проверку миллионов анкет, чтобы они могли сосредоточиться на сложных и спорных кейсах.
Архитектура: Kafka RPC → асинхронный пайплайн → Triton Inference Server + vLLM.
Что получили: рост производительности команды модерации в 7 раз, 50% экономии операционных затрат и увеличение доли анкет «день-в-день» с 0 до 40%. И большую часть истории сервиса всё это жило на 4 vCPU и 16 GB RAM, без учёта внешней VLM.
Самое интересное лежит в разделе про оптимизации. Это шестой раздел, при желании можно сразу перейти в него.
Откуда боль
Тайный покупатель позволяет ритейлу взглянуть на себя глазами клиента. Человек приходит в магазин по сценарию, фотографирует, заполняет анкету с вопросами и отправляет её в систему. Дальше нужно проверить каждую анкету: фото действительно из нашего магазина? Оно сделано в правильной зоне? Проблема соответствует тому, что отмечено в анкете?
Пока поток анкет был небольшой, всё работало. Дальше клуб вырос, и началась беда. По нашим внутренним оценкам, если бы все анкеты, поступающие за день, распечатали, получилась бы стопка высотой полтора метра. Ручная модерация затягивалась на дни, а в пик нагрузки на недели. Из-за этого схлопывался весь смысл продукта: тайный покупатель не видит результата проверки, не получает баллы вовремя и теряет мотивацию участвовать. А магазин получает обратную связь, когда её актуальность уже сомнительна.
Мы поставили себе цель: автоматизировать критичную долю модерации, чтобы анкеты закрывались в день поступления, и сделать это без потери качества, потому что ошибочно отклонённая анкета бьёт по доверию пользователя сильнее, чем долгая проверка.
Почему «просто прикрутить YOLO» не работает
Соблазн любого CV-инженера, столкнувшегося с подобной задачей: «взять пару универсальных моделей, дотюнить и запустить». Мы тоже пошли этим путём в самом начале, но быстро поняли, что «порядок в магазине» – это не один класс, а семейство контекстно-зависимых сущностей.
Чистота во входной зоне и молочном отделе не один и тот же визуальный паттерн. Лужа на входе в дождливый день – это нормально, а лужа возле холодильника с молоком – это разлитый продукт и сигнал «нужно убирать». Универсальный классификатор «чисто/грязно» такие тонкости не ловит.
Самый яркий случай этого периода стал внутренним мемом. Модель, проверявшая чистоту в мясном отделе, регулярно отклоняла фото витрин с фаршем, потому что классифицировала фарш как грязь. Команда прозвала её «веган-версией ИИ». Мясо в её картине мира как будто не существовало, а всё, похожее на «месиво», автоматически попадало в категорию беспорядка.
Из таких эпизодов родились два архитектурных решения, которые мы тащим до сих пор:
Узкие классификаторы под конкретные зоны и задачи, а не один большой мультиклассификатор «Порядок в магазине». Это даёт большую совокупную точность, потому что каждая модель учится на «своих» картинках со «своими» нюансами.
«Безопасный режим»: если модель отдаёт неуверенный или негативный вердикт, анкета уходит на ручную проверку модератору. Лучше потратить десять секунд человеческого времени, чем испортить анкету тайному покупателю.
Эти два принципа определили дизайн всего сервиса. Дальше разберём, как это собрано в код.
Архитектура: Kafka, Pipeline, Triton и VLM
Снаружи сервис выглядит просто. На вход — JSON с фотографией и списком проверок. А на выходе — JSON с результатами по каждой из них. Внутри работает асинхронный pipeline, который конкурентно дёргает Triton и VLM, агрегирует результаты и возвращает ответ.
Схематично архитектура выглядит так:

Несколько решений здесь стоит подсветить отдельно.
Kafka как RPC, а не как event-bus
На первый взгляд странно. Классическое применение Kafka – это асинхронная шина для событий, а у нас сюда подаётся синхронный по смыслу запрос «Дай ответ по фотографии». Мы пошли на это сознательно. Kafka уже стояла в нашей продуктивной шине. Поэтому нам не нужно было поднимать отдельный gateway, и мы получали гарантии доставки и встроенный backpressure при пиках.
NVIDIA Triton Inference Server для CNN-моделей на CPU
Все ONNX-модели крутятся в Triton. Это единый HTTP/gRPC API, встроенный scheduler с dynamic batching, версионирование моделей с горячей подменой, скейлинг инстансов и готовые Prometheus-метрики по каждой модели. Triton закрывает для нас весь операционный слой инференса, и мы можем сосредоточиться на самих моделях.
vLLM для инференса VLM
VLM-модели вынесены отдельно и работают в vLLM на GPU-кластере. vLLM уже был в инфраструктуре X5, поддерживает широкий зоопарк моделей, даёт высокую пропускную способность за счёт continuous batching и PagedAttention. Для нас особенно важен guided decoding. Благодаря ему мы всегда получаем от VLM структурированный JSON, а не свободный текст. vLLM отдает наружу OpenAI-совместимый API, поэтому интеграция свелась к нескольким методам поверх OpenAI Python SDK.
Семафоры для ограничения конкуретности
Воркер запускает обработку сообщений через семафор, ограничивающий число одновременных задач. Это даёт предсказуемое число in-flight-запросов к Triton и VLM. Размер семафора подбирали эмпирически по латентности CPU/GPU, времени ответа VLM и потреблению памяти.
Одновременный инференс нескольких моделей для одной фотографии
Если на фото нужно проверить релевантность, определить зону и оценить чистоту — все три запроса уходят в Triton разом. Общая латентность определяется самой медленной моделью, а не суммой времени выполнения всех моделей.
Зоопарк моделей: 62 проверки через 26 моделей
Сейчас в Triton живут 26 моделей. Через них реализованы 62 уникальных типа проверок (validation steps), которые сервис умеет делать на одной фотографии. То есть в среднем одна модель обслуживает 2,4 разных бизнес-проверки, хотя есть и универсальные, которые участвуют практически во всех проверках. Например, определение того, что фотография сделана из магазина X5.
Категория |
Кол-во проверок |
Модели |
|---|---|---|
Классификация состояний объектов (чистота, наличие тележки, униформы, работоспособность кофемашины и т. д.) |
16 |
EfficientNet/ResNet |
Определение зоны магазина (вход, мясной отдел, зона фруктов и овощей и т. д.) |
7 |
EfficientNet |
Категория SRP-товаров (паста, консервы, молочка, кондитерка…) |
24 |
EfficientNet |
Статус прайс-чекера (available/working/broken) |
4 |
VLM |
Реклама «Апельсина» в зонах, статус КСО |
5 |
VLM |
Поиск цены на ценнике/прайс-чекере |
3 |
VLM |
Определение срока годности товара |
1 |
OCR-pipeline (YOLO11n + PARSeq, Triton BLS) |
Распознавание штрихкодов |
1 |
Barcode model (YOLO11n-OBB + custom decoder ensemble) |
Подсчёт SRP-боксов |
1 |
YOLO11m |
Итого |
62 |
26 моделей |
Большая часть инференса — это ONNX-модели на CPU. Отсюда вытекает неожиданный факт про наш продакшен — он удивительно скромен в ресурсах для своего масштаба. Большую часть жизни сервиса весь Иваныч работал на одном поде с 4 vCPU и 16 GB RAM. Все модели в Triton с агрессивным dynamic batching и Python-BLS держали миллионы фотографий в месяц. А немногочисленные VLM-вызовы уходили в отдельный GPU-кластер с vLLM по OpenAI-совместимому API и в эту смету не входили.
Сейчас из-за роста нагрузки и добавления новых тяжёлых моделей мы переезжаем на GPU-кластер. Но не потому, что CPU не справлялся, а чтобы заранее заложить ресурсов под рост и убрать лишний сетевой хоп до VLM. Узкая специализированная CNN-модель в проде стоит копейки.
Гибрид CNN + VLM: где зовём большую модель
Не всё в нашем зоопарке — узкоспециализированные классификаторы и детекторы. Часть сервисов работают через vision-language-модель Qwen3.6-4B. Логика выбора между CNN и VLM у нас довольно прагматичная:
CNN используем там, где визуальная задача чётко определяется и можно собрать аккуратный размеченный датасет.
VLM — там, где задача сильно завязана на тонкий контекст или разнообразие сцен.
Есть несколько вещей, которые мы заметили в проде и фиксируем как «правила хорошего тона» для VLM в продукте:
Structured output обязателен. В vLLM передаём JSON-схему ответа и валидируем результат через Pydantic. Если ответ не парсится — это явная ошибка, которую мы трекаем отдельной метрикой, а не молча ретраим.
Нулевая температура и стандартизация размеров изображений. Воспроизводимость важнее «разнообразия ответов», а большая дисперсия размеров картинки во few-shot-примерах и промпте может сильно влиять на качество ответов.
Few-shot — это валюта. Мы используем небольшую модель, поэтому большая часть качества на наших задачах берётся не из модели, а из 3–5 примеров в промпте. После каждого изменения примеров мы прогоняем валидационный сет и смотрим, не сдвинулся ли accuracy.
Безопасный режим: когда Иваныч говорит «не уверен»
Любая ML-модель ошибается. И в продукте, где за каждую анкету пользователь получает баллы лояльности, цена ошибки конкретная. Если анкета ошибочно отклонена, это бьёт по доверию пользователя. Если ошибочно принята — это деньги, выплаченные ни за что. Мы решили эту проблему через классический, но прагматичный Human-in-the-loop.
Модели в нашем зоопарке возвращают не только лейбл, но и confidence. Дальше работают пороги, выставленные эмпирически по валидационному сету: при уверенном положительном ответе анкета засчитывается, при уверенном негативном автоматически отклоняется, в «серой зоне» анкета уходит модератору-человеку.
Сейчас в режим ручной проверки попадает ~50% анкет. Это нагрузка, которую команда модерации спокойно держит, и которая позволяет нам держать пороги моделей агрессивно. Мы предпочитаем перестраховаться и отправить анкету человеку, чем выкатить ошибочный вердикт. По сути, безопасный режим превращает recall-fail модели в latency-overhead, а не в качественную деградацию продукта.
Этот же механизм даёт нам поток размеченных примеров для дообучения: каждая анкета, проверенная человеком после Иваныча — это новая разметка.
Мониторинг качества production CV
Prometheus + Grafana + Alertmanager закрывают у нас очевидные вещи: per-request latency, per-model duration, VLM token consumption, error rates по типам ошибок и т. д. Это базовая гигиена, она есть у всех.
В дополнение к этому мы сделали отдельный сервис мониторинга качества моделей. В нём мы, во-первых, смотрим на базовые статистики типа количества поступающих анкет, процент принятых и отклонённых анкет каждой моделью, причины отклонений, например, нерелевантная фотография или ответ пользователя не соответствует выбранному варианту ответа. А, во-вторых, всё тем же Human-in-the-loop замеряем, насколько точно работает каждая из моделей. Каждая N-я анкета отправляется как на модератора, так и на модель, что позволяет оценивать online-метрики и своевременно реагировать на деградацию качества. Кроме того, такой процесс позволяет выявлять hard-negative-примеры, которые ценны для регулярного дообучения моделей.
В production CV метрики «качества» и «работоспособности» — это разные миры, но и те и другие обязательны. Сервис, который отвечает быстро, но всё подряд, классифицирует как «всё ок», это худший вариант инцидента. Потому что его не видно ни в одном дашборде по latency/throughput.
Грабли
«Веган-версия ИИ», которая путала фарш с грязью, показала, что универсальные модели не работают. Но это был не последний урок.
Модель, которая не знала, что бывает 2026 год
Под конец года мы выкатили пайплайн распознавания дат на упаковках. Обучали на реальных фотографиях из Клуба, которые содержали только 2025 год. А когда наступил 2026-й, качество просело, потому что модель таких дат просто никогда не видела. Починили сбором открытых датасетов и генерацией синтетики.
vLLM: багов не меньше, чем поддерживаемых моделей
Когда вышел Qwen 3.5, мы померили качество, убедились, что он лучше, и радостно пересели. На малых нагрузках всё работало. Под нагрузкой vLLM намертво зависал в EngineCore на cuEventSynchronize, при этом продолжая принимать запросы, и уходил в OOM. Причиной оказался баг в реализации модели. Помогла смена бэкенда GDN флагом --gdn-prefill-backend triton. Мораль проста: новые модели нужно тестировать не только на качество, но и на стабильность под нагрузкой.
Сезонность, о которой модель не предупреждали
Грязь в магазине зависит от времени года. Летом полы чистые, в дождливые и снежные месяцы — нет. Модель, обученная на данных одного сезона, в другом начинает ошибаться. Мы увидели это благодаря сервису мониторинга качества. Поэтому вместо одной большой модели на все случаи жизни реализовали регулярное дообучение на свежих данных, отбирая через мониторинг сложные примеры, на которых модель ошибается больше всего.
Из всего этого мы вынесли общий принцип. В проде часто ломается не там, где ожидаешь. Иногда проблема в данных, иногда в инфраструктуре, которая падает только на боевых нагрузках, а иногда в реальном мире, который меняется быстрее обучающей выборки. Продуктивизация не про то, что «обучили лучшую модель», а про то, что «научились быстро находить и закрывать конкретный класс ошибок».
Что мы получили
Если коротко, то за 8 месяцев в проде, мы получили:
0 → 10 миллионов фотографий в месяц. В сентябре 2025 года был MVP с нулевой нагрузкой. В этот же месяц мы вышли на 3 миллиона фото в месяц. К декабрю – на 7 миллионов. К маю 2026 года – уже на 10 миллионов.
0% → 40% анкет «день-в-день». Сейчас в среднем 40% анкет закрываются в день поступления,что было физически невозможно при ручной модерации.
Производительность команды модерации ×7. Команда делает в семь раз больше работы при том же составе.
50% экономия операционных затрат на модерацию.
Ускорение проверки ×5 относительно ручного процесса. То, что раньше занимало 1–2 недели, сейчас делается за часы или дни.
Точность модерации: на пилоте было 79% в рамках 8 шагов в одной анкете, на сегодня это уже 92% в 62 шагах.
Что дальше
Иваныч продолжает учиться. Сейчас мы работаем над несколькими направлениями:
Новые типы проверок. Каждый квартал к существующим 62 слагам проверок добавляется ещё 5-10 новых. Это зоны магазинов, сезонные сценарии вроде зимнего ассортимента или праздничного промо, и новые форматы, например «Перекрёсток» и «Чижик».
Универсализация через VLM. Если CNN дорогой по разметке и переобучению, то VLM с правильным промптом справляется за день. Поэтому мы переводим часть «длинного хвоста» проверок именно туда.
Расширение системы мониторинга качества. Чтобы держать точность на росте, нам нужно ловить дрейф моделей и узкие сценарии раньше, чем они начнут влиять на пользователей.
Без обещаний по срокам, но направление понятное.
Вместо заключения
Год назад команда модераторов физически не справлялась со стопкой анкет высотой в полтора метра. Сегодня большую часть этой стопки разбирает Иваныч. Он не идеален, иногда ошибается, иногда отказывается принимать решение и зовёт человека. Но именно связка «Иваныч плюс модератор-эксперт» оказалась тем решением, которое работает на масштабе.
Самое важное для меня в этом проекте то, что продуктивизация CV не сводится к «обучили модель и задеплоили». Это про архитектуру вокруг моделей: узкую специализацию вместо универсальности, безопасный режим вместо слепого доверия моделям, мониторинги качества рядом с мониторингом latency и регулярное дообучение на свежих данных. Без этих мелочей даже самые крутые модели в обнимку с VLM упрутся в продакшен и развалятся под нагрузкой.
Мы будем продолжать учить Иваныча, добавлять новые проверки, тонкие зоны, режимы. И, как сказала наша менеджер продукта Екатерина: «Будем стараться, чтобы он всегда работал хорошо и быстро».
Спасибо команде
Иваныч — это работа большой команды, а не моя личная история.
За инженерию решения в нашей команде компьютерного зрения CV Hub отвечает Андрей Широбоков — автор архитектуры, главный разработчик сервиса и человек, к которому стекаются все вопросы «А как это работает?» по проекту.
Со стороны бизнеса проект ведёт Екатерина Бычкова, менеджер продукта “Клуба тайных покупателей”. Без её настойчивости, понимания пользовательского опыта и умения проходить через каждый сложный продуктовый разговор, мы бы запускались в полтора раза медленнее.
Отдельное спасибо коллегам из команды модерации, дата-разметки и инфры X5. Каждый из вас вложил свой кусочек в Иваныча.
А как устроен баланс «специализированные CNN vs универсальные VLM» в вашем продакшене? Делитесь, обсудим в комментариях.
Спасибо за внимание. Если статья зашла, поставьте плюс, это поможет нам понять, о чём писать дальше. А если мы в чём-то ошиблись или вам есть что добавить — пишите в комментарии или личку, обычно мы отвечаем быстро.
Также можно прочитать статьи о production AI, опенсорсных моделях и том, как доводить ИИ-решения до работающего продукта:
– Выжимаем максимум из опенсорсных моделей и готовим Text2SQL
– От vibe coding к Spec-Driven Development: как приручить скорость ИИ и довести проект до продакшена