FinCodeCow · AI-инфраструктура

Межъядерный конвейеринг, синхронизация и потоково-упорядоченные аллокации памяти в CUDA

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

Настоящий материал представляет собой обзор главы 11 книги 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).

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

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

Глава 11 книги Fregly завершает блок, посвящённый низкоуровневой оптимизации CUDA. Если главы 9 и 10 были сосредоточены на внутриядерных техниках — арифметической интенсивности, Tensor Cores, специализации варпов и персистентных ядрах, — то глава 11 поднимается на уровень выше: как организовать параллельную работу между ядрами, между GPU и хостом, и между самими GPU. Ключевая идея главы: производительность GPU определяется не только плотностью загрузки SM, но и одновременной работой всех аппаратных подсистем — вычислительных SM, DMA-движков и интерконнектов.

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

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

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

Глава делит материал на две взаимодополняющие части: механику потоков и аллокатора (первые разделы) и практические сценарии — двухуровневые конвейеры, кластеры, PDL и много-GPU оркестрацию (последующие разделы). В отличие от глав 9 и 10, где рассматривались отдельные ядра, здесь акцент смещается на взаимодействие всех аппаратных подсистем GPU одновременно.

• • •

IМеханика потоков CUDA

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

«Поток CUDA — это упорядоченная очередь операций: запусков ядер, копирований памяти, аллокаций и освобождений. Операции внутри одного потока выполняются строго в порядке постановки (FIFO). Операции в разных потоках могут выполняться параллельно, если позволяют аппаратные ресурсы».

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

В главе подчёркивается критическая роль флага cudaStreamNonBlocking. Без него поток подчиняется правилам синхронизации с устаревшим потоком по умолчанию (stream 0), что уничтожает весь параллелизм. Отдельно рассматриваются приоритеты потоков: cudaStreamCreateWithPriority позволяет задать приоритет, который планировщик GPU учитывает при выборе, какой блок потоков запустить на освободившемся SM. Приоритет невытесняющий — работающее ядро не прерывается.

Схема 1. CPU → 2 потока → 5 ядер

Один CPU-поток ставит ядра в два потока CUDA, ядра выполняются параллельно

ЦП поток 1 A B 2 3 поток 1 ker_1 ker_B ker_3 поток 2 ker_A ker_2 время →
Описание: Пять ядер распределены по двум потокам CUDA. ker_1 и ker_3 идут в stream1, ker_A и ker_2 — в stream2. Хост-код cpu_code_1() и cpu_code_2() выполняется параллельно с GPU-работой. Без флага cudaStreamNonBlocking этот параллелизм был бы уничтожен неявной синхронизацией с stream 0.
cudaStream_t stream1, stream2;
cudaStreamCreateWithFlags(&stream1, cudaStreamNonBlocking);
cudaStreamCreateWithFlags(&stream2, cudaStreamNonBlocking);
myKernel<<<grid, block, 0, stream1>>>(...);
cudaMemcpyAsync(dest, src, size, cudaMemcpyHostToDevice, stream2);
Итоги раздела I
Ключевое положение

Поток CUDA — упорядоченная FIFO-очередь. Флаг cudaStreamNonBlocking критичен для предотвращения неявной синхронизации с stream 0.

Приоритеты

cudaStreamCreateWithPriority для latency-sensitive операций, в частности для NCCL. PyTorch использует высокоприоритетные потоки для NCCL-коммуникаций.

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

Явное управление потоками — первый шаг к эффективному использованию всех подсистем GPU.

• • •

IIТрёхстороннее перекрытие

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

«GPU имеет как минимум два независимых DMA-движка: один для Host→Device, другой для Device→Host. Вычислительные SM работают независимо от обоих. Это даёт потенциально трёхстороннее перекрытие: вычислительное ядро для батча N работает на SM, H2D-копирование батча N+1 идёт на DMA H2D, D2H-копирование батча N-1 идёт на DMA D2H».

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

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

Схема 2. Трёхстороннее перекрытие

Вычислительное ядро, H2D-копирование и D2H-копирование работают параллельно

вычисление ядро H2D копирование D2H копирование время →
Описание: Три подсистемы работают одновременно: SM выполняет вычислительное ядро, DMA H2D копирует следующий батч данных, DMA D2H возвращает результаты предыдущего батча. Потоки CUDA позволяют этим трём операциям выполняться параллельно. Порог насыщения наступает, когда вычисления полностью загружают пропускную способность памяти.
Итоги раздела II
Ключевое положение

Четыре условия перекрытия: pinned-память, неблокирующие потоки, асинхронные аллокации, достаточные ресурсы SM.

Ограничение

Когда вычисления насыщают пропускную способность памяти, конвейер копирования замедляется: обе операции делят шину.

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

2–4 потоков достаточно для большинства конвейеров. Большее количество увеличивает накладные расходы и риск превышения лимита резидентных сеток.

• • •

IIIПотоково-упорядоченный аллокатор памяти

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

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

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

Глава подробно разбирает внутреннее устройство пула памяти: каждый поток имеет свою очередь операций, аллокация в потоке A не блокирует поток B, освобождение возвращает блок в пул. Атрибут cudaMemPoolAttrReleaseThreshold управляет тем, сколько памяти пул удерживает перед возвратом ОС. Отдельно рассматривается интеграция с PyTorch: стандартный кэширующий аллокатор уже потоково-осведомлённый; включение бэкенда cudaMallocAsync дополнительно снижает фрагментацию.

Схема 3. Потоково-упорядоченный аллокатор

Три потока аллоцируют и освобождают память в общем пуле без глобальной синхронизации

