FastAPI versus BentoML: что лучше для MLOps и почему

FastAPI versus BentoML: что лучше для MLOps и почему

Что общего у FastAPI с BentoML, чем они отличаются и почему только один из них является полноценным MLOps-инструментом. Смотрим на примере операций разработки и развертывания API сервисов машинного обучения.

Что общего у FastAPI с BentoML и при чем здесь MLOps

С точки зрения промышленной эксплуатации, в проектах машинного обучения следует фокусироваться не только и не столько на точности самих алгоритмов Machine Learning. Огромного внимания требует автоматизация рутинных процессов развертывания и сопровождения ML-моделей, что превратилось в целую концепцию под названием MLOps. Поскольку это направление инженерной науки о данных еще довольно молодое, в нем постоянно появляются новые идеи и инструменты. В частности, одним из самых популярных MLOps-средств считается FastAPI – высокопроизводительный веб-фреймворк для создания API с помощью стандартной аннотации типов Python 3.6+. ML-инженеры часто используют его для развертывания своих моделей в качестве сервисов API.

Впрочем, FastAPI используется не только в ML-проектах. Этот фреймворк отлично подходит для быстрого создания любых REST-приложений на Python. Его дополнительным преимуществом является автогенерация документации API в формате Swagger (открытый стандарт OpenAPI 3) и JSON Schema. Пример создания такого REST API с тестированием методов в Swagger UI мы описывали в этой статье на блоге нашей Школы прикладного бизнес-анализа.

Возвращаясь к тему MLOps, отметим, что способность FastAPI генерировать документацию поддерживает идеи MLOps, а потому этот фреймворк можно отнести к набору инструментальных средств MLOps-Инженера. Однако, это далеко не единственный инструмент этой категории. Сюда же относится BentoML – открытая библиотека для быстрого создания MVP систем машинного обучения. Она упрощает создание API методов для доступа к обученной ML-модели и совместима со всеми крупными фреймворками: Tensorflow, Keras, PyTorch, XGBoost, scikit-learn и fastai.

BentoML поддерживает адаптивную микропакетную обработку данных, а также предоставляет функции управления моделью и ее развертывания. BentoML отлично реализует идеи MLOps, позволяя разработчикам ML-моделей использовать лучшие практики DevOps. Специалисты по Data Science и инженеры Machine Learning используют BentoML для ускорения и стандартизации процесса запуска моделей в производство, создавая масштабируемые и высокопроизводительные сервисы машинного обучения. Библиотека поддерживает непрерывное развертывание, мониторинг и сопровождение сервисов в производственной среде. Пример практического использования BentoML мы рассматривали в этом материале.

Отметим общие функции FastAPI и BentoML с точки зрения MLOps:

  • они оба основаны на Starlette – легковесной ASGI-платформе для создания асинхронных веб-сервисов на Python;
  • автоматическая документация в Swagger UI согласно открытому стандарту OpenAPI 3;
  • асинхронные запросы, что позволяет обрабатывать несколько запросов одновременно, а не выполнять их линейно.

Несмотря на схожесть в этих фундаментальных вопросах, ключевые различия между BentoML и FastAPI для MLOps заключаются в сценариях использования, характерных для разработки и развертывания систем машинного обучения, что мы и рассмотрим далее.

Применение в разработке и развертывании ML-систем

Создание API в машинном обучении обычно начинается в Jupyter-блокноте или Google Colab. Формат сохраненной модели напрямую влияет на то, как она передается на сервер API. В FastAPI используется сериализованный Python-объект pickle, в котором хранится код ML-модели. Но этот формат очень ограничивает возможности разработчика манипулировать им. Помимо вопросов безопасности и производительности, самой большой MLOps-проблемой Pickle-формата является полное отсутствие контроля версий модели и управления зависимостями. Почему лучше не использовать Pickle-формат для сохранения ML-моделей, мы подробно писали в этой статье.

На практике даже в небольшом проекте машинного обучения ML-разработчик может создавать десятки или даже сотни моделей и хранить их в реестре с версионированием. Однако, организовать удобный для использования реестр моделей, сохраненных в Pickle-формате, почти невозможно.

В BentoML есть стандартная процедура сохранения и загрузки моделей на основе среды, в которой они были обучены. А если модели не являются отдельными файлами и имеют зависимости, которые также следует сохранить, в BentoML можно использовать параметры custom_objects и метаданные для сохранения дополнительных объектов и информации о модели:

import bentoml
bentoml.keras.save_model(
    "cnn16",
    model_cnn,
    metadata={"desc": "CNN architecture with 16 starting filters", "owner": "Bex"},
    custom_objects={"model_history": history_object["history"]},
)

