Оптимизация выводов - важнейшая часть генеративных приложений ИИ, используемых в производстве. Эффективное использование LLM в масштабе - сложная задача, и за последние годы было разработано множество методов, позволяющих ускорить и удешевить процесс вывода. Давайте рассмотрим эти методы в этой статье.
Большие языковые модели (БЯМ) основаны на архитектуре трансформаторов, изобретенной в 2017 году Васвани и др. Архитектура трансформаторов обеспечивает превосходную точность, обучение за несколько минут и способности, близкие к человеческим, в различных языковых задачах. Однако эти базовые модели, часто состоящие из десятков и сотен миллиардов параметров, требуют больших затрат на обучение и ресурсов при выводе. Затраты на вывод возрастают при использовании длинных входных контекстов, которые требуют значительных вычислительных мощностей из-за большого объема входных данных. Это делает эффективный вывод критически важной задачей, особенно в части управления памятью и вычислительными ресурсами.

Если говорить более конкретно, то большинство известных LLM - это LLM только для декодеров, такие как GPT-3, GPT-4, LLaMA, Mistral, DeepSeek и т.д. Эти модели предварительно обучаются на задаче каузального моделирования, функционируя как предсказатели следующего слова. Они обрабатывают последовательность лексем в качестве входных данных и производят следующие лексемы авторегрессивно до тех пор, пока не будет достигнуто условие остановки.
Вывод LLM в моделях, использующих только декодер, включает две ключевые фазы: фазу предварительного заполнения и фазу декодирования. На этапе предварительного заполнения модель обрабатывает входные лексемы для вычисления промежуточных состояний (ключей и значений) для генерации первой новой лексемы. Эта фаза, напоминающая матрично-матричную операцию, хорошо распараллеливается и эффективно использует возможности GPU. И наоборот, фаза декодирования генерирует токены по одному, опираясь на состояния предыдущих токенов. Эта матрично-векторная операция ограничена памятью, так как передача данных на GPU, а не скорость вычислений, в первую очередь определяет задержку, что приводит к неполному использованию вычислительной мощности GPU.
Оптимизация этапа декодирования является одним из основных направлений решения проблем, связанных с выводом. Решения включают разработку эффективных механизмов внимания и лучшее управление ключами и значениями для уменьшения узких мест в памяти. В статье освещаются практические подходы к повышению производительности вывода, предполагающие, что читатели имеют базовое представление об архитектуре трансформатора и механизмах внимания. Эти оптимизации имеют решающее значение для повышения пропускной способности и снижения задержек в реальных развертываниях LLM.
Еще одна сложность возникает из-за использования различных токенизаторов в LLM, что влияет на сопоставимость токенов. Токены, примерно эквивалентные четырем английским символам, отличаются по представлению в зависимости от токенизатора, что делает прямые сравнения производительности вывода (например, токенов в секунду) недостоверными. Такая вариативность подчеркивает необходимость стандартизированных оценочных показателей для точной оценки и сравнения производительности LLM в процессе вывода.
Пакетирование - ключевая стратегия для повышения эффективности использования GPU и пропускной способности больших языковых моделей (LLM). Благодаря одновременной обработке нескольких запросов с использованием одной и той же модели пакетная обработка распределяет затраты памяти на весовые коэффициенты модели между запросами, позволяя большим пакетам использовать больше вычислительных мощностей GPU. Однако существует ограничение на размер партии, поскольку слишком большие партии могут привести к переполнению памяти из-за требований LLM к памяти, особенно связанных с кэшированием ключей-значений (KV) (подробнее об этом позже).

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