поток 1 выделение поток 2 выделение поток 3 освобождение Пул памяти CUDA блок блок переиспользуемый блок без глобальной синхронизации
Описание: Каждый поток возвращает блок в пул без остановки других потоков. Аллокация в потоке A не блокирует поток B, освобождение в потоке A возвращает блок в пул, но другой поток не может немедленно его получить — пока не вставлена зависимость через событие. Это устраняет два класса накладных расходов: глобальную синхронизацию и повторную инициализацию драйвера.
cudaMallocAsync(&d_data1, dataSizeBytes, stream1);
computeKernel<<<gridDim, blockDim, 0, stream1>>>(d_data1, d_result1);
cudaFreeAsync(d_data1, stream1);
Итоги раздела III
Ключевое положение

cudaMalloc — блокирующая операция уровня устройства. cudaMallocAsync ставит аллокацию в очередь потока без глобальной синхронизации.

Приводимые в главе цифры

cudaMemPoolAttrReleaseThreshold управляет удержанием памяти. PyTorch включается через PYTORCH_ALLOC_CONF=backend:cudaMallocAsync.

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

Для конвейерных нагрузок используется cudaMallocAsync. Это устраняет глобальные синхронизации.

• • •

IVСобытия CUDA и колбэки хоста

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

«События CUDA — это легковесные маркеры, которые поток записывает в определённой точке своей очереди команд. Другие потоки могут ждать этих маркеров, не блокируя CPU или всё устройство… cudaLaunchHostFunc позволяет зарегистрировать функцию хоста, которая будет вызвана после завершения работы в потоке GPU».

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

Глава подчёркивает флаг cudaEventDisableTiming, снижающий накладные расходы, если событие используется только для синхронизации. Отдельно рассматривается критическое ограничение cudaLaunchHostFunc: из колбэка нельзя вызывать никакие CUDA API — это может привести к взаимоблокировке.

Схема 4. CUDA-события: record → wait

Только consumer-поток ждёт событие; CPU свободен

производитель ядро E потребитель ожидание(E) ядро только потребитель ждёт — ЦП свободен
Описание: Event record + stream wait — тонкая синхронизация без глобальной блокировки. Только consumer-поток ожидает событие, остальные потоки и CPU продолжают работу. Флаг cudaEventDisableTiming снижает накладные расходы, если событие используется только для синхронизации.
cudaEvent_t doneEvent;
cudaEventCreateWithFlags(&doneEvent, cudaEventDisableTiming);
producerKernel<<<grid, block, 0, stream0>>>(...);
cudaEventRecord(doneEvent, stream0);
cudaStreamWaitEvent(stream1, doneEvent, 0);
consumerKernel<<<grid, block, 0, stream1>>>(...);
Итоги раздела IV
Ключевое положение

События CUDA — легковесные маркеры для точечной синхронизации. cudaEventDisableTiming снижает накладные расходы.

Ограничение

Из cudaLaunchHostFunc нельзя вызывать CUDA API.

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

Для координации зависимостей используется cudaStreamWaitEvent — блокируется только конкретный поток.

• • •

VProgrammatic Dependent Launch (PDL)

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

«PDL (Programmatic Dependent Launch) — это механизм, позволяющий одному ядру сигнализировать другому о завершении, не дожидаясь полного сворачивания. Ядро B может начать выполнение ещё до того, как ядро A полностью завершится, если ядро A уже подготовило все необходимые данные».

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

PDL описывается как три компонента: cudaTriggerProgrammaticLaunchCompletion() в первичном ядре, cudaGridDependencySynchronize() в зависимом ядре и cudaLaunchAttributeProgrammaticStreamSerialization на стороне хоста. Эта комбинация позволяет Kernel B перекрывать эпилог Kernel A. Современные исследования показывают, что PDL даёт ускорение до 10–15% на фазах генерации токенов и до 33% при комбинировании с CUDA Graphs на последовательных слоях LLM.

Схема 5. PDL: ядро A триггерит ядро B

B перекрывает эпилог A без обхода через CPU

время → Ядро A основная работа T эпилог Ядро B без обхода через ЦП
Описание: Ядро B стартует, перекрывая эпилог ядра A. Триггер T в теле ядра A сигнализирует о готовности данных. Ядро B использует cudaGridDependencySynchronize() для ожидания сигнала. Размещение триггера — это trial and error: слишком ранний триггер вызывает конкуренцию за ресурсы; слишком поздний не даёт выигрыша.
__global__ void kernel_A(float* d_ptr) {
    cudaTriggerProgrammaticLaunchCompletion();
}
__global__ void kernel_B(float* d_ptr) {
    cudaGridDependencySynchronize();
}
cudaLaunchAttribute attrs[1];
attrs[0].id = cudaLaunchAttributeProgrammaticStreamSerialization;
attrs[0].val.programmaticStreamSerializationAllowed = 1;
Итоги раздела V
Ключевое положение

PDL — межъядерное перекрытие без обхода через CPU. Три компонента: триггер, синхронизация, атрибут запуска.

Современные данные

PDL даёт 10–15% ускорения на генерации токенов, до 33% при комбинации с CUDA Graphs на последовательных слоях.

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

PDL сокращает задержку между последовательными ядрами, но требует ручной настройки размещения триггера.

• • •

VIКластеры блоков потоков и графы CUDA

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

«Кластеры блоков потоков позволяют группировать блоки потоков, которые могут обмениваться данными через Distributed Shared Memory (DSMEM). Один ведущий блок загружает тайл один раз, а остальные блоки в кластере читают данные из его разделяемой памяти через cluster.map_shared_rank… Кооперативные и кластерные запуски сериализуются с другими кооперативными ядрами в том же потоке».

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

Кластеры дают переиспользование тайлов между блоками через DSMEM; TMA-мультикаст позволяет одному DMA-запросу раздать тайл нескольким CTA. Без мультикаста каждый блок выдаёт собственный TMA-запрос — четыре отдельных чтения глобальной памяти. С мультикастом — одно чтение и вещание через DSMEM. Потоки CUDA позволяют перекрывать кластерное ядро в одном потоке с аллокациями, копированиями и некооперативными ядрами в других потоках.

