CUDA 裝置端 Runtime (CUDA Device Runtime)

重點總覽

CUDA device runtime 是一套可在 kernel 內呼叫的 API,提供與 host 端 CUDA Runtime API 大致相同的能力,最常用於 Dynamic Parallelism (CDP2)Device Graph Launch。原型在編譯時自動引入,不需手動 include cuda_device_runtime_api.h。它只是 host runtime 的一個子集:無多 GPU、無 cudaStreamSynchronize/cudaStreamQuery(CDP2 亦無 cudaDeviceSynchronize,可用 tail launch 取代其效果)、stream 必須以 cudaStreamNonBlocking 建立,並提供 fire-and-forget 與 tail launch 等特殊 named stream 取代同步。

項目 重點
引入方式 編譯自動帶入原型,無需 #include
記憶體上限 host 端用 cudaDeviceSetLimit() 設定,kernel 啟動前設好且執行中不可改
device 端配置 cudaMalloc/cudaFree 映射到 device malloc/free,受 heap 大小限制
stream 建立 只能 cudaStreamCreateWithFlags(cudaStreamNonBlocking),無 cudaStreamCreate()
同步替代 cudaStreamSynchronize/Query;用 cudaStreamTailLaunch 確認子 kernel 完成
特殊 stream cudaStreamFireAndForgetcudaStreamTailLaunch、device graph 的 named streams
多 GPU 不支援;只能操作當前執行的 device(但可查詢任意 device 屬性)
編譯前提 fire-and-forget / tail launch 需 64-bit 模式,且不可定義 CUDA_FORCE_CDP1_IF_SUPPORTED

引入與記憶體配置

引入 API

device runtime API 的原型在編譯時自動引入,與 host runtime 一樣,無需手動 #include <cuda_device_runtime_api.h>

資源上限 (Configuration Options)

device runtime 系統軟體的資源由 host 程式透過 cudaDeviceSetLimit() 控制。

Important

Limit 必須在任何 kernel 啟動前設好;GPU 正在執行程式時不可變更

Limit 行為
cudaLimitDevRuntimePendingLaunchCount 保留給「尚未開始執行」的 kernel launch 與 event 緩衝(因未解相依或資源不足)。緩衝滿時,device 端 launch 配置 launch slot 失敗回傳 cudaErrorLaunchOutOfResources,配置 event slot 失敗回傳 cudaErrorMemoryAllocation。預設 launch slot 數為 2048;event slot 數為此 limit 值的 兩倍
cudaLimitStackSize 每個 GPU thread 的 stack 大小(bytes)。driver 會視需要自動增大,且不會在每次 launch 後重設。可用 cudaDeviceSetLimit() 改值(會立即 resize,必要時 device 會阻塞直到先前任務完成),cudaDeviceGetLimit() 查目前值

配置與生命週期 (Allocation and Lifetime)

cudaMalloc() / cudaFree() 在 host 與 device 環境語意不同

Warning

跨環境配置/釋放是錯誤:不可對 device 上 cudaMalloc() 的指標在 host 呼叫 cudaFree(),反之亦然。

cudaMalloc() on Host cudaMalloc() on Device
cudaFree() on Host Supported Not Supported
cudaFree() on Device Not Supported Supported
配置上限 Available device memory cudaLimitMallocHeapSize

記憶體宣告

__device____constant__

file scope 以 __device__ / __constant__ 宣告的變數,在 device runtime 下行為相同

Texture 與 Surface

Warning

device runtime 不支援 legacy module-scope(即 compute capability 2.0 / Fermi 風格)的 texture/surface 於 device 啟動的 kernel 內使用。Module-scope (legacy) texture 可由 host 建立,但只能被 top-level kernel(由 host 啟動者)使用。

Shared Memory 宣告

兩種 shared memory 宣告在 device runtime 下都有效:靜態大小(file/function scope)與 extern 動態大小(由 launch 配置決定)。

__global__ void permute(int n, int *data) {
  extern __shared__ int smem[];
  if (n <= 1)
    return;
  smem[threadIdx.x] = data[threadIdx.x];
  __syncthreads();
  permute_data(smem, n);
  __syncthreads();
  // SMEM 不能傳給 child,故寫回 GMEM
  data[threadIdx.x] = smem[threadIdx.x];
  __syncthreads();

  if (threadIdx.x == 0) {
    permute<<< 1, 256, n/2*sizeof(int) >>>(n/2, data);
    permute<<< 1, 256, n/2*sizeof(int) >>>(n/2, data+n/2);
  }
}

void host_launch(int *data) {
  permute<<< 1, 256, 256*sizeof(int) >>>(256, data);
}

Constant Memory 與 Symbol Addresses

Launch 機制與 Device 管理

SM Id 與 Warp Id

