Разворачиваем раздельные нагрузки LLM на Kubernetes

2 просмотров Источник
Разворачиваем раздельные нагрузки LLM на Kubernetes

С ростом сложности нагрузок на языковые модели (LLM) традиционные монолитные процессы обслуживания начинают достигать своих пределов. Этапы предзаполнения и декодирования имеют принципиально разные профили вычислений, однако традиционные развертывания вынуждают их работать на одном оборудовании, что приводит к недостаточному использованию GPU и неэффективному масштабированию. Раздельное обслуживание решает эту проблему, разделяя конвейер вывода на отдельные этапы, такие как предзаполнение, декодирование и маршрутизация, которые работают как независимые сервисы и могут быть обеспечены и масштабированы по своим условиям.

В данной статье рассматривается, как раздельное инференсное развертывание осуществляется на Kubernetes, исследуются различные экосистемные решения и их работа в кластере, а также оценивается, что они предлагают «из коробки». Прежде чем углубляться в манифесты Kubernetes, полезно понять два режима развертывания для LLM: в агрегированном обслуживании один процесс (или тесно связанная группа процессов) обрабатывает весь жизненный цикл вывода от ввода до вывода. В раздельном обслуживании конвейер разделяется на отдельные этапы, каждый из которых работает как независимый сервис.

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

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

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

Развертывание многоподовой нагрузки (либо агрегированных моделей, либо раздельных моделей) — это лишь половина дела. Как планировщик размещает поды в кластере, напрямую влияет на производительность. Понимание того, как осуществлять планирование, становится ключевым для достижения высокой производительности многоподовой инференции на Kubernetes. Важны три возможности планирования: группировка, иерархическая группировка и размещение с учетом топологии. Эти три возможности определяют, как AI-планировщик, такой как KAI Scheduler, размещает поды в зависимости от ограничений развертывания приложения.

Похожие статьи