[Перевод] Как устроены LLM: разбираем на примере 3-уровневой абстракции

Чтобы оптимизировать производительность больших языковых моделей (LLM), необходимо понимать весь программный стек. Мне не удалось найти подробную статью, которая показывала бы этот стек как общую картину, поэтому я решил написать такую статью сам. Эта статья не является исчерпывающим обзором или набором наилучших практик. Здесь я в большей степени делюсь моими общими представлениями о том, как выглядит ландшафт современных LLM‑систем.
Во‑первых, любая система проектируется с расчётом на достижение конкретных целей с учётом заданных ограничений. В LLM‑системах наиболее принципиальные цели связаны с увеличением пропускной способности и минимизацией задержек. Два этих показателя необходимо оптимизировать в рамках ограничений, задаваемых тремя принципиальными факторами: вычислительные (скорость аппаратных операций и поддерживаемые типы), память (ёмкость и иерархия) и коммуникация (ширина передачи данных в памяти и сети, задержка и иерархия). Эти концепции отлично рассмотрены в книге Scaling Book от Google, а именно в разделе Roofline.
Объясняем 3-уровневую абстракцию
Чтобы сориентироваться в сложностях современных LLM‑систем, полезно рассматривать их в терминах именно этих трёх разных уровней абстрагирования. На каждом уровне мы занимаемся различными типами ограничений, там открываются разные возможности оптимизации. Формируется хороший аппарат для понимания и улучшения больших языковых моделей. Его мы далее и обсудим в деталях.
В этой таблице обобщены основные свойства трёх уровней абстрагирования.
Уровень абстрагирования | Операции | Примеры софта |
Ядро | Скалярные и векторные инструкции, инструкции тайлов | CUDA C/C++ & PTX, Triton |
Граф | Тензорные примитивы | PyTorch, JAX, TensorRT, ONNXRuntime |
Система | Шардирование, объединение в пакеты, выгрузка.. | TensorRT‑LLM, vLLM, Megatron‑LM, VeRL |
Следует отметить, что Triton и CUDA относятся к одной и той же категории, несмотря на то, что интерфейс Triton более высокоуровневый. Но и CUDA, и Triton фундаментально оптимизируют производительность на уровне компилятора и микроархитектуры. Аналогично, PyTorch и TensorRT обслуживают отличающиеся друг от друга практические случаи, но оба оперируют абстракциями на уровне тензоров и оптимизируют граф модели в целом.
Давайте разберём каждый из уровней в отдельности, начиная с самого нижнего уровня абстракции.
Уровень ядра
Основная задача уровня ядра — выполнение программ на уровне микроархитектуры. Ядро — это мельчайшая единица рабочей нагрузки, выполняемая на аппаратном ускорителе, в том числе, на GPU.
Модель программирования на этом уровне сопоставляет доступные аппаратные ресурсы (ядра общего назначения, блоки для перемножения матриц, ближнюю память процессора, память GPU) с программными сущностями (потоки и блоки, локальная память, глобальная память) и предоставляет пользователю инструкции, необходимые для операций над этими сущностями.
Язык программирования CUDA C/C++ де‑факто является стандартным для программирования ядер GPU. По мере того, как в последнее время программирование ядер становится всё более востребованным, на этом уровне появилось из чего выбирать. Наблюдаются два разных тренда, не являющихся взаимоисключающими:
1. Тайловые языки, например, Triton, CuTile и TileLang повышают детализацию управления от потока к блоку и детализацию данных от скаляра к тайлу. Пользователи обрабатывают логику лишь на уровне блока, тогда как упорядочивание данных внутри блоков делегируется компилятору. Такой подход даёт два ключевых преимущества: проще программировать (в особенности для инженеров, разрабатывающих системы машинного обучения и исследователей). Также упрощается поддержка программ и кроссплатформенная совместимость. Например, теперь именно компилятор, а не пользователи, решает, какие инструкции использовать — 128×32 MMA или 32×32 MMA. Так упрощается поддержка различных аппаратных платформ.
2. Фреймворки шаблонов, например, CUTLASS. Поскольку при программировании ядер важнее всего оптимизировать перемножение матриц, основную работу над этой задачей выполняет CUTLASS, а в распоряжении пользователя остаётся настраиваемый «периферийный» код. Тем не менее, пользователи сохраняют полный контроль над кодом.
Производительность измеряется по задержке (пропускная способность также сопоставляется с задержкой в зависимости от формы поступающей на вход информации). Большинство приёмов оптимизации можно подразделить на следующие категории:
1. Локальность данных. Пример: чтобы избежать перемещения данных, задействуем ближний к процессору регистр, кэш и разделяемую память.
2. Эффективность перемещения данных. Пример: во избежание конфликтов между банками памяти используем подмену метода (свиззлинг); перемежаем загрузку данных и вычисления.
3. Специальные инструкции. Пример: используем MMA TensorCore (аккумуляция перемножения матриц) и Hopper TMA (ускоритель тензорной памяти).
Также существуют специфичные оптимизации для разного аппаратного обеспечения или разных поколений аппаратных платформ. К большинству из этих устройств, считая NVIDIA GPU, в общем доступе нет подробной документации, в которой были бы разобраны низкоуровневые детали.
Иллюстрация: ядро GEMM, выполняемое на GPU, из примера с перемножением матриц на Triton MatMul
Здесь нужно отметить несколько ключевых моментов:
1. Что касается локальности выходных данных, это ядро применяет тайлинг на выходе, то есть, дробит рабочую нагрузку по выходным тайлам и распределяет сегментированные таким образом рабочие нагрузки (то есть, блоки) между имеющимися процессорами. В такой конфигурации аккумулятор можно держать локально, обходясь без чтения из главной памяти и без записи в неё.
2. Что касается локальности входных данных, поскольку каждый блок читает строку тайлов из входных данных 1 и столбец тайлов из входных данных 2, он задействует кэш L2 и запускает блоки «в сгруппированном порядке», чтобы переиспользовать поступающие на вход данные.
3. В пределах блоков арифметические операции описываются на языке тайлов, а Triton компилирует язык тайлов в аппаратные инструкции. В этом отличие от CUDA, где пользователи получают детализированный контроль над данными, но вместе с тем вынуждены отвечать за низкоуровневые ядра CUDA, TensorCore и разделяемую память. Конкретный размер MMA зависит от аппаратной части, и именно от него зависит программная часть тайла. Например, H100 GPU поддерживает 256×64×16 FP16 MMA, а TPU (до версии v6e) поддерживает 8×128×128 BF16 MMA.
Другой отличный пример — онлайновый softmax применительно к мгновенному вниманию (Flash Attention). Softmax, как правило, вычисляется по целой строке матрицы внимания, а для этого требуется перемещать значительный объём данных. Эта проблема решается при помощи онлайнового softmax путём преобразования глобальной формулы в рекуррентную. В такой рекуррентной формулировке не требуется никаких операций чтения из глобальной записи в неё.
Графовый уровень
На графовом уровне мы сосредоточимся на оптимизации графа модели, выполняемого на одном GPU. При программировании на этом уровне акцент делается на компонуемости и лёгкости модификации. Исторически компонуемость достигалась за счёт снижения производительности — именно поэтому ONNXRuntime, TensorRT, FasterTransformer использовались в серьёзных сценариях, связанных с инференсом. К настоящему времени PyTorch постепенно преодолел свою изначальную слабость и завоевал значительную долю на рынке инференса. Это связано с двумя факторами: удалось снизить издержки при работе с графами CUDA, torch.compile, так далее; увеличить рабочую нагрузку, передаваемую LLM, по сравнению с которой издержки на работу с фреймворками становятся миниатюрными.
При оптимизациях на уровне графов мы используем характеристики следующих друг за другом ядер и свойства, присущие моделям машинного обучения. Общая цель остаётся прежней: сократить коммуникацию и расход памяти, в то же время, повышая эффективность вычислений. В частности, есть следующие распространённые типы оптимизаций:
При объединении (merging) мы преобразуем множество тензорных операций в одну, которая математически эквивалентна им. Примеры такого рода — объединение Conv/BatchNorm и объединение Multi‑FC. Недавно появилась ещё одна техника объединения —поглощение весов MLA, позволяющая сэкономить память во время декодирования за счёт более эффективных вычислений.
Слияние (fusion) — ещё одна распространённая техника, которую часто путают с объединением. При слиянии мы комбинируем несколько ядер, не меняя математической формулировки. При слиянии обмен данными и вычисления происходят одновременно; так удаётся избежать обратной записи в главную память и сократить издержки на запуск ядра. Например, при операциях слияния Conv/ReLU мы выполняем ReLU прямо на месте вычислений, а не записываем данные в главную память, чтобы затем их оттуда прочитать. При инференсе на множестве GPU слияние GEMM/AllReduce иногда применяется для сокращения задержки. При этом частично готовые выходные данные синхронизируются сразу же по мере вычисления.

