Магнит рассказал, как задеплоил векторный поиск на Triton без GPU
Ведущий ML-разработчик Магнита Стас Чернышев описал, как команда поиска MAGNIT OMNI задеплоила векторный поиск на NVIDIA Triton Inference Server без GPU. Python оставили только для токенизации, а тело модели вынесли в ONNX.
- Инференс одного запроса на CPU через FastAPI занимал около 30 мс, что не устраивало команду
- Модель эмбеддингов BERTA — русскоязычная дистилляция FRIDA от SaluteDevices, веса опубликованы на Hugging Face
- Наружу отдаются первые 192 координаты 768-мерного эмбеддинга, вектор занимает в 4 раза меньше места

Команда поиска MAGNIT OMNI перевела сервис векторного поиска на NVIDIA Triton Inference Server и отказалась от инференса на Python. Об этом в блоге на Хабре рассказал ведущий ML-разработчик Стас Чернышев. По его словам, инференс одного запроса на CPU через FastAPI занимал около 30 мс, что не устраивало команду.
Почему отказались от FastAPI
FastAPI — привычное решение для обёртки моделей в REST, но у него нет инфраструктурных функций из коробки: gRPC, батчинга входных запросов, кэша, health check и метрик. Всё это приходится писать и поддерживать вручную. Кроме того, переход с CPU на GPU требует отдельной работы с драйверами в Dockerfile.
Разработчики сравнили FastAPI, BentoML и Triton по набору возможностей. Динамический батчинг есть у BentoML и Triton, кэш ответов — только у Triton (локальный или на Redis), метрики Prometheus — у BentoML и Triton. Инференс без Python поддерживает только Triton за счёт нативных бэкендов. В BentoML инференс идёт в Python-процессах, а сами разработчики для ускорения предлагают запускать внутри него Triton.
Как устроена модель
Базовая модель эмбеддингов — BERTA, компактная русскоязычная модель, полученная дистилляцией эмбеддингов FRIDA от SaluteDevices. Она наследует систему префиксов FRIDA, в том числе «search_query:» и «search_document:» для асимметричного поиска. По этому признаку команда разделила функционал на два сервиса: эмбеддер запросов и эмбеддер документов.
Эмбеддер запросов работает в онлайне, где приоритет — скорость ответа. Эмбеддинги товаров обновляются при изменении описания товара или модели поиска, там важнее пропускная способность. Разделение также повышает отказоустойчивость.
Отказ от Python
На Python осталась только токенизация: небольшая модель на Python backend добавляет префикс, вызывает токенизатор и передаёт тензоры дальше. Тело модели сконвертировали в ONNX и отдали нативному onnxruntime backend. В репозиторий моделей достаточно положить веса model.onnx и config.pbtxt — остальное Triton делает сам.
Модель обучена с matryoshka-лоссом, поэтому первые 192 координаты 768-мерного эмбеддинга сохраняют почти всё качество. Наружу отдаются именно они, предварительно отнормированные. Вектор занимает в 4 раза меньше места в хранилище, и поиск кандидатов работает быстрее.
Настройка сервиса
Встроенный кэш включается в config.pbtxt модели директивой response_cache, а общий размер кэша задаётся при старте сервера параметром --cache-config. Кэш может быть локальным или на Redis. При профиле нагрузки эмбеддера запросов часто повторяются одни и те же входные данные, поэтому кэш снимает большую часть работы модели. Чтобы после рестарта поток трафика не обрушивался на непрогретый сервис, реализован прогрев: при старте пода прогоняются топ-N запросов, покрывающих большую часть трафика.
Батчинг настраивается через max_batch_size и dynamic_batching с preferred_batch_size и max_queue_delay_microseconds. Количество экземпляров модели задаётся в блоке instance_group, там же выбирается CPU или GPU. Для onnxruntime backend отдельно настраиваются intra_op_thread_count и inter_op_thread_count: первый определяет, сколько ядер получит один экземпляр модели, второй — потоки для независимых операторов графа.
Чернышев предупреждает: если суммарно потоков окажется больше, чем выделено или доступно, сервис начнёт тротлить — latency вырастет, а следом появятся ошибки по таймаутам на клиентах.
Для профиТехнические детали: архитектура, цифры, ссылки
Стек: NVIDIA Triton Inference Server, Python backend для токенизации, onnxruntime backend для тела модели. Модель — BERTA (дистилляция эмбеддингов FRIDA от SaluteDevices), веса на huggingface.co/sergeyzh/BERTA.
- Кэш ответов:
response_cache { enable: true }в config.pbtxt; размер задаётся при старте —--cache-config local,size=3221225472, для Redis —--cache-config redis,host=... --cache-config redis,port=.... - Батчинг:
max_batch_size: 8,dynamic_batching { preferred_batch_size: [2, 3] max_queue_delay_microseconds: 3000 }. - Экземпляры:
instance_group [{ count: 2 kind: KIND_CPU }]. - onnxruntime:
intra_op_thread_count = 5,inter_op_thread_count = 1. При двух экземплярах — 10 ядер под инференс из 12. - Эмбеддинг: 768-мерный, наружу отдаются первые 192 координаты после нормировки.
Что это значит для России
Кейс описывает российскую команду — MAGNIT OMNI, поиск розничной сети «Магнит». Используемая модель BERTA — русскоязычная дистилляция эмбеддингов FRIDA от SaluteDevices, опубликованная на Hugging Face. Стек полностью open source: NVIDIA Triton Inference Server и onnxruntime.
Вопросы и ответы
- Нужен ли GPU для Triton Inference Server в этом кейсе?
- Нет, команда Магнита задеплоила эмбеддеры на CPU: в конфиге указан KIND_CPU, при двух экземплярах под инференс уходит 10 ядер из 12.
- Почему наружу отдаются только 192 координаты эмбеддинга?
- Модель обучена с matryoshka-лоссом, поэтому первые 192 координаты 768-мерного вектора сохраняют почти всё качество, а сам вектор занимает в 4 раза меньше места.