PTX 的 %smid%warpidvolatile 值。device runtime 可能把 thread block 重新排到不同 SM 以更有效管理資源。

Warning

不可假設 %smid / %warpid 在 thread 或 thread block 生命週期內保持不變。

Launch Setup APIs

device 端 kernel launch 使用與 host 相同的 triple chevron <<<>>> 語法。底層由 device runtime library 的系統級機制實現,也可從 PTX 直接以 cudaGetParameterBuffer()cudaLaunchDevice() 呼叫。NVCC front-end 把 <<<>>> 翻譯成這些呼叫。允許應用程式自行呼叫這兩個 API(要求與 PTX 相同),但需自行依規格正確填妥所有必要資料結構;CUDA 保證這些資料結構向後相容(backwards compatibility)。

Launch 函式 與 host 的差異
cudaGetParameterBuffer <<<>>> 自動產生;API 與 host 等效函式不同
cudaLaunchDevice <<<>>> 自動產生;API 與 host 等效函式不同
extern __device__ cudaError_t cudaGetParameterBuffer(void **params);
extern __device__ cudaError_t cudaLaunchDevice(void *kernel,
                                               void *params, dim3 gridDim,
                                               dim3 blockDim,
                                               unsigned int sharedMemSize = 0,
                                               cudaStream_t stream = 0);

Device 管理

支援的 API 子集

host 與 device runtime API 語法相同;語意除非特別標註否則一致。

Runtime API 函式 與 host 的差異 / 細節
cudaGetLastError / cudaPeekAtLastError last error 是 per-thread 狀態,非 per-block
cudaDeviceGetAttribute 可回傳任意 device 的屬性
cudaGetDevice 永遠回傳當前 device ID(與 host 視角一致)
cudaStreamCreateWithFlags 必須cudaStreamNonBlocking
cudaEventCreateWithFlags 必須cudaEventDisableTiming
cudaMemcpyAsync / cudaMemcpy2D/3DAsync / cudaMemset*Async 只支援 async memcpy/set;只允許 device-to-device memcpy;不可傳入 local 或 shared memory 指標
cudaMalloc / cudaFree 不可對 host 建立的指標在 device 端 cudaFree,反之亦然
cudaDeviceGetCacheConfig / cudaDeviceGetLimit / cudaGetErrorString / cudaGetDeviceCount 行為與 host 相同
cudaStreamDestroy / cudaStreamWaitEvent / cudaEventRecord / cudaEventDestroy 行為與 host 相同
cudaFuncGetAttributes / cudaRuntimeGetVersion 行為與 host 相同
cudaOccupancyMaxActiveBlocksPerMultiprocessor / cudaOccupancyMaxPotentialBlockSize / ...VariableSMem 行為與 host 相同
Note

注意:host 端常見的 cudaStreamCreate()cudaStreamSynchronize()cudaStreamQuery()cudaDeviceSynchronize()(CDP2)等未列於支援子集中。

API 錯誤與 Launch 失敗

Warning

launch 後沒有錯誤並不代表 child kernel 成功完成。對於 device 端例外(如存取無效位址),child grid 的錯誤會回報到 host 端

ECC Errors

kernel 內的 code 無法收到 ECC 錯誤通知。ECC 錯誤在整個 launch tree 完成後於 host 側回報;nested 程式執行中發生的 ECC 錯誤會產生例外或繼續執行(視錯誤與配置而定)。

Device Runtime Streams

device runtime 提供 named 與 unnamed (NULL) stream。

Important

Stream handle 不可傳給 parent 或 child grid——stream 應視為建立它的 grid 私有
所有 device stream 必須用 cudaStreamCreateWithFlags(cudaStreamNonBlocking) 建立;cudaStreamCreate() 不可用。
cudaStreamSynchronize()cudaStreamQuery() 不支援;需確認 stream 子 kernel 完成時,改用 cudaStreamTailLaunch

Implicit (NULL) Stream

device runtime 提供單一 implicit 無名 stream,由 thread block 內所有 thread 共享。但因所有 named stream 都以 cudaStreamNonBlocking 建立,NULL stream 的工作不會對其他 stream(含其他 thread block 的 NULL stream)的待處理工作插入隱式相依——即 host 端 NULL stream 的 cross-stream barrier 語意在 device 不支援

Fire-and-Forget Stream

cudaStreamFireAndForget:以更少 boilerplate、無 stream 追蹤開銷送出 fire-and-forget 工作;功能等同於「每次 launch 建新 stream」但更快

// C2 的 launch 不會等 C1 完成
__global__ void P( ... ) {
  C1<<< ... , cudaStreamFireAndForget >>>( ... );
  C2<<< ... , cudaStreamFireAndForget >>>( ... );
}
Warning

fire-and-forget stream 不能用來 record 或 wait event,否則回傳 cudaErrorInvalidValue。定義 CUDA_FORCE_CDP1_IF_SUPPORTED 時不支援,且需 64-bit 編譯。