Граф выполнения оптимизированной модели Llama3. Каждый прямоугольник соответствует ядру, запущенному на GPU.
Объединение обозначается знаком &, а слияние — знаком +.
Квантование, также именуемое обучением с низкой точностью — это одна из наиболее распространённых техник при оптимизации систем машинного обучения и отличный пример коэволюции алгоритмов и аппаратного обеспечения. В эпоху CNN обычно вполне хватало 8-битного квантования. Во времена LLM требуется сильнее сжимать данные, поэтому и уровень квантования растёт. В последние пару лет мне доводилось инициализировать и разрабатывать оптимизатор модели TensorRT, и в рамках этого проекта мы коротко описали квантование. Вот некоторые общие наблюдения о современном состоянии квантования:
1. Этот процесс не ограничивается наиболее обычным квантованием весов и охватывает также активации, кэш ключей и значений, градиенты и коммуникацию.
2. При использовании инференса точность обучения, как правило, снижается.
Разреженность уже имеет богатую историю, корни её прослеживаются до статьи Яна Лекуна «Optimal Brain Damage» (1989). Впоследствии изложенные в ней идеи были переосмыслены на материале современных нейронных сетей, например, в работе Deep Compression. Ранние исследования были сосредоточены на статических подходах к разреженности, в том числе, на тонкой детализации при отсечении весов, отсечении каналов и паттернах разреженности 2:4. Как показывает практика, в эпоху LLM производительность существенно зависит от общего количества параметров, что серьёзно подогревает интерес к динамическим методам обеспечения разреженности, например, предварительное заполнение разреженными данными (prefill sparsity), сжатый кэш ключей/значений и динамическая загрузка ключей/значений.
Среди других распространённых оптимизаций GPU — графы CUDA и многопоточное выполнение. В графах CUDA предварительно записываются графы зависимостей ядра, что позволяет уменьшить задержку при запуске. В свою очередь, многопоточное выполнение накладывается на работу ядер, рассчитанных на малые рабочие нагрузки. Так удаётся задействовать GPU более качественное.
При проектировании моделей с учётом аппаратных ограничений платформы выполняются и другие оптимизации на уровне графов — об этом свидетельствует аппаратная лотерея. Среди ранних примеров такого рода — групповая свёртка (Group Convolution), призванная снизить вычислительную плотность свёртки. В эпоху LLM из‑за ограничения ширины у полосы передачи данных появились такие архитектурные инновации как смесь экспертов (MoE), группы внимания с собственными наборами ключей и значений (GQA), многоголовое латентное внимание (MLA) и модели в пространстве состояний (SSM). MoE можно концептуально описать как достигнутую в результате обучения динамическую разреженность, которая обладает заметным сходством с механизмами жёсткого внимания, если рассматривать экспертные веса как активированные токены.
Системный уровень
На этом уровне работаем с ресурсами, которые нужны модели, а также с ограничениями пода — минимальной воспроизводимой единицы развёртывания. Так, в официальной реализации DeepSeek V3 предусмотрены поды для инференса по 176 GPU каждый и учебный под (целый кластер) на 2048 GPU. На системном уровне мы не столько присматриваемся к деталям модели, сколько абстрагируем её как эластичную программу (движок) со своими потребностями по вычислениям, памяти и коммуникации.

