Повышение производительности моделей Falcon с помощью Amazon SageMaker
Увеличение эффективности моделей Falcon с помощью Amazon SageMaker
Какая оптимальная структура и конфигурация для размещения больших моделей языка (LLM) для приложений искусственного интеллекта, генерирующих текст? Несмотря на изобилие возможностей для обслуживания LLM, это сложный вопрос, на который трудно ответить из-за размеров моделей, различной архитектуры модели, требований к производительности приложений и прочего. Контейнер Amazon SageMaker Large Model Inference (LMI) легко обслуживает LLM, объединяя различные фреймворки и техники, которые оптимизируют развертывание LLM. Контейнер LMI имеет мощный набор обслуживающих инструментов DJL, который является независимым от базового LLM. Он предоставляет параметры на уровне системы, которые могут быть настроены для достижения максимальной производительности инфраструктуры хостинга для данного LLM. Он также поддерживает последние оптимизации, такие как непрерывное формирование пакетов, также известное как итеративное формирование пакетов или последовательное формирование пакетов, которое обеспечивает значительное увеличение пропускной способности.
В более ранней статье мы показали, как можно использовать контейнер LMI для развертывания семейства моделей Falcon на SageMaker. В этой статье мы продемонстрируем, как улучшить пропускную способность и задержку обслуживания Falcon-40B с помощью техник, таких как непрерывное формирование пакетов. Мы также предоставим интуитивное понимание параметров конфигурации, предоставляемых контейнером SageMaker LMI, которые могут помочь вам найти наилучшую конфигурацию для вашего приложения в реальном мире.
Основы генерации текста с использованием LLM
Давайте сначала рассмотрим несколько основных принципов того, как выполнять вывод для LLM при генерации текста.
Прямой проход, активации и кэш KV
Учитывая последовательность токенов на входе, они проходят в прямом проходе
через все слои LLM (например, Falcon), чтобы сгенерировать следующий токен. Прямой проход
означает процесс, при котором входные данные проходят через нейронную сеть для получения выходных данных. В случае генерации текста прямой проход включает подачу начального сида или контекста в языковую модель и генерацию следующего символа или токена в последовательности. Для генерации текстовой последовательности применяется итеративный процесс, что означает, что он повторяется для каждого шага или позиции в выходной последовательности. На каждой итерации модель генерирует следующий символ или токен, который становится частью сгенерированного текста, и этот процесс продолжается до генерации желаемой длины текста.
- Индексируйте ваш контент с помощью нового Веб-сканера для Amazon Kendra.
- Как машинное обучение и искусственный интеллект могут быстро обнаруживать поддельные отзывы?
- Облачные платформы интеграции для ускорения бизнес-трансформации
Генерация текста с использованием языковых моделей, таких как Falcon или GPT, является авторегрессивной
. Это означает, что модель генерирует один токен за раз, при условии предыдущих сгенерированных токенов. Другими словами, на каждой итерации модель берет ранее сгенерированный текст в качестве входных данных и предсказывает следующий токен на основе этого контекста. Как упомянуто в vLLM: простая, быстрая и недорогая обработка LLM с использованием PagedAttention, в процессе авторегрессивной декодировки все входные токены для LLM создают свои тензоры ключей и значений внимания, и эти тензоры хранятся в памяти GPU для генерации следующих токенов. Эти кешированные тензоры ключей и значений часто называются кэшем KV
.
Фазы предварительного заполнения и декодирования
В процессе авторегрессивного декодирования, например, применяемого при генерации текста с использованием языковых моделей, таких как Falcon, обычно выделяются две основные фазы: фаза предварительного заполнения
и фаза декодирования
. Эти фазы являются важными для генерации связного и контекстно значимого текста.
Фаза предварительного заполнения включает в себя следующее:
- Начальный контекст – Фаза предварительного заполнения начинается с начального контекста или исходного текста, предоставленного пользователем. Этот начальный контекст может быть предложением, фразой или даже одним словом. Он задает начальную точку для генерации текста и предоставляет контекст для того, что будет следовать далее.
- Модельное условие – Предоставленный контекст используется для настройки языковой модели. Модель принимает этот контекст в качестве входных данных и генерирует следующий токен (слово или символ) в последовательности на основе своего понимания контекста.
- Генерация токенов – Модель генерирует один токен за раз, предсказывая, что должно следовать дальше в тексте. Этот токен добавляется к контексту, что, фактически, расширяет его.
- Итеративный процесс – Процесс генерации токенов повторяется итерационно. На каждом шаге модель генерирует токен, учитывая обновленный контекст, который теперь включает токены, сгенерированные на предыдущих шагах.
Инициализационная фаза продолжается до тех пор, пока не будет достигнуто предопределенное условие остановки. Это условие может быть максимальной длиной сгенерированного текста, определенным токеном, сигнализирующим конец текста, или любыми другими критериями, установленными пользователем или приложением.
Фаза декодирования включает следующее:
- Завершение – После инициализационной фазы у вас есть частично сгенерированный текст, который может быть неполным или обрезанным. Фаза декодирования отвечает за завершение текста, чтобы сделать его последовательным и грамматически правильным.
- Продолжение с последнего токена – В фазе декодирования модель начинает с последнего сгенерированного токена из инициализационной фазы. Она использует этот токен как начальный контекст и генерирует следующий токен для продолжения текста.
- Итеративное завершение – Как и в инициализационной фазе, процесс генерации токенов снова является итеративным. Модель генерирует по одному токену, на основе предшествующих токенов в последовательности.
- Условие остановки – Фаза декодирования также имеет условие остановки, которое может быть таким же, как в инициализационной фазе, например, достижение максимальной длины или обнаружение токена конца текста. Когда это условие выполняется, процесс генерации останавливается.
Комбинация инициализационной и декодирующей фаз позволяет авторегрессионным моделям генерировать текст, основанный на начальном контексте, и создавать последовательности текста, которые являются последовательными и связанными с контекстом.
Смотрите Система распределенного обслуживания для генеративных моделей на основе трансформеров для подробного объяснения процесса.
Оптимизация прохождения с использованием динамического пакетирования
До сих пор мы говорили только о одном входе. На практике мы ожидаем работу с несколькими запросами, поступающими случайным образом от клиентов приложения для вывода одновременно или пошагово. В традиционном подходе можно использовать основное пакетирование для увеличения пропускной способности и утилизации вычислительных ресурсов GPU. Пакетирование – это эффективное объединение числовых представлений более одного запроса в пакет и параллельное выполнение авторегрессионного прямого прохода. Это интеллектуальное пакетирование выполняется на стороне обслуживания. Сервер SageMaker LMI DJLServing может быть настроен для объединения нескольких запросов в пакет, чтобы обрабатывать их параллельно, установив следующие параметры в serving.properties:
- max_batch_delay = 100 – максимальная задержка для объединения пакета в миллисекундах. Значение по умолчанию – 100 миллисекунд.
- batch_size = 32 – динамический размер пакета. Значение по умолчанию – 1.
Это показывает, что DJLServing будет очередью запросов на протяжении 100 миллисекунд или, если число запросов, находящихся в очереди, будет равно указанному значению batch_size, пакет будет запланирован для выполнения на бэкенде для вывода. Это известно как динамическое пакетирование
. Оно динамично, поскольку размер пакета может изменяться для каждого пакета в зависимости от того, сколько запросов было добавлено за указанное время. Однако, поскольку запросы могут иметь различные характеристики (например, некоторые запросы могут иметь форму 20 токенов для ввода и 500 токенов для вывода, тогда как другие могут быть наоборот, с 500 токенами для ввода, но только с 20 для вывода), некоторые запросы могут завершиться быстрее, чем другие в том же пакете. Это может привести к недостаточному использованию GPU во время ожидания завершения всех запросов в пакете декодирующей стадии, даже если есть дополнительные запросы, ожидающие обработки в очереди. Ниже приведена схема, иллюстрирующая этот процесс.