Конвейерный параллелизм делит модель по вертикали на последовательные куски, каждый из которых содержит подмножество слоев, назначенных отдельному устройству. Например, при четырехстороннем конвейере каждое устройство обрабатывает четверть слоев модели, передавая выходы следующему по порядку устройству. Хотя это значительно снижает требования к памяти для каждого устройства, это приводит к неэффективности, известной как "пузырьки конвейера", когда устройства могут простаивать в ожидании выхода предыдущих слоев. Микропакетная обработка, при которой входные пакеты разбиваются на более мелкие подпакеты для последовательной обработки, может уменьшить количество таких "пузырей", но не устранить их полностью, поскольку время простоя сохраняется как при прямом, так и при обратном проходе.
Тензорный параллелизм, напротив, разбивает отдельные слои по горизонтали на более мелкие вычислительные блоки, которые могут выполняться независимо на разных устройствах. Это особенно эффективно для таких компонентов трансформатора, как блоки внимания и многослойные перцептроны (MLP), где, например, разные головки внимания могут быть назначены на разные устройства для параллельных вычислений. Однако тензорный параллелизм менее эффективен для таких операций, как LayerNorm и Dropout, которые не могут быть легко разделены и должны воспроизводиться на разных устройствах, что приводит к избыточному использованию памяти для хранения активаций. Это ограничение подчеркивает необходимость применения дополнительных подходов для оптимизации эффективности использования памяти.
Параллелизм последовательностей решает проблему неэффективности памяти таких операций, как LayerNorm и Dropout, разделяя их по размерности входной последовательности, используя их независимость от элементов последовательности. Этот метод уменьшает объем памяти, занимаемый избыточными активациями, что делает его ценным дополнением к тензорному параллелизму. Эти методы распараллеливания не являются взаимоисключающими и могут быть объединены для дальнейшей оптимизации больших языковых моделей (БЯМ). Кроме того, специальные стратегии оптимизации для модуля внимания могут улучшить масштабируемость и снизить требования к памяти на GPU, обеспечивая более эффективное обучение и вывод для больших моделей.
В работе 2017 года *Attention Is All You Need*, опубликованной Васвани и др., была представлена модель Transformer, краеугольным камнем которой является самовнимание. Самовнимание позволяет модели оценивать релевантность различных слов в предложении относительно друг друга, улучшая контекстное понимание для таких задач, как обработка естественного языка. В статье формализовано самовнимание, в частности, с помощью механизма масштабированного точечно-производного внимания (SDPA), который отображает запросы и пары ключ-значение на выход, что делает его ключевым компонентом в современных нейронных сетях. Вот несколько наиболее важных приемов оптимизации вычислений внимания:

Многоголовое внимание (MHA) развивает SDPA, выполняя несколько операций внимания параллельно, каждая из которых имеет свои проекции матриц запросов, ключей и значений. Эти параллельные операции, или "головы", фокусируются на различных репрезентативных подпространствах, обогащая понимание входных данных моделью. Выходы этих "голов" объединяются и линейно проецируются, сохраняя вычислительную эффективность, сравнимую с одноглавым вниманием, за счет уменьшения размерности каждой "головы" (например, деления размерности модели на количество "голов", например, 8).
Мультизапросное внимание (MQA) оптимизирует MHA для выводов, разделяя проекции ключей и значений между несколькими головками внимания, сохраняя при этом несколько проекций запросов. Это снижает требования к пропускной способности памяти и размер кэша ключей-значений (KV), что позволяет увеличить размер партии и повысить эффективность использования вычислительных ресурсов. Однако MQA может несколько снижать точность, и для поддержания производительности моделей, использующих его, требуется обучение или тонкая настройка с включенным MQA.
Внимание по сгруппированным запросам (GQA) балансирует между MHA и MQA, группируя головки запросов и разделяя проекции ключевых значений внутри каждой группы, достигая качества, близкого к MHA, при вычислительной эффективности, близкой к MQA. Такие модели, как Llama 2 70B, используют GQA, а модели, обученные на MHA, могут быть адаптированы к GQA с минимальным дополнительным обучением. Как MQA, так и GQA снижают требования к кэш-памяти KV, хотя дальнейшая оптимизация управления кэшем по-прежнему необходима.
FlashAttention улучшает механизмы внимания, изменяя порядок вычислений для более эффективного использования иерархий памяти GPU. В отличие от традиционной послойной обработки, FlashAttention объединяет операции и использует "черепицу" для одновременного вычисления небольших частей выходной матрицы, минимизируя операции чтения/записи в память. Этот алгоритм точного внимания, ориентированный на ввод-вывод, легко интегрируется в существующие модели без модификаций, обеспечивая значительное ускорение за счет оптимизации перемещения данных.
Кэширование KV - важнейшая техника оптимизации, используемая на этапе декодирования больших языковых моделей (БЯМ) для повышения эффективности вычислений самовнушения. На этом этапе каждая сгенерированная лексема зависит от тензоров ключей (K) и значений (V) всех предыдущих лексем, включая те, что были вычислены на этапе предзаполнения и на последующих этапах декодирования. Вместо того чтобы заново вычислять эти тензоры для каждого токена на каждом временном шаге, KV-кэширование хранит их в памяти GPU, добавляя новые тензоры в кэш по мере их вычисления. Как правило, для каждого слоя модели ведется отдельный KV-кэш, что значительно сокращает количество избыточных вычислений и ускоряет процесс декодирования.

