Развертывание моделей в Kubernetes с помощью Seldon Core
Вступление
По иронии судьбы, одна из лучших работ о машинном обучении, когда-либо написанных, имела очень мало общего с самим машинным обучением!
В статье Скрытый технический долг в системах машинного обучения группа исследователей машинного обучения из Google проницательно отметила, что лишь небольшая часть любой реальной системы машинного обучения состоит из кода машинного обучения и что обычно требуемый окружающая инфраструктура [модели] обширна и сложна .
Другими словами, нелегко преобразовать модель из одной ячейки в ноутбуке Jupyter в часть реального производственного программного обеспечения.
Обслуживающая инфраструктура
Как показано на диаграмме выше, одной из наиболее важных частей любой производственной системы машинного обучения является обслуживающая инфраструктура, в которой заинтересованные стороны обычно фактически взаимодействуют с кодом машинного обучения.
И для многих предприятий эта инфраструктура является своего рода механизмом оркестровки контейнеров, таким как Kubernetes.
Но прежде чем углубляться в технические подробности обслуживания моделей в Kubernetes с использованием фреймворка, такого как Seldon Core, зачем вообще испытывать все эти проблемы?
Образцы граждан первого класса
Гораздо проще поддерживать сложную систему и управлять ею, если она определяет и обеспечивает истинное разделение задач по основным функциональным областям.
Проще говоря, вы хотите, чтобы весь код машинного обучения был самодостаточным и не зависел от времени выполнения более широкого приложения, частью которого он является.
Вот почему, например, использование моделей в качестве автономных образов Docker или их размещение в пользовательских приложениях Flask или Shiny обычно не одобряется в производственной среде - они скрывают биты машинного обучения под множеством слоев, что затрудняет управление.
В идеале вы хотите иметь возможность использовать те же идиомы развертывания и управления, которые вы используете для других своих вычислительных ресурсов, таких как ЦП, хранилище и т. Д., Но с вашими моделями.
Таким образом, специалисты по обработке данных и / или инженеры по машинному обучению могут сосредоточиться на дизайне и производительности модели, в то время как ваш специалист по MLop может сосредоточиться на развертывании модели и инфраструктуре вокруг нее.
В мире Kubernetes это означает, что модели должны определяться как истинные объекты Kubernetes (YAML / CRD) и управляться с помощью kubectl.
Входит Селдон Ядро.
Seldon Core - это проект с открытым исходным кодом, который предоставляет платформу обслуживания моделей общего назначения для вашего кластера Kubernetes. Он решает три основные задачи:
- Поддерживает множество фреймворков машинного обучения на разных языках.
- Автоматически предоставляет модели как веб-службу REST или gRPC.
- Поддерживает управление полным жизненным циклом развертывания модели, включая работоспособность, телеметрию, безопасность и масштабирование.
В этой статье мы собираемся развернуть простую службу модели в стиле REST под Seldon Core, которая принимает образ, а затем возвращает его без изменений.
В процессе вы увидите, насколько легко внедрить свой ML-код для выполнения логического вывода для создания полноценного микросервиса модели.
К концу статьи вы должны иметь твердое представление о том, как развертывать модели под Seldon Core в Kubernetes.
График вывода
API прогнозирования Seldon Core позволяет определить модель как граф вывода.
Запросы проходят через каждый узел графа, в конечном итоге попадая в листовой узел Модель, который выполняет какую-то функцию прогнозирования в вашей любимой среде машинного обучения (например, Tensorflow, scikit-learn) и возвращает результат.
Seldon Core поддерживает несколько различных типов узлов, что позволяет создавать более сложные рабочие процессы графа вывода, как показано на диаграмме выше.
Однако самый простой граф вывода - это граф с одним корневым узлом модели , который и будет реализован в нашем примере службы.
Предпосылки
Следующие предположения делаются при прохождении следующего урока:
- У вас есть доступ к кластеру Kubernetes (≥1,12), на котором уже установлено ядро Seldon Core, или вы имеете административный доступ к кластеру для его установки.
Seldon Core легко установить: Просто следуйте нескольким простым инструкциям, чтобы развернуть его.
- У вас установлен Docker, и вам удобно писать файлы Docker, а также создавать и загружать образы в локальный реестр кластера.
Выберите язык, любой язык…
В настоящее время Seldon Core поддерживает множество языков, включая Python, R, Java, NodeJS, а теперь и Go (альфа).
Но как?
Основной единицей выполнения в любом кластере k8s является Pod, который всегда поддерживается одним или несколькими контейнерами Docker.
Seldon Core предлагает вам сначала реализовать код своей модели в их API прогнозирования, а затем обернуть его в контейнер Docker.
Создаваемый вами образ можно создать либо напрямую с помощью Dockerfile, либо с помощью инструмента OpenShift s2i с образами оболочки Seldon Core в качестве основы.
Для нашего простого примера службы на основе Python мы создадим его напрямую, используя несколько простых директив Dockerfile.
К вашим услугам
Хорошо, давайте создадим код простой службы Python, которую мы можем развернуть в Seldon Core, используя его API прогнозирования.
Наш сервис просто считывает изображение как тензор, а затем возвращает его.
Создайте файл с именем MyModel.py следующим образом :
#!/usr/bin/env python3 import io import logging import numpy as np from PIL import Image logger = logging.getLogger('__mymodel__') class MyModel(object): def __init__(self): logger.info("initializing...") logger.info("load model here...") self._model = None logger.info("model has been loaded and initialized...") def predict(self, X, features_names): """ Seldon Core Prediction API """ logger.info("predict called...") # Use Pillow to convert to an RGB image then reverse channels. logger.info('converting tensor to image') img = Image.open(io.BytesIO(X)).convert('RGB') img = np.array(img) img = img[:,:,::-1] logger.info("image size = {}".format(img.shape)) if self._model: logger.info("perform inference here...") # This will serialize the image into a JSON tensor logger.info("returning prediction...") # Return the original image sent in RGB return img[:,:,::-1]
Давайте разберемся:
- Наш класс обслуживания модели - это простой объект Python, который реализует метод pred (). Обратите внимание, что в коде вашей реальной модели API-интерфейс Seldon Core не требуется. Все, что вам нужно сделать, это создать объект класса с помощью метода pred (), и все готово!
- Метод predic () принимает в качестве входных данных тензор X и список feature_names. Эти типы определены спецификацией protobuf Seldon Core, которую можно найти здесь. В нашем примере службы мы будем использовать только тензорный объект, поскольку наша модель принимает изображения.
- Нам нужно преобразовать массив байтов, такой как объект X, переданный Seldon Core, в изображение RGB.
- Затем мы меняем местами каналы изображения, чтобы создать окончательное изображение BGR для обработки (подходящее, скажем, для некоторой обработки OpenCV).
- Обратите внимание, что мы можем внедрить загрузку модели при создании экземпляра класса, а затем очень легко выполнить логический вывод с помощью функции predic ().
Заверните!
Теперь, когда у нас есть класс нашей модели, давайте воспользуемся этим кодом для создания контейнера Docker с использованием API-интерфейсов оболочки Seldon Core.
Создайте Dockerfile, который выглядит так:
FROM python:3.7-buster RUN apt-get update && DEBIAN_FRONTEND=noninteractive && apt-get install -y \ curl \ python3-setuptools && \ apt-get clean && apt-get autoremove -y && rm -rf /var/lib/apt/lists/* COPY requirements.txt . RUN curl -s https://bootstrap.pypa.io/get-pip.py -o get-pip.py && python3 get-pip.py && rm -f get-pip.py RUN pip3 install --no-cache numpy Pillow seldon-core # Seldon Core specific COPY . /microservice WORKDIR /microservice ENV MODEL_NAME MyModel ENV API_TYPE REST ENV SERVICE_TYPE MODEL ENV PERSISTENCE 0 CMD exec seldon-core-microservice $MODEL_NAME $API_TYPE --service-type $SERVICE_TYPE --persistence $PERSISTENCE EXPOSE 5000
Давайте разберемся:
- Большая часть файла Dockerfile является довольно стандартной платой за проезд, поскольку мы создаем небольшой образ прерывателя Python 3.7 с некоторыми требованиями времени выполнения.
- Обратите внимание, что мы устанавливаем модуль Python seldon-core как часть среды выполнения нашей модели.
- Устанавливаем наше приложение в каталог / microservice контейнера.
- Объект вашего класса модели фактически загружается приложением seldon-core-microservice и представляет его как веб-приложение Flask внутри (мы это поймем, когда фактически развернем модель).
- Seldon Core поддерживает API веб-служб REST и gRPC. Здесь мы указываем REST, поскольку мы собираемся протестировать нашу службу с помощью простой операции http POST.
Создайте образ и отправьте его в репозиторий кластера:
$ docker build --no-cache -t my-model:0.1 . Sending build context to Docker daemon 6.144kB Step 1/14 : FROM python:3.7-buster ---> 879165535a54 ... Step 14/14 : EXPOSE 5000 ---> Running in 8e9f588abe89 ---> 83b0a4682783 Successfully built 83b0a4682783 $ docker tag my-model:latest <your repo>/my-model:0.1 $ docker push <your repo>/my-model:0.1
Подавать на местном уровне
Теперь, когда у нас есть созданный образ, давайте протестируем его, запустив наш seldon-core-microservice как автономный контейнер докеров:
$ docker run -d --rm --name my-model -p 5000:5000 my-model:0.1 5aa997b0b093612f88499e13260cf96ee6d9931749f2dfa23ee3d61d303c2cc5 $ docker logs -f my-model 2020-02-21 01:49:38,651 - seldon_core.microservice:main:190 - INFO: Starting microservice.py:main ... 2020-02-21 01:49:38,659 - __mymodel__:__init__:15 - INFO: initializing... 2020-02-21 01:49:38,659 - __mymodel__:__init__:16 - INFO: load model here... 2020-02-21 01:49:38,659 - __mymodel__:__init__:18 - INFO: model has been loaded and initialized... 2020-02-21 01:49:38,659 - seldon_core.microservice:main:325 - INFO: REST microservice running on port 5000 2020-02-21 01:49:38,659 - seldon_core.microservice:main:369 - INFO: Starting servers * Serving Flask app "seldon_core.wrapper" (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: off 2020-02-21 01:49:38,673 - werkzeug:_log:113 - INFO: * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
Обратите внимание на то, что класс MyModel, который вы написали выше, был загружен и обслуживается как приложение Flask на порту 5000. Конечная точка прогнозирования модели находится в '/ prediction' или '/ api / v0. 1 / предсказания.
Магазин в местном магазине
Давайте протестируем нашу службу MyModel, написав простой клиентский скрипт, который отправит файл изображения POST в конечную точку прогнозирования нашей модели. Мы будем использовать этот же сценарий для тестирования нашего развертывания на Kubernetes, просто изменив URL-адрес.
Вот наш клиент, my-model-client.py:
#!/usr/bin/env python3 import base64 import json import logging import os import numpy as np import requests import sys from PIL import Image logger = logging.getLogger('__mymodelclient__') logger.setLevel(logging.INFO) logger.addHandler(logging.StreamHandler()) if __name__ == '__main__': url = sys.argv[1] path = sys.argv[2] # base64 encode image for HTTP POST data = {} with open(path, 'rb') as f: data['binData'] = base64.b64encode(f.read()).decode('utf-8') logger.info("sending image {} to {}".format(path, url)) response = requests.post(url, json = data, timeout = None) logger.info("caught response {}".format(response)) status_code = response.status_code js = response.json() if response.status_code == requests.codes['ok']: logger.info('converting tensor to image') data = js.get('data') tensor = data.get('tensor') shape = tensor.get('shape') values = tensor.get('values') logger.info("output image shape = {}".format(shape)) # Convert Seldon tensor to image img_bytes = np.asarray(values) img = img_bytes.reshape(shape) Image.fromarray(img.astype(np.uint8)).save('result.jpg') logger.info('wrote result image to result.jpg') elif response.status_code == requests.codes['service_unavailable']: logger.error('Model service is not available.') elif response.status_code == requests.codes['internal_server_error']: logger.error('Internal model error.')
Давайте разберемся:
- Мы передаем URL-адрес, по которому мы хотим опубликовать изображение, и путь к самому изображению.
- Нам нужно закодировать изображение в формате base64 для подготовки к POST для нашего микросервиса Seldon Core, работающего локально как контейнер.
- Мы отправляем POST со строкой JSON, которая содержит ключ binData и изображение в кодировке base64 в качестве его значения.
- Если POST был успешным (HTTP STATUS OK 200), мы читаем ключ data из ответа JSON и извлекаем тензор, который на самом деле является нашим результирующим изображением.
- Тензор имеет ключ shape и values - ключ значений - это само изображение в виде массива интенсивностей пикселей.
- Мы используем Pillow для записи значений тензора в виде файла JPEG с именем «result.jpg».
Давайте теперь протестируем наш сервис с помощью этого скрипта:
$ file test_image.jpg test_image.jpg: JPEG image data, JFIF standard 1.00, resolution (DPI), density 0x0, segment length 16, comment: "LEAD Technologies Inc. V1.01", baseline, precision 8, 1280x960, components 3 $ python3 my-model-client.py http://localhost:5000/api/v0.1/predictions test_image.jpg sending image test_image.jpg to http://localhost:5000/api/v0.1/predictions caught response <Response [200]> converting tensor to image result image shape = [960, 1280, 3] wrote result image to result.jpg $ file result.jpg result.jpg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 1280x960, components 3
Давайте также посмотрим сообщения журнала док-контейнера, который обработал изображение:
$ docker logs my-model 2020-02-21 15:32:33,363 - __mymodel__:predict:22 - INFO: predict called... 2020-02-21 15:32:33,391 - __mymodel__:predict:27 - INFO: image size = (960, 1280, 3) 2020-02-21 15:32:33,391 - __mymodel__:predict:33 - INFO: returning prediction... 2020-02-21 15:32:33,977 - seldon_core.user_model:client_class_names:166 - INFO: class_names is not implemented 2020-02-21 15:32:33,977 - seldon_core.user_model:client_custom_tags:134 - INFO: custom_tags is not implemented 2020-02-21 15:32:33,977 - seldon_core.user_model:client_custom_tags:134 - INFO: custom_tags is not implemented 2020-02-21 15:32:33,977 - seldon_core.user_model:client_custom_metrics:307 - INFO: custom_metrics is not implemented 2020-02-21 15:32:34,271 - werkzeug:_log:113 - INFO: 172.17.0.1 - - [21/Feb/2020 15:32:34] "POST /api/v0.1/predictions HTTP/1.1" 200 -
Разверните его!
Как и все Kubernetes, Seldon Core определяет свой собственный объект развертывания, называемый SeldonDeployment, через файл Custom Resource Definition (CRD).
Давайте определим SeldonDeployment нашей модели с помощью файла seldon-deploy.yaml:
apiVersion: machinelearning.seldon.io/v1alpha2 kind: SeldonDeployment metadata: name: my-model spec: name: my-deployment predictors: - componentSpecs: - spec: containers: - name: my-model-graph image: <your cluster's registry>/my-model:0.1 graph: children: [] endpoint: type: REST name: classifier type: MODEL annotations: predictor_version: "0.1" seldon.io/svc-name: my-model-svc name: my-graph replicas: 1
Давайте разберемся:
- SeldonDeployment состоит из одного или нескольких предикторов, которые определяют, какие модели содержатся в этом развертывании. Примечание. Возможно, вы захотите определить более одного предсказателя для сценариев типа «канареечный» или «многорукий бандит».
- Каждый предиктор состоит из спецификации Pod, которая определяет образ Docker для кода вашей модели, который мы создали выше.
- Поскольку SeldonDeployment - это тип развертывания, каждый предиктор поддерживается одним или несколькими ReplicaSets, которые определяют, сколько модулей должно быть создано для поддержки вашей модели (граф вывода). Это один из способов масштабирования развертывания Seldon в соответствии с вашими вычислительными потребностями.
- Мы также устанавливаем настраиваемое имя службы, так как SeldonDeployment будет автоматически выставлять наш микросервис как объект Service Kubernetes.
Давайте создадим SeldonDeployment и посмотрим на развертывание, а также на создаваемые объекты:
$ kubectl get seldondeployments NAME AGE my-model 6s $ kubectl create -f seldon-deploy.yaml seldondeployment.machinelearning.seldon.io/my-model created $ kubectl get all NAME READY STATUS RESTARTS AGE pod/my-deployment-my-graph-20302ae-5cfc6c47f4-m78ll 2/2 Running 0 80s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/my-model-svc ClusterIP 172.26.126.119 <none> 8000/TCP,5001/TCP 55s service/seldon-9d8927429acc983eba0168e21059f589 ClusterIP 172.28.210.215 <none> 9000/TCP 80s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/my-deployment-my-graph-20302ae 1/1 1 1 80s NAME DESIRED CURRENT READY AGE replicaset.apps/my-deployment-my-graph-20302ae-5cfc6c47f4 1 1 1 80s
Обратите внимание на несколько вещей:
- Созданный под, запускающий код вашей модели, на самом деле имеет в себе два контейнера - код вашей модели, а также вспомогательный компонент seldon-container-engine, который вводится во время развертывания.
- seldon-container-engine будет упорядочивать все запросы и ответы в формате сообщений Seldon Core, а также отслеживать состояние и телеметрию вашего модельного контейнера.
- Служба ClusterIP предоставляется через порт 8000, чтобы мы могли взаимодействовать с нашим микросервисом.
Теперь, когда наша модель микросервиса запущена и работает, давайте протестируем ее, используя тот же клиент, что и выше. Нам нужно либо настроить общедоступный Ingress, либо использовать перенаправление портов, чтобы создать прямое соединение с нашим объектом Сервиса.
В нашем простом примере давайте просто воспользуемся простой переадресацией портов:
$ kubectl port-forward svc/my-model-svc 8000:8000 Forwarding from 127.0.0.1:8000 -> 8000 Forwarding from [::1]:8000 -> 8000 Handling connection for 8000 $ python3 my-model-client.py http://localhost:8000/api/v0.1/predictions test_image.jpg sending image test_image.jpg to http://localhost:8000/api/v0.1/predictions caught response <Response [200]> converting tensor to image result image shape = [960, 1280, 3] wrote result image to result.jpg
Успех!
Мы просто отправили изображение для вывода и получили ответ от нашего микросервиса, работающего в нашем кластере.
Теперь вы можете открыть свое исходное изображение и результат, возвращенный нашим микросервисом, и он должен выглядеть одинаково.
Чтобы проверить, мы можем даже выгрузить журналы внутри самого модуля:
$ kubectl logs deployment.apps/my-deployment-my-graph-20302ae -c my-model-graph ... 2020-02-22 01:44:49,163 - __mymodel__:predict:22 - INFO: predict called... 2020-02-22 01:44:49,223 - __mymodel__:predict:27 - INFO: image size = (960, 1280, 3) 2020-02-22 01:44:49,223 - __mymodel__:predict:33 - INFO: returning prediction... 2020-02-22 01:44:49,948 - seldon_core.user_model:client_class_names:166 - INFO: class_names is not implemented 2020-02-22 01:44:49,949 - seldon_core.user_model:client_custom_tags:134 - INFO: custom_tags is not implemented 2020-02-22 01:44:49,949 - seldon_core.user_model:client_custom_tags:134 - INFO: custom_tags is not implemented 2020-02-22 01:44:49,949 - seldon_core.user_model:client_custom_metrics:307 - INFO: custom_metrics is not implemented 2020-02-22 01:44:50,355 - werkzeug:_log:113 - INFO: 127.0.0.1 - - [22/Feb/2020 01:44:50] "POST /predict HTTP/1.1" 200 -
Очистить
$ kubectl delete -f seldon-deploy.yaml seldondeployment.machinelearning.seldon.io "my-model" deleted
Наше развертывание SeldonDeployment и связанные с ним объекты теперь прекращены.
Заключение
Я надеюсь, что, пройдя это короткое руководство, вы глубже поймете, как использовать Seldon Core для развертывания ваших моделей в качестве микросервисов в Kubernetes.
В целом рабочий процесс разработки Seldon Core выглядит следующим образом:
- Закодируйте свой граф вывода как простой набор объектов Python, следующих за API прогнозирования Seldon Core.
- Оберните код модели и среду выполнения как контейнер Docker.
- Сначала протестируйте свой контейнер локально, чтобы убедиться, что код вашей модели работает должным образом.
- Разверните свою модель как микросервис как объект SeldonDeployment.
- Протестируйте свой микросервис SeldonDeployment, создав Ingress или используя переадресацию портов для отправки запросов к нему.
Seldon Core поддерживает множество расширенных функций, которые я не затронул в этой статье, поэтому я рекомендую вам внимательно изучить обширную документацию проекта, чтобы лучше понять его общий дизайн и обширный набор функций.
Но, надеюсь, эта статья дала вам достаточно опыта, чтобы заинтересовать вас, почему специализированная обслуживающая инфраструктура, такая как Seldon Core, стоит вашего времени и энергии.