Динамическая визуализация пакетирования – обратите внимание на неактивные окна в конце запросов 2 и 3
Оптимизация пропускной способности с использованием непрерывного пакетирования
С использованием непрерывного пакетирования
, также известного как итеративное
или роликовое
пакетирование, мы используем разные этапы предварительной загрузки и декодирования. Для активации непрерывного пакетирования DJServing предоставляет следующие дополнительные конфигурации в файле serving.properties:
- Engine=MPI – Мы рекомендуем использовать движок MPI для непрерывного пакетирования.
- Option.rolling_batch=auto или lmi-dist – Мы рекомендуем использовать auto, так как это автоматически выберет наиболее подходящий алгоритм роликового пакетирования вместе с другими оптимизациями в будущем.
- Option.max_rolling_batch_size=32 – Это ограничивает количество одновременных запросов. По умолчанию значение равно 32.
С непрерывным пакетированием стек обслуживания (DJLServing) не ожидает завершения всех запросов одного пакета на этапе декодирования. Вместо этого, на логической паузе (в конце каждой итерации этапа декодирования) он добавляет в пакет дополнительные запросы, ожидающие выполнения, пока текущий пакет еще обрабатывается (отсюда название “роликовой пачки”). Эта проверка ожидающих запросов выполняется в конце каждой итерации этапа декодирования. Помните, что для каждого запроса нам нужно выполнить этап предварительной загрузки, а затем последовательный этап декодирования. Поскольку мы можем обрабатывать все токены из начального запроса параллельно на этапе предварительной загрузки, всякий раз, когда поступает новый запрос, мы временно приостанавливаем этап декодирования запросов одного пакета, временно сохраняем его кэш KV и активации в памяти и запускаем этап предварительной загрузки новых запросов.
Размер этого кэша можно настроить с помощью следующей опции:
- Option.max_rolling_batch_prefill_tokens=1024 – Ограничивает количество одновременно сохраняемых предварительных токенов в кэше для роликовой пачки (между этапами декодирования и предварительной загрузки)
Когда предварительная загрузка завершена, мы объединяем новые и старые приостановленные запросы в новую роликовую пачку, которая может продолжить этап декодирования параллельно. Обратите внимание, что старые приостановленные запросы могут продолжать этап декодирования с того места, где они остановились, а новые запросы начнут с их первого нового токена.