Требования к памяти для LLM на GPU в основном определяются двумя компонентами: весами модели и кэшем KV. Веса модели, состоящие из параметров модели, занимают значительный объем памяти; например, модель с 7 миллиардами параметров, такая как Llama 2 7B с 16-битной точностью, требует около 14 ГБ. Кэш KV, с другой стороны, хранит тензоры самовнимания, чтобы избежать повторных вычислений, а его размер определяется такими факторами, как количество слоев, головки внимания, размеры головок и точность. Для каждого маркера размер кэша вычисляется как 2 * num_layers * (num_heads * dim_head) * precision_in_bytes, где коэффициент 2 учитывает матрицы K и V. Для партии входных данных общий размер KV-кэша зависит от размера партии и длины последовательности, потенциально достигая значительных размеров, например ~2 ГБ для модели Llama 2 7B с длиной последовательности 4 096 и размером партии 1.
Эффективное управление KV-кэшем сопряжено с трудностями, поскольку его объем линейно растет с размером партии и длиной последовательности, что может ограничивать пропускную способность и усложнять обработку длинноконтекстных входов. Чаще всего неэффективность возникает из-за статического перераспределения ресурсов, когда память резервируется для максимальной поддерживаемой длины последовательности (например, 2 048 лексем), независимо от фактического размера входных данных. Это приводит к значительным потерям памяти или фрагментации, поскольку большая часть зарезервированного пространства часто остается неиспользованной в течение всего времени выполнения запроса, занимая ценные ресурсы памяти GPU.
Для устранения этих недостатков алгоритм PagedAttention использует новый подход, вдохновленный подкачкой операционной системы. Он делит кэш KV на блоки фиксированного размера, каждый из которых представляет собой определенное количество маркеров, которые могут храниться в памяти несмежно. Таблица блоков отслеживает эти блоки, извлекая их по мере необходимости во время вычислений внимания. При появлении новых жетонов дополнительные блоки выделяются динамически. Этот метод минимизирует потери памяти, устраняя необходимость в смежном распределении и избыточном выделении, позволяет увеличить размер партии и повысить пропускную способность, что делает его значительным достижением в управлении кэш-памятью KV для LLM.
В этом разделе мы обсуждаем различные методы оптимизации больших языковых моделей (LLM) для снижения потребления памяти и повышения производительности на графических процессорах. Основные методы включают квантование, разреженность и дистилляцию, каждый из которых направлен на различные аспекты эффективности модели. Эти методы изменяют весовые коэффициенты моделей, используют аппаратное ускорение GPU и переносят знания в более мелкие модели, позволяя более крупным моделям работать на ограниченном оборудовании при сохранении производительности. Эти методы могут снижать точность модели, поэтому их следует использовать с осторожностью.
Квантование снижает точность весов и активаций модели, как правило, с 32 или 16 бит до 8 или менее бит, что позволяет моделям занимать меньше памяти и передавать данные более эффективно. В то время как квантование весов является простым делом из-за их фиксированной природы после обучения, квантование активаций является более сложным из-за выбросов, которые расширяют их динамический диапазон. Такие методы, как LLM.int8(), решают эту проблему путем выборочного применения более высокой точности к определенным активациям или повторного использования динамического диапазона квантованных весов для активаций, хотя для GPU может потребоваться обратное преобразование весов к более высокой точности для выполнения операций.
Разреженность предполагает обрезку значений модели, близких к нулю, что позволяет создавать разреженные матрицы, требующие меньше памяти. Графические процессоры поддерживают структурированную разреженность, например, представление двух из каждых четырех значений в виде нулей, что ускоряет вычисления. Сочетание разреженности с квантованием может еще больше увеличить скорость выполнения. Исследования оптимальных разреженных представлений для LLM продолжаются, что указывает на перспективность повышения скорости вычислений.
Дистилляция переносит знания из более крупной модели "учителя" в более мелкую модель "ученика", уменьшая размер при сохранении производительности. Например, DistilBERT достигает уменьшения размера на 40 % и увеличения скорости на 60 % по сравнению с BERT, сохраняя при этом 97 % своих возможностей. Дистилляция может включать в себя имитацию результатов работы учителя или использование данных, полученных от учителя, для обучения, а такие методы, как "Дистилляция шаг за шагом!", включают в себя обоснования для эффективного обучения. Однако ограничительные лицензии на многие передовые LLM ограничивают доступность подходящих моделей учителей для дистилляции.
Спекулятивный вывод, также известный как спекулятивная выборка или вспомогательная генерация, - это метод распараллеливания выполнения авторегрессионных моделей большого языка (LLM), таких как модели в стиле GPT, которые обычно генерируют текст токен за токеном. При стандартном выполнении каждая лексема зависит от контекста всех предыдущих лексем, что делает параллельную генерацию невозможной, поскольку n-я лексема должна быть сгенерирована до (n+1)-й. Спекулятивный вывод решает эту проблему, используя "более дешевую" черновую модель для предсказания нескольких будущих лексем одновременно, которые затем проверяются или отвергаются параллельно основной моделью, что позволяет быстрее генерировать текст.
Процесс включает в себя генерацию чернового продолжения нескольких лексем с помощью менее ресурсоемкого метода, а затем параллельную верификацию основной модели с использованием чернового варианта в качестве спекулятивного контекста. Если модель проверки совпадает с черновыми лексемами, они принимаются; в противном случае несовпадающие лексемы и последующие отбрасываются, и процесс повторяется с новой черновой моделью. Черновые лексемы могут генерироваться с использованием различных подходов, таких как обучение нескольких моделей, точная настройка нескольких голов на предварительно обученной модели для предсказания будущих лексем или использование меньшей черновой модели наряду с большей, более способной моделью проверки, каждая из которых имеет свои собственные компромиссы.
Дезагрегированный вывод - это метод, при котором вычислительные задачи распределяются по разным аппаратным средствам для оптимизации производительности, стоимости и использования ресурсов. В частности, разделяются этапы предварительного заполнения и декодирования. Дезагрегирование этих этапов позволяет распределить каждую из них по аппаратным средствам, наиболее подходящим для ее вычислительных требований, что повышает эффективность и масштабируемость.