Пример системы инференса, в которой используется упрощённая версия, представленная в этой статье
Начиная с этого уровня наблюдаются существенные расхождения между фреймворками для инференса и обучения. Во фреймворках для инференса, таких как vLLM, SGLang и TensorRT‑LLM, акцент делается на высокопроизводительном ядре, параллелизме, умном объединении запросов в пакеты, а также эффективном управлении ключами и значениями. Фреймворкам для обучения свойственны стремительные итерации, идущие вслед за развитием алгоритмов. Многие такие фреймворки служат опорой (скаффолдом) для реализации параллелизма (таковы, например, Megatron‑LM, DeepSpeed). В последнее время, когда пошло на подъём обучение с подкреплением, появились дополнительные связи между обучением и инференсом — например, в VeRL делается упор на бесшовную интеграцию с Megatron‑LM, vLLM и другими фреймворками.
Опять же, сосредоточимся на таких оптимизациях, которые позволяют лучше использовать системные вычисления, память и коммуникацию.
Параллелизм принципиально важен как для обучения, так и для инференса. Эта тема хорошо объясняется во многих источниках, например, в документации к NeMo и документации к DeepSpeed.
Почти все стратегии распараллеливания улучшают пропускную способность. Не все они уменьшают задержку. Выбор варианта параллелизма зависит от ограничений по памяти и коммуникации. Как правило, объём вычислений — это постоянная величина, за исключением таких изолированных случаев как параллелизм данных с полным шардированием (FSDP) и сравнением внимания при тензорном параллелизме и параллелизме данных. В следующей таблице обобщены наиболее распространённые стратегии распараллеливания.
Тип | Описание | За и против |
Параллелизм данных | Распараллеливание на уровне пакетов | Задержка не снижается. В процессе инференса коммуникация отсутствует, в процессе обучения коммуникация слабая. |
Параллелизм конвейеров | Распараллеливание на уровне пакетов и слоёв | Задержка не снижается. Слабая коммуникация как при инференсе, так и при обучении. Экономится память модели. |
Параллелизм тензоров | Распараллеливание слоёв FC на уровне строк и столбцов, внимание в головном измерении | Задержка снижается. Высокие расходы на коммуникацию. Экономится память модели. |
Экспертный параллелизм | MoE распараллеливается на уровне экспертов | Задержка в областях с большими пакетами снижается. Коммуникация обходится дешевле, чем при тензорном параллелизме, когда активированных экспертов меньше, чем рангов тензорного параллелизма. Экономится память модели. |
Параллелизм последовательностей | В измерении последовательностей распараллеливается нормализация слоёв или слои проецирования внимания | Дополняет тензорный параллелизм. Коммуникация обходится дёшево. |
Параллелизм контекста | Все слои распараллеливаются в измерении последовательностей | Задержка снижается при предварительной загрузке контекста длинными фрагментами. Коммуникация обходится дорого. |
ZeRO | Шардированная оптимизация состояний (стадия 1), градиентов (стадия 2) и весов (стадия 3) | В основном для обучения. Вычисления и коммуникация перемежаются, но в каждом шарде всё равно выполняется весь объём вычислений. |
FSDP | Веса шардов, градиенты и состояния оптимизатора | Почти идентично ZeRO, см. соответствие между FSDP и DeepSpeed. |
Если требуется подобрать правильную стратегию инференса при работе с не слишком большой моделью (<100 ГБ), то обычно принято вертикально масштабировать тензорный параллелизм вплоть до такого уровня, на котором задержка при коммуникации станет нетривиальной. Затем переходим к горизонтальному масштабированию, при котором отдельно распараллеливаются конвейеры и данные. При работе с сильно разреженными MoE‑моделями складывается ситуация, в которой экспертный параллелизм окажется более предпочтительным, чем тензорный, и выбрать правильную модель параллелизма для данного кластера становится сложнее. Вот почему появляются такие инструменты как NVIDIA Dynamo, помогающие оптимизировать стратегии параллелизма. Сложность также применима к параллелизму времени обучения; этот параметр обычно проектируется и настраивается вручную под конкретную архитектуру модели и конфигурации ЦОД.
Оптимизации систем инференса
При работе с системами инференса основная метрика, характеризующая пропускную способность — это общее количество токенов в секунду (TPS). Основные метрики для оценки задержки — время на получение выходного токена (TPOT) и время до первого токена (TTFT).
Сразу напрашивается такая оптимизация: избежать перевычисления, а значит, задействовать кэш ключей и значений, кэшировать префиксы, практиковать выгрузку и так далее. Кроме того, ключевая оптимизация пропускной способности — это объединение задач в пакеты (батчинг). В сущности, многие методы — просто варианты умного батчинга. При непрерывном батчинге агрегируются запросы переменной длины, работа растягивается между этапами предварительной загрузки и декодирования. С кэшем ключей и значений связаны такие оптимизации, как постраничное внимание. Благодаря такому подходу увеличивается максимально достижимый размер пакета в пределах имеющихся ограничений по памяти. Даже приёмы борьбы с задержками, например, спекулятивное декодирование, можно рассматривать как варианты батчинга.
На фундаментальном уровне всегда требуется искать компромисс между задержкой и пропускной способностью, и этот компромисс зависит от выбранных вами вариантов параллелизма. Как правило, чтобы уменьшить время на генерацию токена, непременно требуется уменьшить количество токенов, генерируемых конкурентно. Данная взаимосвязь бмла проиллюстрирована на конференции GTC 2025 в виде кривой компромисса. Так получилось, что сразу несколько друзей пожаловались мне, что эта иллюстрация контринтуитивна..

