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 | cudaStreamFireAndForget、cudaStreamTailLaunch、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() 控制。
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 環境語意不同:
- host 端
cudaMalloc():從未使用的 device memory 配置新區段。 - device 端:映射到 device 側的
malloc()/free(),故總可配置量受 malloc heap size 限制,可能小於可用 device memory。
跨環境配置/釋放是錯誤:不可對 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 下行為相同:
- 所有 kernel(無論由 host 或 device 啟動)皆可讀寫
__device__變數。 - 所有 kernel 對 module scope 的
__constant__都有相同視角。
Texture 與 Surface
- device code 不可建立或銷毀 texture / surface 物件。
- host 建立的 texture/surface 物件可在 device 自由使用與傳遞;動態建立的 texture 物件永遠有效,可由 parent 傳給 child kernel。
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
- Constant 不可從 device 修改;只能從 host 改。若 host 在某 grid 存取該 constant 的生命週期內並行修改它,行為未定義。
__device__symbol 可在 kernel 內直接用&取址(皆在可見位址空間);__constant__symbol 也可取址,但指標指向唯讀資料。- 因可直接引用 symbol,
cudaMemcpyToSymbol()、cudaGetSymbolAddress()這類引用 symbol 的 API 不需要也不支援。故執行中的 kernel 即使在 child launch 前也不能改 constant data。
Launch 機制與 Device 管理
SM Id 與 Warp Id
PTX 的 %smid 與 %warpid 是 volatile 值。device runtime 可能把 thread block 重新排到不同 SM 以更有效管理資源。
不可假設 %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 管理
- device runtime 無多 GPU 支援:只能操作當前正在執行的 device。
- 但允許查詢系統中任意 CUDA capable 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 相同 |
注意:host 端常見的 cudaStreamCreate()、cudaStreamSynchronize()、cudaStreamQuery()、cudaDeviceSynchronize()(CDP2)等未列於支援子集中。
API 錯誤與 Launch 失敗
- 任何函式都可能回傳錯誤碼(型別
cudaError_t);最後一個錯誤碼以 per-thread 記錄,用cudaGetLastError()取得。 - device 端 launch 可能因多種原因失敗;需呼叫
cudaGetLastError()判斷 launch 是否出錯。
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。
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」但更快。
- 立即排程,不相依於先前 grid 的完成。
- 沒有其他 grid launch 能相依於 fire-and-forget 的完成(除了 parent grid 結尾的隱式同步)——故 tail launch 或 parent stream 的下一個 grid 都不會在 parent 的 fire-and-forget 工作完成前啟動。
// C2 的 launch 不會等 C1 完成
__global__ void P( ... ) {
C1<<< ... , cudaStreamFireAndForget >>>( ... );
C2<<< ... , cudaStreamFireAndForget >>>( ... );
}
fire-and-forget stream 不能用來 record 或 wait event,否則回傳 cudaErrorInvalidValue。定義 CUDA_FORCE_CDP1_IF_SUPPORTED 時不支援,且需 64-bit 編譯。
Tail Launch Stream
cudaStreamTailLaunch:讓一個 grid 排程「在自己完成之後才啟動」的新 grid,多數情況可達成等同 cudaDeviceSynchronize() 的效果。
- 每個 grid 有自己的 tail launch stream。
- parent 的所有 非 tail 工作(ordinary / per-thread / fire-and-forget)會在 tail stream 啟動前隱式同步完成。
- 送入同一 grid 的 tail launch stream 的兩個 grid 依序執行:後者要等前者及其所有後代完成。
// 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 >>>( ... );
}
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? | cudaStreamNonBlocking;cudaStreamCreateWithFlags()(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 側回報 |