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,檢查 cuDriverGetVersioncuGetProcAddress
Guidelines version 用「對應 typedef 的常數」硬編;勿用 CUDA_VERSIONcuDriverGetVersion;先驗證 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。

核心使用情境

最重要的應用是「toolkit 沒更新、但 driver 已更新」時,仍能存取新 driver 帶來的新 API;以及在執行期才決定要呼叫哪個版本的 driver 符號。

Driver Function Typedefs

CUDA Toolkit 在其 include/ 目錄提供一系列 header,內含所有 CUDA driver API 的 function pointer typedef,用來協助取得 driver API 進入點。

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
// 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)
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);
不要為了「方便」傳更高版本

為取 _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 版本的兩種方式:

目標 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 時應遵守的準則:

為何不能用 cuDriverGetVersion 當版本引數

版本引數的語意是「我要綁定哪個 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 有兩類錯誤,要靠不同回傳管道區分:

  1. API/usage 錯誤:透過 CUresult 回傳值給出錯誤碼,例如把 pfnNULL、傳入無效 flags。
  2. 找不到所請求的 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
    }
}
兩個失敗碼的判讀心法

  • 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.hcudaTypedefs.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_VERSIONcuDriverGetVersion);先確認 driver 版本足夠
兩類失敗 (1) API/usage → CUresult;(2) 找不到符號 → CUdriverProcAddressQueryResult (driverStatus)
VERSION_NOT_SUFFICIENT driver 有符號但比 cudaVersion 晚加入 → 升 cudaVersion 即可,driver 已夠新
SYMBOL_NOT_FOUND driver 找不到符號 → driver 太舊 或 名稱拼錯(如大寫 CU 開頭);cudaVersion 無關