CUDA Dynamic Parallelism

重點總覽

CUDA Dynamic Parallelism(CDP)讓「在 GPU 上執行的 kernel」能直接從 device code 再啟動新的 kernel,把 launch 決策搬到 runtime、減少 host↔device 來回,特別適合遞迴、不規則迴圈等不符合單層平坦平行性的演算法。

項目 重點
本質 device code 透過 <<< >>> 啟動 child grid,產生資料相依的動態平行工作
版本 CDP2 為 CUDA 12.0+ 預設、CC 9.0+ 唯一版本;CDP1 為 legacy(-DCUDA_FORCE_CDP1_IF_SUPPORTED,將被移除)
Parent/Child 巢狀關係:parent 必須等所有 child 完成才算結束(implicit synchronization)
物件範圍 streams/events 由 device API 建立者只在「建立它的 grid」內有效
Stream 排序 同一 stream 內 in-order(fire-and-forget 例外);named stream 全 grid 共用、NULL stream 僅 block 內共用
並行 CDP 不引入任何新的並行保證;不可依賴 block 間或 parent/child 間並行
記憶體共享 global/mapped 共用指標;local/shared 不共用;texture 唯讀共用
Global 一致性 weak consistency;唯一完全一致點是 child 被 invoke 的瞬間
取回 child 結果 移除 cudaDeviceSynchronize() 後,parent 退出前只能靠 cudaStreamTailLaunch kernel
Device Runtime per-thread API、語意比照 host runtime,可在 divergent code 中無死鎖呼叫
限制 連結 device runtime 即有 overhead;pending launch pool 大小可設
PTX 層 cudaLaunchDevice() + cudaGetParameterBuffer(),需連結 cudadevrt
Important

本筆記描述的是 CDP2(CUDA 12.0+ 預設、CC 9.0+ 唯一版本)。CDP2 沒有 cudaDeviceSynchronize(),改用 cudaStreamTailLaunchcudaStreamFireAndForget 兩種特殊 stream 達成同步與 fire-and-forget。CDP1 文件已不在本指南,且預計於未來版本移除。

Introduction 與 Overview

Execution Environment:Parent / Child Grids

parent grid ┌─────────────────────────────────────────┐
            │ thread0: child_launch<<<...>>>          │
            │            └─ child grid ┌──────────┐    │
            │                          │  ......  │    │
            │                          └────┬─────┘    │
            │   (parent 不算完成,直到 child 全部完成) │
            └──────────────────────┬──────────────────┘
                          implicit synchronization 後 parent 結束

Scope of CUDA Primitives

Streams and Events

Warning

跨界使用一律 undefined:host 建立的 streams/events 在任何 kernel 內使用為 undefined;parent grid 建立的 streams/events 在 child grid 內使用同樣為 undefined。

Ordering and Concurrency

Stream 種類 共享範圍 多 thread 同時送入的結果
named stream 全 grid 共用 由 thread 排程決定順序
implicit NULL stream(同 block) 僅 thread block 內共用 in-order
implicit NULL stream(跨 block) 不共用 可能並行
Warning

並行程度會隨 device 配置、應用 workload、runtime 排程而變動。依賴不同 block 之間(或 parent/child 之間)的並行是不安全的,會造成 undefined behavior。

Memory Coherence and Consistency

Parent 與 child 共享同一份 global 與 constant memory,但各自擁有獨立的 local 與 shared memory。Child 永遠不能存取 parent 的 local/shared,反之亦然。

Memory Space Parent/Child 可用相同指標?
Global Memory Yes
Mapped Memory Yes
Local Memory No
Shared Memory No
Texture Memory Yes(read-only)

Global Memory

__global__ void parent_launch(int *data) {
    data[threadIdx.x] = threadIdx.x;
    __syncthreads();                 // 讓 child 看到 data[0..255] 全部寫入
    if (threadIdx.x == 0) {
        child_launch<<< 1, 256 >>>(data);
        // tail launch:等 child 完成,才能在 parent 退出前讀到 child 結果
        tail_launch<<< 1, 256, 0, cudaStreamTailLaunch >>>(data);
    }
}

重點:因 thread 0 負責 launch,child 與「thread 0 看到的 memory」一致;__syncthreads() 確保 child 看到 data[0]=0 … data[255]=255(若無此同步只保證看到 data[0]=0)。child 只在 implicit synchronization 時返回,其修改不保證對 parent 可見,要靠 tail launch 取回。

時間軸(parent 視角)
 寫 data + __syncthreads ──▶ child_launch ──▶ (parent 抵達 implicit sync)
                                  │                     │
                                  └─ child 跑完 ─────────┴─▶ tail_launch 讀到 child 結果

Mapped Memory

Shared and Local Memory

Local Memory(細節)

// 非法:x_array 在 parent 的 local memory
int x_array[10];
child_launch<<< 1, 1 >>>(x_array);
__device__ int value;                  // 正確:global 儲存
__device__ void x() { value = 5; child<<<1,1>>>(&value); }

__device__ void y() { int value = 5;   // 非法:local 儲存
    child<<<1,1>>>(&value); }

Texture Memory

Programming Interface:Basics

__global__ void parentKernel() {
    childKernel<<<1,1>>>();                          // launch child
    if (cudaSuccess != cudaGetLastError()) return;
    // tail launch:隱含同步,等 child 完成
    tailKernel<<<1,1,0,cudaStreamTailLaunch>>>();
}
// host: parentKernel<<<1,1>>>(); ... cudaDeviceSynchronize();