Визуализация непрерывного или итеративного пакетирования – обратите внимание, что бездействующее время заменено последующими запросами
Вы, возможно, уже поняли, что непрерывное пакетирование – это практически аналогичный подход, с которым мы естественно параллелизуем задачи в нашей повседневной жизни. У нас есть сообщения, электронные письма, уведомления на телефон (возможные новые запросы), поступающие в случайное время (аналогично множеству запросов, поступающих в случайном распределении для GPU). Все это происходит, пока мы выполняем наши текущие задачи – составление электронных писем, кодирование, участие в совещаниях (аналогично текущим задачам в GPU). На логических паузах мы приостанавливаем наши текущие задачи и проверяем уведомления, чтобы решить, требуется ли действие с нашей стороны, и при необходимости мы добавляем его к текущим задачам (роликовая пачка в реальной жизни) или ставим на список дел (очередь).
Совместимое использование памяти GPU: как об этом думать
Рекомендуется провести тестирование нагрузки модели, чтобы определить наиболее эффективную конфигурацию для вашего делового случая. Чтобы понять, давайте визуализируем объем памяти GPU при загрузке модели и последующей обработке запросов в непрерывной партии. В этом посте предположим, что мы загружаем модель Falcon-40B на один из экземпляров G5, установленных с GPU NVIDIA A10G, каждый с 24 ГБ памяти. Обратите внимание, что аналогичное понимание также применимо к экземплярам p3, p4 и p5, которые поставляются с GPU серии V100, A100 и H100.
Ниже приведен обзор приблизительной оценки общего объема памяти, необходимого для обслуживания Falcon-40B:
- Размер модели = Количество параметров модели (40 миллиардов для Falcon-40B) x 4 байта на параметр (для FP32) = 160 ГБ
- Приблизительный общий объем памяти, необходимый для загрузки Falcon-40B для вывода = Размер модели (=160 ГБ) + KV кэш (кэш внимания) (= 20 ГБ) + Дополнительный накладный расход памяти фреймворками машинного обучения (приблизительно 2 ГБ)

