Из этого руководства вы узнаете, как быстро развернуть модели машинного обучения с помощью FastAPI, Redis и Docker.

Если вы хотите перемотать вперед, репозиторий сопутствующего кода предоставит вам модель классификации изображений за считанные минуты.

Обзор

Существует ряд отличных руководств по завершению модели машинного обучения с помощью Flask. Однако, когда я увидел эту потрясающую серию сообщений Адриана Роузброка, я подумал, что его подход был немного более готовым к производству и хорошо подходил для докеризации. Докеризация этой настройки не только значительно упрощает настройку и работу с Docker Compose, но и становится более легко масштабируемой для производства.

По окончании обучения вы сможете:

1. Создайте веб-сервер с помощью FastAPI (с Uvicorn) для обслуживания наших конечных точек машинного обучения.

2. Создайте сервер модели машинного обучения, который обслуживает модель классификации изображений Keras (ResNet50 обучен на ImageNet).

3. Используйте Redis в качестве очереди сообщений для передачи запросов и ответов между веб-сервером и модельным сервером.

4. Используйте Docker Compose, чтобы раскрутить их всех!

Архитектура

Мы будем использовать ту же архитектуру из вышеупомянутых сообщений Адриана Роузброка, но заменим фреймворки веб-сервера (FastAPI + Uvicorn на Flask + Apache) и, что более важно, поместим всю установку в контейнер для простоты использования. Мы также будем использовать большую часть кода Адриана, поскольку он проделал великолепную работу с обработкой, сериализацией и борьбой с несколькими ошибками NumPy.

Основная функция веб-сервера - обслуживать /predict конечную точку, через которую другие приложения будут вызывать нашу модель машинного обучения. Когда вызывается конечная точка, веб-сервер направляет запрос в Redis, который действует как очередь сообщений в памяти для многих одновременных запросов. Модельный сервер просто опрашивает очередь сообщений Redis на предмет наличия пакета изображений, классифицирует пакет изображений, а затем возвращает результаты в Redis. Веб-сервер получает результаты и возвращает их.

Репозиторий кода

Вы можете найти весь код, использованный в этом руководстве, здесь:



Создание веб-сервера

Я решил использовать tiangolo/uvicorn-gunicorn-fastapi для веб-сервера. Этот образ Docker предоставляет аккуратный стек ASGI (Uvicorn, управляемый Gunicorn с платформой FastAPI), который обещает значительные улучшения производительности по сравнению с более распространенным flask-uwsgi-nginx на основе WSGI.

Это решение было в значительной степени вызвано желанием опробовать стек ASGI, а высококачественные образы докеров, такие как tiangolo, значительно упростили эксперименты. Кроме того, как вы увидите в коде позже, написание простых конечных точек HTTP в FastAPI не слишком отличается от того, как мы это делали во Flask.

webserver/Dockerfile довольно прост. Он берет вышеупомянутый образ, устанавливает необходимые требования Python и копирует код в контейнер:

FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7

COPY requirements.txt /app/

RUN pip install -r /app/requirements.txt

COPY . /app

Файл webserver/main.py запускает сервер FastAPI, открывая конечную точку/predict, которая принимает загруженное изображение, сериализует его, отправляет в Redis и опрашивает полученные прогнозы.

Код в основном сохраняется как есть, с некоторыми дополнительными функциями для среды Dockerized, а именно с разделением вспомогательных функций и параметров для веб-сервера и сервера модели. Кроме того, параметры передаются в контейнер Docker через переменные среды (подробнее об этом позже).

Построение сервера модели

modelserver/Dockerfile тоже довольно прост:

FROM python:3.7-slim-buster

COPY requirements.txt /app/

RUN pip install -r /app/requirements.txt
# Download ResNet50 model and cache in image                       RUN python -c "from keras.applications import ResNet50; ResNet50(weights='imagenet')"
COPY . /app

CMD ["python", "/app/main.py"]

Здесь я использовал изображение python:3.7-slim-buster. Вариант slim уменьшает общий размер изображения примерно на 700 МБ. Вариант alpine не работает с тензорным потоком, поэтому я решил не использовать его.

Я также решил загрузить модель машинного обучения в Dockerfile, чтобы она была кэширована в образе Docker. В противном случае модель будет загружена в момент запуска сервера модели. Это не проблема, кроме добавления задержки в несколько минут к процессу репликации (поскольку каждый запускающийся рабочий процесс должен сначала загрузить модель).

И снова Dockerfile устанавливает требования, а затем запускает файл main.py.

Сервер модели опрашивает Redis для получения пакета изображений для прогнозирования. Пакетный вывод особенно эффективен для моделей глубокого обучения, особенно при работе на графическом процессоре. Параметр BATCH_SIZE можно настроить так, чтобы обеспечить минимальную задержку.

Мы также должны использовать pipeline в redis-py (это неправильное название, поскольку он по умолчанию является транзакционным в redis-py) для реализации атомарного выталкивания нескольких элементов слева (см. Строки 45–48). Это становится важным для предотвращения состояний гонки, когда мы реплицируем серверы модели.

Собираем все вместе с Docker Compose

Мы создаем 3 сервиса - Redis, сервер модели и веб-сервер - все они находятся в одной сети Docker.

«Глобальные» параметры находятся в файле app.env, а параметры, специфичные для службы (такие как SERVER_SLEEP и BATCH_SIZE), передаются в контейнеры как переменные среды.

Параметры deploy используются только для Docker Swarm (подробнее об этом в следующем посте) и будут игнорироваться Docker Compose.

Мы можем раскрутить все с помощью docker-compose up, который будет создавать образы и запускать различные службы. Вот и все!

Тестирование конечных точек

Теперь протестируйте сервис, свернув конечные точки:

$ curl http://localhost
"Hello World!"
$ curl -X POST -F [email protected] http://localhost/predict
{"success":true,"predictions":[{"label":"dingo","probability":0.6836559772491455},{"label":"Pembroke","probability":0.17909787595272064},{"label":"basenji","probability":0.07694739103317261},{"label":"Eskimo_dog","probability":0.01792934536933899},{"label":"Chihuahua","probability":0.005690475460141897}]}

Успех! Вероятно, в ImageNet нет класса «сиба-ину», так что «динго» пока подойдет. Достаточно близко.

Нагрузочное тестирование с Locust

Locust - это инструмент нагрузочного тестирования, предназначенный для нагрузочного тестирования веб-сайтов. Он предназначен для веб-сайтов с нагрузочным тестированием, но также отлично подходит для простых конечных точек HTTP, таких как наша.

Его легко запустить и запустить. Сначала установите его с помощью pip install locustio, затем запустите, запустив в каталоге проекта:

locust --host=http://localhost

При этом используется предоставленный locustfile для проверки конечной точки /predict. Обратите внимание, что мы указываем хосту на localhost - мы проверяем время отклика нашей службы машинного обучения без реальной задержки в сети.

Теперь укажите в браузере http://localhost:8089, чтобы получить доступ к веб-интерфейсу locust.

Мы смоделируем 50 пользователей (все они вылуплены в начале).

Время отклика p95 около 5000 мс означает, что 95% запросов должны выполняться в течение 5 секунд. В зависимости от вашего варианта использования и ожидаемой нагрузки это может быть слишком медленно.

Заключение

В этом посте мы увидели, как создать службу машинного обучения Dockerized с помощью Keras, FastAPI и Redis. Мы также провели нагрузочный тест и увидели, что производительность может быть менее чем адекватной.

В этом посте я проиллюстрирую использование Docker Swarm для простого масштабирования нашего модельного сервера для повышения производительности: