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(保留給未來版本)