Схема 6. TMA-мультикаст в кластер

Ведущий CTA загружает один тайл и вещает его остальным CTA через DSMEM

ведущий CTA cp.async.bulk.tensor ведомый 0 map_shared_rank() ведомый 1 map_shared_rank() ведомый 2 map_shared_rank() cluster.sync() перед использованием
Описание: Один TMA-запрос раздаёт тайл нескольким CTA через DSMEM. Ведущий блок загружает данные один раз, follower-блоки читают их через map_shared_rank. Перед использованием обязателен cluster.sync(). Кластеры ко-планируются в GPC: если размер кластера не делит число SM на GPC, часть SM простаивает.
cudaStreamBeginCapture(captureStream, cudaStreamCaptureModeGlobal);
cudaMallocAsync(&dptr, size, captureStream);
kernelA<<<grid, block, 0, captureStream>>>(dptr);
cudaStreamEndCapture(captureStream, &graph);
cudaGraphInstantiate(&execGraph, graph, nullptr, nullptr, 0);
cudaGraphLaunch(execGraph, captureStream);
Итоги раздела VI
Ключевое положение

Кластеры дают переиспользование тайлов через DSMEM. CUDA Graphs фиксируют аллокации как узлы графа.

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

Комбинация CUDA Graphs + cudaMallocAsync устраняет повторяющиеся накладные расходы на аллокацию при каждой итерации.

Ограничение

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

• • •

VIIМного-GPU и NCCL

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

«PyTorch использует высокоприоритетные потоки для NCCL-коммуникаций».

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

В PyTorch DistributedDataParallel NCCL-коммуникации запускаются на выделенных высокоприоритетных потоках. Параметр is_high_priority_stream=True в ProcessGroupNCCL.Options позволяет драйверу CUDA приоритезировать NCCL-ядра, когда есть ожидающие вычислительные ядра. Это перекрытие может скрыть 70–90% времени коммуникаций на NVLink-соединённых GPU. Граф зависимостей через события связывает цепочку A→B→D, тогда как поток C работает полностью параллельно.

Схема 7. Граф зависимостей через события

Поток A → поток B → поток D синхронизированы событиями; поток C независим

поток A поток B поток C поток D граф межпоточных зависимостей
Описание: Событие связывает цепочку A→B→D. Поток C работает полностью параллельно, не дожидаясь остальных. CPU при этом не блокируется — все зависимости разрешаются на стороне устройства.
nccl_options = dist.ProcessGroupNCCL.Options(is_high_priority_stream=True);
dist.init_process_group("nccl", pg_options=nccl_options);
backwardLayer<<<grid, block, 0, computeStream>>>(...);
cudaEventRecord(gradReady, computeStream);
cudaStreamWaitEvent(ncclStream, gradReady, 0);
ncclAllReduce(grad, grad, size, ncclFloat, ncclSum, comm, ncclStream);
Итоги раздела VII
Ключевое положение

NCCL на высокоприоритетном потоке с cudaStreamWaitEvent позволяет перекрывать коммуникации с вычислениями.

Современные данные

Перекрытие может скрыть 70–90% времени коммуникаций на NVLink-соединённых GPU.

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

is_high_priority_stream=True — стандартная практика в production-кластерах.

• • •

VIIIПрактика в LLM

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

«Нагрузки обучения и инференса LLM — это сценарии, где межъядерный параллелизм через потоки CUDA даёт наибольший выигрыш. Каждый слой трансформера требует дополнительной памяти для промежуточных вычислений… Поскольку разные мини-батчи могут различаться по длине, размеры scratch-буферов варьируются».

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

В главе приводятся четыре сценария применения потоково-упорядоченного аллокатора в LLM: scratch-буферы переменного размера, растущий KV-кэш при авторегрессивной декодировке, послойная конвейеризация и перекрытие вычислений с NCCL-коммуникациями. С cudaMallocAsync расширение KV-кэша выполняется в том же потоке, который запускает ядро attention, без остановки других потоков.

Схема 8. Block-wide barrier останавливает все потоки

Пока не все потоки достигли __syncthreads() — никто не продолжает

Потоки (warps) Warp 0 Warp 1 Warp 2 Warp 3 ✓ __syncthreads() После барьера Никто не прошёл пока все не прибудут ⚠ Block-wide barrier = idle warps. Используйте pipeline API для точечной синхронизации.
Описание: Барьер уровня блока останавливает все варпы, даже готовые. В конвейерах это приводит к простоям SM. Вместо блокирующих барьеров используется cuda::pipeline с точечными producer/consumer handoff'ами.
Итоги раздела VIII
Ключевое положение

В LLM потоки CUDA применяются для scratch-буферов, растущего KV-кэша, послойной конвейеризации и перекрытия NCCL.

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

Для per-batch scratch-буферов используется cudaMallocAsync: каждый мини-батч получает ровно столько памяти, сколько нужно.

Вывод для инженеров

Перекрытие computation и communication достигается через cudaEventRecord + cudaStreamWaitEvent. Это позволяет all-reduce идти параллельно с вычислением следующего слоя.

• • •

IXПроверка прогнозов

Подтверждено Прогноз 1: потоково-упорядоченный аллокатор становится стандартом

«В конвейерной нагрузке вызов cudaMalloc остановит все потоки».

Подтверждено. К 2026 году cudaMallocAsync стал стандартом де-факто для production-нагрузок. PyTorch, TensorFlow и JAX поддерживают его. Исследования IEEE показывают, что асинхронный аллокатор достигает микросекундной задержки против 100–500 мкс у cudaMalloc. PyTorch PR #188110 добавил механизм trim-and-retry для обработки OOM в stream-ordered pool.

Подтверждено Прогноз 2: NCCL на высокоприоритетном потоке

