Lazy Loading 與 Error Log Management

重點總覽

項目 重點
Lazy Loading 目的 延後載入 CUDA module 到「真正需要」時,縮短程式初始化時間;對只用到少數 kernel 的程式(如 library)效益最大
預設狀態 CUDA 12.3 起所有平台預設啟用,由 CUDA_MODULE_LOADING 環境變數控制
雙重需求 runtime ≥ 11.7 且 driver ≥ 515 才可用;兩者缺一即全部 eager 載入
compiler 需求 無;pre-11.7 編譯的 SASS/PTX 也能享受 lazy loading(仍需 11.7+ runtime)
kernel 例外 含 managed variable 的 module 仍 eager 載入,不受 lazy loading 影響
啟用/停用 CUDA_MODULE_LOADING=LAZY / =EAGER
檢查是否啟用 driver API cuModuleGetLoadingMode(須先 cuInit
強制 eager 單一 kernel cuModuleGetFunction()cudaFuncGetAttributes() 會觸發載入
三大潛在陷阱 concurrent kernel 假設、開機即吃滿 VRAM、效能量測被初始化污染
Error Log 目的 將 CUDA API 錯誤以「白話英文」描述病因,補足只有非零回傳碼的不足
Error Log 啟用 CUDA_LOG_FILE(stdout / stderr / 檔案路徑);亦可事後用 API dump
Error Log API 僅在 CUDA Driver;分「callback 註冊」與「log 輸出管理」兩類

Lazy Loading 介紹

Lazy loading 透過「等到 CUDA module 真正被需要時才載入」來縮短程式初始化時間。對於只使用所含 kernel 中一小部分的程式特別有效,這在使用 library 時很常見。

變更歷史(Change History)

  • 11.7:首次引入,預設停用。
  • 12.2:Linux 預設啟用。
  • 12.3:效能改善,並對 Windows 預設啟用(至此所有平台預設啟用)。

eager loading(傳統)            lazy loading(預設)
program start                   program start
   │ 載入全部 module                │ 只初始化,不載入 kernel
   ▼                               ▼
[m0][m1][m2]...[mN] 全載入         launch kernel kX  ──► 此時才載入 mX
   │                               ▼
   ▼                            [mX] 載入
執行                              執行(其餘 module 不佔資源)

Lazy Loading 的需求(Requirements)

Lazy loading 是 CUDA runtime 與 driver 的「聯合功能」,只有同時滿足 runtime 與 driver 版本需求時才可用。

兩個版本門檻是「AND」關係

runtime ≥ 11.7 driver ≥ 515 缺一不可。任一不滿足,整個程式回退到 eager 載入。compiler 版本與此無關。

Lazy Loading 的使用(Usage)

啟用與停用

在執行期檢查是否啟用

用 driver API cuModuleGetLoadingMode 查詢,注意呼叫前 CUDA 必須先初始化。

#include <cuda.h>
#include <assert.h>
#include <iostream>
int main() {
    CUmoduleLoadingMode mode;
    assert(CUDA_SUCCESS == cuInit(0));                       // 必先初始化
    assert(CUDA_SUCCESS == cuModuleGetLoadingMode(&mode));
    std::cout << "CUDA Module Loading Mode is "
              << ((mode == CU_MODULE_LAZY_LOADING) ? "lazy" : "eager")
              << std::endl;
    return 0;
}

重點:先 cuInit(0),再用 cuModuleGetLoadingMode 取得 mode,比對 CU_MODULE_LAZY_LOADING 判斷 lazy 或 eager。

強制單一 module/kernel 立即(eager)載入

載入 kernel 與變數是自動發生的,不需顯式載入。若想在「不執行」的情況下顯式載入某個 kernel:

cuModuleLoad() 不保證立即載入

即便呼叫 cuModuleLoad(),也不保證 module 會被立刻載入;要確實觸發載入請改用上述兩個 API。

Lazy Loading 的潛在陷阱(Potential Hazards)

Lazy loading 設計上不需修改應用程式即可使用,但當程式未完全遵循 CUDA programming model 時會有以下注意事項。

對 concurrent kernel execution 的影響

大型記憶體配置(Large Memory Allocations)

對效能量測(Performance Measurements)的影響

三大陷阱共同的解法

「preload 你要用的 kernel」幾乎是萬用招:能避開並行 deadlock、避開 VRAM 配置失敗,也能讓 benchmark 不受初始化污染。

Error Log Management

Error Log Management 機制讓 CUDA API 錯誤能以「白話英文」(plain-English) 格式回報,描述問題成因。

Background(背景)

Activation(啟用)

Output(輸出格式)

Log 輸出格式如下:

[Time][TID][Source][Severity][API Entry Point] Message

例如,當開發者嘗試把 Error Log 的 logs dump 到一個未配置的 buffer 時,會產生如下實際錯誤訊息:

[22:21:32.099][25642][CUDA][E][cuLogsDumpToMemory] buffer cannot be NULL

對比之下,過去開發者只會得到回傳碼 CUDA_ERROR_INVALID_VALUE,若呼叫 cuGetErrorString 也頂多得到 “invalid argument”。

API Description(API 說明)

CUDA Driver 提供兩類 API 與 Error Log Management 互動:callback 註冊log 輸出管理

callback 簽章與註冊/反註冊:

// callback 簽章:每當產生一筆 error log 就被呼叫
void callbackFunc(void *data, CUlogLevel logLevel, char *message, size_t length);

// 註冊;userData 原封不動傳給 callback;callback_out 須由呼叫者保存供反註冊用
CUresult cuLogsRegisterCallback(CUlogsCallback callbackFunc, void *userData,
                                CUlogsCallbackHandle *callback_out);

CUresult cuLogsUnregisterCallback(CUlogsCallbackHandle callback);

log 輸出管理 — 核心概念是 log iterator,它指向 buffer 的「目前結尾」:

// 取得目前 iterator;flags 目前必須為 0(其餘保留給未來版本)
CUresult cuLogsCurrent(CUlogIterator *iterator_out, unsigned int flags);

// 把 log buffer dump 到檔案或記憶體
CUresult cuLogsDumpToFile(CUlogIterator *iterator, const char *pathToFile,
                          unsigned int flags);
CUresult cuLogsDumpToMemory(CUlogIterator *iterator, char *buffer, size_t *size,
                            unsigned int flags);

dump 行為要點:

log buffer(環狀,最多 100 筆)
┌───┬───┬───┬─ ... ─┬────┐
│ 0 │ 1 │ 2 │       │ 99 │
└───┴───┴───┴─ ... ─┴────┘
              ▲iterator(指向目前結尾)
cuLogsCurrent → 取得 iterator
cuLogsDumpToFile/Memory(iterator) → 從 iterator 起 dump,並把 iterator 推到新結尾

cuLogsDumpToMemory 的額外注意事項:

  1. buffer 整體會被 null-terminated,但個別 log entry 之間只用換行字元 (\n) 分隔。
  2. buffer 最大尺寸為 25600 bytes。
  3. size 提供的空間不足以容納所有想要的 logs,會在第一筆加上註記,且最舊、塞不下的 entry 不會被 dump。
  4. 函式返回後,size 會包含實際寫入 buffer 的位元組數。

Limitations and Known Issues(限制與已知問題)

  1. log buffer 上限 100 筆;達上限後最舊的 entry 被覆寫,log dump 會包含一行 rollover 註記。
  2. 尚未涵蓋所有 CUDA API,這是持續進行中的專案。
  3. Error Log 的 log 位置(若有給定)在「真正產生 log」前不會被檢查有效性。
  4. 這些 API 目前僅在 CUDA Driver 提供;未來版本會在 CUDA Runtime 加入對等 API。
  5. log 訊息未在地化,所有 log 一律為美式英文 (US English)。
路徑有效性延後檢查

因為 log 位置直到「實際產生 log」才會驗證(限制 3),一個無效的 CUDA_LOG_FILE 路徑在無錯誤、無 log 的執行中可能完全不會報錯,容易誤判設定正確。

考試/測驗重點

主題 必記重點
預設啟用版本 CUDA 12.3 起所有平台預設啟用 lazy loading(11.7 引入但預設停用;12.2 Linux 預設啟用)
雙版本門檻 runtime ≥ 11.7 AND driver ≥ 515;缺一即全部 eager
compiler 不需 compiler 支援;pre-11.7 的 SASS/PTX 也能 lazy load
managed variable 含 managed variable 的 module 仍 eager 載入
控制變數/值 CUDA_MODULE_LOADING = LAZY / EAGER
檢查 mode API cuModuleGetLoadingMode(須先 cuInit;比對 CU_MODULE_LAZY_LOADING)
強制 eager 載入單一 kernel cuModuleGetFunction() / cudaFuncGetAttributes();cuModuleLoad() 不保證立即載入
三大 hazard concurrent kernel 序列化 deadlock / 開機吃滿 VRAM 配置失敗 / benchmark 被初始化污染
hazard 解法 preload kernel;EAGER;cudaMallocAsync;warmup 迭代
Error Log 啟用 設 CUDA_LOG_FILE = stdout / stderr / 檔案路徑
Error Log 格式 [Time][TID][Source][Severity][API Entry Point] Message
Error Log API 位置 僅 CUDA Driver;未來才加入 Runtime
dump iterator NULL = dump 整個 buffer(最多 100 筆);非 NULL = 從該筆起並更新 iterator
cuLogsDumpToMemory buffer 上限 25600 bytes;entry 以 \n 分隔、整體 null-terminated;size 回傳實寫位元組數
Error Log 限制 buffer 上限 100 筆(rollover 註記);未涵蓋全部 API;路徑延後驗證;僅 US English
flags 參數 目前一律必須為 0(保留給未來版本)