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

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

Служба шардинга

Служба сегментирования — это мозг, стоящий за операцией. Он получает уведомления из репозитория моделей всякий раз, когда модель создается, обновляется или удаляется. Служба сегментирования также получает уведомления от сервера API Kubernetes всякий раз, когда пользователь развертывает, обновляет или удаляет свое приложение машинного обучения.

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

Осколки:

  1. Создать новый сегмент
  2. Удалить существующий шард
  3. Инициировать непрерывное обновление осколков

Модели:

  1. Добавление новых моделей в существующий сегмент
  2. Удаление моделей из сегмента
  3. Обновить версию существующей модели в существующем сегменте
  4. Перемещайте обновленные модели из одного шарда в другой.

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

Проблемы

  1. Нулевое время простоя: приложение вывода машинного обучения должно всегда иметь возможность обслуживать трафик; при обновлении приложения модели обновляются даже при перемещении моделей из одного сегмента в другой.
  2. Стабильность. Стратегия сегментирования и распределение моделей по сегментам никогда не должны приводить к нестабильности базового приложения для логического вывода.

Нулевое время простоя

Нулевое время простоя действительно является требованием любой производственной системы. Однако это создает уникальный набор проблем при работе с многопользовательским приложением вывода ML. Существует несколько сценариев, которые могут привести к простою моделей обслуживания, поэтому давайте рассмотрим каждый вариант использования.

Обновления приложений

Когда приложение вывода ML обновляется, обновленный контейнер Docker, в котором размещается приложение, должен распространяться на каждый сегмент, не вызывая простоя. К счастью, Kubernetes позаботится об этом из коробки; поскольку каждый сегмент приложения машинного обучения содержит собственное развертывание, мы можем указать стратегию непрерывного обновления в развертываниях, чтобы разрешить это.

spec:
  replicas: 10
  selector:
    matchLabels:
      app: inference-application-shard-1
  strategy:
    rollingUpdate:
      maxSurge: 50%
      maxUnavailable: 50%
    type: RollingUpdate

В этом примере мы указали maxUnavailable равным 50 %, это означает, что только половина модулей в развертывании сегмента (т. е. 5 в нашем примере) может быть отключена для обновления в любой момент; остальные реплики будут обслуживать трафик.

Служба сегментирования выпускает последовательное обновление для всех сегментов с обновленным контейнером Docker при обнаружении обновленного развертывания.

Обновления, добавления и удаления моделей

Часто используемые модели добавляются, удаляются или обновляются в осколках. Каждый раз, когда это происходит, нам нужно по-прежнему обслуживать трафик для остальных моделей. Для этого существует несколько требований, которым должны соответствовать приложения вывода ML:

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

Перемещение моделей из шарда в шард

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

На рис. 2 показана модель B1, обновленная до модели B2, однако потребление памяти моделью B2 увеличилось и больше не помещается в осколке 1. Сначала мы находим подходящий осколок для модели B2, а затем добавляем модель в осколок 2. После модели B2 успешно загружен в сегмент 2 и готов обслуживать трафик, мы модифицируем виртуальную службу, связанную с сегментами 1 и 2, чтобы перенаправить трафик для арендатора, связанного с моделью. Эта последовательность процедур гарантирует, что арендованный трафик для модели B никогда не пропадет; может быть короткий период времени, в течение которого трафик обслуживается более старой версией модели, но это допустимо.

Стабильность

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

Упаковка ящиков

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

Группировка операций модели

Чтобы добавить, удалить или обновить модели и соответствующие им правила маршрутизации, мы используем 3 команды, которые являются идемпотентными;

set_models(shard,[models…]) — Add, delete or update models within a given shard
set_route(shard, [tenants…]) — Set routing rules for a given shard
update_shard(shard) — Issue a rolling update of a given shard

Давайте взглянем на несколько примеров некоторых основных операций с моделями и на то, как мы используем 3 приведенные выше команды для их инструментирования.

Пример 1: добавление модели к существующему сегменту

Предполагая, что сегмент с именем shard_1 уже содержит модель model_A_v1, давайте добавим еще одну модель model_B_v1, принадлежащую арендаторам tenant_A и tenant_B соответственно.

set_models(shard_1, [model_A_v1, model_B_v1])
update_shard(shard_1)
set_route(shard_1, [tenant_A, tenant_B])

Пример 2: Объединение операций

Опираясь на предыдущий пример, предположим, что теперь мы хотим обновить model_A_v1 до model_A_v2 и добавить model_C_v1 в shard_1 для арендатор_C.

set_models(shard_1, [model_A_v2, model_B_v1, model_C_v1])
update_shard(shard_1)
set_route(shard_1, [tenant_A, tenant_B, tenant_C])

Здесь мы видим, что когда у нас есть несколько операций для выполнения на одном осколке, мы можем сгруппировать их вместе.

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

Последние мысли

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