«PyTorch использует высокоприоритетные потоки для NCCL-коммуникаций».

Подтверждено. Параметр is_high_priority_stream доступен в ProcessGroupNCCL.Options с флагом по умолчанию False, но в production-кластерах он включается. Это позволяет NCCL-ядрам получать приоритет при конкуренции с вычислительными ядрами.

Подтверждено Прогноз 3: PDL даёт ускорение

«Ядро B может начать выполнение ещё до того, как ядро A полностью завершится».

Подтверждено. Исследования на RTX PRO 6000 показывают 10% ускорения на фазах генерации токенов. На DGX Spark — 4–5%. Triton PR #6394 демонстрирует до 33% ускорения на последовательных слоях LLM при комбинации PDL + CUDA Graphs.

Подтверждено Прогноз 4: TMA-мультикаст даёт переиспользование

«Один ведущий блок загружает тайл один раз, а остальные блоки читают данные из его разделяемой памяти».

Подтверждено. TMA-мультикаст позволяет одному DMA-запросу раздать тайл всем CTA кластера. Без мультикаста каждый блок выдаёт собственный TMA-запрос — четыре отдельных чтения глобальной памяти. С мультикастом — одно чтение и вещание через DSMEM.

• • •

XЗа пределами главы

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

Взаимодействие с CUDA Graphs. При захвате графа cudaMallocAsync записывается как узел аллокации, а cudaFreeAsync — как узел освобождения. При воспроизведении графовые аллокации переиспользуют те же адреса. Это означает, что cudaMallocAsync и CUDA Graphs усиливают друг друга: аллокации становятся частью графа, устраняя накладные расходы при каждой итерации.

PDL требует ручной настройки. Размещение триггера launch completion — это trial and error. Слишком ранний триггер вызывает конкуренцию за ресурсы; слишком поздний не даёт выигрыша. В некоторых ядрах (например, mul_mat_vec_q) ранний триггер даёт отрицательный эффект.

Кластеры ко-планируются в GPC. Если размер кластера не делит число SM на GPC, часть SM простаивает. Blackwell вводит preferred/fallback размеры кластеров для смягчения этой квантованности.

Многопроцессная аллокация. В production-нагрузках на одном GPU часто работают несколько процессов (MPS или MIG). В этом случае каждый процесс имеет свой пул памяти, и переиспользование блоков между процессами невозможно. В мультитенантных inference-серверах применяется MIG для изоляции пулов или MPS с настройкой активных потоков.

Nsight Systems и NVTX. Временная шкала Nsight Systems может содержать тысячи событий, и идентификация проблем требует навигации по NVTX-диапазонам. Без NVTX временная шкала остаётся нечитаемой для многопоточных конвейеров.

NIXL для disaggregated inference. NIXL — библиотека для point-to-point передач в disaggregated inference — использует потоки CUDA для асинхронных передач и интегрируется с потоково-упорядоченным аллокатором. Для inference с передачей KV-кэша между prefill и decode-узлами NIXL является предпочтительным инструментом, а не NCCL send/recv.

• • •

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

  1. Четыре подсистемы GPU. SM, DMA H2D, DMA D2H, аллокатор. Цель — одновременная работа всех четырёх.
  2. Потоки CUDA — FIFO-очереди. cudaStreamNonBlocking критичен для предотвращения неявной синхронизации.
  3. Трёхстороннее перекрытие. Вычисление на SM, H2D-копирование на DMA H2D, D2H-копирование на DMA D2H — одновременно.
  4. cudaMallocAsync вместо cudaMalloc. Устраняет глобальные синхронизации в конвейере.
  5. Поток 0 — глобальный барьер. Используется только для одноразовых операций.
  6. События CUDA — точечная синхронизация. cudaEventDisableTiming снижает накладные расходы.
  7. PDL — межъядерное перекрытие. Три компонента: триггер, синхронизация, атрибут запуска. Даёт до 33% ускорения на последовательных слоях.
  8. Кластеры + TMA-мультикаст. Переиспользование тайлов через DSMEM. Один DMA-запрос раздаёт тайл всем CTA кластера.
  9. CUDA Graphs + cudaMallocAsync. Аллокации фиксируются в графе, устраняя повторяющиеся накладные расходы.
  10. NCCL на высокоприоритетном потоке. Перекрытие коммуникаций с вычислениями скрывает 70–90% времени коммуникаций.
  11. Профилирование через Nsight Systems. Проверка перекрытия, stream 0, pinned-памяти, отсутствия cudaMalloc в конвейере.
Общий итог обзора

Глава 11 книги Fregly — систематический разбор межъядерного параллелизма в CUDA. Ключевая идея: производительность GPU определяется не только плотностью загрузки SM, но и тем, насколько эффективно используются все подсистемы одновременно. Потоки CUDA, потоково-упорядоченный аллокатор, события, PDL, кластеры и графы — инструменты, позволяющие всем аппаратным блокам работать параллельно.

Проверка прогнозов показывает: книга оказалась точной. Потоково-упорядоченный аллокатор стал стандартом; NCCL на высокоприоритетных потоках используется в production; PDL даёт до 33% ускорения на последовательных слоях; TMA-мультикаст подтверждён исследованиями. Главный вывод остаётся неизменным: явное управление потоками, потоково-упорядоченная аллокация и точечная синхронизация через события — три принципа, переживающих поколения.

XIIЗаключение

Межъядерный параллелизм с потоками CUDA — не дополнительная оптимизация, а фундамент производительности современных GPU. Без потоков GPU большую часть времени простаивает: пока копируются данные, SM молчит; пока работает ядро, DMA-движки бездействуют; пока один GPU ждёт данные от другого, его вычислительные ресурсы не используются.

