Узнайте, как развернуть модели машинного обучения в кластерах Kubernetes и реализовать автомасштабирование для вашего развертывания с помощью HPA и KEDA.

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

Развертывание модели может быть таким же простым, как реализация сервера Flask, а затем экспорт его конечных точек для вызова пользователями. Тем не менее, создать систему, способную надежно и надежно обслуживать большое количество запросов со строгими требованиями к времени отклика или пропускной способности, непросто.

Для среднего и крупного бизнеса системы должны иметь возможность масштабирования для обработки более тяжелых рабочих нагрузок без существенного изменения кодовой базы. Возможно, корпорация расширяется и нуждается в масштабируемой системе для обработки растущего числа запросов (эта характеристика — масштабируемость). Бизнесу необходимо, чтобы система могла адаптироваться к колебаниям трафика (эта характеристика — эластичность). Эти характеристики могут быть достигнуты, если системы способны автоматически масштабироваться в зависимости от объема трафика.

В этом руководстве мы узнаем, как развертывать модели машинного обучения в кластерах Kubernetes с помощью Seldon Core. Мы также научимся реализовывать автомасштабирование для нашего развертывания с помощью HPA и KEDA. Код для этого урока можно найти в этом репозитории.

Обучить модель PyTorch

Чтобы пройти процесс развертывания, нам понадобится модель. Мы используем модель из этого учебника с официального сайта PyTorch. Это простая модель классификации изображений, которая может легко работать с ЦП, поэтому мы можем протестировать весь процесс развертывания на локальных компьютерах, таких как ваш ноутбук.

Предположим, вы находитесь в папке toy-model этого репозитория. Вы можете обучить модель на наборе данных CIFAR10 с помощью:

python train.py
# Output: model.pth -> the trained weights of the model

Seldon Core использует Triton Inference Server для обслуживания моделей PyTorch, поэтому нам нужно подготовить модель в формате, в котором ее можно будет обслуживать с помощью Triton. Во-первых, нам нужно экспортировать модель в TorchScript (также можно обслуживать модели PyTorch с помощью python backend от Triton, но, как правило, это менее эффективно и сложнее в развертывании).

Трассировка и создание сценариев — это два метода экспорта модели в TorchScript. Выбор между ними до сих пор остается дискуссионным, в этой статье исследуются преимущества и недостатки обоих методов. Мы будем использовать метод трассировки для экспорта модели:

python export_to_ts.py -c model.pth -o model.ts
# Output: model.ts 
# -> serialized model containing both trained weights and the model's architecture

Triton загружает модели из хранилища моделей. Он должен содержать информацию, необходимую серверу для обслуживания модели, такую ​​как информация о входе/выходе модели, используемая серверная часть… Репозиторий модели должен иметь следующую структуру:

<model-repository-path>/
    <model-name>/
      [config.pbtxt]
      [<output-labels-file> ...]
      <version>/
        <model-definition-file>
      <version>/
        <model-definition-file>
      ...
    <model-name>/
      [config.pbtxt]
      [<output-labels-file> ...]
      <version>/
        <model-definition-file>
      <version>/
        <model-definition-file>
      ...
    ...

В нашем случае у нас есть только одна модель. Назовем модель cifar10-pytorch, наш репозиторий модели должен иметь следующую структуру:

cifar10-model
└── cifar10-pytorch
    ├── 1
    │   └── model.ts
    └── config.pbtxt

cifar10-model — имя репозитория, cifar10-pytorch — имя модели, а model.ts — модель TorchScript, которую мы только что экспортировали. config.pdtxt определяет, как модель должна обслуживаться с помощью Triton:

platform: "pytorch_libtorch"
default_model_filename: "model.ts"
max_batch_size: 0
input [
  {
    name: "image__0"
    data_type: TYPE_UINT8
    dims: [-1, 3, -1, -1]
  }
]
output [
  {
    name: "probs__0"
    data_type: TYPE_FP32
    dims: [-1, 10]
  }
]

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