Предварительное заполнение требует значительных вычислительных затрат, требуя значительных матричных умножений для обработки всей входной подсказки и создания кэша KV. На этом этапе выгодно использовать высокопроизводительное оборудование, например GPU или TPU, которые отлично справляются с параллельными вычислениями. Поскольку предварительное заполнение является одноразовой задачей для каждого запроса на вывод, ее можно переложить на централизованный мощный вычислительный узел, оптимизированный для таких рабочих нагрузок. Такая схема позволяет ускорить обработку больших запросов и снизить нагрузку на менее мощные устройства, что делает ее идеальной для облачных сред или центров обработки данных, где имеется высокопроизводительное оборудование.
Декодирование, напротив, связано с памятью и включает в себя итеративное генерирование маркеров, в значительной степени зависящее от доступа к кэшам KV. Оно требует меньшей вычислительной мощности, но требует быстрого доступа к памяти, что делает его подходящим для менее мощного, оптимизированного по памяти оборудования, такого как процессоры или пограничные устройства. Перенос декодирования на отдельное оборудование - потенциально более близкое к конечному пользователю, например, на локальные серверы или пограничные устройства - позволяет снизить задержки и требования к пропускной способности сети. Такое разделение позволяет гибко развертывать системы, в которых предварительное заполнение выполняется на высокопроизводительных облачных серверах, а декодирование - на локальных или пограничных устройствах, оптимизируя распределение ресурсов и обеспечивая эффективное масштабирование для таких приложений, как чат-боты реального времени или интерактивные системы искусственного интеллекта.
В последнее время было изобретено множество методов оптимизации выводов для улучшения производительности LLM.
Реализация этих техник требует глубокого понимания архитектуры LLM и используемого оборудования, поэтому, как правило, проще использовать существующий движок вывода, в котором уже реализованы эти техники, например vLLM, TensorRT-LLM, LMDeploy и т. д. Мы реализовали эти техники в нашем собственном механизме вывода в NLP Cloud и написали статью в блоге о механизмах вывода, если вы хотите развернуть свои собственные модели: вы можете прочитать его здесь.
Если вы не можете или не хотите самостоятельно развертывать собственные LLM, вы можете воспользоваться NLP Cloud и использовать быстрые генеративные модели ИИ в масштабе производства. Попробуйте быстрый вывод на NLP Cloud прямо сейчас!
Если у вас есть вопросы о механизмах вывода в целом, пожалуйста, не стесняйтесь спрашивать нас, мы всегда рады помочь!
Julien
Технический директор NLP Cloud