Потоки CUDA, потоково-упорядоченный аллокатор, события, PDL, кластеры блоков потоков и графы CUDA — инструменты, которые позволяют всем аппаратным блокам GPU работать одновременно. В нагрузках LLM, где конвейеризация батчей, слоёв и коммуникаций определяет сквозную задержку, владение этими инструментами — разница между 30% и 90% утилизации GPU.

По мере развития оборудования параллелизм становится всё более многоуровневым: внутри варпа (Tensor Cores), внутри блока (разделяемая память), внутри кластера (DSMEM), между ядрами (PDL, потоки) и между GPU (NCCL, NIXL). Понимание того, как эти уровни наслаиваются, — ключ к извлечению максимальной производительности из высокопроизводительного AI-оборудования.

Источники: Chris Fregly, «AI Systems Performance Engineering» (O'Reilly, 2026), Глава 11 — цитаты приводятся в ограниченном объёме в целях обзора и критического анализа. Официальная документация NVIDIA CUDA 13.1 (Stream-Ordered Memory Allocator, CUDA Graphs); IEEE IPDPSW 2026 (Quantitative Characterization of CUDA Memory Allocators); GitHub PR #22522 (llama.cpp PDL), PR #6394 (Triton PDL), PR #188110 (PyTorch cudaMallocAsync trim-and-retry); DAK arXiv 2604.26074 (TMA Multicast).

FinCodeCow · AI Infrastructure

Inter-Kernel Pipelining, Synchronization, and Stream-Ordered Memory Allocations in CUDA

A review of Chapter 11 of Chris Fregly's «AI Systems Performance Engineering» (O'Reilly, 2026) — with technical depth, forecast verification, and systematization of key points
About the source

This material is a review of Chapter 11 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, forecast verification, and technical details beyond the original text based on official NVIDIA documentation and open sources. Quotes are provided in limited volume for review and critical analysis purposes.

§Chapter Context

Chapter 11 of Fregly's book completes the block dedicated to low-level CUDA optimization. While Chapters 9 and 10 focused on intra-kernel techniques — arithmetic intensity, Tensor Cores, warp specialization, and persistent kernels — Chapter 11 rises to the next level: how to organize parallel work between kernels, between GPU and host, and between GPUs themselves. The key idea: GPU performance is determined not only by how tightly individual SMs are loaded, but also by how efficiently all hardware subsystems — compute SMs, DMA engines and interconnects — are used simultaneously.

Quote from the book · Chapter 11

«So far, we have focused on the intra-kernel tools — cuda::pipeline double-buffering, warp specialization, persistent kernels, and thread-block clusters — to keep the SMs busy for a single kernel. In this chapter we keep those kernels and show how to pipeline across kernels and batches with CUDA streams, events, and the stream-ordered memory allocator».

Exposition of content

The chapter divides the material into two complementary parts: the mechanics of streams and the allocator (first sections) and practical scenarios — two-level pipelines, clusters, PDL and multi-GPU orchestration (later sections). Unlike Chapters 9 and 10, which examined individual kernels, here the emphasis shifts to the interaction of all GPU hardware subsystems simultaneously.

• • •

ICUDA Stream Mechanics

Quote from the book · Chapter 11

«A CUDA stream is an ordered queue of operations: kernel launches, memory copies, allocations, and deallocations. Operations within a stream execute strictly in issue order (FIFO). Operations in different streams may execute in parallel if hardware resources allow».

Exposition of content

The chapter emphasizes the critical role of the cudaStreamNonBlocking flag. Without it, the stream is subject to stream 0 synchronization rules, which destroys all parallelism. Stream priorities via cudaStreamCreateWithPriority are considered separately: the scheduler uses priority when choosing which thread block to launch on a freed SM. Priority is non-preemptive — a running kernel is not interrupted.

Figure 1. CPU → 2 streams → 5 kernels

One CPU thread enqueues kernels into two CUDA streams; kernels execute in parallel

CPU thread 1 A B 2 3 stream 1 ker_1 ker_B ker_3 stream 2 ker_A ker_2 time →
Description: Five kernels are distributed across two CUDA streams. ker_1 and ker_3 go into stream1, ker_A and ker_2 — into stream2. Host code cpu_code_1() and cpu_code_2() runs in parallel with GPU work. Without the cudaStreamNonBlocking flag, this parallelism would be destroyed by implicit synchronization with stream 0.
cudaStream_t stream1, stream2;
cudaStreamCreateWithFlags(&stream1, cudaStreamNonBlocking);
cudaStreamCreateWithFlags(&stream2, cudaStreamNonBlocking);
myKernel<<<grid, block, 0, stream1>>>(...);
cudaMemcpyAsync(dest, src, size, cudaMemcpyHostToDevice, stream2);
Section I Summary
Key point

A CUDA stream is an ordered FIFO queue. cudaStreamNonBlocking is critical to prevent implicit synchronization with stream 0.

Priorities

cudaStreamCreateWithPriority for latency-sensitive operations, in particular NCCL. PyTorch uses high-priority streams for NCCL communications.

Practical consequence

Explicit stream management is the first step to efficient utilization of all GPU subsystems.

• • •

IIThree-Way Overlap

Quote from the book · Chapter 11

«The GPU has at least two independent DMA engines: one for Host→Device, one for Device→Host. Compute SMs operate independently of both, giving potentially three-way overlap: the compute kernel for batch N runs on SMs, H2D copy of batch N+1 goes on DMA H2D, D2H copy of batch N-1 goes on DMA D2H».

Exposition of content

The chapter formulates four conditions for correct overlap: pinned host memory, non-blocking streams, async allocations, and sufficient SM resources. The chapter separately stipulates the saturation threshold: when computations fully load memory bandwidth and the copy pipeline tries to use the same bus, both operations slow down to about 50% each.

Figure 2. Three-way overlap

Compute kernel, H2D copy, and D2H copy run in parallel