Среди продвинутых оптимизаций, позволяющих вырваться за пределы данной кривой — в частности, дизагрегация предварительной загрузки и декодирования и спекулятивное декодирование. Такую дизагрегацию уже некоторое время задействуют в продакшен‑системах, обслуживающих большие языковые модели, а в настоящее время она поддерживается в различных экосистемах ещё шире благодаря таким фреймворкам, как SGLang и TensorRT‑LLM. Спекулятивное декодирование — отличный пример совместного проектирования системы и алгоритмов.
Оптимизации систем обучения
Если сравнить системы для обучения с системами для инференса, то дл первой категории характерны иные ограничения: требования к задержке (обусловленные требованиями к пропускной способности) не столь строги, но приходится иметь дело со стремительным развитием методологий и архитектур моделей. Часто приходится выполнять быстрые итерации — на этапах, когда в работу вводятся новые архитектуры или новые приёмы обучения. Мне приходилось убедиться на собственном опыте, что системы для обучения бывают оптимизированы хуже, чем системы для инференса (всё‑таки, из этого правила есть исключения, например, DeepSeek).
Традиционно основной акцент делается на предобучении (пожалуй, это актуально и сейчас). Когда приходится работать в масштабах целого флота GPU, ориентируясь, прежде всего, на пропускную способность, основным показателем становится коэффициент использования FLOPS‑модели (MFU). Как мы уже обсудили, оптимизация коренным образом зависит от распараллеливания.
В масштабах предобучения важнейшим фактором для поддержания высокого MFU (не считая подбора стратегий распараллеливания) является устойчивость системы. К ключевым методам такого рода относится обеспечение отказоустойчивости и асинхронная расстановка меток — среди распространённых решений такого рода можно назвать NVIDIA Resiliency Extension.
Оптимизация постобучения исторически считалась менее важной, поскольку по вычислительному масштабу эта задача уступает постобучению. Но благодаря быстрому развитию обучения с подкреплением (RL) ситуация в этой сфере принципиально изменилась. Обучение с подкреплением привносит новые вызовы, возникающие на уровне всей системы и далеко не ограничивающиеся традиционными вопросами, связанными с параллелизмом. Я называю это явление проблемой параллелизма и размещения, она подробно обсуждается в HybridFlow:
1. Зависимость от множества моделей: взаимозависимости между множественными моделями (актор, критик, вознаграждающая модель, так далее) порождает фундаментальную дилемму. Приходится либо располагать модели поближе друг к другу (колокация) за счёт драгоценной памяти GPU, либо распределять их по разным GPU за счёт простоев во время вычислений.
2. Неоднородность рабочих нагрузок: обучение и выкатывание (генерация) — это фундаментально разные паттерны вычислений. Для достижения оптимальной производительности зачастую требуется предусмотреть выделенные фреймворки для инференса, и при этом периодически перешардировать веса между итерациями.
Обучение с подкреплением на основе верифицируемых вознаграждений (RLVR) привносит дополнительные сложности. Кластеры GPU, оптимизированные для предобучения, зачастую характеризуются высокой пропускной способностью при передаче данных по сети, но относительно скромной мощностью ЦП. Когда поступающие извне сигналы вознаграждения требуют выполнять задачи именно на ЦП, а не на видеокартах, в конвейере обучения (в неожиданных точках) могут возникать узкие места.
Тогда как при проектировании систем для обучения с подкреплением учитываются как фреймворки для инференса, так и фреймворки для обучения, фундаментальная проблема лежит в плоскости выделения ресурсов в пределах ограничений, задаваемых параметрами ЦОД. Таким образом, она укладывается в уже выделенный нами системный уровень абстрагирования. Заглядывая в будущее, было бы интересно посмотреть, смогут ли такие компании как OpenAI или робототехнические фирмы успешно состыковать цикл обучения с внешними средами — Интернетом, мирами симуляций или физической реальностью.
Исключения из трёхуровневой абстракции
Притом, что трёхуровневая абстракция предоставляет полезный каркас, в рамках которого удобно понимать большинство LLM‑систем, существуют архитектуры и методы, не вписывающиеся в эти границы.
Архитектуры потоков данных и мегаядра. В некоторых специализированных интегральных схемах (напр., SambaNova, Cerebras, Groq) используются дата‑центричные модели программирования, в которых операции выполняются сразу же после того, как станет доступна часть данных. В рамках таких моделей в состав «ядра» может входить целый блок трансформеров или даже полноценная модель (см. презентацию по SambaNova на конференции HotChips 2024). При таком подходе к проектированию удаётся существенно снизить задержку за счёт усложнения самого программирования. Использование SRAM‑памяти на кристалле, обладающей высокой пропускной способностью — отдельная тема, которую лучше рассмотреть в другой статье. Недавно в сообществе GPU стали исследовать и схожие концепции, например, MegaKernel, призванные исключить простои GPU путём массового слияния ядер.
Спекулятивное декодирование. Спекулятивное декодирование — изящная техника, корни которой связаны с наблюдениями, сделанными при глубоком анализе систем (например, в пакетном режиме задачи выполняются быстрее, чем в последовательном). Инженерная сторона этого процесса хорошо проработана на уровне машинного обучения (черновая модель/голова) и статистики (выборка с отклонением), поэтому может идеально подходить под исходное распределение модели. Спекулятивное декодирование демонстрирует силу сквозной оптимизации стека, поэтому при классификации её сложно отнести к какому‑то конкретному уровню оптимизации. Не сомневаюсь, что, если в ближайшие десять лет трансформерные архитектуры станут преобладающими, то спекулятивное декодирование станет их краеугольным камнем, подобно спекулятивному выполнению в современных ЦП.
В заключение
Проектирование систем основано на динамической базе, в которой постоянно развиваются оптимальные компромиссы, учитывающие физические ограничения, архитектуру модели и приложения. Решения, которые сегодня кажутся оптимальными, завтра могут превратиться в узкие места. До появления LLM большинство рабочих нагрузок для машинного обучения можно было вполне точно описать в виде 2-уровневой абстракции (ядро и графовые слои). В настоящее время, с возникновением новых парадигм обучения и экосистем приложений может устареть и предложенная здесь 3-уровневая структура, её потребуется расширить дополнительными программными слоями.
Благодарности: спасибо за ценные замечания к этой статье, высказанные Жюльеном Дему, Сунь Ханем и Чжунь Янем.
Дополнительные ссылки
1. Semi‑analysis: стена памяти: https://semianalysis.com/2024/09/03/the‑memory‑wall/
2. Thunderkitten: двумерная плитка, одномерный вектор: https://hazyresearch.stanford.edu/blog/2024-05-12-tk
3. Выступление по CUDA на конференции GTC 2025: https://www.nvidia.com/en‑us/on‑demand/session/gtc25-s72383/
4. Книга о масштабировании: https://jax‑ml.github.io/scaling‑book/
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.