Как стать автором
Обновить
284.43
Слёрм
Учебный центр для тех, кто работает в IT

Разворачиваем AI-приложение в кластере k8s

Время на прочтение8 мин
Количество просмотров459

Привет, Хабр! С появлением AI-приложений* этот термин довольно быстро влетел в пантеон технологий и занял свое почетное место где-то около Kubernetes, а может даже и выше. По мере того, как упрощается работа с AI, растет его популярность среди обычных людей, а также нарастают темпы внедрения этой технологии как для разработки, так и для компаний и корпораций. Пользу AI при этом сложно переоценить, будь то готовый реферат за 5 минут, объяснение, как работает определенная функция в коде, или обзвон роботами с предложением подключить очередную услугу. Этическую часть применения AI во всех аспектах я не возьмусь оценить, вместо этого давайте рассмотрим экзотическую техническую реализацию.

Меня зовут Павел Минкин, тружусь в качестве DevOps-инженера в FinTech-компании. Интересуюсь технологиями, автоматизирую все, что попадает под руку, верю в DevSecOps, провожу вебинары.

Давайте ответим на вопросы, которые витают в воздухе, но остаются незаданными: а что произойдет, если засунуть AI-приложение в кластер? А надо ли это делать? И как это сделать минимальным количеством инструментов? А можно без GPU?

Эта статья написана по теоретической части вебинара «Ansible в действии. Разворачиваем кластер с Kuberspray и запускаем Al-приложение». Выбирайте, какой формат для вас удобнее. Посмотреть запись вебинара можно по ссылкам: 

Что ж, приступим!

Знакомимся с AI

Будем использовать минимальное количество инструментов, чтобы новички могли повторить, не утонув при этом во всех нюансах. По возможности буду добавлять рекомендации для углубления и улучшения проекта. Если есть что предложить или улучшить, тоже присоединяйтесь. Challenge accepted – AI, Kubernetes, минимальное количество инструментов! Репозиторий также в наличии.

Разворачивать будем ASR Whisper — модель распознавания речи общего назначения. Из основных функций: умеет преобразовывать аудио в текст, определять язык, а также переводить аудио на нужный язык из внушительного списка.

Иными словами, на вход модели передаем аудио файл, на выходе получаем текст в оригинале или уже переведенный. То есть, на вход модели передаем аудио файл, на выходе получаем текст в оригинале или уже переведенный. Такая функциональность позволяет создать, например, свой собственный голосовой помощник J.A.R.V.I.S., как у Тони Старка из Железного человека, или что-то более приземленное, будь то субтитры для фильма, транскрибация звонка или робота помощника. ASR Whisper, конечно, не всемогущ и будет выступать в качестве модуля в реализации таких систем, однако при этом будет работать локально, а значит данные не будут передаваться на сервера OpenAI.

Запускаем минимальную работоспособную версию.

Прежде чем запускать модель в оркестраторе k8s, ее можно запустить локально при помощи Docker и протестировать. Образ возьмем готовый, но для production/продуктового/боевого (выбери своего бойца) контура нужно будет создать свой, так как официальных нет на данный момент, а лишние риски безопасности еще никому седых волос не убавили.

docker run -p 9000:9000 \
  -e ASR_MODEL=tiny \
  -e ASR_ENGINE=faster_whisper \
  onerahmet/openai-whisper-asr-webservice:latest

Данная команда запустит ASR Whisper в докере, после чего загрузит необходимые пакеты и будет готова слушать на порту 9000.

Две переменные окружения ASR_MODEL и ASR_ENGINE отвечают за выбор модели (tiny, base, small, medium, large-v3, и т.д.) и выбор движка (openai_whisper, faster_whisper, whisperx) соответственно. От выбора модели и движка будет зависеть, какого размера будет итоговый контейнер, и также количество ресурсов, которые контейнер сможет использовать. Данный конкретный образ оптимизирован для CPU расчетов, поэтому нам не потребуется GPU для расчетов. С другой стороны, на одном CPU расчеты будут занимать дольше времени.

Итак, контейнер запущен, идем в браузер http://localhost:9000, выбираем POST, затем Try It Out.

Опции можно выставить по желанию, например, transcribe для вывода текста или translate для перевода. Также есть word_timestamps для отображения времени фраз, что может быть полезным для разметки видео.

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

Пример выполнения  транскрибации
Пример выполнения  транскрибации

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

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

curl -X POST \ 
    -H "content-type: multipart/form-data" \
    -F "audio_file=@/home/pavelm/privet.m4a" \
    http://localhost:9000/asr?output=json | jq '.text'

Запрос типа POST, заголовок и путь к файлу, который можно изменить на свой /home/pavelm/privet.m4a. Утилиту jq используем для показа только готового вывода текста, без дополнительных полей json ответа.

Теперь, если мы захотим устроить стресс-тест и откроем десяток терминалов (вместо этого можно пойти путем утилиты k6, но у нас челлендж!), а в них, насколько позволит скорость рук, одновременно отправим относительно большой текстовый файл, мегабайт на 10, то произойдет следующее. Первый запрос начнет обрабатываться моделью, а остальные будут ожидать в очереди. Еще через минуту остальные запросы отвалятся по стандартному таймауту. 

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

Проблема и решение

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

Для реализации такой оркестрации мы будем использовать k8s**.

Собрал, задеплоил, победил!

Нужно решить вопрос с созданием кластера, можно «распылить» свой собственный кластер при помощи kubespray (узнать подробнее можно, посмотрев первую часть вебинара), либо создать готовый managed кластер у облачных провайдеров, также можно использовать minikube для локального тестирования.

