Готовь сани летом, а план доставки ML-модели конечным пользователям — еще на этапе разработки. Иначе даже самая крутая обученная система будет пылиться без дела, а большинство пользователей о ней даже не узнает.
Запуск ML-моделей в production-среде — это тот еще квест:
- необходимо хранить и версионировать модели;
- нужен мониторинг ресурсопотребления, производительности и дрейфа данных в реальном времени;
- нужна поддержка разнородных сценариев — пакетная обработка или стриминг.
К счастью, есть специальные инструменты, которые существенно облегчают такую задачу. В этой статье рассмотрим сервинг модели Grounding DINO при помощи популярного фреймворка BentoML. Под катом вас ждет много кода, скриншоты и практические советы.
P. S. Первая версия статьи написана для блогаDeepSchool. На Хабре публикуется с изменениями и дополнениями.
Кратко о BentoML
Для начала рассмотрим в самых общих чертах, что такое BentoML и с чем его едят. Это open-source-инструмент, который помогает:
- оборачивать модель в сервис и обращаться к нему по API;
- масштабировать сервис, а также объединять несколько сервисов в пайплайны;
- мониторить сервис, используя Prometheus и Grafana;
- хранить и версионировать модели.
Инструмент написан на Python — в этом его сила и слабость одновременно. Например, BentoML уступает Triton по скорости работы, но первым проще пользоваться. Другой козырь BentoML — интеграции с разными библиотеками: Hugging Face, scikit-learn, PyTorch, Gradio, MLflow и многими другими.
В платном инструменте BentoCloud есть возможность деплоить сервис одной командой и отслеживать показатели модели в UI. Также у него существует бесплатная версия для деплоя в кубереYatai. Правда, эта версия пока сыровата, и пользоваться ей сложнее.
Тем не менее у BentoMLмножество примеров деплоя, в том числе и для LLM. Также стоит отметить подробнуюдокументациюи обширные сообщества вSlackиблог. Всё это помогает пользователям лучше разобраться в нюансах применения инструмента.
Пару слов о Grounding DINO
«Минутка рекламы» BentoML закончена. Теперь о том, что за зверь этот Grounding DINO. Модель подробно описана в статье"Grounding DINO: Marrying DINO with Grounded Pre-Training for Open-Set Object Detection". По сути, это «сборная солянка» из нескольких довольно сложных моделей. Главная ее фишка — технологияOpen-Set Object Detection (OSOD),то естьдетектирование объектов, чьих классов нет в обучающей выборке.
Скажем, нам нужно детектировать слонов на мировой черепахе как один объект. «Классические» closed-set детекторы вроде Faster R-CNN или YOLO так не умеют: они детектируют ровно то, что было размечено при обучении. Можно заранее разметить «слон на черепахе» как отдельный класс и обучить модель сразу на него — тогда всё заработает. Но изменить логику детектирования после обучения уже не выйдет: как разметили, так и будет находить.
Grounding DINOвместо таких танцев с бубнами добавляет еще одну модальность — язык (это и есть тот самый Grounding). Модель обрабатывает изображение и текстовый запрос совместно, обучаясь выравнивать визуальные и текстовые представления в общем семантическом пространстве. Благодаря этому детектор привязывается не к жёсткому списку классов из датасета, а к смыслу текстового запроса — и находит то, что вы описали словами, даже если такого класса при обучении не было. Поэтому «слон на черепахе как один объект» для нее уже не проблема.
С инструментами познакомились — теперь можно переходить к самому главному.
Собственно к сервингу
Не станем пускаться с места в карьер и разберем в статье простейший вариант такого сервинга, включая запуск и использование созданного сервиса. Весь код статьи и инструкция по его запуску доступны врепозитории. Пример инференса модели Grounding DINO в dev-окружении на одной картинке выглядит следующим образом:
Для запуска кода выше необходимо создать виртуальное окружение и установить зависимости согласно инструкции в README.md.
Чтобы обернуть этот код в сервис с помощью BentoML, нужно выполнить следующие шаги:
- определить конфигурацию для сборки сервиса;
- создать класс GroundingDinoService. В нем будет вся логика сервиса: инференс модели и отрисовка результатов на изображении;
- добавить валидацию входных параметров.
В итоге мы получим Bento — артефакт, предназначенный для развертывания ML-сервиса и включающий исходный код сервиса, все необходимые зависимости и сохраненные модели. С его помощью создается Docker-образ с автоматически сгенерированным API-сервером.
Запущенный сервис будет работать через HTTP API. Он начнет принимать изображение, промпт и пороги для инференса. В ответ сервис станет выдавать класс объекта на изображении, координаты bounding boxes и конфиденс.
Вся логика сервиса реализуется в файле service.py.
Определение конфигурации сборки сервиса
BentoML создает докерезированные сервисы. Dockerfile генерируется автоматически. Можно воспользоваться этой дефолтной версией или создать свой кастомный Dockerfile. Чтобы получить докер-контейнер, нужно создать Bento-объект.
Определим кастомную конфигурацию сборки нашего сервиса:
В этой части кода указывается базовый Docker-образ, версия Питона, дистрибутив, зависимости и всё, что обычно указывается в Dockerfile.
Больше интересной информации про настройку runtime_image можно найти в документации (разделDefine the runtime environment).
Определение класса сервиса
При определении сервисов BentoML применяется классовый подход. Каждый класс — этопролетариат или буржуазияотдельный сервис, который может выполнять определенные задачи: предварительную обработку данных или инференс. Класс, указывающий на сервис BentoML, оборачивается декоратором@bentoml.service. Сервис может предоставлять доступ к одному или нескольким API-интерфейсам через HTTP.
Основные параметры сервиса определяются при помощи декоратора@bentoml.serviceдля соответствующего класса:
Поля, доступные для конфигурации сервиса, находятся в разделе документацииСonfigurations. Чтобы не утонуть в множащихся параметрах, можно вынести определение сервиса в yaml-файлbentoml_configuration.yamlи перезаписать нужное из декоратора.
Модель в сервисе задается как переменная класса —hf_model, которая скачивается средствами BentoML. Таким образом, однократно загружаются веса модели при сборке Docker-образа. Параллельно выполняется оптимизированное скачивание весов. Если скачивать веса модели в конструкторе, это приведет к ошибкеmodel NotFound. Bento попросту не сможет создать ссылку на модель, привязанную к конкретному сервису.
В конструктор класса переносится уже инициализация процессора и модели.
Реализация API
С определением классов в общих чертах разобрались. Теперь самое время реализовать ключевые методы в классе GroundingDinoService:
- _detect— приватный метод, содержащий основную логику инференса модели. Здесь находится вся логика из начального кода детекции объектов;
- detect_image— публичный метод, представляющий собой обертку над_detect, которая возвращает результаты детекции;
- render— публичный метод, который возвращает изображение с отрисованными результатами метода_detect.
Все методы обрабатывают одни и те же параметры — изображение и характеристики для инференса.
Реализация вспомогательных функцийserialize_detectionsиdraw_detectionsдоступна в исходниках репозитория на GitHub.
Методыdetect_imageиrenderпомечаются декоратором@bentoml.api. Так легким движением руки эти методы автоматически превращаются в HTTP endpoints нашего сервиса. Теперь их можно вызывать извне через REST API. Подробнее про создание API с BentoML в разделеService APIsдокументации.
Валидация входных параметров сервиса
Как же отделить зерна от плевел и проверить параметры, которые подаются на вход в BentoML? Для этого поддерживается использованиеPydantic. Он позволяет описать классы-модели для валидации входных параметров сервиса.
Сама модель для такой валидации выглядит следующим образом:
Запуск сервиса
И вот наступает момент истины — запуск сервисов на BentoML. Здесь мы встаем перед развилкой: запускаться локально либо в Docker. Локальный запуск удобен, когда нужно протестировать сервис, внести изменение и не ждать каждый раз сборки. Применение Docker подходит для работы в production-среде.
Запуск сервиса локально для тестирования приложения
Для начала необходимовзять шаманский бубенвыполнить в терминале следующую команду:
Бинго! Команда выполнена, и по ссылкеhttp://localhost:3025/в браузере открывается Swagger UI.
По умолчанию сервис запускается на порту 3000. Однако порт можно поменять, скажем, на 3025 или любой другой с помощью флага--port, как это сделали мы в примере выше. Не забудем «поднять» еще один флаг--reloadв значении True. Теперь при корректировке кода сервис автоматически принимает изменения без перезапуска.
Остальные параметры можно посмотреть в документации (разделbentoml serve).
Запуск сервиса c Docker
Теперь пойдем по второму пути и запустим сервис в Docker. Для этого сначала соберем Bento, выполнив в терминале командуbentoml build.
По умолчанию она упаковывает все файлы в каталог, из которого выполняется. Чтобы отсеять ненужные файлы или каталоги, просто укажите их в файле .bentoignore. Команда нужна для подготовки артефакта нашего сервиса, который будет содержать исходный код и сохраненные модели.
На скриншоте выше показан вывод в консоль после успешного создания Bento. Далее возможны несколько сценариев. Например, можно создать образ сервиса. Dockerfile генерируется автоматически, после чего он доступен для просмотра внутри собранного контейнера.
Сборка же образа сервиса выполняется следующей командой в терминале:
В свою очередь, запуска сервиса на GPU выполняется такой командой:
Готово! Образы и контейнеры можно посмотреть стандартным способом через Docker.
Запущенный сервис доступен по адресу http://localhost:3025.
Использование сервиса
Запустить сервис — это только полдела. Отправлять запросы к нему можно при помощи кода, любого http client или Swagger UI, который генерируется в BentoML при запуске сервиса.
Согласно Swagger, в сервисе доступно два endpoint:
- /detect_image— возвращает json с классами, ббоксами и конфиденсами;
- /render— отрисовывает боксы на картинке, возвращает и сохраняет ее в папке images.
Рассмотрим каждый из этих двух вариантов подробнее.
Пример использования endpoint /render
Нажимаем кнопкуTry it out, загружаем фото и вводим параметры:
Изображение с котикамив репозитории
НажимаемExecuteи видим такой результат:
Аналогичный запрос можно выполнить через консольную утилиту cURL, получив изображение с отрисованными ббоксами в папке images:
Пример использования endpoint /detect_image
Теперь перейдем ко второму детекту. Отправим запрос на endpoint/detect_imageчерез SDK. Код доступен в файле client.py репозитория:
В этой статье мы не стали вдаваться в сложные «игры разума» с кодом, а рассмотрели процесс создания простого сервиса на BentoML. Если обобщить, для этого нужно выполнить несколько шагов:
- обернуть код инференса в класс;
- добавить валидацию входных параметров через Pydantic;
- обернуть методы класса декоратором@bentoml.api;
- обернуть реализованный класс декоратором@bentoml.servicec нужными параметрами.
Магия в том, что всё это делается в одном Python-файле менее чем на 100 строк!
Простота — это отлично, однако у BentoML есть и другие полезные функции для более сложных сценариев:
- версионирование и хранение моделей с помощью Bento локально и на S3;
- сборка и деплой сервиса с gitlab CI/CD;
- синхронность и асинхронность запросов;
- асинхронный инференс;
- онлайн- и офлайн-батчинг и многое другое.
Если вам интересно узнать об обработке таких сценариев при помощи BentoML в следующих статьях, не стесняйтесь и пишите об этом в комментариях.