Развертывание моделей в 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, стоит вашего времени и энергии.