Слой под стеком обслуживания. Любая операция модели в конечном счёте выполняется как ядро GPU, и качество ядер определяет, какая доля оплаченного оборудования действительно используется. Писать ядра самостоятельно приходится редко, но чтение этого слоя объясняет, почему системы над ним ведут себя именно так.
Как GPU выполняет работу
Модель исполнения
GPU выполняют тысячи потоков синхронными группами (варпами), собранными в блоки и назначенными на потоковые мультипроцессоры. Потоки одного варпа делят общий поток инструкций, поэтому расходящиеся ветвления сериализуются и тратят линии впустую. Задержку памяти скрывает именно достаточное число резидентных варпов на каждом SM (occupancy).
Иерархия памяти
Сначала регистры, затем небольшая быстрая разделяемая память и кэши на кристалле, затем крупная HBM вне кристалла. Разрыв в пропускной способности между памятью на кристалле и вне его составляет порядки, поэтому производительность ядра это в основном вопрос того, насколько редко данные пересекают эту границу и насколько хорошо объединяются обращения.
Тензорные ядра
Выделенные блоки матричного умножения с накоплением дают большую часть FLOPS современного GPU при пониженной точности (FP16/BF16, FP8, INT8). Ядра, которые не загружают тензорные ядра или используют отсутствующую в оборудовании точность, оставляют большую часть кристалла простаивать.
Ограничение вычислениями или памятью
Каждое ядро упирается либо в арифметическую пропускную способность, либо в пропускную способность памяти, смотря что закончится раньше. Prefill у LLM нагружает вычисления; decode нагружает пропускную способность. Понимание того, по какую сторону этой черты находится операция, подсказывает, поможет ли больше FLOPS или меньше перемещений данных.
Что на самом деле делает оптимизация ядер
Слияние: объединить соседние операции (matmul + смещение + активация, подшаги внимания) в одно ядро, чтобы промежуточные результаты оставались на кристалле, а не ходили туда-обратно через HBM, и заодно исчезают накладные расходы на запуск каждого ядра.
Тайлинг: обрабатывать данные блоками под размер разделяемой памяти, чтобы каждый загруженный тайл переиспользовался много раз до вытеснения.
Раскладка и точность: располагать тензоры так, чтобы обращения объединялись, и выбирать точность, которую тензорные ядра исполняют нативно.
Горячие места у LLM: преобладают ядра матричного умножения, внимания, нормализации и активации; движок обслуживания в первом приближении это планировщик вокруг горстки таких ядер.
Работа над ядрами дополняет системный уровень: continuous batching или prefix caching меняют то, какая работа выполняется, а ядра меняют то, насколько быстро выполняется каждая её единица. О первом см. оптимизация обслуживания.
FlashAttention, канонический пример
Стандартное внимание материализует матрицу оценок размера N x N в HBM, поэтому трафик памяти растёт квадратично с длиной последовательности, и операция становится ограниченной пропускной способностью. FlashAttention перестраивает вычисление: он прогоняет тайлы матриц запросов, ключей и значений через память на кристалле, наращивает нормализацию softmax по мере поступления тайлов и вовсе не записывает полную матрицу оценок. Математический результат тот же, а трафик памяти составляет малую долю прежнего, и именно это сделало обслуживание длинного контекста практичным.
Последующие версии перенастраивались под каждое поколение оборудования (лучшее разбиение работы, асинхронные конвейеры тензорных ядер, пути с пониженной точностью), и сегодня эта техника поставляется внутри PyTorch и всех крупных движков обслуживания. Урок обобщается: самые крупные выигрыши на ядрах дают перестройка вычислений вокруг иерархии памяти, а не микронастройка инструкций.
Лестница инструментов
Упорядочено по нарастанию контроля и стоимости. Начинайте сверху; спускайтесь только тогда, когда профилирование доказало, что уровня выше недостаточно.
1. Библиотеки производителяcuBLAS, cuDNN и подобные: стандартные операции, предварительно настроенные под каждую архитектуру. Отлично работают в своих рамках; межоперационного слияния нет, и всё это привязано к NVIDIA.
2. Компиляторыtorch.compile, XLA, TVM: берут граф модели и автоматически порождают слитые, настроенные ядра. Большой выигрыш при малых усилиях на стандартных архитектурах; необычные операции могут выпасть из быстрого пути.
3. DSL для ядерTriton и подобные: писать собственные ядра на похожем на Python языке уровня тайлов, пока компилятор берёт на себя низкоуровневые детали. Удачная середина для нестандартных вариантов внимания и слитых операций без полноценной экспертизы в CUDA.
4. Ядра, написанные вручнуюCUDA (или ROCm) с явным управлением памятью и варпами, ровно так устроен и сам FlashAttention. Максимальная производительность, высокая экспертиза, реальная нагрузка на сопровождение, привязка к оборудованию.
5. Стеки переносимостиБолее новые экосистемы (например, Mojo/MAX и другие работы на базе MLIR) стремятся к единой кодовой базе ядер для разных производителей; взвесьте их зрелость против той привязки, которую они снимают.
Что учесть при выборе: насколько стандартны ваши операторы, сколько стоит предельная производительность, какое оборудование нужно поддерживать и в чём сильна ваша команда, в компиляторах или в ядрах. Сначала профилируйте; узкое место часто оказывается не там, куда указывает интуиция.