Driver Entry Point Access
重點總覽
| 項目 | 重點 |
|---|---|
| 目的 | 取得 CUDA driver function 的位址,並透過 function pointer 呼叫;自 CUDA 11.3 起提供 |
| 類比 | 等同 POSIX 的 dlsym、Windows 的 GetProcAddress |
| 四大能力 | Driver API 取址 / Runtime API 取址 / 取 per-thread default stream 版本 / 用新 driver 存取新功能 |
| Typedef header | toolkit 的 include/ 提供,如 cudaTypedefs.h;只定義 typedef,不定義實際 function pointer |
| 版本命名 | driver 符號以 _v* 後綴標版本(首版無後綴);typedef 如 PFN_cuMemAlloc_v3020(v 後接 CUDA 版本號) |
| Driver API 取址 | cuGetProcAddress(name, &pfn, version, flags, &driverStatus);version 須對應 ABI 版本 |
| Runtime API 取址 | cudaGetDriverEntryPointByVersion(name, &pfn, version, flags, &driverStatus) |
| 版本必須精確 | 傳入版本要「正好」對應 typedef 版本;傳更高版本可能在未來換回更新符號 → undefined behavior |
| 無效版本 | 版本過低(小於符號引入版本)→ 回傳 CUDA_ERROR_NOT_FOUND |
| per-thread stream | 符號帶 _ptsz/_ptds;可用編譯旗標/巨集,或 *_PER_THREAD_DEFAULT_STREAM / *_LEGACY_STREAM flags |
| 存取新功能 | 舊 toolkit + 新 driver:手動宣告 typedef,檢查 cuDriverGetVersion 後 cuGetProcAddress |
| Guidelines | version 用「對應 typedef 的常數」硬編;勿用 CUDA_VERSION 或 cuDriverGetVersion;先驗證 driver 版本 |
| 失敗診斷 | 兩類:(1) API/usage 錯誤→CUresult;(2) 找不到符號→CUdriverProcAddressQueryResult (driverStatus) |
介紹(Introduction)
Driver Entry Point Access APIs 提供一種方式來取得 CUDA driver function 的位址。自 CUDA 11.3 起,使用者可用這些 API 取得的 function pointer 呼叫可用的 CUDA driver API。
- 這些 API 的功能類比於 POSIX 平台上的
dlsym與 Windows 上的GetProcAddress。 - 提供的四大能力:
- 用 CUDA Driver API 取得 driver function 位址(
cuGetProcAddress)。 - 用 CUDA Runtime API 取得 driver function 位址(
cudaGetDriverEntryPointByVersion)。 - 請求 driver API 的 per-thread default stream 版本。
- 在較舊的 toolkit 上、搭配較新的 driver,存取新的 CUDA 功能。
- 用 CUDA Driver API 取得 driver function 位址(
最重要的應用是「toolkit 沒更新、但 driver 已更新」時,仍能存取新 driver 帶來的新 API;以及在執行期才決定要呼叫哪個版本的 driver 符號。
Driver Function Typedefs
CUDA Toolkit 在其 include/ 目錄提供一系列 header,內含所有 CUDA driver API 的 function pointer typedef,用來協助取得 driver API 進入點。
- Table 27 摘要每個 API header 對應的 typedef header:
| API header file | API Typedef header file |
|---|---|
cuda.h |
cudaTypedefs.h |
cudaGL.h |
cudaGLTypedefs.h |
cudaProfiler.h |
cudaProfilerTypedefs.h |
cudaVDPAU.h |
cudaVDPAUTypedefs.h |
cudaEGL.h |
cudaEGLTypedefs.h |
cudaD3D9.h |
cudaD3D9Typedefs.h |
cudaD3D10.h |
cudaD3D10Typedefs.h |
cudaD3D11.h |
cudaD3D11Typedefs.h |
- 這些 header 只定義 typedef,不定義實際 function pointer。以
cuMemAlloc為例:
// cudaTypedefs.h(typedef 定義,非實際指標)
typedef CUresult (CUDAAPI *PFN_cuMemAlloc_v3020)(CUdeviceptr_v2 *dptr, size_t bytesize);
typedef CUresult (CUDAAPI *PFN_cuMemAlloc_v2000)(CUdeviceptr_v1 *dptr, unsigned int bytesize);
重點:typedef 名稱中的 _v3020/_v2000 是「引入該版本符號的 CUDA 版本號」(3.2 = 3020、2.0 = 2000),不是符號自身的 _v2 後綴。
版本命名規則(versioned naming scheme)
CUDA driver 符號採以版本為基礎的命名:除首版外,名稱帶 _v* 後綴。當某 driver API 的簽章或語意改變時,就遞增對應 driver 符號的版本號。
driver 符號 對應 typedef 引入版本
cuMemAlloc (首版,無 _v 後綴) → PFN_cuMemAlloc_v2000 CUDA 2.0 (2000)
cuMemAlloc_v2 (簽章/語意變更) → PFN_cuMemAlloc_v3020 CUDA 3.2 (3020)
- 用 typedef 可在程式中更輕鬆地宣告對應型別的 function pointer:
PFN_cuMemAlloc_v3020 pfn_cuMemAlloc_v2; // 對應 cuMemAlloc_v2 符號
PFN_cuMemAlloc_v2000 pfn_cuMemAlloc_v1; // 對應首版 cuMemAlloc 符號
- 符號名後綴
_v2:是 driver API 自身的 ABI 版本。 - typedef 名後綴
_vNNNNN:是「引入該符號的 CUDA 版本號」,也是要傳給cuGetProcAddress的版本引數。
Driver Function Retrieval
搭配 Driver Entry Point Access APIs 與適當 typedef,即可取得任一 CUDA driver API 的 function pointer。
Using the Driver API(cuGetProcAddress)
Driver API 需要一個 CUDA version 引數,用來取得所請求 driver 符號的 ABI 相容版本。CUDA driver API 有 per-function ABI,以 _v* 後綴表示。以 cuStreamBeginCapture 為例:
// cuda.h
CUresult CUDAAPI cuStreamBeginCapture(CUstream hStream);
CUresult CUDAAPI cuStreamBeginCapture_v2(CUstream hStream, CUstreamCaptureMode mode);
// cudaTypedefs.h(v10000 = CUDA 10.0,v10010 = CUDA 10.1)
typedef CUresult (CUDAAPI *PFN_cuStreamBeginCapture_v10000)(CUstream hStream);
typedef CUresult (CUDAAPI *PFN_cuStreamBeginCapture_v10010)(CUstream hStream, CUstreamCaptureMode mode);
#include <cudaTypedefs.h>
PFN_cuStreamBeginCapture_v10000 pfn_cuStreamBeginCapture_v1;
PFN_cuStreamBeginCapture_v10010 pfn_cuStreamBeginCapture_v2;
// 取首版符號:版本須「正好」是 10000
cuGetProcAddress("cuStreamBeginCapture", &pfn_cuStreamBeginCapture_v1, 10000,
CU_GET_PROC_ADDRESS_DEFAULT, &driverStatus);
// 取 _v2 符號:版本須「正好」是 10010
cuGetProcAddress("cuStreamBeginCapture", &pfn_cuStreamBeginCapture_v2, 10010,
CU_GET_PROC_ADDRESS_DEFAULT, &driverStatus);
- 取
_v1須傳 10.0(10000),取_v2須傳 10.1(10010)。 - 傳入「無效(過低)」版本會回傳
CUDA_ERROR_NOT_FOUND;上例若傳小於 10000 即為無效。
為取 _v2 而傳 11030 仍會回 _v2 符號;但若未來 CUDA 11.3 釋出假想的 _v3,同樣呼叫在搭配 11.3 driver 時會改回 _v3 符號。由於 _v2 與 _v3 的 ABI/簽章可能不同,用為 _v2 設計的 _v10010 typedef 去呼叫 _v3 函式會是 undefined behavior。版本引數應「精確」對應你所用的 typedef。
Using the Runtime API(cudaGetDriverEntryPointByVersion)
Runtime API cudaGetDriverEntryPointByVersion 以提供的 CUDA version 取得 ABI 相容版本,方式與 cuGetProcAddress 相同。
#include <cudaTypedefs.h>
int cudaVersion;
// 須安裝 CUDA driver >= 11.2,否則 cuGetProcAddress 會報錯
status = cuDriverGetVersion(&cudaVersion);
if (cudaVersion >= 11020) {
PFN_cuMemAllocAsync_v11020 pfn_cuMemAllocAsync;
cudaGetDriverEntryPointByVersion("cuMemAllocAsync", &pfn_cuMemAllocAsync,
11020, cudaEnableDefault, &driverStatus);
}
// 呼叫前同時檢查 driverStatus 與指標
if (driverStatus == cudaDriverEntryPointSuccess && pfn_cuMemAllocAsync) {
pfn_cuMemAllocAsync(...);
}
重點:cuMemAllocAsync 於 CUDA 11.2 引入,故最低 CUDA 版本為 11020;呼叫前先驗證 driverStatus == cudaDriverEntryPointSuccess 且指標非空。
Retrieve Per-thread Default Stream Versions
部分 driver API 可設定為 default stream 或 per-thread default stream 語意;帶 per-thread default stream 語意的符號名以 _ptsz 或 _ptds 後綴。例如 cuLaunchKernel 的 per-thread default stream 變體為 cuLaunchKernel_ptsz。此設定會影響同步行為。
取得 default stream / per-thread default stream 版本的兩種方式:
- 編譯旗標/巨集:用
--default-stream per-thread編譯旗標,或定義巨集CUDA_API_PER_THREAD_DEFAULT_STREAM,取得 per-thread default stream 行為。 - 取址 flags 強制:
- default(legacy)stream:
CU_GET_PROC_ADDRESS_LEGACY_STREAM/cudaEnableLegacyStream - per-thread default stream:
CU_GET_PROC_ADDRESS_PER_THREAD_DEFAULT_STREAM/cudaEnablePerThreadDefaultStream
- default(legacy)stream:
| 目標 | Driver API flag | Runtime API flag |
|---|---|---|
| legacy(預設) | CU_GET_PROC_ADDRESS_LEGACY_STREAM |
cudaEnableLegacyStream |
| per-thread | CU_GET_PROC_ADDRESS_PER_THREAD_DEFAULT_STREAM |
cudaEnablePerThreadDefaultStream |
Access New CUDA Features(舊 toolkit + 新 driver)
雖建議安裝最新 toolkit 以取得新 driver 功能,但若無法更新 toolkit,可用此 API 僅靠「更新後的 driver」存取新功能。假設使用者在 CUDA 12.3,想用 12.5 driver 才有的新 API cuFoo:
int main() {
// 12.3 的 cudaTypedefs.h 沒有 cuFoo 的 typedef,需手動宣告原型
typedef CUresult (CUDAAPI *PFN_cuFoo_v12050)(...);
PFN_cuFoo_v12050 pfn_cuFoo = NULL;
CUdriverProcAddressQueryResult driverStatus;
int cudaVersion;
CUresult status = cuDriverGetVersion(&cudaVersion); // 須 driver >= 12.5
if (cudaVersion >= 12050) {
// cuFoo 於 12050 引入,故版本傳 12050
status = cuGetProcAddress("cuFoo", &pfn_cuFoo, 12050,
CU_GET_PROC_ADDRESS_DEFAULT, &driverStatus);
if (status == CUDA_SUCCESS && pfn_cuFoo) {
pfn_cuFoo(...);
} else {
printf("Cannot retrieve the address to cuFoo - driverStatus = %d\n", driverStatus);
assert(0);
}
}
}
重點:toolkit 沒有對應 typedef 時,自行手寫 PFN_* 原型,先 cuDriverGetVersion 確認 driver 夠新,再 cuGetProcAddress。
另一情境是取得「在 minor 版本釋出的某 API 新版本」。cuDeviceGetUuid 的 _v2 版本巨集要到 major 邊界才會 bump,故在 11.4+ 期間需如下取得 _v2:
// 原始 typedef vs _v2 typedef(注意 v 後的版本號不同)
typedef CUresult (CUDAAPI *PFN_cuDeviceGetUuid_v9020)(CUuuid *uuid, CUdevice_v1 dev);
typedef CUresult (CUDAAPI *PFN_cuDeviceGetUuid_v11040)(CUuuid *uuid, CUdevice_v1 dev);
#include <cudaTypedefs.h>
status = cuDeviceGet(&dev, 0); // 取得 device 0
status = cuDriverGetVersion(&cudaVersion);// 須 driver >= 11.4
if (cudaVersion >= 11040) {
PFN_cuDeviceGetUuid_v11040 pfn_cuDeviceGetUuid;
status = cuGetProcAddress("cuDeviceGetUuid", &pfn_cuDeviceGetUuid, 11040,
CU_GET_PROC_ADDRESS_DEFAULT, &driverStatus);
if (CUDA_SUCCESS == status && pfn_cuDeviceGetUuid) {
pfn_cuDeviceGetUuid(&uuid, dev);
}
}
重點:兩版簽章相同,差異在 typedef 的版本號(9020 vs 11040);要取 _v2 行為,版本就要傳 11040。
Guidelines for cuGetProcAddress
使用 cuGetProcAddress 時應遵守的準則:
- 版本引數要「對應 typedef 版本」硬編:不要用編譯期常數(如
CUDA_VERSION)或動態版本(如cuDriverGetVersion回傳值)當作cuGetProcAddress的版本引數。 - 先檢查 driver 版本足夠:在呼叫
cuGetProcAddress前,先用cuDriverGetVersion確認目前 driver 版本足夠;否則預期會出錯,或可能回到「非預期的符號」。
版本引數的語意是「我要綁定哪個 typedef 的 ABI」。若改傳動態 driver 版本,當部署到更新的 driver 時,可能取回比你 typedef 還新的符號,造成 ABI/簽章不符的 undefined behavior(與上面「傳更高版本」的陷阱同源)。
Guidelines for Runtime API Usage
除非另有說明,runtime API cudaGetDriverEntryPointByVersion 因同樣允許使用者請求特定 CUDA driver 版本,其準則與 driver entry point cuGetProcAddress 相同。
Determining cuGetProcAddress Failure Reasons
cuGetProcAddress 有兩類錯誤,要靠不同回傳管道區分:
- API/usage 錯誤:透過
CUresult回傳值給出錯誤碼,例如把pfn傳NULL、傳入無效 flags。 - 找不到所請求的 driver API:編碼在
CUdriverProcAddressQueryResult *symbolStatus(即driverStatus)中,協助分辨「driver 為何找不到符號」。
cuGetProcAddress(name, &pfn, cudaVersion, flags, &driverStatus)
│
├─ CUresult != CUDA_SUCCESS ──► (1) API/usage 錯誤
│ (pfn=NULL、flags 非法 ...)
│
└─ CUresult == CUDA_SUCCESS ──► 檢視 driverStatus
│
├─ CU_GET_PROC_ADDRESS_SUCCESS
│ 符號找到且版本足夠 → 可呼叫 pfn()
│
├─ CU_GET_PROC_ADDRESS_VERSION_NOT_SUFFICIENT
│ driver 有此符號,但它比 cudaVersion 更晚加入
│ →「升級 cudaVersion 即可,driver 已 OK」
│
└─ CU_GET_PROC_ADDRESS_SYMBOL_NOT_FOUND
driver 中找不到符號
→「driver 太舊」或「名稱拼錯」
以 cuDeviceGetExecAffinitySupport(CUDA 11.4 引入)為例:
#include <cuda.h>
CUdriverProcAddressQueryResult driverStatus;
cudaVersion = ...;
status = cuGetProcAddress("cuDeviceGetExecAffinitySupport", &pfn, cudaVersion, 0, &driverStatus);
if (CUDA_SUCCESS == status) {
if (CU_GET_PROC_ADDRESS_VERSION_NOT_SUFFICIENT == driverStatus) {
// cudaVersion < 11.4,但跑在 >= 11.4 的 driver:升級 cudaVersion 即可
} else if (CU_GET_PROC_ADDRESS_SYMBOL_NOT_FOUND == driverStatus) {
// driver < 11.4(或名稱拼錯):cudaVersion 給多少都不重要
} else if (CU_GET_PROC_ADDRESS_SUCCESS == driverStatus && pfn) {
pfn(); // cudaVersion 與 driver 皆 >= 11.4
}
}
CU_GET_PROC_ADDRESS_VERSION_NOT_SUFFICIENT:符號在 driver 中有找到,但它比傳入的cudaVersion更晚加入。例:cudaVersion為 11030 或更低、driver ≥ 11.4,因cuDeviceGetExecAffinitySupport於 11.4(11040)才加入,就得到此結果。CU_GET_PROC_ADDRESS_SYMBOL_NOT_FOUND:在 driver 中找不到符號。可能原因:- driver 太舊不支援該函式(如開發時用 11.4+ driver、
cuGetProcAddress成功,但部署到 11.3 driver 就改回此狀態)。 - 名稱拼錯:如把符號寫成
CUDeviceGetExecAffinitySupport(開頭大寫CU),字串不符即找不到。
- driver 太舊不支援該函式(如開發時用 11.4+ driver、
VERSION_NOT_SUFFICIENT→ 問題在「你的 cudaVersion 引數太低」,driver 其實夠新。SYMBOL_NOT_FOUND→ 問題在「driver 太舊」或「拼字錯」,此時 cudaVersion 給多少都無關。
考試/測驗重點
| 主題 | 必記重點 |
|---|---|
| 起始版本 / 類比 | CUDA 11.3 起提供;類比 POSIX dlsym、Windows GetProcAddress |
| 兩個取址 API | Driver: cuGetProcAddress;Runtime: cudaGetDriverEntryPointByVersion |
| typedef header | cuda.h → cudaTypedefs.h;header 只定義 typedef,不定義實際 function pointer |
| 符號版本命名 | 首版無後綴,之後 _v2/_v3;簽章或語意改變才 bump 版本 |
| typedef 版本號意義 | PFN_xxx_vNNNNN 的 NNNNN = 引入該符號的 CUDA 版本(2.0=2000、3.2=3020、10.1=10010) |
| 版本引數須精確 | 傳「正好對應 typedef」的版本;傳更高版本未來可能回到更新符號 → undefined behavior |
| 無效版本回傳 | 版本低於符號引入版本 → CUDA_ERROR_NOT_FOUND |
| per-thread stream 後綴 | _ptsz / _ptds;如 cuLaunchKernel_ptsz;影響同步行為 |
| per-thread 取得方式 | --default-stream per-thread 旗標 / CUDA_API_PER_THREAD_DEFAULT_STREAM 巨集;或取址 flags |
| per-thread / legacy flags | *_PER_THREAD_DEFAULT_STREAM / *_LEGACY_STREAM(Runtime: cudaEnable*) |
| 新功能存取 | 舊 toolkit + 新 driver:手寫 typedef + cuDriverGetVersion 檢查 + cuGetProcAddress |
| Guidelines | 版本用硬編常數(勿用 CUDA_VERSION 或 cuDriverGetVersion);先確認 driver 版本足夠 |
| 兩類失敗 | (1) API/usage → CUresult;(2) 找不到符號 → CUdriverProcAddressQueryResult (driverStatus) |
| VERSION_NOT_SUFFICIENT | driver 有符號但比 cudaVersion 晚加入 → 升 cudaVersion 即可,driver 已夠新 |
| SYMBOL_NOT_FOUND | driver 找不到符號 → driver 太舊 或 名稱拼錯(如大寫 CU 開頭);cudaVersion 無關 |
Related Notes
- 04-CUDA-Features/06-Stream-Ordered-Memory-Allocator
- 04-CUDA-Features/09-Green-Contexts
- 04-CUDA-Features/10-Lazy-Loading-and-Error-Log
- 01-Introduction-to-CUDA/05-CUDA-Platform
- 03-Advanced-CUDA/08-CUDA-Driver-API
- 03-Advanced-CUDA/10-Tour-of-CUDA-Features
- 04-CUDA-Features/Practice-CUDA-Features
- 00-Dashboard/Exam-Traps