Из нюансов: В репозитории для ai-app в deployment настроен affinity, это особенность тестового стенда, где у нас было две ноды – одна, помощнее, для перемалывания аудио, и вторая, минимальная, для loadbalancer. Если не применить affinity, кластер будет работать нестабильно и даже одна реплика приложения не сможет запуститься и отработать.

Для деплоя будем применять kubectl и манифесты, нам будет достаточно deployment, service и ingress, а также secret. Secret нужен для поддержки TLS и содержит сертификат и ключ (которые можно получить при помощи certbot). Все-таки стремимся к production-ready и применяем защиту для запросов, чтобы все было по взрослому.

Подключаем кубконфиг и деплоим приложение:

kubectl apply -f ai-app/ # применяем сразу все манифесты в директории. Обязательно перед этим нужно изменить значения для сертификата и ключа. 

В текущем виде конфигурация приложения похожа на ту, что мы делали в Docker вначале, но уже в Kubernetes. Также один инстанс приложения и также есть проблемы с таймаутом.

Сделаем ручное масштабирование в deployment.yaml параметра replicas, выставим 16 вместо 1 (здесь можно ориентироваться на количество cpu ноды непосредственно, 16 было у нашего тестового стенда). Конечно, гораздо лучше настроить HPA политику для автоматического масштабирования по нагрузке.

Можем снова запускать «стресс-тест», и в этот раз уже 16 терминалов и запросы будут обработаны в 16 «потоков». Для небольшой нагрузки вполне себе хороший результат. Но не для большого приложения, которое отправляет десятки и сотни (тысячи) запросов. При шестнадцати одновременно занятых подах остальные запросы также будут в очереди и будут отваливаться по таймауту.

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

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

Сделать это можно в ingress.yaml, в параметре nginx.ingress.kubernetes.io/proxy-body-size, в репозитории установлено значение 500m (500 Мб).

Если понизить значение до 10 Мб, тогда поды будут отрабатывать гораздо быстрее, и «очередь» будет разгребаться быстрее. Часть проблем это также решит, но если количество запросов в единицу времени будет расти, первоначальная проблема также вернется, еще никто не отменял возвращения  необходимости все-таки отправлять большие файлы.

Данную ситуацию могут разрешить следующие пути или комбинации путей:

  1. Добавление прокси с логикой определения размера файла и направления на тот сервис, который их должен обрабатывать(условно один – до 10 Мб, другой – от 10 Мб до 500 Мб)

  2. Создание различных endpoint’ов ingress(/small – до 10 Мб /large от 10 до 500 Мб)

  3. Добавление брокера(kafka, rabbitMQ) сообщений для того, чтобы не потерять вообще никакие сообщения. Здесь будет нужна поддержка режима ждуна на стороне клиента, когда ответ может приходить не сразу.

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

Мониторинг

Нагрузку на воркер ноду можно посмотреть, подключившись к воркер ноде по ssh утилитами top, htop или иными, более привычными. Однако это будет не совсем мониторинг, а, скорее, наблюдение за текущим состоянием. Это может быть удобно для проведения нагрузочного тестирования или в каких-то случаях создания профиля нагрузки, ну и в целом для челленджа. В остальных случаях будет гораздо более продуктивно применять системы мониторинга, такие как стек GPN(Grafana, Prometheus, Node Exporter)

Дальнейшие улучшения проекта

Начать стоит с параметризация через HELM чарт или kustomize для более удобного деплоя через переменные.

Также совершенно не лишним будет добавить упомянутый мониторинг и алертинг на основе стека GNP.

ASR стартует не сразу, ему нужны пакеты, их можно подключить через  директорию с кешем и\или настроить Liveness, readiness, startup пробы для стабильности.

Проделанное «стресс-тестирование» не защитит от всех возможных кейсов повышения утилизации ресурсов, поэтому добавление лимитов в namespace и deployment логично дополнит картину.

Вишенкой на торте будут настроенные VPA и HPA для автоматического масштабирование.

Бонус

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

Закругляемся

В заключение важно отметить, что Kubernetes может применяться как платформа для оркестрации различных проектов, которые основаны на нейронных сетях, однако нужно понимать задачи, которые такая платформа должна решать. Не для всех случаев работы с LLM такой подход будет эффективен. В приведенном выше примере рассмотрено масштабирование с учетом особенностей синхронного ASR Whisper, с образом, специально заточенным под работу с CPU. Более крупные модели, а также модели, задействующие GPU для работы их запуск в кластере  это отдельное приключение на 20 минут(привет Рику и Морти).

*Формулировка, конечно, вольная, правильно говорить нейронные сети или LLM, но все всё равно говорят искусственный интеллект (AI). Кстати, как вы относитесь к выражению «оплата по карте»?

**Можно, конечно, использоваться Docker swarm, но технология мертва и не применяется.

P.S. При написании статьи не нагрелся ни один CPU или GPU и не думала ни одна LLM.


Материалы подготовлены в рамках обучающих курсов Слёрма. 

На курсе «Ansible: Infrastructure as Code» вы научитесь ключевым навыкам, а также поймёте принципы и подходы к IaC. 

Стартовый курс для администраторов «Kubernetes: База» позволит за 6 недель освоить основы работы с Kubernetes: c системой автоматизации развертывания, масштабирования и управления приложениями в контейнерах.

Теги:
Хабы:
+5
Комментарии3

Публикации

Информация

Сайт
to.slurm.io
Дата регистрации
Дата основания
Численность
51–100 человек
Местоположение
Россия
Представитель
Антон Скобин