Если вы хотите увидеть более реалистичный пример того, как экспортировать и обслуживать модель PyTorch с помощью Triton, взгляните на этот пост. Он демонстрирует, как использовать Triton для обслуживания модели MaskRCNN от Detectron2, популярной модели для сегментации экземпляров и используемой во многих реальных системах компьютерного зрения.

Triton может получать доступ к моделям из локальной файловой системы или облачных служб хранения (например, S3, Google Storage или Azure Storage). Поскольку мы собираемся развернуть модель в Kubernetes, использование облачного хранилища более удобно, поскольку все узлы в кластере Kubernetes могут получить доступ к одним и тем же моделям. В этом руководстве мы будем использовать AWS S3 в качестве хранилища моделей. Предположим, что у вас уже есть учетная запись AWS, давайте создадим корзину S3 и загрузим подготовленную нами папку:

aws s3 cp --recursive cifar10-model s3://<YOUR_BUCKET>/cifar10-model

Замените <YOUR_BUCKET> на имя вашего ведра. Теперь у нас есть репозиторий модели на AWS S3, и мы можем приступить к развертыванию модели.

Развертывание моделей с помощью Seldon Core

Мы развернем модель в кластере Kubernetes с помощью Seldon Core, фреймворка, специализирующегося на развертывании и мониторинге модели машинного обучения. Давайте создадим локальный кластер Kubernetes, чтобы протестировать процесс развертывания на нашей локальной машине.

Вид можно использовать для создания локальных кластеров. На момент написания у Seldon Core была проблема с k8s ≥ 1.25, поэтому мы должны использовать версию 1.24 или старше. Чтобы указать версию k8s с помощью Kind, просто выберите образ с соответствующей версией для запуска кластера. Следующая команда создает локальный кластер с именем kind-seldon и k8s==1.24.7:

kind create cluster --name seldon --image kindest/node:v1.24.7

Кроме того, убедитесь, что на вашем локальном компьютере установлены docker, kubectl, helm. Переключение контекста kubectl на kind-seldon указывает kubectl на подключение к только что созданному кластеру по умолчанию:

kubectl cluster-info --context kind-seldon

Установите ядро ​​​​Селдон

Мы будем использовать Istio в качестве Ingress кластера и Seldon Core в качестве обслуживающей платформы. Инструкцию по установке вы можете найти здесь. После установки Istio и Seldon Core выполните следующие команды, чтобы проверить, правильно ли они установлены:

kubectl get svc -n istio-system
# NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)                                                                      AGE
# istio-egressgateway    ClusterIP      10.96.90.103   <none>        80/TCP,443/TCP                                                               3m29s
# istio-ingressgateway   LoadBalancer   10.96.229.8    <pending>     15021:30181/TCP,80:32431/TCP,443:30839/TCP,31400:32513/TCP,15443:32218/TCP   3m28s
# istiod                 ClusterIP      10.96.195.7    <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP                                        8m48s

Проверьте, работает ли шлюз Istio:

kubectl get gateway -n istio-system
# NAME             AGE
# seldon-gateway   5m17s

Проверьте, работает ли контроллер Seldon:

kubectl get pods -n seldon-system
# NAME                                        READY   STATUS    RESTARTS   AGE
# seldon-controller-manager-b74d66684-qndf6   1/1     Running   0          4m18Create an Istio gateway to manage the cluster’s traffic:

Если вы этого не сделали, убедитесь, что метка istio-injection включена:

kubectl label namespace default istio-injection=enabled

Шлюз Istio работает на порту 80 в кластере, нам нужно перенаправить порт с вашего локального компьютера на этот порт, чтобы мы могли получить к нему внешний доступ:

kubectl port-forward -n istio-system svc/istio-ingressgateway 8080:80

Работа с Seldon Core

Если репозиторий вашей модели хранится в частной корзине, вам необходимо предоставить разрешение на доступ к этой корзине из кластера. Это можно сделать, создав секрет, а затем ссылаясь на него при создании развертывания. Это шаблон для создания секретов для корзин S3:

apiVersion: v1
kind: Secret
metadata:
  name: seldon-rclone-secret
type: Opaque
stringData:
  RCLONE_CONFIG_S3_TYPE: s3
  RCLONE_CONFIG_S3_PROVIDER: aws
  RCLONE_CONFIG_S3_ENV_AUTH: "false"
  RCLONE_CONFIG_S3_ACCESS_KEY_ID: "<AWS_ACCESS_KEY_ID>"
  RCLONE_CONFIG_S3_SECRET_ACCESS_KEY: "<AWS_SECRET_ACCESS_KEY>"

Замените AWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY вашим фактическим идентификатором ключа доступа AWS и секретным ключом доступа. Создайте секрет:

kubectl apply -f secret.yaml

Мы можем развернуть модель с этим манифестом, обратите внимание, что созданный секрет упоминается в манифесте с ключом envSecretRefName. Убедитесь, что spec.predictors[].graph.name соответствует имени модели, которое вы загрузили в репозиторий моделей. Примените манифест для создания развертывания:

kubectl apply -f cifar10-deploy.yaml

Если это ваше первое развертывание в этом кластере, загрузка необходимых образов Docker займет некоторое время. Проверьте, успешно ли развернута модель:

kubectl get deploy
# NAME                                READY   UP-TO-DATE   AVAILABLE   AGE
# cifar10-default-0-cifar10-pytorch   1/1     1            1           41m

Я создал скрипт с помощью Locust для тестирования развернутых моделей. Вам нужно сначала установить требования, необходимые для запуска скрипта:

pip install -r requirements.txt

Учитывая, что порт localhost:8080 был переадресован на шлюз кластера, выполните следующую команду, чтобы отправить запросы на модели, развернутые с помощью Seldon:

locust -f test.py --headless -u 100 -r 10 --run-time 180 -H http://localhost:8080

Если ваше имя развертывания или имена модели отличаются, вы можете соответствующим образом изменить URL-адрес развертывания в сценарии. URL развернутой модели соответствует протоколу вывода Seldon Core:

/seldon/{namespace}/{model_repo}/v2/models/{model_name}/versions/{model_version}/infer

Мы развернули нашу пользовательскую модель с помощью Seldon Core и протестировали ее, отправив в модель запросы на вывод. В следующем разделе мы рассмотрим, как масштабировать развертывание для обработки большего количества пользователей или более высокого трафика.

Автомасштабирование Pod с помощью HPA

Что касается масштабируемости, Kubernetes предлагает HPA (Horizontal Pod Autoscaling). Когда определенные показатели достигают своих пороговых значений для ресурса (например, ЦП или памяти), HPA может добавить больше модулей для обработки более тяжелых рабочих нагрузок.

Установить сервер метрик

HPA необходимо получать метрики из агрегированного API, который обычно предоставляется через Сервер метрик. Вы можете установить сервер метрик для своего кластера с помощью:

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

Если ваш кластер локальный, вам также необходимо отключить проверку сертификата, передав аргумент -kubelet-insecure-tls серверу:

kubectl patch -n kube-system deployment metrics-server --type=json \
  -p '[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'

Развертывание моделей с HPA

Мы можем включить HPA в манифесте развертывания, добавив hpaSpec для соответствующего компонента:

hpaSpec:
  maxReplicas: 2
  metrics:
  - resource:
      name: cpu  # This can be either "cpu" or "memory"
      targetAverageUtilization: 50
    type: Resource
  minReplicas: 1

Спецификация HPA сообщает развертыванию о необходимости масштабирования, когда текущее значение метрики (в данном случае использование ЦП) превышает 50 % от желаемого значения, а максимальное количество реплик, которые может иметь развертывание, равно 2.

Примените этот манифест для создания развертывания с HPA, убедитесь, что вы заменили <YOUR_BUCKET> на имя вашей корзины и что секрет для доступа к корзине (как упоминалось в предыдущем разделе) создан:

kubectl apply -f cifar10-deploy-hpa.yaml

Вы можете увидеть текущее значение метрики с помощью:

kubectl get hpa
# NAME                                REFERENCE                                      TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
# cifar10-default-0-cifar10-pytorch   Deployment/cifar10-default-0-cifar10-pytorch   0%/50%    1         2         1          98m

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

kubectl get pods
# NAME                                                 READY   STATUS    RESTARTS   AGE
# cifar10-default-0-cifar10-pytorch-7744fdc4dd-vzx4x   3/3     Running   0          101m

Теперь мы можем протестировать развернутую модель с помощью нашего тестового скрипта (о котором говорилось в предыдущем разделе):

locust -f test.py --headless -u 100 -r 10 --run-time 180 -H http://localhost:8080

Отслеживая текущее значение метрики с помощью kubectl get hpa -w, вы увидите, что через некоторое время значение метрики превышает пороговое значение, и HPA инициирует создание нового модуля:

kubectl get pods
# NAME                                                 READY   STATUS    RESTARTS   AGE
# cifar10-default-0-cifar10-pytorch-7744fdc4dd-pgpxm   3/3     Running   0          10s
# cifar10-default-0-cifar10-pytorch-7744fdc4dd-vzx4x   3/3     Running   0          108m

Если текущее значение метрики ниже порогового значения для определенного периода (по умолчанию это 5 минут), HPA уменьшит масштаб развертывания. Период можно настроить с помощью флага аргумента --horizontal-pod-autoscaler-downscale-stabilization на kube-controller-manager:

kubectl get pods
# NAME                                                 READY   STATUS        RESTARTS   AGE
# cifar10-default-0-cifar10-pytorch-7744fdc4dd-pgpxm   3/3     Terminating   0          7m3s
# cifar10-default-0-cifar10-pytorch-7744fdc4dd-vzx4x   3/3     Running       0          114m

В этом разделе мы узнали, как увеличивать и уменьшать количество модулей в зависимости от загрузки ЦП. В следующем разделе мы будем использовать KEDA для более гибкого масштабирования нашего развертывания на основе пользовательских показателей.

Автомасштабирование Pod с помощью KEDA

KEDA умеет получать метрики из многих источников (они называются скейлерами), список поддерживаемых скейлеров смотрите здесь. Мы настроим KEDA для получения метрик с сервера Prometheus и будем отслеживать метрики для запуска масштабирования пода. Сервер Prometheus собирает метрики из развертываний Seldon в кластере.

Установите Seldon Monitoring и KEDA

Следуйте этой инструкции, чтобы установить стек Селдона для мониторинга, в который входит сервер Prometheus. Теперь в пространстве имен seldon-monitoring должны присутствовать следующие модули:

kubectl get pods -n seldon-monitoring
# NAME                                                    READY   STATUS    RESTARTS     AGE
# alertmanager-seldon-monitoring-alertmanager-0           2/2     Running   3 (8h ago)   26h
# prometheus-seldon-monitoring-prometheus-0               2/2     Running   2 (8h ago)   26h
# seldon-monitoring-blackbox-exporter-dbbcd845d-qszj8     1/1     Running   1 (8h ago)   26h
# seldon-monitoring-kube-state-metrics-7588b77796-nrd9g   1/1     Running   1 (8h ago)   26h
# seldon-monitoring-node-exporter-fmlh6                   1/1     Running   1 (8h ago)   26h
# seldon-monitoring-operator-6dc8898f89-fkwx8             1/1     Running   1 (8h ago)   26h

Проверьте, создан ли монитор модуля для Seldon Core:

kubectl get PodMonitor -n seldon-monitoring
# NAME                AGE
# seldon-podmonitor   26h

Выполните следующую команду, чтобы включить KEDA в Seldon Core:

helm upgrade seldon-core seldon-core-operator \
    --repo https://storage.googleapis.com/seldon-charts \
    --set keda.enabled=true \
    --set usageMetrics.enabled=true \
    --set istio.enabled=true \
    --namespace seldon-system

Установите KEDA в кластер и убедитесь, что ранее установленный KEDA (если он был) полностью удален:

kubectl delete -f https://github.com/kedacore/keda/releases/download/v2.9.1/keda-2.9.1.yaml
kubectl apply -f https://github.com/kedacore/keda/releases/download/v2.9.1/keda-2.9.1.yaml

Развертывание моделей с помощью KEDA

У нас все настроено. Давайте создадим развертывание Seldon с помощью KEDA. Как и в случае с HPA, чтобы включить KEDA в развертывании, нам нужно всего лишь включить kedaSpec в манифест развертывания. Рассмотрим следующую спецификацию:

kedaSpec:
  pollingInterval: 15
  minReplicaCount: 1
  maxReplicaCount: 2
  triggers:
  - type: prometheus
    metadata:
      serverAddress: http://seldon-monitoring-prometheus.seldon-monitoring.svc.cluster.local:9090
      metricName: access_frequency
      threshold: '20'
      query: avg(rate(seldon_api_executor_client_requests_seconds_count{deployment_name=~"cifar10"}[1m]))

serverAddress — это адрес сервера Prometheus внутри кластера, это должен быть URL-адрес сервиса Prometheus, мы можем проверить сервис с помощью kubectl get svc -n seldon-monitoring. Когда значение метрики превысит threshold, будет запущено масштабирование. query — это среднее количество запросов в секунду для запущенных реплик, это показатель, который мы хотим отслеживать.

Примените этот манифест для развертывания модели:

kubectl apply -f cifar10-deploy-keda.yaml

Запустим автомасштабирование, отправив запросы в деплоймент:

locust -f test.py --headless -u 100 -r 10 --run-time 180 -H http://localhost:8080

Через несколько секунд вы увидите созданный новый модуль:

kubectl get pods
# NAME                                                 READY   STATUS     RESTARTS      AGE
# cifar10-default-0-cifar10-pytorch-5dc484599c-2zrv8   3/3     Running    3 (18m ago)   35h
# cifar10-default-0-cifar10-pytorch-5dc484599c-ljk74   0/3     Init:0/2   0             9s

Подобно HPA, уменьшение масштаба будет запущено после определенного периода (по умолчанию 5 минут) низкого трафика.

kubectl get pods -w
# NAME                                                 READY   STATUS    RESTARTS      AGE
# cifar10-default-0-cifar10-pytorch-5dc484599c-2zrv8   3/3     Running   3 (22m ago)   35h
# cifar10-default-0-cifar10-pytorch-5dc484599c-ljk74   3/3     Running   0             3m55s
# cifar10-default-0-cifar10-pytorch-5dc484599c-ljk74   3/3     Terminating   0             5m49s
# cifar10-default-0-cifar10-pytorch-5dc484599c-ljk74   2/3     Terminating   0             6m
# cifar10-default-0-cifar10-pytorch-5dc484599c-ljk74   1/3     Terminating   0             6m1s
# cifar10-default-0-cifar10-pytorch-5dc484599c-ljk74   0/3     Terminating   0             6m3s

Заключение

Мы узнали, как развертывать модели машинного обучения в кластерах Kubernetes с помощью Seldon Core. Хотя мы в основном сосредоточились на развертывании моделей PyTorch, процедуры, показанные в этом руководстве, можно использовать для развертывания моделей из других платформ.

Мы также сделали развертывания масштабируемыми с помощью HPA и KEDA. По сравнению с HPA, KEDA предоставляет более гибкие способы масштабирования системы на основе метрик Prometheus (или других поддерживаемых масштабаторов от KEDA). Технически мы можем реализовать любые правила масштабирования из метрик, которые можно получить с сервера Prometheus.

Первоначально опубликовано на https://tintn.github.io 9 января 2023 г.