Если вы когда-нибудь наблюдали, как LLM генерирует длинный ответ, и задавались вопросом, почему после мгновенной реакции на промпт скорость генерации падает, вы в реальном времени наблюдаете аппаратное «узкое место». Большинство разработчиков винят свой код на Python, фреймворк или огромный размер модели. Они профилируют функции, меняют оптимизаторы и выкраивают миллисекунды на предобработке. Ничего из этого не решает реальную проблему. Ограничение скорости — не в вашем программном обеспечении. Оно в кремнии.
Каждая задача инференса большой языковой модели зависит от двух физических характеристик GPU, установленного в вашем сервере: того, насколько быстро он может производить вычисления, и того, насколько быстро он может перемещать данные в нужное место для этих вычислений.
Математика — это дешево. Перемещение данных — нет
Маркетологи GPU обожают говорить о вычислительной мощности. Триллионы операций с плавающей запятой в секунду. Цифры поражают воображение. Но вычисления — это лишь половина истории. Вторая половина — это пропускная способность памяти, то есть скорость, с которой данные перемещаются из высокоскоростной памяти в вычислительные ядра, где происходит сама арифметика.
LLM не может работать быстрее, чем позволяет самое слабое звено из этих двух. Представьте себе профессиональную кухню с двадцатью шеф-поварами. Духовки разогреты, ножи наточены, и каждый повар готов к работе. Но продукты доставляют на велосипеде, по одной корзине за раз. Работа кухни замирает. Наем дополнительных поваров это не исправит. Покупка более быстрых духовок тоже не поможет. «Узкое место» — это дорога.
В современных GPU для дата-центров арифметические блоки настолько мощные, что они часто завершают вычисления и простаивают, тратя циклы в ожидании, пока веса и активации будут поступать из памяти. Этот дисбаланс — не баг в вашем коде. Это физическая реальность архитектуры чипов. Пропускная способность памяти не поспевает за чистой вычислительной мощностью, и LLM особенно чувствительны к этому дисбалансу, так как их прямой проход (forward pass) требует обращения к каждому параметру для каждого выходного токена.
Почему промпты обрабатываются быстро, а генерация — медленно
Инференс LLM делится на две отдельные фазы, и они нагружают оборудование совершенно по-разному.
Prefill (префилл) происходит, когда ваш промпт впервые попадает в модель. Все токены поступают одновременно. GPU может обрабатывать их параллельно, используя большие матрично-матричные умножения. Тысячи арифметических блоков работают одновременно, и нагрузка остается высокой. Эта фаза ограничена вычислительной мощностью (compute-bound). Тот внезапный всплеск скорости, который вы видите в начале? Это GPU делает именно то, для чего он был создан.
Decode (декодирование) — вот где начинаются проблемы. Когда модель генерирует следующий токен, она делает это по одному токену за раз. Этот этап опирается на матрично-векторные операции, которые используют лишь крошечную долю параллельной мощности GPU. Хуже того, каждый новый токен заставляет GPU заново загружать все веса модели из памяти. Арифметические блоки хотят работать, но вместо этого они ждут. Декодирование ограничено пропускной способностью памяти (memory-bound). GPU фактически работает как дорогостоящий регулировщик, перебрасывая параметры туда-сюда через шину памяти, пока математические движки «остывают». Вот почему ответ из ста слов может занять десять секунд, хотя анализ начального промпта показался мгновенным.
KV-кеш делает ситуацию еще интереснее. Во время декодирования модель сохраняет тензоры ключей (key) и значений (value) для каждого предыдущего токена, чтобы не пересчитывать механизм внимания (attention) с нуля. Этот кеш растет вместе с длиной последовательности. Он также находится в памяти. Таким образом, GPU не просто перезагружает веса, он считывает и записывает постоянно расширяющийся кеш при каждом прямом проходе. Вычислительные ядра почти не напрягаются, в то время как шина памяти работает на износ за них обоих.
Борьба со «стеной памяти»
Инженеры разработали целый арсенал методов, позволяющих уменьшить объем перемещаемых данных или, по крайней мере, распределить затраты на их перемещение.
Batching (пакетирование) — самый простой способ. Если запрос одного пользователя заставляет полностью загружать веса из памяти, то обработка восьми или шестнадцати запросов одновременно позволяет GPU распределить эту нагрузку между всеми ними. Веса считываются один раз и используются для каждой последовательности в пакете. В промышленной эксплуатации сложные системы планирования динамически группируют запросы (иногда это называют continuous или in-flight batching), чтобы GPU редко простаивал. Это разница между одним автобусом и шестнадцатью отдельными автомобилями на одном и том же маршруте.
Квантование напрямую решает проблему пропускной способности. Веса моделей обычно хранятся в форматах с плавающей запятой разрядностью 16 бит. Сжимая их до восьмибитных или даже четырехбитных целых чисел, вы буквально сокращаете объем данных, передаваемых по шине, вдвое или более. Модели все еще требуется достаточная точность для генерации связного вывода, но современные методы пост-тренировочного квантования позволяют значительно уменьшить объем занимаемой моделью памяти без потери качества. Меньше передаваемых данных означает меньше времени ожидания на контроллере памяти.
FlashAttention перестраивает механизм внимания так, чтобы промежуточные результаты оставались внутри быстрой встроенной памяти GPU. Стандартный механизм внимания должен был записывать большие матрицы внимания во внешнюю медленную память, а затем считывать их обратно. FlashAttention разбивает вычисления на более мелкие блоки (tiles), которые помещаются в SRAM, выполняет операции softmax и масштабирования непосредственно на чипе и записывает обратно в высокоскоростную память только конечные результаты. Он обменивает небольшое увеличение вычислительной нагрузки на гораздо меньшее количество обращений к основной памяти, что почти всегда является выгодной стратегией.
PagedAttention решает другую проблему неэффективного использования памяти. Во время декодирования KV-кэш растет непредсказуемо. Традиционные системы выделяют фиксированные непрерывные блоки памяти для каждой последовательности, что приводит к появлению больших «дыр», когда одни последовательности заканчиваются раньше, а другие разрастаются. PagedAttention заимствует концепцию виртуальной памяти из операционных систем. Он хранит записи KV-кэша в блоках фиксированного размера, которые могут выделяться не непрерывно и сопоставляться через таблицу косвенной адресации. Это предотвращает простой памяти внутри зарезервированных, но полупустых буферов и позволяет увеличивать размер батчей, что, в свою очередь, повышает общую пропускную способность, заставляя шину памяти выполнять полезную работу вместо борьбы с накладными расходами на фрагментацию.
Смените вопрос
Когда задержки (latency) резко возрастают, слишком многие команды задаются вопросом, стоит ли им перейти на модель поменьше или переписать свой сервер инференса. Эти вопросы важны, но они вторичны. Первым делом нужно спросить о самом железе. Ваша GPU действительно занята вычислениями или она «голодает» из-за нехватки данных?
Изучите метрики утилизации. Проанализируйте насыщение пропускной способности памяти наряду с загрузкой вычислительных ядер GPU. Если во время декодирования вы видите высокую конкуренцию за память (memory contention) и низкую арифметическую интенсивность, то у вас проблема не в архитектуре модели. У вас проблема в физике. Решение не в более чистом коде на Python. Оно заключается в более агрессивном батчинге, квантовании весов для более быстрого прохождения через «трубу», реструктуризации механизма внимания для работы на чипе и управлении KV-кэшем, чтобы можно было уместить более крупные батчи, не исчерпав память.
Как только вы начнете смотреть на инференс под этим углом, оптимизация станет механическим процессом. Вы перестанете гнаться за мифами о том, что «интеллект модели замедляет процесс», и начнете принимать инженерные решения, основанные на реальных возможностях оборудования. Именно этот сдвиг в мышлении отличает масштабируемые продакшн-системы от тех, что просто работают.