Tail Launch Stream

cudaStreamTailLaunch:讓一個 grid 排程「在自己完成之後才啟動」的新 grid,多數情況可達成等同 cudaDeviceSynchronize() 的效果。

// C2 只在 C1 完成後 launch
__global__ void P( ... ) {
  C1<<< ... , cudaStreamTailLaunch >>>( ... );
  C2<<< ... , cudaStreamTailLaunch >>>( ... );
}

// C 只在所有 X、F、P 完成後 launch
__global__ void P( ... ) {
  C<<< ... , cudaStreamTailLaunch >>>( ... );
  X<<< ... , cudaStreamPerThread >>>( ... );
  F<<< ... , cudaStreamFireAndForget >>>( ... );
}

tail launch stream 的行為「如同被插入在 parent grid 與 parent stream 的下一個 grid 之間」——parent stream 的下一個 grid 不會在 parent 的 tail 工作完成前啟動。

parent stream 順序:
  ... → [P1] → [P1 的 tail: C] → [P2] → ...
            └─ C 完成後 P2 才 launch

每個 grid 只有一條 tail launch stream;要 tail launch 並行多個 grid,可借一層 helper grid 用 fire-and-forget 展開:

// C1 與 C2 在 P 完成後並行 launch
__global__ void T( ... ) {
  C1<<< ... , cudaStreamFireAndForget >>>( ... );
  C2<<< ... , cudaStreamFireAndForget >>>( ... );
}
__global__ void P( ... ) {
  ...
  T<<< ... , cudaStreamTailLaunch >>>( ... );
}
Warning

tail launch stream 同樣不能 record/wait event(回傳 cudaErrorInvalidValue)、定義 CUDA_FORCE_CDP1_IF_SUPPORTED 時不支援、需 64-bit 編譯。

考試/測驗重點

題型 關鍵答案
device runtime 最常用於哪些情境? Dynamic Parallelism (CDP2) 與 Device Graph Launch
需要手動 include device runtime header 嗎? 不需要,原型編譯時自動引入
device runtime 資源上限在哪設?何時? host 端 cudaDeviceSetLimit(),且須在任何 kernel 啟動前、執行中不可改
pending launch slot 預設多少?event slot 呢? launch slot 預設 2048;event slot 為 limit 值的兩倍
pending launch 緩衝滿時的錯誤碼? launch slot:cudaErrorLaunchOutOfResources;event slot:cudaErrorMemoryAllocation
device 端 cudaMalloc 配置上限受什麼限制? malloc heap size (cudaLimitMallocHeapSize),可能小於可用 device memory
可否 host cudaFree 一個 device cudaMalloc 的指標? 不可,跨環境是錯誤
device code 能建立 texture/surface 物件嗎? 不能;host 建立的可在 device 使用與傳遞
device 上能修改 __constant__ 嗎? 不能,只唯讀;只能 host 改(且並行存取時改之為未定義)
為何 device runtime 不支援 cudaMemcpyToSymbol symbol 可直接用 & 取址,故這類 API 不需要
%smid/%warpid 可信賴不變嗎? 不可,是 volatile,block 可能被重排到不同 SM
device 端建立 stream 必須傳什麼 flag?用哪個 API? cudaStreamNonBlockingcudaStreamCreateWithFlags()cudaStreamCreate() 不可用)
device 端建立的 stream handle 可否傳給 parent/child grid? 不可,stream 為建立它的 grid 私有
device 端 event 建立必須傳什麼 flag? cudaEventDisableTiming
device 端 memcpy 限制? 只 async、只 device-to-device、不可傳 local/shared 指標
cudaGetLastError 是 per-thread 還是 per-block? per-thread
launch 後無錯誤代表 child 成功完成嗎? 不代表
想知道 stream 子 kernel 是否完成,用什麼? 送 kernel 進 cudaStreamTailLaunch(無 cudaStreamSynchronize/Query
device NULL stream 有 host 的 cross-stream barrier 嗎? 沒有,不對其他 stream 插入隱式相依
fire-and-forget 與 tail launch 的根本差異? fire-and-forget 立即排程、不被相依;tail launch 在 grid 完成後才啟動,類似 cudaDeviceSynchronize
fire-and-forget / tail launch 的編譯前提? 64-bit 模式,且不可定義 CUDA_FORCE_CDP1_IF_SUPPORTED
每個 grid 有幾條 tail launch stream?如何 tail 並行多 grid? 一條;用 helper grid(tail launch)內以 fire-and-forget 展開多個 child
device runtime 支援多 GPU 嗎? 不支援,只能操作當前 device;但可查詢任意 device 屬性
ECC 錯誤何時/何處回報? kernel 內無通知;launch tree 完成後在 host 側回報