Визуализация памяти – понимание объема памяти загруженной модели Falcon-40B
Для Falcon-40B, если мы сжимаем модель, квантуя ее до данных типа bfloat16 (2 байта), размер модели становится приблизительно 80 ГБ. Как видите, это по-прежнему больше памяти, поддерживаемой одним акселераторным устройством, поэтому нам необходимо использовать технику разделения модели (шардинг) с особым подходом к тензорному параллелизму и разделить модель на несколько акселераторных устройств. Предположим, что мы выбрали g5.24xlarge, который имеет 4 устройства GPU A10G. Если мы настраиваем DJLServing (serving.properties) следующим образом, то можно ожидать, что 80 ГБ весов модели будут равномерно распределены по всем 4 GPU:
- option.tensor_parallel_degree%0Aoption.-,tensor_parallel_degree,-%3D2%0A%23%20specify%20per) = 4 или 8, либо использовать max (максимальное количество обнаруженных GPU на экземпляре)
Установив tensor_parallel_degree равный 4, около 20 ГБ из 24 ГБ видеопамяти GPU (приблизительно 84%) уже используется, еще до обработки одного запроса. Оставшиеся 16% GPU будут использоваться для KV кэша для входящих запросов. Возможно, для вашего бизнес-сценария и его требований к задержке и пропускной способности, 2-3 ГБ оставшейся памяти более чем достаточно. Если нет, вы можете увеличить размер экземпляра до g5.48xlarge, который имеет 8 GPU, и установить tensor_parallel_degree равным 8. В таком случае только примерно 10 ГБ доступной памяти из 24 ГБ каждого GPU будет использоваться для весов модели, и у нас будет около 60% оставшейся памяти GPU для активаций и KV кэша. Интуитивно можно понять, что такая конфигурация может позволить нам иметь большую пропускную способность. Кроме того, поскольку у нас теперь больший буфер, мы можем увеличить параметры max_rolling_batch_prefill_tokens и max_rolling_batch_size, чтобы дополнительно оптимизировать пропускную способность. Вместе эти два параметра будут управлять предварительными выделениями активаций и KV кэша для модели. Большее число для этих двух параметров будет коррелировать с большей пропускной способностью, предполагая, что у вас есть достаточно буфера для KV кэша в видеопамяти GPU.
Непрерывная пакетная обработка с PagedAttention
PagedAttention – это новый оптимизационный алгоритм, разработанный UC Berkeley, который улучшает процесс непрерывной пакетной обработки, позволяя кеш внимания (кеш KV) быть непоследовательным путем выделения памяти в блоках фиксированного размера или страницах. Это вдохновлено концепцией виртуальной памяти и страничными концепциями, используемыми операционными системами.
Согласно документу vLLM, кеш внимания каждой последовательности токенов разбивается на блоки и отображается на физические блоки через таблицу блоков. Во время вычисления внимания ядро PagedAttention может использовать таблицу блоков для эффективного извлечения блоков из физической памяти. Это значительно снижает потери памяти и позволяет использовать больший размер пакета, повышает использование графического процессора и увеличивает пропускную способность.
Сравнение производительности
Для обеспечения эффективного нагрузочного тестирования конфигурации вашего развёртывания рекомендуется начать с рассмотрения бизнес-сценария и ясной определенности характеристик ввода и вывода для приложения на основе LLM. Например, если вы работаете над случаем использования сводки для центра обработки вызовов, ввод может состоять из более крупных текстовых данных, таких как 500-токеновый чат-перевод между оператором службы поддержки и клиентом, и вывод может быть относительно небольшим, около 100 токенов, представляющими сводку перевода. С другой стороны, если вы работаете над сценарием генерации кода, ввод может быть коротким, например, 15 токенов, наподобие “напишите эффективную реализацию на Python для описания всех ресурсов EC2, включая пагинацию”, но вывод может быть значительно больше, до 500 токенов. Также важно рассмотреть, является ли достижение более низкой задержки или максимизация пропускной способности наивысшим приоритетом для вашего конкретного сценария.
Получив комплексное представление о бизнес-сценарии, вы можете проанализировать и определить оптимальную конфигурацию для вашей среды хостинга. В этом контексте среда хостинга включает различные ключевые элементы, включая тип экземпляра и другие параметры конфигурации, такие как tensor_parallel_degree, max_rolling_batch_size, max_rolling_batch_prefill_tokens и другие. Наша задача – определить наиболее эффективную настройку для поддержки требований по времени отклика, пропускной способности и качества вывода модели.
В нашем анализе мы провели испытания производительности, чтобы проиллюстрировать преимущества непрерывной пакетной обработки по сравнению с традиционной динамической пакетной обработкой. Мы использовали конфигурации, указанные в следующей таблице в serving.properties для динамической пакетной обработки и итеративной пакетной обработки с использованием контейнера LMI на SageMaker.
Динамическая пакетная обработка | Непрерывная пакетная обработка | Непрерывная пакетная обработка с PagedAttention |
engine=Python option.model _id=tiiuae/falcon-40b option.ten sor_parallel_degree=8 option.dtype=fp16 batch_size=4 max_batch_delay=100 opt ion.trust_remote_code = true |
engine = MPI option.model_id = {{s3_url}} opt ion.trust_remote_code = true option.t ensor_parallel_degree = 8 option.m ax_rolling_batch_size = 32 option.rolling_batch = auto option.dtype = fp16 option.max_rolling _batch_prefill_tokens = 1024 o ption.paged_attention = False |
engine = MPI option.model_id = {{s3_url}} opt ion.trust_remote_code = true option.t ensor_parallel_degree = 8 option.m ax_rolling_batch_size = 32 option.rolling_batch = auto option.dtype = fp16 option.max_rolling _batch_prefill_tokens = 1024 o ption.paged_attention = True |
Две конфигурации были протестированы для Falcon-40B с использованием типа данных FP16 на ml.g5.48xlarge в нескольких различных сценариях, которые представляют реальные приложения:
- Небольшое количество входных токенов с большим количеством сгенерированных токенов – В этом сценарии количество входных токенов было фиксировано на уровне 32, а было сгенерировано 128 новых токенов
Стратегия пакетирования | Пропускная способность (токенов/сек) | Задержка p90 (сек) |
Динамическое пакетирование | 5.53 | 58.34 |
Непрерывное пакетирование | 56.04 | 4.74 |
Непрерывное пакетирование с использованием PagedAttention | 59.18 | 4.76 |
- Большой вход с малым количеством сгенерированных токенов – Здесь мы фиксируем количество входных токенов на уровне 256 и запрашиваем от LLM сводку ввода в 32 токена
Стратегия пакетирования | Пропускная способность (токенов/сек) | Задержка p90 (сек) |
Динамическое пакетирование | 19.96 | 59.31 |
Непрерывное пакетирование | 46.69 | 3.88 |
Непрерывное пакетирование с использованием PagedAttention | 44.75 | 2.67 |
Мы видим, что непрерывное пакетирование с использованием PagedAttention обеспечивает улучшение пропускной способности в 10 раз в сценарии 1 и в 2,3 раза в сценарии 2 по сравнению с использованием динамического пакетирования на SageMaker при использовании контейнера LMI.
Заключение
В этой статье мы рассмотрели, как LLM использует память и объяснили, как непрерывное пакетирование улучшает пропускную способность с использованием контейнера LMI на SageMaker. Мы продемонстрировали преимущества непрерывного пакетирования для Falcon-40B с использованием контейнера LMI на SageMaker, показав результаты бенчмарков. Код можно найти в репозитории на GitHub.