compute kernel H2D copy D2H copy time →
Description: Three subsystems work simultaneously: SMs run the compute kernel, DMA H2D copies the next batch, DMA D2H returns the previous batch. CUDA streams let these three operations execute in parallel. The saturation threshold occurs when compute fully loads memory bandwidth.
Section II Summary
Key point

Four conditions for overlap: pinned memory, non-blocking streams, async allocations, sufficient SM resources.

Limitation

When computations saturate memory bandwidth, the copy pipeline slows down: both operations share the bus.

Practical consequence

2–4 streams suffice for most pipelines. More increases overhead and risks exceeding the resident grid limit.

• • •

IIIStream-Ordered Memory Allocator

Quote from the book · Chapter 11

«cudaMalloc is a device-wide blocking operation. Before returning, it performs global synchronization: all pending kernels and copies in all streams must complete… In a pipelined workload, a cudaMalloc call will stop all streams. The overlap you counted on is destroyed».

Exposition of content

The chapter explains the internal structure of the memory pool: each stream has its own operation queue, an allocation in stream A does not block stream B, a free returns the block to the pool. The cudaMemPoolAttrReleaseThreshold attribute controls memory retention. PyTorch integration is considered separately: the default caching allocator is already stream-aware; enabling the cudaMallocAsync backend further reduces fragmentation.

Figure 3. Stream-Ordered Allocator

Three streams allocate and free memory in a shared pool without global synchronization

stream 1 alloc stream 2 alloc stream 3 free CUDA Memory Pool block block reusable block no global synchronization
Description: Each stream returns blocks to the pool without stopping other streams. An allocation in stream A doesn't block stream B, a free in stream A returns a block to the pool. This eliminates two classes of overhead: global synchronization and driver re-initialization.
cudaMallocAsync(&d_data1, dataSizeBytes, stream1);
computeKernel<<<gridDim, blockDim, 0, stream1>>>(d_data1, d_result1);
cudaFreeAsync(d_data1, stream1);
Section III Summary
Key point

cudaMalloc is a device-wide blocking operation. cudaMallocAsync enqueues allocation on the stream without global synchronization.

Figures cited

cudaMemPoolAttrReleaseThreshold controls memory retention. PyTorch enables it via PYTORCH_ALLOC_CONF=backend:cudaMallocAsync.

Practical consequence

Pipelined workloads use cudaMallocAsync. This eliminates global synchronizations.

• • •

IVCUDA Events and Host Callbacks

Quote from the book · Chapter 11

«CUDA events are lightweight markers that a stream records at a point in its command queue. Other streams can wait on these markers without blocking the CPU or the whole device… cudaLaunchHostFunc lets you register a host function that will be called after work in a GPU stream completes».

Exposition of content

The chapter emphasizes the cudaEventDisableTiming flag, which reduces overhead when the event is used only for synchronization. A critical limitation of cudaLaunchHostFunc is considered separately: you cannot call any CUDA API from the callback — this may deadlock.

Figure 4. CUDA events: record → wait

Only the consumer stream waits for the event; the CPU is free

producer kernel E consumer wait(E) kernel only the consumer waits — CPU is free
Description: Event record + stream wait — fine-grained synchronization without global blocking. Only the consumer stream waits for the event; other streams and the CPU continue. The cudaEventDisableTiming flag reduces overhead when the event is used only for synchronization.
cudaEvent_t doneEvent;
cudaEventCreateWithFlags(&doneEvent, cudaEventDisableTiming);
producerKernel<<<grid, block, 0, stream0>>>(...);
cudaEventRecord(doneEvent, stream0);
cudaStreamWaitEvent(stream1, doneEvent, 0);
consumerKernel<<<grid, block, 0, stream1>>>(...);
Section IV Summary
Key point

CUDA events are lightweight markers for fine-grained synchronization. cudaEventDisableTiming reduces overhead.

Limitation

CUDA APIs cannot be called from cudaLaunchHostFunc.

Practical consequence

For coordinating dependencies, cudaStreamWaitEvent is used — it blocks only the specific stream.

• • •

VProgrammatic Dependent Launch (PDL)

Quote from the book · Chapter 11

«PDL (Programmatic Dependent Launch) lets one kernel signal another about completion without waiting for a full drain. Kernel B may begin before Kernel A fully finishes, provided A has already produced the necessary data».

Exposition of content

PDL is described as three components: cudaTriggerProgrammaticLaunchCompletion() in the primary kernel, cudaGridDependencySynchronize() in the dependent kernel, and cudaLaunchAttributeProgrammaticStreamSerialization on the host side. Modern research shows PDL delivers up to 10–15% speedup on token generation and up to 33% when combined with CUDA Graphs on successive LLM layers.

Figure 5. PDL: kernel A triggers kernel B

B overlaps A's epilogue without a CPU round-trip

time → Kernel A main work T epilogue Kernel B no CPU round-trip
Description: Kernel B starts, overlapping kernel A's epilogue. Trigger T in kernel A signals data readiness. Kernel B uses cudaGridDependencySynchronize() to wait for the signal. Trigger placement is trial and error: too early causes resource contention; too late gives no benefit.
__global__ void kernel_A(float* d_ptr) {
    cudaTriggerProgrammaticLaunchCompletion();
}
__global__ void kernel_B(float* d_ptr) {
    cudaGridDependencySynchronize();
}
cudaLaunchAttribute attrs[1];
attrs[0].id = cudaLaunchAttributeProgrammaticStreamSerialization;
attrs[0].val.programmaticStreamSerializationAllowed = 1;
Section V Summary
Key point

PDL is inter-kernel overlap without a CPU round-trip. Three components: trigger, synchronization, launch attribute.

Current data

PDL delivers 10–15% speedup on token generation, up to 33% when combined with CUDA Graphs on successive layers.

Practical consequence

PDL reduces latency between successive kernels but requires manual placement tuning of the trigger.

• • •

VIThread Block Clusters and CUDA Graphs