重點:device 端用 cudaGetLastError() 檢查 launch 錯誤;cudaStreamTailLaunch 讓 tail kernel 等 child 完成後才跑。編譯需開啟 relocatable device code 並連結 cudadevrt

nvcc -arch=sm_75 -rdc=true hello_world.cu -o hello -lcudadevrt

C++ Language Interface for CDP

Device-Side Kernel Launch

kernel_name<<< Dg, Db, Ns, S >>>([kernel arguments]);
參數 型別 說明
Dg dim3 grid 的維度與大小
Db dim3 每個 thread block 的維度與大小
Ns size_t 動態 shared memory bytes(選填,預設 0)
S cudaStream_t 關聯 stream,必須在「發出此呼叫的同一 grid」內配置(選填,預設 NULL stream)

Events

Synchronization

Device Management

Programming Guidelines

Performance

Implementation Restrictions and Limitations

Compatibility and Interoperability

情境 CDP2(CUDA 12.0+ 預設) CDP1(-DCUDA_FORCE_CDP1_IF_SUPPORTED
編譯 device code 引用 cudaDeviceSynchronize 會編譯錯誤 引用 cudaStreamTailLaunchcudaStreamFireAndForget 編譯錯誤;引用 cudaDeviceSynchronize 且為 sm_90+ 編譯也錯誤
CC < 9.0 使用新介面 使用 legacy 介面
CC ≥ 9.0 使用新介面 仍用新介面;若 device code 引用 cudaDeviceSynchronize,function load 回傳 cudaErrorSymbolNotFound(為 CC<9.0 編譯但以 JIT 跑在 9.0+ 時可能發生)
Warning

CDP1 的 function 不能 launch CDP2 的 function,反之亦然。若 CDP1 function 的 call graph 內含 CDP2 function(或相反),function load 時會得到 cudaErrorCdpVersionMismatch

Device-side Launch from PTX

針對以 PTX 為目標、要在自家語言支援 dynamic parallelism 的編譯器/語言實作者,device 端 launch 由兩個 PTX 可存取的 API 完成:cudaLaunchDevice()cudaGetParameterBuffer()。前者用「由後者取得並填入參數」的 parameter buffer 來啟動指定 kernel;若 kernel 不帶參數,parameter buffer 可為 NULL(不需呼叫 cudaGetParameterBuffer())。

cudaLaunchDevice

extern "C" __device__
cudaError_t cudaLaunchDevice(void *func, void *parameterBuffer,
                             dim3 gridDimension, dim3 blockDimension,
                             unsigned int sharedMemSize,
                             cudaStream_t stream);

cudaGetParameterBuffer

extern "C" __device__
void *cudaGetParameterBuffer(size_t alignment, size_t size);

Parameter Buffer Layout

parameter buffer (max 4KB)
┌──────────┬─pad─┬───────────────┬─────────────┐
│ param0   │     │ param1(對齊)  │ param2(對齊) │ ...
└──────────┴─────┴───────────────┴─────────────┘
 每個 param 偏移 = 大於前一 param 末端、且為自身 size 倍數的最小值

考試/測驗重點

主題 常考點 / 陷阱
版本 CDP2 = CUDA 12.0+ 預設、CC 9.0+ 唯一;CDP1 需 -DCUDA_FORCE_CDP1_IF_SUPPORTED,將被移除
同步機制 CDP2 無 cudaDeviceSynchronize;改用 cudaStreamTailLaunch(等 child)與 cudaStreamFireAndForget
取回 child 結果 parent 退出前只能靠送進 cudaStreamTailLaunch 的 kernel;child 寫入「不保證」對 parent 可見
Global 一致性 weak consistency;唯一完全一致點 = child 被 invoke 當下;launch 前 parent 寫入對 child 可見
記憶體可傳指標 global / mapped / texture(唯讀) 可;local / shared 不可(傳了 undefined / error)
判斷指標 __isGlobal() 判斷是否 global,可安全傳給 child;compiler 會嘗試對 local/shared 指標警告
非法傳遞 把 shared/local 指標傳給 cudaMemcpyAsync / cudaMemsetAsync 非法(它們可能 invoke child kernel)
Stream 共享 named stream 全 grid 共用;NULL stream 僅 block 內共用;同 stream in-order(fire-and-forget 例外)
並行保證 CDP 無新並行保證;block 間、parent/child 間皆不可依賴並行
Events 僅支援 cudaStreamWaitEvent;須用 cudaEventCreateWithFlags + cudaEventDisableTiming
Device 管理 不支援 cudaSetDevice / cudaGetDeviceProperties;cudaDeviceGetAttribute 可查他 device
物件範圍 device 建立的 stream/event 僅在「建立它的 grid」有效;跨 grid / host↔device 皆 undefined
編譯連結 nvcc -rdc=true ... -lcudadevrt;device-side launch 需連結 cudadevrt
Pending launch cudaDeviceSetLimit + cudaLimitDevRuntimePendingLaunchCount 設定 launch pool 大小
版本不相容 CDP1 與 CDP2 互相 launch / 同一 call graph 混用 → cudaErrorCdpVersionMismatch
PTX 層 cudaLaunchDevice + cudaGetParameterBuffer;buffer 64-byte 對齊、不可重排、最大 4KB