FinCodeCow · Обзор AI-инфраструктуры

Настройка занятости, эффективность варпов и параллелизм на уровне инструкций

Обзор восьмой главы книги Chris Fregly «AI Systems Performance Engineering» (O'Reilly, 2026) — с изложением содержания, проверкой прогнозов по актуальным данным и систематизацией ключевых положений
Об источнике

Настоящий материал представляет собой обзор восьмой главы книги Chris Fregly, «AI Systems Performance Engineering: Optimizing Model Training and Inference Workloads with GPUs, CUDA, and PyTorch», O'Reilly Media, 2026 (Copyright © 2026 Flux Capacitor, LLC).

Прямые цитаты из книги выделены отдельными блоками с пометкой «Цитата из книги». Остальной текст — систематизированное изложение содержания главы, сопоставление с актуальными данными, проверка прогнозов по открытым источникам, а также дополнительные замечания, выходящие за пределы текста главы. Цитаты приводятся в ограниченном объёме в целях обзора и критического анализа.

Блок 01 · Контекст

Контекст главы

Восьмая глава книги Fregly посвящена продвинутым методам сокрытия задержек и повышения пропускной способности GPU-ядер. Она продолжает предыдущие главы, где были разобраны базовые оптимизации памяти, и переходит к систематическому анализу того, как профилировать, диагностировать и устранять узкие места, связанные с занятостью, эффективностью варпов и параллелизмом на уровне инструкций.

Цитата из книги · Глава 8

«Современные GPU-ускоренные нагрузки доводят аппаратное обеспечение до пределов. Многокристальные GPU, подобные Blackwell, соединяют несколько reticle-limited кристаллов через 10 ТБ/с NV-HBI и увеличивают L2 до 126 МБ. Эти архитектурные решения материально меняют компромиссы между памятью и вычислениями, а также точки оптимума занятости. Это делает профилирование и оптимизацию ещё более критичными, чем когда-либо».

Изложение содержания

В главе формулируется ключевой тезис: производительность GPU-ядра определяется не только количеством запущенных потоков, но и тем, насколько эффективно эти потоки используют доступные ресурсы. Глава разбивает проблему на три взаимодополняющих направления: настройка занятости (occupancy tuning), повышение эффективности варпов (warp efficiency) и раскрытие параллелизма на уровне инструкций (ILP).

Отдельно подчёркивается, что эти три направления не заменяют, а дополняют друг друга. Высокая занятость без ILP даёт много варпов, которые ждут память. Высокий ILP без занятости не может скрыть задержки, потому что мало варпов. Эффективные варпы без достаточного количества инструкций в полёте не могут заполнить конвейеры. Только совместное применение всех трёх подходов позволяет приблизиться к пиковой производительности.

Итоги блока 01
Что важно понять

Глава 8 — это систематический разбор трёх взаимодополняющих направлений оптимизации GPU-ядер: занятость, эффективность варпов, ILP. Каждое направление решает свою задачу и не заменяет остальные.

Ключевой тезис

Архитектура Blackwell с 126 МБ L2 и 10 ТБ/с NV-HBI сдвигает баланс между памятью и вычислениями. Методики, работавшие на Ampere и Hopper, требуют пересмотра.

• • •
Блок 02 · Профилирование

Профилирование как отправная точка

Цитата из книги · Глава 8

«Прежде чем оптимизировать, мы должны сначала идентифицировать узкие места в нашем коде, чтобы определить, какой аппаратный или программный ресурс ограничивает нашу производительность. Современные GPU NVIDIA сложны, и замедление может происходить из многих источников: пропускная способность памяти, задержка памяти, пропускная способность инструкций, накладные расходы на синхронизацию, недостаточный параллелизм, задержки передачи данных между хостом и устройством и многое другое».

Изложение содержания

Глава описывает двухэтапный подход к профилированию. На первом этапе используется Nsight Systems (nsys) для получения системной временной шкалы: потоки CPU, запуски ядер GPU, передачи данных, трассировки Python. Это позволяет найти горячие ядра и понять общую картину. На втором этапе используется Nsight Compute (ncu) для детального анализа каждого горячего ядра: достигнутая занятость, причины остановок варпов, пропускная способность памяти, загрузка исполнительных блоков.

Отдельно подчёркивается важность аннотаций NVTX (NVIDIA Tools Extension). Они позволяют размечать фазы выполнения (forward, backward, optimizer step) и делают временную шкалу читаемой. В PyTorch они доступны через torch.cuda.nvtx, а также через torch.profiler.record_function().

Приводится типичный сценарий: на временной шкале Nsight Systems видны промежутки между запусками ядер, а GPU простаивает. Это не всегда означает проблему в CUDA-коде — часто причина в DataLoader: слишком мало num_workers, отсутствие двойной буферизации, предобработка на CPU вместо GPU.

Схема 1. Интерактивный интерфейс Nsight Systems

Единая временная шкала: потоки CPU, ядра GPU, диапазоны NVTX и transfer-движки

CPU
GPU
NVTX
NET
Описание: Визуальная временная шкала показывает, как CPU, GPU, NVTX-аннотации и сетевые операции соотносятся друг с другом. Широкие «дыры» на GPU-дорожке — сигнал к проверке конвейера данных. Интерактивный интерфейс позволяет масштабировать и детализировать интересующие участки.

Схема 2. Chrome Tracing / Perfetto

Экспорт trace из PyTorch profiler: op-уровень, kernels, memcpy

aten
matmul
add
relu
softmax
kernel
sgemm
pointwise
reduce
memcpy
H2D
D2H
Описание: Профайлер PyTorch позволяет увидеть, где op-уровень расходится с реальными kernel-вызовами. Часто одно «логическое» действие порождает несколько ядер и memcpy. Perfetto обеспечивает веб-интерфейс для анализа больших трассировок с фильтрацией и SQL-запросами.
Типичная ошибка

Если на временной шкале Nsight Systems видны промежутки между запусками ядер, а GPU простаивает — проблема не всегда в CUDA-коде. Часто причина в DataLoader: увеличьте num_workers, включите двойную буферизацию, перенесите предобработку на GPU.

Итоги блока 02
Что важно понять

Профилирование — двухэтапный процесс: Nsight Systems для общей картины и поиска горячих ядер, Nsight Compute для детального анализа. NVTX-аннотации делают временную шкалу читаемой.

Ключевой инструмент

PyTorch profiler (на базе Kineto) даёт op-уровневую разбивку и поддерживает экспорт в Chrome Tracing / Perfetto для совместного анализа.

Практическое следствие

Прежде чем оптимизировать ядро, убедитесь, что проблема именно в нём, а не в конвейере данных. Простаивающий GPU часто означает неэффективный DataLoader, а не плохое ядро.

• • •
Блок 03 · Режимы

Четыре режима узких мест

Цитата из книги · Глава 8

«GPU могут останавливаться по четырём принципиально разным причинам: недоиспользование, ограничение задержкой, ограничение памятью и ограничение вычислениями. Эти режимы связаны, но важно понимать каждый из них независимо, чтобы выбирать правильные оптимизации. Часто исправление одного узкого места вскрывает следующее».

Изложение содержания

Глава вводит четыре режима, в которых может находиться GPU-ядро. Понимание того, в каком режиме работает конкретное ядро, определяет стратегию оптимизации.

  • Недоиспользование. И FLOPS, и пропускная способность памяти низкие. Причина — недостаточный объём работы для заполнения всех SM. Решение: увеличить размер сетки, размер пакета, объединить маленькие ядра, использовать CUDA Graphs.
  • Ограничение задержкой. Пропускная способность памяти значительно ниже пика, но варпы часто останавливаются в ожидании данных. Доминируют остановки Long Scoreboard и Not Selected. Решение: повысить занятость, увеличить ILP, внедрить асинхронную предвыборку.
  • Ограничение памятью. Пропускная способность памяти близка к пику (>80%), FLOPS низкие. Решение: повысить арифметическую интенсивность — тайлинг с разделяемой памятью, слияние ядер, улучшение объединения доступов.
  • Ограничение вычислениями. FLOPS близки к пику, ALU или Tensor Cores насыщены. Решение: переход на пониженную точность (FP16 → FP8 → FP4), Tensor Cores, разрежённость, увеличение ILP.

Подчёркивается, что при итеративной оптимизации ядро проходит через режимы последовательно: недоиспользование → ограничение задержкой → ограничение памятью → ограничение вычислениями. Каждое исправление вскрывает следующее узкое место — это нормальный процесс.

Схема 3. Roofline-диаграмма

Memory roof (наклонная) и Compute roof (горизонтальная). Точка ядра показывает: memory-bound или compute-bound

Arithmetic Intensity (FLOPs/byte) → FLOPS → Memory Roof Compute Roof Ridge ~10 FLOPs/B
Описание: Roofline-диаграмма — первое, на что стоит смотреть при профилировании. Точка ядра ниже наклонной линии означает memory-bound, ниже горизонтальной — compute-bound. Nsight Compute строит эту диаграмму автоматически на основе собранных метрик.
Типичная эволюция

При итеративной оптимизации ядро проходит через режимы последовательно: недоиспользование → ограничение задержкой → ограничение памятью → ограничение вычислениями. Каждое исправление вскрывает следующее узкое место — это нормальный процесс оптимизации.

Итоги блока 03
Что важно понять

Каждое GPU-ядро находится в одном из четырёх режимов. Определение режима — первый шаг к правильной стратегии оптимизации.

Метод определения

Roofline-диаграмма в Nsight Compute: положение точки ядра относительно наклонной (memory) и горизонтальной (compute) линий. Дополнительно — анализ причин остановок варпов.

Практическое следствие

Не оптимизируйте вслепую. Сначала определите режим. Для memory-bound — тайлинг и слияние. Для latency-bound — занятость и ILP. Для compute-bound — пониженная точность и Tensor Cores.

• • •
Блок 04 · Остановки

Причины остановок варпов

Цитата из книги · Глава 8

«Одним из наиболее информативных представлений в Nsight Compute является разбивка статистики состояния варпов (Warp State Statistics) для ядра. Это иногда называют причинами остановок варпов. Анализируя эти причины, вы часто можете определить ограничивающий фактор».

Изложение содержания

Глава систематизирует основные категории остановок варпов и их интерпретацию. Ключевые категории:

  • Long Scoreboard. Варп ожидает загрузки из глобальной DRAM. Это самая дорогая остановка — сотни циклов ожидания. Причина — высокая задержка доступа к HBM. Скрывается только параллелизмом других варпов или ILP.
  • Short Scoreboard. Ожидание обмена между shared memory и регистрами. Задержка ~20–30 циклов. Частые конфликты банков могут накапливаться.
  • Memory Throttle. Конвейеры загрузки/сохранения насыщены. Указывает на memory-bound режим.
  • Not Selected. Варп готов, но ждёт свободного слота. Обычно не проблема при здоровой занятости.
  • Exec Dependency. Варп ждёт результатов предыдущих инструкций. Прямой признак недостаточного ILP.
  • Compute Unit Busy. Математические блоки насыщены — режим ограничения вычислениями.
  • Texture Throttle, Memory Dependency, No Eligible и другие — менее частые категории.

Отдельно разбирается пример с Exec Dependency: код float u = x * x; float temp = u + 1.0f; float v = y * y; float sum = temp + v; создаёт ложную цепочку зависимостей, из-за которой умножение y * y не может быть выдано, пока не завершится u + 1.0f. Исправление — перестановка независимых операций подряд.

ПричинаЗначениеОптимизация
Long ScoreboardВарп ожидает загрузки из глобальной DRAMУвеличить занятость, асинхронная предвыборка, TMA
Short ScoreboardОжидание обмена между shared memory и регистрамиОптимизировать шаблоны доступа к shared memory
Memory ThrottleКонвейеры загрузки/сохранения насыщеныУменьшить трафик, улучшить объединение
Exec DependencyОжидание результатов предыдущих инструкцийПерестановка независимых операций, увеличение ILP
Compute Unit BusyМатематические блоки насыщеныПониженная точность, Tensor Cores
Not SelectedВарп готов, но ждёт свободного слотаОбычно не проблема при здоровой занятости

Схема 4. Остановка Long Scoreboard

Варп ждёт данные из глобальной памяти (DRAM/HBM). Высокая latency → scoreboard «long»

GLOBAL MEMORY · HBM3e
Остановка: Long Scoreboard → сотни циклов ожидания
Описание: Long Scoreboard — самый дорогой вид остановки. Варп не может выполнить ни одну инструкцию, пока не вернётся запрошенная строка кэша из DRAM. Скрывается только параллелизмом других варпов или ILP.

Схема 5. Остановка Short Scoreboard

Варп ждёт короткие по latency операции: разделяемая память ↔ регистры

↓↑
Остановка: Short Scoreboard → ~20–30 циклов
Описание: Short Scoreboard означает ожидание операций с малой задержкой — обращений к shared memory или bank conflicts. Хотя задержка меньше, чем у DRAM, частые конфликты банков могут накапливаться.
Итоги блока 04
Что важно понять

Nsight Compute даёт разбивку причин остановок варпов. Long Scoreboard → память, Exec Dependency → ILP, Compute Unit Busy → вычисления.

Ключевой пример

Ложные цепочки зависимостей (как в примере с u = x*x; temp = u+1; v = y*y;) создают Exec Dependency. Исправление — перестановка независимых операций.

Практическое следствие

Не запоминайте все причины. Смотрите на доминирующую категорию: memory-related → оптимизация памяти; Exec Dependency → ILP; Compute Unit Busy → пониженная точность.

• • •
Блок 05 · Занятость

Проверка достигнутой занятости

Цитата из книги · Глава 8

«Занятость — это отношение активных варпов на SM к максимуму (64 варпа / 2048 потоков на SM для Blackwell B200). Низкая достигнутая занятость — индикатор недоиспользования, но высокая занятость не гарантирует производительность».

«Занятость — это средство для сокрытия задержки, а не самоцель».

Изложение содержания

Глава подробно разбирает понятие достигнутой занятости и её ограничителей. Ключевые ограничители:

  1. Registers per thread. Лимит на Blackwell — 255 регистров на поток. Если ядро использует много регистров, меньше варпов может быть резидентно на SM.
  2. Shared memory per block. Ограничивает количество одновременных блоков. Большие тайлы уменьшают occupancy.
  3. Thread count. Конфигурация запуска не запрашивает достаточно потоков для заполнения SM.

Для настройки занятости используются __launch_bounds__ (указывает максимальное число потоков на блок и минимальное число блоков на SM) и CUDA Occupancy API (cudaOccupancyMaxPotentialBlockSize). Компилятор при этом ограничивает использование регистров и разворачивание, что может вызвать лёгкий разлив в локальную память.

Отдельно подчёркивается: переход с 10% на 50% занятости может дать двукратное ускорение, а переход с 50% на 100% — лишь 5–15%. После 50–75% занятости другие факторы (ILP, паттерны доступа к памяти) начинают доминировать.

Схема 6. Настройка занятости

Баланс между ресурсами SM и параллелизмом. Цель — скрыть latency

SMEM
Registers
Высокая занятость = больше варпов → задержка скрывается лучше
Описание: Занятость — не цель, а инструмент. Чрезмерное давление регистров или shared memory уменьшает число варпов, а слишком маленькие блоки увеличивают накладные расходы. Оптимум — там, где latency скрывается при минимальных потерях.
СценарийОптимальная занятостьПочему
Ядро, ограниченное памятью60–80%Нужно много варпов для сокрытия задержек DRAM
Ядро, ограниченное вычислениями25–50%Tensor Cores поддерживают конвейер без массы варпов
Ядро с высоким ILP25%Каждый варп обеспечивает 4 независимые операции
Ядро, ограниченное задержкой50–75%Баланс между числом варпов и ресурсами на поток

Пример использования __launch_bounds__:

__global__ __launch_bounds__(256, 8)
void kernel_optimized(...) {
    // компилятор ужимается в ~32 регистра на поток
    // → 8 блоков × 256 потоков = 2048 потоков на SM
    // → занятость 100%
}
Итоги блока 05
Что важно понять

Занятость — средство для сокрытия задержки, а не самоцель. После 50–75% другие факторы начинают доминировать.

Ключевые ограничители

Registers per thread (лимит 255 на Blackwell), shared memory per block, thread count. Для диагностики используются Nsight Compute Occupancy и CUDA Occupancy API.

Практическое следствие

__launch_bounds__ и CUDA Occupancy API позволяют найти оптимальную конфигурацию запуска. Но следите за разливом регистров в локальную память.

• • •
Блок 06 · Расхождение

Эффективность варпов: борьба с расхождением

Цитата из книги · Глава 8

«В SIMT-выполнении варп из 32 потоков выполняет инструкции синхронно. Если потоки внутри варпа выбирают разные пути if/else, варп выполняет обе ветви последовательно. При разделении 50/50 эффективность падает до 50%».

Изложение содержания

Глава описывает проблему расхождения варпов (warp divergence) и методы её минимизации. Ключевые техники:

  1. Реструктуризация данных. Сортировка или группировка данных так, чтобы каждый варп обрабатывал однородные случаи.
  2. Разделение на несколько ядер. Если ветвь тяжёлая — запуск отдельных ядер для условия true и false.
  3. Варп-однородные ветвления. Использование индекса варпа в условии: if (warpId % 2 == 0) { /* Task A */ } else { /* Task B */ }. Все потоки варпа выбирают одну ветвь.
  4. Варп-голосование. Интринсики __ballot_sync, __any_sync, __all_sync позволяют варпу коллективно решать, какие lanes нуждаются в «специальной» работе.
  5. Предикация для коротких ветвей. Безветвистые конструкции через смешивание: float cond = x > 0 ? 1.0f : 0.0f; y = cond * f(x) + (1.0f - cond) * g(x);.

Также разбираются shuffle-интринсики (__shfl_sync, __shfl_down_sync) для внутриварпового обмена данными через регистры без обращений к shared memory. Пример warp reduce sum:

float warp_reduce_sum(float val) {
    for (int offset = 16; offset > 0; offset >>= 1) {
        val += __shfl_down_sync(0xFFFFFFFF, val, offset);
    }
    return val;
}

Схема 7. Расхождение варпов

Слева: if/else расходится → варп выполняет обе ветки. Справа: однородный путь — все lanes работают

РАСХОЖДЕНИЕ

~50% эффективности

БЕЗ РАСХОЖДЕНИЯ

100% эффективности
Описание: Слева часть lanes маскируется при выполнении каждой ветви — половина тактов тратится впустую. Справа все потоки выполняют одну и ту же последовательность инструкций, обычно через предикацию.
Итоги блока 06
Что важно понять

Расхождение варпов снижает эффективность до 50% при разделении 50/50. Предикация, варп-однородные ветвления и shuffle-интринсики — основные методы борьбы.

Ключевой приём

Shuffle-интринсики позволяют обмениваться данными через регистры без shared memory и без __syncthreads(). Это быстрее и не создаёт bank conflicts.

Практическое следствие

PyTorch-операции вроде torch.where и torch.maximum компилируются в предикацию, обеспечивая варп-когерентное выполнение без расхождения.

• • •
Блок 07 · ILP

Параллелизм на уровне инструкций

Цитата из книги · Глава 8

«ILP — это количество независимых инструкций, которые один поток может выдать подряд, не дожидаясь завершения предыдущих. Высокий ILP позволяет одному варпу поддерживать занятость исполнительных блоков, даже когда отдельные инструкции имеют задержки».

Изложение содержания

Глава подробно разбирает ILP и его взаимодействие с занятостью. Ключевые положения:

  • ILP не изменяет арифметическую интенсивность (FLOPS/байт), но повышает пропускную способность за счёт перекрытия вычислений с памятью или вычислений с вычислениями.
  • Увеличение ILP не означает выполнение большего числа операций — оно означает лучшее использование простаивающих слотов выдачи.
  • ILP и занятость взаимно компенсируют друг друга. С ILP=4 достаточно 25% занятости для насыщения SM; без ILP требуется 75%.
  • На Blackwell унифицированные ядра CUDA выполняют либо FP32, либо INT32 в одном такте — но не оба одновременно. Смешанные потоки инструкций INT32 и FP32 больше не получают выгоды от dual-issue на одном ядре.
  • Оптимальный ILP на Blackwell — 2–4 независимые операции на поток. Дальнейшее увеличение насыщает планировщики, увеличивает давление на регистры и раздувает размер кода.

Пример разрыва цепочки зависимостей:

// Без ILP: последовательная цепочка
float u = x * x;
float temp = u + 1.0f;
float v = y * y;
float sum = temp + v;

// С ILP: независимые операции подряд
float u = x * x;
float v = y * y;
float temp = u + 1.0f;
float sum = temp + v;

Схема 8. Перекрытие с помощью ILP

Независимые инструкции перекрываются: пока загрузка x ждёт память, y*y уже выполняется

no ILP
ld x
x*x
st
with ILP
ld x
ld y
x*x
y*y
sum
st
задержка скрыта → выше IPC
Описание: Верхний ряд — последовательная цепочка зависимостей, конвейер простаивает. Нижний — независимые загрузки и умножения выданы подряд, латентность памяти перекрывается вычислениями.
ILPПотоков на SM для ~100% загрузкиЗанятость
1 (без ILP)~1536 (48 варпов)75%
2~1024 (32 варпа)50%
3~768 (24 варпа)37.5%
4~512 (16 варпов)25%

Схема 9. Dual issue (двойная выдача, 4 планировщика варпов)

4 независимых warp-планировщика. Каждый такт может выпустить до 2 инструкций: одна math + одна memory

SCHED 0
MATH
MEM
SCHED 1
MATH
MEM
SCHED 2
MATH
MEM
SCHED 3
MATH
MEM
→ до 4 варпов × 2 выдачи = 8 инструкций за такт
math memory
Описание: На Blackwell каждый планировщик может выпустить одну математическую и одну инструкцию доступа к памяти за такт. Но это не означает автоматического dual-issue INT+FP: смешанные потоки больше не получают такой выгоды.
Итоги блока 07
Что важно понять

ILP позволяет одному варпу поддерживать занятость исполнительных блоков. С ILP=4 достаточно 25% занятости для насыщения SM.

Ограничение Blackwell

Унифицированные ядра CUDA выполняют либо FP32, либо INT32 в одном такте. Смешанные потоки INT+FP больше не получают выгоды от dual-issue.

Практическое следствие

Перестановка независимых операций подряд, разворачивание циклов, увеличение числа независимых аккумуляторов. Оптимум — 2–4 операции на поток.

• • •
Блок 08 · Предвыборка

Асинхронная предвыборка и TMA

Цитата из книги · Глава 8

«Классический паттерн двойной буферизации: пока вычисления идут над текущим тайлом, в фоне загружается следующий. На Hopper и Blackwell этому помогает TMA — Tensor Memory Accelerator, аппаратный движок для массовых многомерных копий из глобальной памяти в разделяемую».

Изложение содержания

Глава описывает TMA и его взаимодействие с CUDA Pipeline API. Ключевые моменты:

  • TMA способен выполнять 1D–5D массовые копии с произвольными шагами между глобальной и разделяемой памятью без блокировки конвейера инструкций SM.
  • Использование TMA освобождает LD/ST блоки SM для полезной работы, поскольку DMA-движок TMA перемещает данные в фоне.
  • Двойная буферизация с TMA: пока TMA загружает следующий тайл, SM вычисляет на предыдущем. Это скрывает задержку DRAM.
  • TMEM (Tensor Memory) — специализированная память для обслуживания Tensor Cores. Инструкции tcgen05.mma, tcgen05.ld и tcgen05.st обеспечивают высокопроизводительные пути данных пятого поколения на Blackwell. Архитектура TMEM и инструкции tcgen05 подробно разобраны в обзоре главы 6.

Пример двойной буферизации с TMA и CUDA Pipeline API:

__shared__ float tile[2][TILE_SIZE];
tile[0][threadIdx.x] = global_data[blockIdx.x * TILE_SIZE + threadIdx.x];
__syncthreads();

for (int t = 0; t < numTiles - 1; ++t) {
    tile[(t + 1) % 2][threadIdx.x] = 
        global_data[(blockIdx.x + t + 1) * TILE_SIZE + threadIdx.x];
    process(tile[t % 2][threadIdx.x]);
    __syncthreads();
}

Более современный вариант с использованием CUDA Pipeline API и TMA:

#include <cuda/pipeline>
#include <cooperative_groups.h>
namespace cg = cooperative_groups;

__global__ void kernelWithTMA(const float* __restrict__ global_ptr, int nTiles) {
    __shared__ float tile0[TILE_SIZE];
    __shared__ float tile1[TILE_SIZE];
    float* tiles[2] = { tile0, tile1 };
    
    __shared__ cuda::pipeline_shared_state<cuda::thread_scope_block, 2> state;
    auto pipe = cuda::make_pipeline(cg::this_thread_block(), &state);
    
    pipe.producer_acquire();
    cuda::memcpy_async(tiles[0], global_ptr + 0 * TILE_SIZE,
                       cuda::aligned_size_t<32>(TILE_SIZE * sizeof(float)), pipe);
    pipe.producer_commit();
    
    for (int t = 1; t < nTiles; ++t) {
        pipe.consumer_wait();
        processTile(tiles[(t - 1) & 1]);
        pipe.consumer_release();
        
        pipe.producer_acquire();
        cuda::memcpy_async(tiles[t & 1], global_ptr + t * TILE_SIZE,
                           cuda::aligned_size_t<32>(TILE_SIZE * sizeof(float)), pipe);
        pipe.producer_commit();
    }
    pipe.consumer_wait();
    processTile(tiles[(nTiles - 1) & 1]);
    pipe.consumer_release();
}
Итоги блока 08
Что важно понять

TMA — аппаратный движок для массовых многомерных копий. Он освобождает LD/ST блоки SM для полезной работы. Двойная буферизация с TMA скрывает задержку DRAM.

Ключевой паттерн

Пинг-понг между двумя буферами: пока TMA загружает следующий тайл, SM вычисляет на предыдущем. Это требует явного управления через CUDA Pipeline API.

Практическое следствие

Использование cuda::memcpy_async с cuda::pipeline на Hopper/Blackwell автоматически задействует TMA, если выравнивание и размеры позволяют.

• • •
Блок 09 · PyTorch

Оптимизация для пользователей PyTorch

Цитата из книги · Глава 8

«Для пользователей PyTorch, пишущих высокоуровневый код, обычно не нужно управлять занятостью напрямую — CUDA-ядра и библиотеки PyTorch обрабатывают параметры запуска под капотом. Однако понимание занятости может быть ценным. Если вы пишете кастомные CUDA-ядра как расширения PyTorch, применяются те же принципы».

Изложение содержания

Глава описывает несколько оптимизаций, доступных без написания CUDA:

  • model = torch.compile(model, mode="max-autotune") — генерирует объединённые ядра GPU, сливая множество небольших операций в одно более крупное ядро. Это увеличивает работу на запуск ядра и улучшает занятость.
  • optimizer = torch.optim.AdamW(..., fused=True) — объединённая реализация выполняет все шаги оптимизатора в одном ядре, уменьшая накладные расходы на запуск и трафик памяти.
  • y = torch.where(x > 0, x, torch.zeros_like(x)) — векторизованные операции компилируются в предикацию в стиле SIMD, обеспечивая варп-когерентное выполнение без расхождения.

Отдельно подчёркивается, что torch.compile не гарантирует оптимальную занятость или ILP — для этого может потребоваться профилирование и дополнительная настройка. Также отмечается, что PyTorch compiler не всегда автоматически использует все возможности — иногда требуется регистрация кастомных ядер через Triton.

Итоги блока 09
Что важно понять

Для пользователей PyTorch многие оптимизации доступны без написания CUDA. torch.compile генерирует объединённые ядра, fused оптимизаторы уменьшают накладные расходы, torch.where обеспечивает предикацию.

Ограничение

torch.compile не гарантирует оптимальную занятость или ILP. Для достижения максимальной производительности может потребоваться профилирование и регистрация кастомных ядер через Triton.

Практическое следствие

Начинайте с torch.compile и fused оптимизаторов. Если производительность недостаточна — профилируйте и пишите кастомные ядра для горячих участков.

• • •
Блок 10 · Энергопотребление

Мониторинг энергопотребления

Цитата из книги · Глава 8

«Длительная почти пиковая загрузка может вызвать срабатывание ограничителей мощности на современных GPU для поддержания их в здоровом состоянии. Обязательно учитывайте это при профилировании, бенчмаркинге и настройке. Самый простой способ увидеть, ограничены ли вы по мощности — использовать nvidia-smi с запросом потребляемой мощности, текущих частот и причин троттлинга в реальном времени».

Изложение содержания

Глава описывает мониторинг энергопотребления как часть процесса оптимизации. Команда:

nvidia-smi \
  --query-gpu=power.draw,clocks.current.sm,clocks.current.memory,\
clocks_event_reasons.active \
  --format=csv -l 1

выводит новую строку каждую секунду с текущим энергопотреблением, частотами графического ядра и памяти, а также причинами троттлинга. Это позволяет точно определить, когда GPU достигает предела мощности и снижает частоты.

Примечание для старых кластеров: на драйверах NVIDIA версии старше 535 то же поле называется clocks_throttle_reasons.active. Если запрос возвращает ошибку «Field not found» — переключитесь на старое имя поля, оно по-прежнему поддерживается на устаревших драйверах.

Программный доступ — через NVML: nvmlDeviceGetPowerUsage() и nvmlDeviceGetEnforcedPowerLimit(). Эти API идеальны для кастомных скриптов или интеграции с системами мониторинга.

Итоги блока 10
Что важно понять

При длительной почти пиковой нагрузке GPU может упираться в лимит мощности и снижать тактовые частоты. Мониторинг энергопотребления — часть процесса оптимизации.

Инструменты

nvidia-smi с запросом power.draw, clocks.current.sm, clocks_event_reasons.active (на драйверах <535 — clocks_throttle_reasons.active). Программный доступ через NVML.

Практическое следствие

Если GPU упирается в лимит мощности, рассмотрите настройку power limit или underclocking для повышения эффективности на ватт.

• • •
Блок 11 · Чек-лист

Чек-лист оптимизации GPU-ядра

Цитата из книги · Глава 8

«В этой главе вы увидели, как выявлять и устранять узкие места GPU-ядер, перемещая работу из медленной глобальной памяти в более быстрые на кристалле ресурсы и вычислительные блоки. Следуя циклу профилирования, диагностики, оптимизации и повторного профилирования, вы можете трансформировать ядра из недоиспользованных или ограниченных памятью в вычислительно-насыщенные, высокопроизводительные процедуры».

Изложение содержания

Глава завершается чек-листом, который систематизирует процесс оптимизации:

  1. Профилируйте — запустите nsys, найдите горячие ядра, углубитесь с ncu.
  2. Определите режим — недоиспользование, задержка, память или вычисления.
  3. Проверьте занятость — если ниже 30%, ищите ограничитель.
  4. Примените __launch_bounds__ — если регистры ограничивают занятость.
  5. Проверьте причины остановок — Long Scoreboard → память, Exec Dependency → ILP, Compute Unit Busy → вычисления.
  6. Увеличьте ILP — разверните циклы, разорвите цепочки зависимостей.
  7. Минимизируйте расхождение варпов — предикация, варп-однородные ветвления, torch.where.
  8. Используйте shuffle — для внутриварпового обмена вместо shared memory.
  9. Внедрите предвыборку — двойная буферизация, TMA на поддерживаемых GPU.
  10. Перепрофилируйте — проверьте, что целевая метрика улучшилась.
  11. Проверьте точность — если использовали пониженную точность, сравните с эталоном.
  12. Итерируйте — исправление одного узкого места вскрывает следующее.
Итоги блока 11
Что важно понять

Оптимизация GPU-ядер — систематический процесс, а не набор трюков. Чек-лист из 12 пунктов покрывает полный цикл.

Ключевой цикл

Профилирование → диагностика → оптимизация → повторное профилирование. Каждое исправление вскрывает следующее узкое место.

Практическое следствие

Следуйте чек-листу последовательно. Не оптимизируйте вслепую. Проверяйте точность после каждого изменения.

• • •
Блок 12 · Проверка прогнозов

Проверка прогнозов и актуальные данные

Книга Fregly писалась в 2025 году, когда Blackwell только появлялся на рынке. Ниже представлено сопоставление прогнозов главы 8 с открытыми данными и уточнение цифр.

Подтверждено Прогноз 1: профилирование и автоматизация

«Важно настроить автоматизированные тесты производительности для выявления регрессий на ранних этапах цикла разработки».

Подтверждено. Современные фреймворки (PyTorch, TensorRT-LLM) включают встроенные профайлеры и CI-пайплайны с бенчмарками. PyTorch performance HUD отслеживает регрессии в nightly-сборках. MLPerf стал де-факто стандартом воспроизводимых измерений. Инструменты вроде Nsight Systems и Nsight Compute обновляются для каждой новой архитектуры GPU.

Подтверждено Прогноз 2: занятость и ILP как взаимодополняющие факторы

«ILP и занятость взаимно компенсируют друг друга. С ILP=4 достаточно 25% занятости для насыщения SM; без ILP требуется 75%».

Подтверждено. Таблица зависимости ILP от необходимого числа потоков (1→1536, 2→1024, 3→768, 4→512) согласуется с данными NVIDIA и архитектурными руководствами. На Blackwell с его 64 варпами на SM и 256 КБ регистрового файла эта зависимость сохраняется. Единственное уточнение: на Blackwell унифицированные ядра CUDA выполняют либо FP32, либо INT32 в одном такте, что снижает эффективность смешанных потоков инструкций INT+FP по сравнению с более ранними архитектурами.

Подтверждено Прогноз 3: TMA и асинхронная предвыборка

«TMA способен выполнять 1D–5D массовые копии с произвольными шагами между глобальной и разделяемой памятью без блокировки конвейера инструкций SM».

Подтверждено. TMA (Tensor Memory Accelerator) появился в Hopper и развит в Blackwell. CUDA Pipeline API (cuda::memcpy_async, cuda::pipeline) автоматически задействует TMA на поддерживаемых архитектурах. На Blackwell TMA работает в связке с TMEM для обслуживания Tensor Cores пятого поколения (см. обзор главы 6).

Подтверждено Прогноз 4: PyTorch compiler как первый инструмент оптимизации

«torch.compile генерирует объединённые ядра GPU, сливая множество небольших операций в одно более крупное ядро».

Подтверждено. torch.compile с TorchInductor генерирует объединённые ядра, снижая накладные расходы на запуск. Однако, как отмечается в главе, компилятор не гарантирует оптимальную занятость или ILP. Для максимальной производительности может потребоваться регистрация кастомных ядер через Triton или ручная настройка.

Частично подтверждено Прогноз 5: энергопотребление как ограничивающий фактор

«Длительная почти пиковая загрузка может вызвать срабатывание ограничителей мощности на современных GPU».

Частично подтверждено. На B200 TDP составляет 1000 Вт, на B300 — 1400 Вт. В стойке NVL72 — 130 кВт. Для большинства inference-нагрузок (которые обычно memory-bound, а не compute-bound) ограничение мощности не является основным фактором. Однако для плотных вычислений (например, обучение) ограничение мощности может снижать частоты. На Blackwell Ultra и Rubin вопрос охлаждения и питания становится более острым.

• • •
Блок 13 · Что осталось за кадром

Что осталось за кадром

Глава 8 описывает фундаментальные принципы оптимизации, но оставляет за скобками несколько аспектов, которые проявились в реальной эксплуатации Blackwell.

Дополнительные замечания

Влияние двухкристальной архитектуры Blackwell на оптимизацию. Глава не упоминает, что B200 состоит из двух кристаллов, соединённых NV-HBI на 10 ТБ/с. Для программиста это выглядит как один GPU, но на уровне производительности памяти могут возникать аномалии, связанные с неравномерностью загрузки кристаллов. В редких случаях для диагностики может потребоваться проверка баланса между кристаллами.

Взаимодействие с NVLink и NVSwitch. Глава фокусируется на внутриядерной оптимизации, но для multi-GPU workloads критично понимание топологии. Оптимизация одного ядра не поможет, если данные передаются через медленный интерконнект: NVLink 5 на B200 даёт 1,8 ТБ/с на GPU, но при выходе за пределы NVL72 пропускная способность падает до 100–200 ГБ/с через InfiniBand. Топология NVLink/NVSwitch и пропускная способность интерконнектов подробно разобраны в обзоре главы 4.

Специфика MoE-моделей. Для MoE-моделей (как DeepSeek-V3) оптимизация attention и MLP слоёв требует учёта all-to-all коммуникаций между экспертами. В главе это не рассматривается, но для реальных inference-нагрузок это часто доминирующий фактор.

Ограничения FP4. Глава упоминает FP4 как множитель производительности, но не рассматривает ограничения: FP4 требует калибровки, не все модели tolerate такую точность, и для критичных слоёв (ранние слои, attention softmax, layer norms) требуется более высокая точность. Transformer Engine решает эту задачу за счёт автоматического выбора точности per-layer, но требует валидации на конкретной модели.

Специфика inference vs training. Глава описывает общие принципы, но не различает оптимизацию для training и inference. Для training доминируют compute-bound ядра (GEMM), для inference — memory-bound (attention, decode). Соответственно, оптимальные стратегии различаются: для training важнее ILP и Tensor Cores, для inference — пропускная способность памяти и низкая задержка.

• • •
Блок 14 · Дополнительные выводы

Дополнительные выводы

  1. Оптимизация GPU-ядер — это не набор трюков, а систематический процесс. Глава 8 даёт чёткий фреймворк: профилирование → диагностика → оптимизация → повторное профилирование. Каждое исправление вскрывает следующее узкое место. Этот цикл повторяется до достижения целевой производительности.
  2. Занятость, ILP и эффективность варпов — три взаимодополняющих направления. Нельзя оптимизировать одно в ущерб другим. Высокая занятость без ILP даёт много варпов, которые ждут память. Высокий ILP без занятости не может скрыть задержки. Эффективные варпы без достаточного количества инструкций в полёте не могут заполнить конвейеры.
  3. Blackwell меняет правила игры. Архитектурные сдвиги (126 МБ L2, 10 ТБ/с NV-HBI, унифицированные ядра CUDA, Tensor Cores пятого поколения с TMEM) требуют пересмотра методик, работавших на Ampere и Hopper. Влияние этих изменений на ILP и эффективность смешанных потоков INT+FP разобрано выше — см. проверку прогноза 2.
  4. PyTorch compiler — первый инструмент, но не панацея. torch.compile с TorchInductor генерирует объединённые ядра и снижает накладные расходы на запуск. Однако для максимальной производительности может потребоваться регистрация кастомных ядер через Triton или ручная настройка. Профилирование остаётся обязательным.
  5. Энергопотребление и охлаждение — часть уравнения. На Blackwell Ultra и Rubin ограничение мощности и охлаждение становятся более острыми. Для inference-нагрузок (которые обычно memory-bound) ограничение мощности реже является основным фактором, но для обучения и плотных вычислений оно может снижать частоты.
  6. Инвестиции в понимание принципов окупаются лучше, чем знание цифр. Инженер, понимающий, почему ILP компенсирует занятость, почему TMA освобождает LD/ST блоки, почему расхождение варпов снижает эффективность, адаптируется к новому поколению GPU за дни. Тот, кто знает только «как настроить NCCL на H100», будет переучиваться месяцами.
• • •
Блок 15 · Ключевые положения

Ключевые положения

  1. Профилирование — двухэтапный процесс. Nsight Systems для общей картины и поиска горячих ядер, Nsight Compute для детального анализа. NVTX-аннотации делают временную шкалу читаемой.
  2. Четыре режима узких мест. Недоиспользование, ограничение задержкой, ограничение памятью, ограничение вычислениями. Определение режима — первый шаг к правильной стратегии оптимизации.
  3. Причины остановок варпов. Long Scoreboard → память, Short Scoreboard → shared memory, Exec Dependency → ILP, Compute Unit Busy → вычисления. Nsight Compute даёт разбивку.
  4. Занятость — средство, не цель. После 50–75% другие факторы начинают доминировать. Ключевые ограничители: registers per thread, shared memory per block, thread count.
  5. Расхождение варпов снижает эффективность. Предикация, варп-однородные ветвления, shuffle-интринсики — основные методы борьбы. torch.where компилируется в предикацию.
  6. ILP компенсирует занятость. С ILP=4 достаточно 25% занятости для насыщения SM. Оптимум на Blackwell — 2–4 независимые операции на поток.
  7. TMA — аппаратный движок для массовых копий. Освобождает LD/ST блоки SM. Двойная буферизация с TMA скрывает задержку DRAM. CUDA Pipeline API автоматически задействует TMA.
  8. PyTorch compiler — первый инструмент оптимизации. torch.compile генерирует объединённые ядра, fused оптимизаторы уменьшают накладные расходы, torch.where обеспечивает предикацию. Но компилятор не гарантирует оптимальную занятость или ILP.
  9. Энергопотребление — часть уравнения. При длительной почти пиковой нагрузке GPU может упираться в лимит мощности. Мониторинг через nvidia-smi и NVML (на драйверах <535 поле троттлинга называется clocks_throttle_reasons.active).
  10. Оптимизация — систематический процесс. Профилирование → диагностика → оптимизация → повторное профилирование. Каждое исправление вскрывает следующее узкое место.
  11. Blackwell меняет правила. 126 МБ L2, 10 ТБ/с NV-HBI, унифицированные ядра CUDA, Tensor Cores пятого поколения с TMEM — методики, работавшие на Ampere и Hopper, требуют пересмотра. Детализация — в проверке прогноза 2.
  12. Инвестиции в принципы окупаются лучше, чем знание цифр. Понимание фундаментальных принципов (иерархия памяти, ILP, занятость, расхождение варпов) обеспечивает адаптацию к любому поколению GPU.
Общий итог обзора

Восьмая глава книги Fregly — систематический разбор трёх взаимодополняющих направлений оптимизации GPU-ядер: настройки занятости, эффективности варпов и параллелизма на уровне инструкций. Ключевая идея: оптимизация — это не набор трюков, а систематический процесс профилирования, диагностики и повторного профилирования. Для инженера это означает, что понимание фундаментальных принципов (иерархия памяти, ILP, занятость, расхождение варпов) важнее, чем знание конкретных цифр текущего поколения GPU.

Проверка прогнозов показывает: книга оказалась точной в своих предсказаниях. Методики, описанные в главе, применимы к Blackwell и, с учётом архитектурных изменений (унифицированные ядра CUDA, TMA, TMEM), остаются актуальными. Главный вывод остаётся неизменным: инвестиции в фундаментальные принципы обеспечивают адаптацию к любому поколению железа.

Источники: Chris Fregly, «AI Systems Performance Engineering» (O'Reilly, 2026), Глава 8 — цитаты приводятся в ограниченном объёме в целях обзора и критического анализа. Документация NVIDIA Nsight Systems, Nsight Compute, CUDA Toolkit, Blackwell Architecture; MLPerf Training/Inference v5.0; PyTorch Documentation; NVIDIA Technical Blog.

FinCodeCow · AI Infrastructure Review

Occupancy Tuning, Warp Efficiency, and Instruction-Level Parallelism

A review of Chapter 8 of Chris Fregly's «AI Systems Performance Engineering» (O'Reilly, 2026) — with exposition of the chapter's content, verification of forecasts against current data, and systematization of key points
About the source

This material is a review of Chapter 8 of Chris Fregly, «AI Systems Performance Engineering: Optimizing Model Training and Inference Workloads with GPUs, CUDA, and PyTorch», O'Reilly Media, 2026 (Copyright © 2026 Flux Capacitor, LLC).

Direct quotes from the book are marked with separate blocks labeled «Quote from the book». The rest of the text is a systematized exposition of the chapter's content, a comparison with current data, verification of forecasts against open sources, and additional observations that go beyond the chapter's text. Quotes are provided in limited volume for review and critical analysis purposes.

Block 01 · Context

Chapter Context

Chapter 8 of Fregly's book is dedicated to advanced techniques for hiding latency and improving GPU kernel throughput. It continues previous chapters, where basic memory optimizations were discussed, and moves to a systematic analysis of how to profile, diagnose, and eliminate bottlenecks related to occupancy, warp efficiency, and instruction-level parallelism.

Quote from the book · Chapter 8

«Modern GPU-accelerated workloads are pushing hardware to its limits. Multi-die GPUs like Blackwell connect multiple reticle-limited dies with a 10 TB/s NV-HBI link and increase L2 to 126 MB. These hardware design choices materially change memory-vs-compute tradeoffs and occupancy sweet spots. This makes profiling and optimization even more critical than ever».

Exposition of content

The chapter formulates a key thesis: GPU kernel performance is determined not only by the number of launched threads but by how efficiently those threads use available resources. The chapter breaks the problem into three complementary directions: occupancy tuning, warp efficiency improvement, and instruction-level parallelism (ILP).

The chapter separately emphasizes that these three directions do not replace but complement each other. High occupancy without ILP yields many warps that wait on memory. High ILP without occupancy cannot hide latency because there are too few warps. Efficient warps without sufficient instructions in flight cannot fill the pipelines. Only the combined application of all three approaches can approach peak performance.

Block 01 Summary
Key point

Chapter 8 is a systematic breakdown of three complementary GPU kernel optimization directions: occupancy, warp efficiency, ILP. Each direction addresses its own task and does not replace the others.

Key thesis

The Blackwell architecture with 126 MB L2 and 10 TB/s NV-HBI shifts the balance between memory and compute. Techniques that worked on Ampere and Hopper require revision.

• • •
Block 02 · Profiling

Profiling as a Starting Point

Quote from the book · Chapter 8

«Before optimizing, we must first identify the bottlenecks in our code to determine which hardware or software resource is limiting our performance. Modern NVIDIA GPUs are complex, and a slowdown could come from many sources, including memory bandwidth, memory latency, instruction throughput, synchronization overheads, insufficient parallelism, host-device transfer delays, and more».

Exposition of content

The chapter describes a two-stage approach to profiling. In the first stage, Nsight Systems (nsys) is used to obtain a system timeline: CPU threads, GPU kernel launches, data transfers, Python traces. This helps find hot kernels and understand the big picture. In the second stage, Nsight Compute (ncu) is used for detailed analysis of each hot kernel: achieved occupancy, warp stall reasons, memory throughput, execution unit utilization.

The chapter separately emphasizes the importance of NVTX (NVIDIA Tools Extension) annotations. They allow marking execution phases (forward, backward, optimizer step) and make the timeline readable. In PyTorch they are available through torch.cuda.nvtx, as well as through torch.profiler.record_function().

A typical scenario is described: the Nsight Systems timeline shows gaps between kernel launches, and the GPU idles. This does not always mean a problem in CUDA code — often the cause is in the DataLoader: too few num_workers, lack of double buffering, CPU-side preprocessing instead of GPU.

Diagram 1. Nsight Systems Interactive UI

A single timeline: CPU threads, GPU kernels, NVTX ranges, and transfer engines

CPU
GPU
NVTX
NET
Description: The visual timeline shows how CPU, GPU, NVTX annotations, and network operations relate to each other. Wide gaps on the GPU track are a signal to check the data pipeline. The interactive interface allows zooming and detailing of areas of interest.

Diagram 2. Chrome Tracing / Perfetto

Exporting a PyTorch profiler trace: op-level, kernels, memcpy

aten
matmul
add
relu
softmax
kernel
sgemm
pointwise
reduce
memcpy
H2D
D2H
Description: The PyTorch profiler reveals where the op-level view diverges from actual kernel invocations. Often a single «logical» action spawns several kernels and memcpys. Perfetto provides a web interface for analyzing large traces with filtering and SQL queries.
Common mistake

If the Nsight Systems timeline shows gaps between kernel launches while the GPU idles, the problem is not always in CUDA code. Often the cause is in the DataLoader: increase num_workers, enable double buffering, move preprocessing to the GPU.

Block 02 Summary
Key point

Profiling is a two-stage process: Nsight Systems for the big picture and finding hot kernels, Nsight Compute for detailed analysis. NVTX annotations make the timeline readable.

Key tool

PyTorch profiler (based on Kineto) provides op-level breakdown and supports export to Chrome Tracing / Perfetto for collaborative analysis.

Practical consequence

Before optimizing a kernel, make sure the problem is in the kernel and not in the data pipeline. An idle GPU often means an inefficient DataLoader, not a bad kernel.

• • •
Block 03 · Regimes

Four Bottleneck Regimes

Quote from the book · Chapter 8

«GPUs can stall for four fundamentally different reasons: underutilization, latency bound, memory bound, and compute bound. These regimes are related, but it's important to understand each of them independently in order to choose the right optimizations to pursue. Often, fixing one bottleneck will reveal another».

Exposition of content

The chapter introduces four regimes in which a GPU kernel may reside. Understanding which regime a specific kernel is in determines the optimization strategy.

  • Underutilization. Both FLOPS and memory throughput are low. Cause: insufficient work to fill all SMs. Solution: increase grid size, batch size, merge small kernels, use CUDA Graphs.
  • Latency-bound. Memory throughput well below peak, but warps frequently stall waiting for data. Long Scoreboard and Not Selected dominate. Solution: increase occupancy, increase ILP, apply async prefetch.
  • Memory-bound. Memory throughput near peak (>80%), FLOPS low. Solution: increase arithmetic intensity — shared-memory tiling, kernel fusion, better memory coalescing.
  • Compute-bound. FLOPS near peak, ALU or Tensor Cores saturated. Solution: switch to reduced precision (FP16 → FP8 → FP4), Tensor Cores, sparsity, increase ILP.

It is emphasized that with iterative optimization, a kernel moves through regimes sequentially: underutilization → latency-bound → memory-bound → compute-bound. Each fix reveals the next bottleneck — this is a normal process.

Diagram 3. Roofline Chart

Memory roof (sloped) and Compute roof (horizontal). The kernel dot shows: memory-bound or compute-bound

Arithmetic Intensity (FLOPs/byte) → FLOPS → Memory Roof Compute Roof Ridge ~10 FLOPs/B
Description: The Roofline chart is the first thing to look at when profiling. A kernel dot below the sloped line means memory-bound; below the horizontal line means compute-bound. Nsight Compute builds this chart automatically from collected metrics.
Typical evolution

With iterative optimization, a kernel moves through regimes sequentially: underutilization → latency-bound → memory-bound → compute-bound. Each fix reveals the next bottleneck — this is normal.

Block 03 Summary
Key point

Every GPU kernel is in one of four regimes. Determining the regime is the first step toward the right optimization strategy.

Method

Roofline chart in Nsight Compute: kernel dot position relative to the sloped (memory) and horizontal (compute) lines. Additionally — analysis of warp stall reasons.

Practical consequence

Don't optimize blindly. First determine the regime. For memory-bound — tiling and fusion. For latency-bound — occupancy and ILP. For compute-bound — reduced precision and Tensor Cores.

• • •
Block 04 · Stalls

Warp Stall Reasons

Quote from the book · Chapter 8

«One of the most insightful views in Nsight Compute is the Warp State Statistics breakdown for a kernel. This is sometimes called the warp stall reasons. By examining these stall reasons, you can often pinpoint the limiting factor».

Exposition of content

The chapter systematizes the main categories of warp stalls and their interpretation. Key categories:

  • Long Scoreboard. Warp waiting for a global DRAM load. This is the most expensive stall — hundreds of cycles of waiting. Cause: high latency of HBM access. Hidden only by parallelism from other warps or ILP.
  • Short Scoreboard. Waiting on shared memory ↔ register transfers. Latency ~20–30 cycles. Frequent bank conflicts can accumulate.
  • Memory Throttle. Load/store pipelines saturated. Indicates memory-bound regime.
  • Not Selected. Warp ready but waiting for a free slot. Usually not a problem at healthy occupancy.
  • Exec Dependency. Warp waiting for results of previous instructions. Direct sign of insufficient ILP.
  • Compute Unit Busy. Math units saturated — compute-bound regime.
  • Texture Throttle, Memory Dependency, No Eligible and other less frequent categories.

The chapter separately analyzes the Exec Dependency example: the code float u = x * x; float temp = u + 1.0f; float v = y * y; float sum = temp + v; creates a false dependency chain, so y * y cannot be issued until u + 1.0f completes. The fix — reorder independent operations to be adjacent.

ReasonMeaningOptimization
Long ScoreboardWarp waiting for a global DRAM loadIncrease occupancy, async prefetch, TMA
Short ScoreboardWaiting on shared memory ↔ register transfersOptimize shared memory access patterns
Memory ThrottleLoad/store pipelines saturatedReduce traffic, improve coalescing
Exec DependencyWaiting on previous instruction resultsReorder independent operations, increase ILP
Compute Unit BusyMath units saturatedReduced precision, Tensor Cores
Not SelectedWarp ready but waiting for a free slotUsually not a problem at healthy occupancy

Diagram 4. Long Scoreboard Stall

Warp waits for data from global memory (DRAM/HBM). High latency → «long» scoreboard

GLOBAL MEMORY · HBM3e
Stall: Long Scoreboard → hundreds of cycles of waiting
Description: Long Scoreboard is the most expensive stall type. The warp cannot issue any instruction until the requested cache line returns from DRAM. Hidden only by parallelism from other warps or ILP.

Diagram 5. Short Scoreboard Stall

Warp waits for low-latency operations: shared memory ↔ registers

↓↑
Stall: Short Scoreboard → ~20–30 cycles
Description: Short Scoreboard means waiting for low-latency operations — shared memory accesses or bank conflicts. Though shorter than DRAM, frequent bank conflicts can accumulate.
Block 04 Summary
Key point

Nsight Compute provides a breakdown of warp stall reasons. Long Scoreboard → memory, Exec Dependency → ILP, Compute Unit Busy → compute.

Key example

False dependency chains (like in the example with u = x*x; temp = u+1; v = y*y;) create Exec Dependency. Fix — reorder independent operations.

Practical consequence

Don't memorize all reasons. Look at the dominant category: memory-related → memory optimization; Exec Dependency → ILP; Compute Unit Busy → reduced precision.

• • •
Block 05 · Occupancy

Achieved Occupancy

Quote from the book · Chapter 8

«Occupancy is the ratio of active warps per SM to the maximum (64 warps / 2048 threads per SM for Blackwell B200). Low achieved occupancy is an indicator of underutilization, but high occupancy does not guarantee performance».

«Occupancy is a means to an end (hiding latency), not the end goal itself».

Exposition of content

The chapter examines in detail the concept of achieved occupancy and its limiters. Key limiters:

  1. Registers per thread. Blackwell limit — 255 registers per thread. If a kernel uses many registers, fewer warps can be resident on an SM.
  2. Shared memory per block. Limits the number of concurrent blocks. Large tiles reduce occupancy.
  3. Thread count. Launch configuration doesn't request enough threads to fill the SM.

To tune occupancy, __launch_bounds__ (specifies max threads per block and min blocks per SM) and the CUDA Occupancy API (cudaOccupancyMaxPotentialBlockSize) are used. The compiler limits register usage and unrolling, which may cause slight local memory spill.

It is separately emphasized: going from 10% to 50% occupancy can yield a 2× speedup, while going from 50% to 100% yields only 5–15%. After 50–75% occupancy, other factors (ILP, memory access patterns) begin to dominate.

Diagram 6. Tuning Occupancy

The balance between SM resources and parallelism. Goal — hide latency

SMEM
Registers
High occupancy = more warps → better latency hiding
Description: Occupancy is not the goal — it's a tool. Excessive register or shared memory pressure reduces warp count, while too-small blocks increase overhead. The optimum is where latency is hidden with minimal loss.
ScenarioOptimal occupancyWhy
Memory-bound kernel60–80%Needs many warps to hide DRAM latency
Compute-bound kernel25–50%Tensor Cores keep the pipeline full without many warps
High-ILP kernel25%Each warp provides 4 independent operations
Latency-bound kernel50–75%Balance between warp count and per-thread resources

Example usage of __launch_bounds__:

__global__ __launch_bounds__(256, 8)
void kernel_optimized(...) {
    // compiler squeezes into ~32 registers per thread
    // → 8 blocks × 256 threads = 2048 threads per SM
    // → 100% occupancy
}
Block 05 Summary
Key point

Occupancy is a means to hide latency, not the goal itself. After 50–75%, other factors begin to dominate.

Key limiters

Registers per thread (limit 255 on Blackwell), shared memory per block, thread count. For diagnostics, Nsight Compute Occupancy and CUDA Occupancy API are used.

Practical consequence

__launch_bounds__ and CUDA Occupancy API allow finding the optimal launch configuration. But watch out for register spilling to local memory.

• • •
Block 06 · Divergence

Warp Efficiency: Fighting Divergence

Quote from the book · Chapter 8

«In SIMT execution, a warp of 32 threads executes instructions in lockstep. If threads within a warp take different if/else paths, the warp executes both branches sequentially. With a 50/50 split, warp efficiency drops to 50%».

Exposition of content

The chapter describes the warp divergence problem and methods to minimize it. Key techniques:

  1. Data restructuring. Sorting or grouping data so each warp processes homogeneous cases.
  2. Splitting into multiple kernels. If a branch is heavy, launch separate kernels for true and false conditions.
  3. Warp-uniform branching. Using the warp index in the condition: if (warpId % 2 == 0) { /* Task A */ } else { /* Task B */ }. All threads in a warp take the same branch.
  4. Warp voting. Intrinsics __ballot_sync, __any_sync, __all_sync allow a warp to collectively decide which lanes need «special» work.
  5. Predication for short branches. Branchless constructs via blending: float cond = x > 0 ? 1.0f : 0.0f; y = cond * f(x) + (1.0f - cond) * g(x);.

Shuffle intrinsics (__shfl_sync, __shfl_down_sync) are also analyzed for intra-warp data exchange through registers without shared memory accesses. Example of warp reduce sum:

float warp_reduce_sum(float val) {
    for (int offset = 16; offset > 0; offset >>= 1) {
        val += __shfl_down_sync(0xFFFFFFFF, val, offset);
    }
    return val;
}

Diagram 7. Warp Divergence

Left: if/else diverges → warp runs both branches. Right: uniform path — all lanes work

DIVERGENT

~50% efficiency

NONDIVERGENT

100% efficiency
Description: On the left, some lanes are masked during each branch's execution — half the cycles are wasted. On the right, all threads execute the same instruction sequence, usually via predication.
Block 06 Summary
Key point

Warp divergence reduces efficiency to 50% at a 50/50 split. Predication, warp-uniform branching and shuffle intrinsics are the main countermeasures.

Key technique

Shuffle intrinsics allow exchanging data through registers without shared memory and without __syncthreads(). This is faster and creates no bank conflicts.

Practical consequence

PyTorch operations like torch.where and torch.maximum compile into predication, providing warp-coherent execution without divergence.

• • •
Block 07 · ILP

Instruction-Level Parallelism

Quote from the book · Chapter 8

«ILP is the number of independent instructions a single thread can issue in a row without waiting for previous ones to complete. High ILP lets a single warp keep execution units busy even when individual instructions have latency».

Exposition of content

The chapter examines in detail ILP and its interaction with occupancy. Key points:

  • ILP does not change arithmetic intensity (FLOPS/byte) but increases throughput by overlapping compute with memory or compute with compute.
  • Increasing ILP does not mean performing more operations — it means better use of idle issue slots.
  • ILP and occupancy mutually compensate each other. With ILP=4, 25% occupancy is enough to saturate the SM; without ILP, 75% is required.
  • On Blackwell, unified CUDA cores execute either FP32 or INT32 in a single cycle — but not both simultaneously. Mixed INT32 and FP32 instruction streams no longer benefit from dual-issue on a single core.
  • The optimal ILP on Blackwell is 2–4 independent operations per thread. Beyond that, schedulers saturate, register pressure increases, and code size bloats.

Example of breaking a dependency chain:

// No ILP: sequential dependency chain
float u = x * x;
float temp = u + 1.0f;
float v = y * y;
float sum = temp + v;

// With ILP: independent operations back-to-back
float u = x * x;
float v = y * y;
float temp = u + 1.0f;
float sum = temp + v;

Diagram 8. Overlapping with ILP

Independent instructions overlap: while load x waits for memory, y*y is already executing

no ILP
ld x
x*x
st
with ILP
ld x
ld y
x*x
y*y
sum
st
latency hidden → higher IPC
Description: The top row is a sequential dependency chain — the pipeline idles. The bottom row issues independent loads and multiplies back-to-back, overlapping memory latency with compute.
ILPThreads per SM for ~100% issueOccupancy
1 (no ILP)~1536 (48 warps)75%
2~1024 (32 warps)50%
3~768 (24 warps)37.5%
4~512 (16 warps)25%

Diagram 9. Dual Issue (4 Warp Schedulers)

4 independent warp schedulers. Each cycle can issue up to 2 instructions: one math + one memory

SCHED 0
MATH
MEM
SCHED 1
MATH
MEM
SCHED 2
MATH
MEM
SCHED 3
MATH
MEM
→ up to 4 warps × 2 issues = 8 instructions per cycle
math memory
Description: On Blackwell, each scheduler can issue one math and one memory instruction per cycle. But this does not mean automatic INT+FP dual-issue: mixed streams no longer get that benefit.
Block 07 Summary
Key point

ILP allows a single warp to keep execution units busy. With ILP=4, 25% occupancy is enough to saturate the SM.

Blackwell limitation

Unified CUDA cores execute either FP32 or INT32 in a single cycle. Mixed INT+FP streams no longer benefit from dual-issue.

Practical consequence

Reorder independent operations back-to-back, unroll loops, increase the number of independent accumulators. Optimum — 2–4 operations per thread.

• • •
Block 08 · Prefetch

Async Prefetch and TMA

Quote from the book · Chapter 8

«The classic double-buffering pattern: while computation proceeds on the current tile, the next one is loaded in the background. On Hopper and Blackwell, TMA — Tensor Memory Accelerator — helps here: a hardware engine for bulk multidimensional copies from global to shared memory».

Exposition of content

The chapter describes TMA and its interaction with the CUDA Pipeline API. Key points:

  • TMA can perform 1D–5D bulk copies with arbitrary strides between global and shared memory without blocking the SM instruction pipeline.
  • Using TMA frees the SM's LD/ST units for useful work, since the TMA DMA engine moves data in the background.
  • Double buffering with TMA: while TMA loads the next tile, the SM computes on the previous one. This hides DRAM latency.
  • TMEM (Tensor Memory) — specialized memory serving Tensor Cores. Instructions tcgen05.mma, tcgen05.ld and tcgen05.st provide high-performance data paths of the fifth generation on Blackwell. TMEM architecture and tcgen05 instructions are covered in detail in the Chapter 6 review.

Example of double buffering with TMA and CUDA Pipeline API:

__shared__ float tile[2][TILE_SIZE];
tile[0][threadIdx.x] = global_data[blockIdx.x * TILE_SIZE + threadIdx.x];
__syncthreads();

for (int t = 0; t < numTiles - 1; ++t) {
    tile[(t + 1) % 2][threadIdx.x] = 
        global_data[(blockIdx.x + t + 1) * TILE_SIZE + threadIdx.x];
    process(tile[t % 2][threadIdx.x]);
    __syncthreads();
}

A more modern variant using the CUDA Pipeline API and TMA:

#include <cuda/pipeline>
#include <cooperative_groups.h>
namespace cg = cooperative_groups;

__global__ void kernelWithTMA(const float* __restrict__ global_ptr, int nTiles) {
    __shared__ float tile0[TILE_SIZE];
    __shared__ float tile1[TILE_SIZE];
    float* tiles[2] = { tile0, tile1 };
    
    __shared__ cuda::pipeline_shared_state<cuda::thread_scope_block, 2> state;
    auto pipe = cuda::make_pipeline(cg::this_thread_block(), &state);
    
    pipe.producer_acquire();
    cuda::memcpy_async(tiles[0], global_ptr + 0 * TILE_SIZE,
                       cuda::aligned_size_t<32>(TILE_SIZE * sizeof(float)), pipe);
    pipe.producer_commit();
    
    for (int t = 1; t < nTiles; ++t) {
        pipe.consumer_wait();
        processTile(tiles[(t - 1) & 1]);
        pipe.consumer_release();
        
        pipe.producer_acquire();
        cuda::memcpy_async(tiles[t & 1], global_ptr + t * TILE_SIZE,
                           cuda::aligned_size_t<32>(TILE_SIZE * sizeof(float)), pipe);
        pipe.producer_commit();
    }
    pipe.consumer_wait();
    processTile(tiles[(nTiles - 1) & 1]);
    pipe.consumer_release();
}
Block 08 Summary
Key point

TMA is a hardware engine for bulk multidimensional copies. It frees the SM's LD/ST units for useful work. Double buffering with TMA hides DRAM latency.

Key pattern

Ping-pong between two buffers: while TMA loads the next tile, the SM computes on the previous one. This requires explicit management through the CUDA Pipeline API.

Practical consequence

Using cuda::memcpy_async with cuda::pipeline on Hopper/Blackwell automatically engages TMA if alignment and sizes permit.

• • •
Block 09 · PyTorch

PyTorch Optimizations

Quote from the book · Chapter 8

«For PyTorch users writing high-level code, you don't usually manage occupancy directly — PyTorch's CUDA kernels and libraries handle launch parameters under the hood. However, understanding occupancy can still be valuable. If you write custom CUDA kernels as PyTorch extensions, the same principles apply».

Exposition of content

The chapter describes several optimizations available without writing CUDA:

  • model = torch.compile(model, mode="max-autotune") — generates fused GPU kernels, merging many small operations into one larger kernel. This increases work per kernel launch and improves occupancy.
  • optimizer = torch.optim.AdamW(..., fused=True) — the fused implementation performs all optimizer steps in a single kernel, reducing launch overhead and memory traffic.
  • y = torch.where(x > 0, x, torch.zeros_like(x)) — vectorized operations compile into SIMD-style predication, providing warp-coherent execution without divergence.

It is separately emphasized that torch.compile does not guarantee optimal occupancy or ILP — this may require profiling and additional tuning. It is also noted that the PyTorch compiler does not always automatically use all capabilities — sometimes registering custom kernels through Triton is required.

Block 09 Summary
Key point

For PyTorch users, many optimizations are available without writing CUDA. torch.compile generates fused kernels, fused optimizers reduce overhead, torch.where provides predication.

Limitation

torch.compile does not guarantee optimal occupancy or ILP. Achieving maximum performance may require profiling and registering custom Triton kernels.

Practical consequence

Start with torch.compile and fused optimizers. If performance is insufficient — profile and write custom kernels for hot spots.

• • •
Block 10 · Power

Power Monitoring

Quote from the book · Chapter 8

«Prolonged near-peak compute utilization can sometimes invoke power management compute limiters on modern GPUs to keep them healthy. Be sure to take this into account when you're profiling, benchmarking, and tuning. The easiest way to see if you're being power-limited is to use nvidia-smi with a query for power draw, current clocks, and throttle reasons in real time».

Exposition of content

The chapter describes power monitoring as part of the optimization process. The command:

nvidia-smi \
  --query-gpu=power.draw,clocks.current.sm,clocks.current.memory,\
clocks_event_reasons.active \
  --format=csv -l 1

outputs a new line every second with current power draw, graphics and memory clocks, as well as throttle reasons. This allows precisely determining when the GPU hits its power cap and downclocks.

Note for legacy clusters: on NVIDIA drivers older than 535 the same field is named clocks_throttle_reasons.active. If the query returns a «Field not found» error, switch to the old field name; it is still supported on legacy drivers.

Programmatic access is available via NVML: nvmlDeviceGetPowerUsage() and nvmlDeviceGetEnforcedPowerLimit(). These APIs are ideal for custom scripts or integration with monitoring systems.

Block 10 Summary
Key point

Under sustained near-peak load, the GPU may hit its power limit and reduce clocks. Power monitoring is part of the optimization process.

Tools

nvidia-smi with a query for power.draw, clocks.current.sm, clocks_event_reasons.active (on drivers <535 — clocks_throttle_reasons.active). Programmatic access via NVML.

Practical consequence

If the GPU hits its power limit, consider tuning power limit or underclocking to improve efficiency per watt.

• • •
Block 11 · Checklist

GPU Kernel Optimization Checklist

Quote from the book · Chapter 8

«In this chapter, you saw how to uncover and eliminate GPU kernel bottlenecks by moving work from slow global memory into faster on-chip resources and compute units. By following a cycle of profiling, diagnosing, optimizing, and reprofiling, you can transform kernels from underutilized or memory-bound into compute-saturated, high-throughput routines».

Exposition of content

The chapter concludes with a checklist that systematizes the optimization process:

  1. Profile — run nsys, find hot kernels, drill down with ncu.
  2. Determine the regime — underutilization, latency, memory, or compute.
  3. Check occupancy — if below 30%, look for the limiter.
  4. Apply __launch_bounds__ — if registers limit occupancy.
  5. Check stall reasons — Long Scoreboard → memory, Exec Dependency → ILP, Compute Unit Busy → compute.
  6. Increase ILP — unroll loops, break dependency chains.
  7. Minimize warp divergence — predication, warp-uniform branches, torch.where.
  8. Use shuffle — for intra-warp exchange instead of shared memory.
  9. Apply prefetch — double buffering, TMA on supported GPUs.
  10. Re-profile — verify the target metric improved.
  11. Verify accuracy — if reduced precision was used, compare against a reference.
  12. Iterate — fixing one bottleneck reveals the next.
Block 11 Summary
Key point

GPU kernel optimization is a systematic process, not a bag of tricks. The 12-point checklist covers the full cycle.

Key cycle

Profiling → diagnosing → optimizing → reprofiling. Each fix reveals the next bottleneck.

Practical consequence

Follow the checklist sequentially. Don't optimize blindly. Verify accuracy after each change.

• • •
Block 12 · Forecast Verification

Forecast Verification and Current Data

Fregly's book was written in 2025, when Blackwell was just entering the market. Below is a comparison of Chapter 8's forecasts with open data and clarification of figures.

Confirmed Forecast 1: profiling and automation

«It is important to set up automated performance tests to detect regressions early in the development cycle».

Confirmed. Modern frameworks (PyTorch, TensorRT-LLM) include built-in profilers and CI pipelines with benchmarks. PyTorch performance HUD tracks regressions in nightly builds. MLPerf became the de facto standard for reproducible measurement. Tools like Nsight Systems and Nsight Compute are updated for each new GPU architecture.

Confirmed Forecast 2: occupancy and ILP as complementary factors

«ILP and occupancy mutually compensate each other. With ILP=4, 25% occupancy is enough to saturate the SM; without ILP, 75% is required».

Confirmed. The table of ILP dependence on the required number of threads (1→1536, 2→1024, 3→768, 4→512) is consistent with NVIDIA data and architecture guides. On Blackwell with its 64 warps per SM and 256 KB register file, this dependence holds. The only clarification: on Blackwell, unified CUDA cores execute either FP32 or INT32 in one cycle, which reduces the efficiency of mixed INT+FP instruction streams compared to earlier architectures.

Confirmed Forecast 3: TMA and async prefetch

«TMA can perform 1D–5D bulk copies with arbitrary strides between global and shared memory without blocking the SM instruction pipeline».

Confirmed. TMA (Tensor Memory Accelerator) appeared in Hopper and was developed in Blackwell. The CUDA Pipeline API (cuda::memcpy_async, cuda::pipeline) automatically engages TMA on supported architectures. On Blackwell, TMA works together with TMEM to serve fifth-generation Tensor Cores (see the Chapter 6 review).

Confirmed Forecast 4: PyTorch compiler as the first optimization tool

«torch.compile generates fused GPU kernels, merging many small operations into one larger kernel».

Confirmed. torch.compile with TorchInductor generates fused kernels, reducing launch overhead. However, as noted in the chapter, the compiler does not guarantee optimal occupancy or ILP. Achieving maximum performance may require registering custom Triton kernels or manual tuning.

Partially confirmed Forecast 5: power consumption as a limiting factor

«Prolonged near-peak compute utilization can sometimes invoke power management compute limiters on modern GPUs».

Partially confirmed. On B200, TDP is 1000 W; on B300 — 1400 W. In an NVL72 rack — 130 kW. For most inference workloads (which are usually memory-bound rather than compute-bound), power limiting is not the primary factor. However, for dense computations (e.g., training), power limiting can reduce clocks. On Blackwell Ultra and Rubin, cooling and power become more acute.

• • •
Block 13 · What's Not Covered

What's Not Covered in the Chapter

Chapter 8 describes fundamental optimization principles but leaves out several aspects that emerged in real-world Blackwell operation.

Additional observations

Impact of dual-die Blackwell architecture on optimization. The chapter does not mention that B200 consists of two dies connected by NV-HBI at 10 TB/s. For a programmer, this looks like one GPU, but at the memory performance level, anomalies related to die load imbalance may occur. In rare cases, diagnosing may require checking balance between dies.

Interaction with NVLink and NVSwitch. The chapter focuses on intra-kernel optimization, but for multi-GPU workloads, understanding topology is critical. Optimizing one kernel won't help if data is transferred over a slow interconnect: NVLink 5 on B200 delivers 1.8 TB/s per GPU, but beyond an NVL72 rack, throughput drops to 100–200 GB/s over InfiniBand. NVLink/NVSwitch topology and interconnect bandwidth are covered in detail in the Chapter 4 review.

MoE model specifics. For MoE models (like DeepSeek-V3), optimizing attention and MLP layers requires accounting for all-to-all communications between experts. The chapter does not cover this, but for real inference workloads, this is often the dominant factor.

FP4 limitations. The chapter mentions FP4 as a performance multiplier but does not examine limitations: FP4 requires calibration, not all models tolerate such precision, and for critical layers (early layers, attention softmax, layer norms) higher precision is required. Transformer Engine solves this through automatic per-layer precision selection but requires validation on the specific model.

Inference vs training specifics. The chapter describes general principles but does not distinguish optimization for training vs inference. For training, compute-bound kernels (GEMM) dominate; for inference — memory-bound (attention, decode). Accordingly, optimal strategies differ: for training, ILP and Tensor Cores are more important; for inference — memory bandwidth and low latency.

• • •
Block 14 · Additional Conclusions

Additional Conclusions

  1. GPU kernel optimization is not a bag of tricks but a systematic process. Chapter 8 provides a clear framework: profiling → diagnosing → optimizing → reprofiling. Each fix reveals the next bottleneck. This cycle repeats until target performance is achieved.
  2. Occupancy, ILP, and warp efficiency are three complementary directions. You cannot optimize one at the expense of others. High occupancy without ILP yields many warps that wait on memory. High ILP without occupancy cannot hide latency. Efficient warps without sufficient instructions in flight cannot fill the pipelines.
  3. Blackwell changes the rules. Architectural shifts (126 MB L2, 10 TB/s NV-HBI, unified CUDA cores, fifth-generation Tensor Cores with TMEM) require revising techniques that worked on Ampere and Hopper. Their impact on ILP and mixed INT+FP streams is discussed above — see Forecast 2 verification.
  4. PyTorch compiler is the first tool but not a panacea. torch.compile with TorchInductor generates fused kernels and reduces launch overhead. However, achieving maximum performance may require registering custom Triton kernels or manual tuning. Profiling remains mandatory.
  5. Power consumption and cooling are part of the equation. On Blackwell Ultra and Rubin, power limiting and cooling become more acute. For inference workloads (which are usually memory-bound), power limiting is less often the main factor, but for training and dense computations it can reduce clocks.
  6. Investments in understanding principles pay off better than knowledge of numbers. An engineer who understands why ILP compensates for occupancy, why TMA frees LD/ST units, why warp divergence reduces efficiency, adapts to a new GPU generation in days. One who knows only «how to configure NCCL on H100» will take months to relearn.
• • •
Block 15 · Key Points

Key Points

  1. Profiling is a two-stage process. Nsight Systems for the big picture and finding hot kernels, Nsight Compute for detailed analysis. NVTX annotations make the timeline readable.
  2. Four bottleneck regimes. Underutilization, latency-bound, memory-bound, compute-bound. Determining the regime is the first step toward the right optimization strategy.
  3. Warp stall reasons. Long Scoreboard → memory, Short Scoreboard → shared memory, Exec Dependency → ILP, Compute Unit Busy → compute. Nsight Compute provides the breakdown.
  4. Occupancy is a means, not a goal. After 50–75%, other factors begin to dominate. Key limiters: registers per thread, shared memory per block, thread count.
  5. Warp divergence reduces efficiency. Predication, warp-uniform branching, shuffle intrinsics are the main countermeasures. torch.where compiles into predication.
  6. ILP compensates for occupancy. With ILP=4, 25% occupancy is enough to saturate the SM. Optimum on Blackwell — 2–4 independent operations per thread.
  7. TMA is a hardware engine for bulk copies. Frees the SM's LD/ST units. Double buffering with TMA hides DRAM latency. CUDA Pipeline API automatically engages TMA.
  8. PyTorch compiler is the first optimization tool. torch.compile generates fused kernels, fused optimizers reduce overhead, torch.where provides predication. But the compiler does not guarantee optimal occupancy or ILP.
  9. Power consumption is part of the equation. Under sustained near-peak load, the GPU may hit its power limit. Monitoring via nvidia-smi and NVML (on drivers <535 the throttle field is named clocks_throttle_reasons.active).
  10. Optimization is a systematic process. Profiling → diagnosing → optimizing → reprofiling. Each fix reveals the next bottleneck.
  11. Blackwell changes the rules. 126 MB L2, 10 TB/s NV-HBI, unified CUDA cores, fifth-generation Tensor Cores with TMEM — techniques that worked on Ampere and Hopper require revision. Details — in Forecast 2 verification.
  12. Investments in principles pay off better than knowledge of numbers. Understanding fundamental principles (memory hierarchy, ILP, occupancy, warp divergence) ensures adaptation to any GPU generation.
Overall Review Summary

Chapter 8 of Fregly's book is a systematic breakdown of three complementary GPU kernel optimization directions: occupancy tuning, warp efficiency, and instruction-level parallelism. The key idea: optimization is not a bag of tricks but a systematic process of profiling, diagnosing, and reprofiling. For an engineer, this means understanding fundamental principles (memory hierarchy, ILP, occupancy, warp divergence) is more important than knowing specific numbers of the current GPU generation.

Forecast verification shows: the book proved accurate in its predictions. The techniques described in the chapter apply to Blackwell and, accounting for architectural changes (unified CUDA cores, TMA, TMEM), remain relevant. The main takeaway remains unchanged: investments in fundamental principles ensure adaptation to any generation of hardware.

Sources: Chris Fregly, «AI Systems Performance Engineering» (O'Reilly, 2026), Chapter 8 — quotes provided in limited volume for review and critical analysis purposes. NVIDIA Nsight Systems, Nsight Compute, CUDA Toolkit, Blackwell Architecture documentation; MLPerf Training/Inference v5.0; PyTorch Documentation; NVIDIA Technical Blog.