Quote from the book · Chapter 11

«Thread block clusters let groups of thread blocks share data through Distributed Shared Memory (DSMEM). A leader block loads a tile once, and other blocks read from its shared memory via cluster.map_shared_rank… Cooperative and cluster launches serialize with other cooperative kernels in the same stream».

Exposition of content

Clusters provide tile reuse across blocks via DSMEM; TMA multicast lets one DMA request broadcast a tile to multiple CTAs. Without multicast, each block issues its own TMA request — four separate global memory reads. With multicast — one read and broadcast via DSMEM. CUDA streams allow overlapping a cluster kernel in one stream with allocations, copies, and non-cooperative kernels in other streams.

Figure 6. TMA multicast to a cluster

The leader CTA loads a tile once and broadcasts it to other CTAs via DSMEM

leader CTA cp.async.bulk.tensor follower 0 map_shared_rank() follower 1 map_shared_rank() follower 2 map_shared_rank() cluster.sync() before use
Description: One TMA request distributes a tile to multiple CTAs via DSMEM. The leader block loads data once, follower blocks read it via map_shared_rank. cluster.sync() is mandatory before use. Clusters are co-scheduled within a GPC: if the cluster size doesn't divide the number of SMs per GPC, some SMs idle.
cudaStreamBeginCapture(captureStream, cudaStreamCaptureModeGlobal);
cudaMallocAsync(&dptr, size, captureStream);
kernelA<<<grid, block, 0, captureStream>>>(dptr);
cudaStreamEndCapture(captureStream, &graph);
cudaGraphInstantiate(&execGraph, graph, nullptr, nullptr, 0);
cudaGraphLaunch(execGraph, captureStream);
Section VI Summary
Key point

Clusters provide tile reuse via DSMEM. CUDA Graphs fix allocations as graph nodes.

Practical consequence

The combination CUDA Graphs + cudaMallocAsync eliminates repeated memory allocation overhead on each iteration.

Limitation

Cooperative and cluster launches serialize with other cooperative kernels in the same stream.

• • •

VIIMulti-GPU and NCCL

Quote from the book · Chapter 11

«PyTorch uses high-priority streams for NCCL communications».

Exposition of content

In PyTorch DistributedDataParallel, NCCL communications run on dedicated high-priority streams. The is_high_priority_stream=True option in ProcessGroupNCCL.Options lets the CUDA driver prioritize NCCL kernels when there are waiting compute kernels. This overlap can hide 70–90% of communication time on NVLink-connected GPUs. The dependency graph via events links the chain A→B→D, while stream C runs fully in parallel.

Figure 7. Dependency graph via events

Stream A → stream B → stream D synchronized by events; stream C is independent

stream A stream B stream C stream D cross-stream dependency graph
Description: Events link the chain A→B→D. Stream C runs fully in parallel, not waiting for the others. The CPU is not blocked — all dependencies are resolved on the device side.
nccl_options = dist.ProcessGroupNCCL.Options(is_high_priority_stream=True);
dist.init_process_group("nccl", pg_options=nccl_options);
backwardLayer<<<grid, block, 0, computeStream>>>(...);
cudaEventRecord(gradReady, computeStream);
cudaStreamWaitEvent(ncclStream, gradReady, 0);
ncclAllReduce(grad, grad, size, ncclFloat, ncclSum, comm, ncclStream);
Section VII Summary
Key point

NCCL on a high-priority stream with cudaStreamWaitEvent allows overlapping communications with computations.

Current data

Overlap can hide 70–90% of communication time on NVLink-connected GPUs.

Practical consequence

is_high_priority_stream=True is standard practice in production clusters.

• • •

VIIIPractice in LLMs

Quote from the book · Chapter 11

«LLM training and inference workloads are scenarios where inter-kernel parallelism via CUDA streams delivers the greatest benefit. Each transformer layer requires additional memory for intermediate computations… Since different mini-batches may vary in length, scratch buffer sizes vary».

Exposition of content

The chapter cites four scenarios for using the stream-ordered allocator in LLMs: variable-size scratch buffers, growing KV cache during autoregressive decoding, layer pipelining, and overlapping computation with NCCL communications. With cudaMallocAsync, KV cache expansion happens in the same stream that launches the attention kernel, without stopping other streams.

Figure 8. Block-wide barrier stalls all threads

Until all threads reach __syncthreads(), nobody continues

Threads (warps) Warp 0 Warp 1 Warp 2 Warp 3 ✓ __syncthreads() After the barrier Nobody passed until all arrive ⚠ Block-wide barrier = idle warps. Use pipeline API for fine-grained sync.
Description: A block-level barrier stalls all warps, even ready ones. In pipelines, this causes SM idle time. Instead of blocking barriers, cuda::pipeline with fine-grained producer/consumer handoffs is used.
Section VIII Summary
Key point

In LLMs, CUDA streams are used for scratch buffers, growing KV cache, layer pipelining, and NCCL overlap.

Practical consequence

For per-batch scratch buffers, cudaMallocAsync is used: each mini-batch gets exactly the memory it needs.

Takeaway for engineers

Overlapping computation and communication is achieved via cudaEventRecord + cudaStreamWaitEvent. This lets all-reduce proceed in parallel with computing the next layer.

• • •

IXForecast Verification

Confirmed Forecast 1: stream-ordered allocator becomes standard

«In a pipelined workload, a cudaMalloc call will stop all streams».

Confirmed. By 2026, cudaMallocAsync became the de facto standard for production workloads. PyTorch, TensorFlow, and JAX support it. IEEE research shows the async allocator achieves microsecond-scale latency versus 100–500 µs for cudaMalloc. PyTorch PR #188110 added a trim-and-retry mechanism for handling OOM in the stream-ordered pool.

Confirmed Forecast 2: NCCL on a high-priority stream

«PyTorch uses high-priority streams for NCCL communications».