После вызова функции save_model(), BentoML сохраняет их в локальном реестре моделей, который находится по адресу ~/bentoml/models. Команда list выведет содержимое этого каталога:

$ bentoml models list
Tag                     Module         Size        Creation Time        Path
cnn16:2uo5fkgxj27exuqj  bentoml.keras  5.81 KiB    2022-12-19 08:36:52  ~/bentoml/models/cnn16/2uo5fkgxj27exuqj
cnn16:nb5vrfgwfgtjruqj  bentoml.keras  5.80 KiB    2022-12-19 21:36:27  ~/bentoml/models/cnn16/nb5vrfgwfgtjruqj

Даже если модели сохраняются под одним и тем же именем, им присваиваются уникальные идентификаторы. Выбрав лучшую модель из реестра, ею можно поделиться, экспортировав в единый формат архива .bentomodel, независимо от структуры модели:

$ bentoml models export cnn:version_tag .

Аналогично можно импортировать любую ML-модель в архив .bentomodel со всеми ее пользовательскими объектами и метаданными, добавив ее в свой собственный реестр моделей. Впрочем, BentoML не только упрощает и автоматизирует управление реестром моделей, но и облегчает работу с входными и выходными данными API.

REST API обычно работают с данными в формате JSON, который не очень подходит для упаковки векторов машинного обучения. Предположим, ML-модель обучена на датасете с четырьмя фичами. Ее форматы ввода/вывода в FastAPI будут выглядеть так:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import numpy as np
import pickle

app = FastAPI()

class Input(BaseModel):
   feature_1: float
   feature_2: float
   feature_3: float
   feature_4: float

class Output(BaseModel):
   prediction: float

FastAPI использует Pydantic для проверки данных. Это означает, что фреймворк выдаст ошибку, если форма ввода и типы данных не соответствуют входному классу. Но когда ML-модель имеет сотню фичей для обучения, определить структуру таких больших массивов NumPy или DataFrames в классах Python почти невозможно.

В BentoML проверка входных данных массива NumPy выполняется следующим образом:

import bentoml
from bentoml.io import NumpyNdarray


runner = bentoml.sklearn.get("model_name:latest").to_runner()

svc = bentoml.Service("classifier", runners=[runner])

# The important part 
@svc.api(input=NumpyNdarray(), output=NumpyNdarray())
def classify(input_series: np.ndarray) -> np.ndarray:

   result = runner.predict.run(input_series)
   return result

Класс NumpyNdarray проверяет входные данные на соответствие массивам NumPy любой формы в одной строке кода. Чтобы применить к входным данным определенную форму, достаточно указать всего один параметр:

@svc.api(input=NumpyNdarray(shape=(-1,15), enforce_shape=True), output=NumpyNdarray())

Предоставление (-1, 15) параметру формы подтвердит, что входные данные имеют как можно больше строк, но всегда 15 фичей. В FastAPI и Pydantic это выглядит сложнее:

class Input(BaseModel):
   data: List[conlist(item_type=float, min_items=15, max_items=15)]

В реальности ML-модели работают не только с массивами NumPy: данные, отправляемые в API, могут быть любыми, от датафреймов Pandas до двоичных файлов, таких как изображения или аудио. FastAPI и Pydantic проверяют только стандартные типы данных. А BentoML содержит классы проверки для типов данных, характерных для машинного обучения. Есть даже специальный валидатор MultiPart, который позволяет передавать в модель несколько типов данных. Этот класс особенно полезен в задачах компьютерного зрения, когда для модели Machine Learning надо предоставить изображения и табличные метаданные во время логического вывода.

Когда скрипт сервиса готов, его необходимо упаковать для развертывания. Проще всего сделать это с помощью Docker, упаковав API в образ, чтобы разместить его в различных операционных системах и облачных средах. Чтобы сделать это в FastAPI, необходимо знать, как создать Dockerfile:

# Get the Fast API image with Python version 3.7
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7

# Create the directory for the container
WORKDIR /app
COPY requirements.txt ./requirements.txt

# Install the dependencies
RUN pip install --no-cache-dir -r requirements.txt

COPY ./app.py ./

# Copy the serialized model
COPY ./models/cnn_model.pkl ./models/cnn_model.pkl

# Run by specifying the host and port
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "80"]

В BentoML все гораздо проще – в корневом каталоге создается YAML-файл bentofile.yaml со следующим шаблоном:

service: "service.py:service_name"
include:
- "*.py"
python:
 packages:
  - scikit_learn
  - numpy
  - tensorflow

В поле include следует указать все файлы, необходимые скрипту сервиса для запуска. В разделе пакеты перечисляются зависимости проекта. Далее следует вызвать bentoml build на терминале:

$ bentoml build

И команда сборки упакует весь проект в автономный архив внутри локального хранилища Bento. Чтобы преобразовать архив в Docker-образ, достаточно выполнить всего 1 команду:

$ bentoml containerize model_name:latest

Теперь можно развернуть полученный Docker-образ в любой среде. Работа FastAPI на этом заканчивается – фреймворк помогает создать API, но не развернуть его. В BentoML есть специальная вспомогательная библиотека под названием bentoctl, которая позволяет развертывать контейнерные API на любой облачной платформе (AWS, GCP, Azure, Heroku). Например, для развертывания ML-модели из реестра в AWS SageMaker потребуется всего несколько команд:

$ pip install bentoctl terraform
$ bentoctl operator install aws-sagemaker
$ export AWS_ACCESS_KEY_ID=REPLACE_WITH_YOUR_ACCESS_KEY
$ export AWS_SECRET_ACCESS_KEY=REPLACE_WITH_YOUR_SECRET_KEY
$ bentoctl init
$ bentoctl build -b model_name:latest -f deployment_config.yaml
$ terraform init
$ terraform apply -var-file=bentoctl.tfvars -auto-approve

Как мы недавно отмечали, большинство современных ML-моделей использует графические процессоры для рабочих нагрузок. Особенно это характерно для проектов глубокого обучения. Поэтому рекомендуется создавать Docker-образ, поддерживающий ускорение GPU. Но, чтобы запустить логический вывод на графическом процессоре, необходима библиотека CUDA от NVIDIA, установить которую не так-то просто из-за наличия множества версий под разные ML-фреймворки (TensorFlow, PyTorch или XGBoost).

В FastAPI для установки CUDA на Docker-образ нужен большой Dockerfile:

FROM nvidia/cuda:11.2.0-runtime-ubuntu20.04

# install utilities
RUN apt-get update && \
   apt-get install --no-install-recommends -y curl

ENV CONDA_AUTO_UPDATE_CONDA=false \
   PATH=/opt/miniconda/bin:$PATH
RUN curl -sLo ~/miniconda.sh https://repo.anaconda.com/miniconda/Miniconda3-py38_4.9.2-Linux-x86_64.sh \
   && chmod +x ~/miniconda.sh \
   && ~/miniconda.sh -b -p /opt/miniconda \
   && rm ~/miniconda.sh \
   && sed -i "$ a PATH=/opt/miniconda/bin:\$PATH" /etc/environment

# Installing python dependencies
RUN python3 -m pip --no-cache-dir install --upgrade pip && \
   python3 --version && \
   pip3 --version

RUN pip3 --timeout=300 --no-cache-dir install torch==1.10.0+cu113 -f https://download.pytorch.org/whl/cu113/torch_stable/

COPY ./requirements.txt .
RUN pip3 --timeout=300 --no-cache-dir install -r requirements.txt

# Copy model files
COPY ./model /model

# Copy app files
COPY ./app /app
WORKDIR /app/
ENV PYTHONPATH=/app
RUN ls -lah /app/*

COPY ./start.sh /start.sh
RUN chmod +x /start.sh

EXPOSE 80
CMD ["/start.sh"]

В BentoML установить CUDA так же просто, как добавить одно поле в bentofile.yaml:

service: "service.py:service_name"
include:
- "*.py"
python:
packages:
- torch
- torchvision
- torchaudio
docker:
distro: ubuntu
python_version: "3.8.12"
cuda_version: "11.6.2"

Затем следует снова запустить команду containerize:

$ bentoml containerize model_name:latest

Фреймворки PyTorch и TensorFlow позволяют группировать входные данные, используя векторизацию для одновременного выполнения операций с несколькими входами благодаря специальной аппаратной структуре современных графических процессоров. Чтобы наилучшим образом использовать GPU после успешной установки CUDA, инфраструктура API должна разрешать автоматическую группировку входных данных, чего не делает FastAPI. А в BentoML эту функцию можно включить с помощью одного параметра при сохранении моделей, поддерживающих пакетную обработку:

bentoml.pytorch.save_model(
   name="my-model",
   model=model,
   signatures={
       "__call__": {
           "batchable": True,
           "batch_dim": (0, 0),
       },
   },
)

Таким образом, FastAPI не очень хорошо поддерживает сценарии использования, характерные для машинного обучения, в отличие от специализированного MLOps-фреймворка BentoML.

Как применять эти и другие средства MLOps в проектах аналитики больших данных и машинного обучения, вы узнаете на специализированных курсах в нашем лицензированном учебном центре обучения и повышения квалификации для разработчиков, менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data в Москве:

Источники

  1. https://pub.towardsai.net/bentoml-vs-fastapi-the-best-ml-model-deployment-framework-and-why-its-bentoml-f0ed26cae88d
  2. https://www.bentoml.com/
  3. https://fastapi.tiangolo.com/