Confirmed. The is_high_priority_stream option is available in ProcessGroupNCCL.Options with a default of False, but it is enabled in production clusters. This allows NCCL kernels to get priority when competing with compute kernels.

Confirmed Forecast 3: PDL delivers speedup

«Kernel B may begin before Kernel A fully finishes».

Confirmed. Studies on RTX PRO 6000 show 10% speedup on token generation. On DGX Spark — 4–5%. Triton PR #6394 demonstrates up to 33% speedup on successive LLM layers when combining PDL + CUDA Graphs.

Confirmed Forecast 4: TMA multicast provides reuse

«A leader block loads a tile once, and other blocks read from its shared memory».

Confirmed. TMA multicast lets one DMA request broadcast a tile to all CTAs in a cluster. Without multicast, each block issues its own TMA request — four separate global memory reads. With multicast — one read and broadcast via DSMEM.

• • •

XBeyond the Chapter

Additional observations

Interaction with CUDA Graphs. During stream capture, cudaMallocAsync is recorded as an allocation node, cudaFreeAsync as a free node. On replay, graph allocations reuse the same addresses. This means cudaMallocAsync and CUDA Graphs reinforce each other: allocations become part of the graph, eliminating overhead on each iteration.

PDL requires manual tuning. Placement of the launch completion trigger is trial and error. Too early a trigger causes resource contention; too late gives no benefit. In some kernels (e.g., mul_mat_vec_q), an early trigger has a negative effect.

Clusters co-schedule within a GPC. If the cluster size doesn't divide the number of SMs per GPC, some SMs idle. Blackwell introduces preferred/fallback cluster sizes to soften this quantization.

Multi-process allocation. In production workloads, multiple processes often run on one GPU (MPS or MIG). In this case, each process has its own memory pool, and block reuse between processes is impossible. In multi-tenant inference servers, MIG is used for pool isolation or MPS with active thread tuning.

Nsight Systems and NVTX. The Nsight Systems timeline may contain thousands of events, and identifying problems requires navigating NVTX ranges. Without NVTX, the timeline remains unreadable for multithreaded pipelines.

NIXL for disaggregated inference. NIXL — a library for point-to-point transfers in disaggregated inference — uses CUDA streams for asynchronous transfers and integrates with the stream-ordered allocator. For inference with KV cache transfer between prefill and decode nodes, NIXL is the preferred tool rather than NCCL send/recv.

• • •

XIKey Points

  1. Four GPU subsystems. SMs, DMA H2D, DMA D2H, allocator. The goal is simultaneous operation of all four.
  2. CUDA streams are FIFO queues. cudaStreamNonBlocking is critical to prevent implicit synchronization.
  3. Three-way overlap. Compute on SMs, H2D copy on DMA H2D, D2H copy on DMA D2H — simultaneously.
  4. cudaMallocAsync instead of cudaMalloc. Eliminates global synchronizations in the pipeline.
  5. Stream 0 is a global barrier. Used only for one-off operations.
  6. CUDA events are fine-grained synchronization. cudaEventDisableTiming reduces overhead.
  7. PDL is inter-kernel overlap. Three components: trigger, synchronization, launch attribute. Delivers up to 33% speedup on successive layers.
  8. Clusters + TMA multicast. Tile reuse via DSMEM. One DMA request broadcasts a tile to all CTAs in a cluster.
  9. CUDA Graphs + cudaMallocAsync. Allocations are fixed in the graph, eliminating repeated overhead.
  10. NCCL on a high-priority stream. Overlapping communications with computations hides 70–90% of communication time.
  11. Profiling with Nsight Systems. Check stream overlap, stream 0, pinned memory, absence of cudaMalloc in the pipeline.
Overall Review Summary

Chapter 11 of Fregly's book is a systematic review of inter-kernel parallelism in CUDA. The key idea: GPU performance is determined not only by how tightly individual SMs are loaded, but also by how efficiently all subsystems are used simultaneously. CUDA streams, the stream-ordered allocator, events, PDL, clusters, and graphs are tools that let all hardware blocks work in parallel.

Forecast verification shows: the book proved accurate. The stream-ordered allocator became standard; NCCL on high-priority streams is used in production; PDL delivers up to 33% speedup on successive layers; TMA multicast is confirmed by research. The main takeaway remains unchanged: explicit stream management, stream-ordered allocation, and fine-grained event synchronization are three principles that survive generations.

XIIConclusion

Inter-kernel parallelism with CUDA streams is not an add-on optimization but the foundation of modern GPU performance. Without streams, the GPU idles most of the time: while data is copied, SMs are silent; while a kernel runs, DMA engines idle; while one GPU waits for data from another, its compute resources go unused.

CUDA streams, the stream-ordered allocator, events, PDL, thread block clusters, and CUDA graphs are tools that let all hardware blocks work simultaneously. In LLM workloads where pipelining batches, layers, and communications determines end-to-end latency, mastery of these tools is the difference between 30% and 90% GPU utilization.

As hardware evolves, parallelism becomes more layered: within a warp (Tensor Cores), within a block (shared memory), within a cluster (DSMEM), between kernels (PDL, streams), and between GPUs (NCCL, NIXL). Understanding how these levels stack is the key to extracting maximum performance from high-performance AI hardware.

Sources: Chris Fregly, «AI Systems Performance Engineering» (O'Reilly, 2026), Chapter 11 — quotes provided in limited volume for review and critical analysis purposes. Official NVIDIA CUDA 13.1 documentation (Stream-Ordered Memory Allocator, CUDA Graphs); IEEE IPDPSW 2026 (Quantitative Characterization of CUDA Memory Allocators); GitHub PR #22522 (llama.cpp PDL), PR #6394 (Triton PDL), PR #188110 (PyTorch cudaMallocAsync trim-and-retry); DAK arXiv 2604.26074 (TMA Multicast).