編譯器最佳化提示、診斷與 Warp Matrix (Compiler Hints, Diagnostics and Warp Matrix)
重點總覽
本篇涵蓋三組 device code 擴充:編譯器最佳化提示(給編譯器「假設/期望」資訊以產生更好的碼)、除錯與診斷(assert、__brkpt()、診斷 pragma),以及 Warp Matrix Functions (WMMA)(用 Tensor Core 做 D=A*B+C 的 warp 級矩陣乘加)。提示類函式在 device code 永遠可用,host 端支援與否視 host 編譯器而定;WMMA 需 compute capability 7.0+、整個 warp 協作,且 fragment 是架構特定的不透明結構。
| 項目 | 重點 |
|---|---|
#pragma unroll |
放在迴圈正前方;無引數=完全展開(trip count 為常數時),0/1=不展開,非正整數/超過 INT_MAX=忽略並警告 |
__builtin_assume(pred) |
假設條件為真;執行期為假則 UB,有副作用則 unspecified |
__builtin_expect(in, exp) |
分支預測提示,回傳 in;類似 C++20 [[likely]]/[[unlikely]] |
__builtin_unreachable() |
告知控制流不會到達此點;到達則 UB |
assert() |
條件為 0 時中止 kernel,印訊息並使 context 損毀(後續呼叫回 cudaErrorAssert) |
nv_diag_* pragma |
調整特定診斷訊息嚴重度,只影響 nvcc 前端,不影響 host 編譯器 |
| WMMA | nvcuda::wmma,Tensor Core 做 D=A*B+C,CC 7.0+、全 warp 協作 |
| WMMA 對齊 | mptr 需 256-bit 對齊;ldm 為 16 bytes 倍數 |
編譯器最佳化提示 (Compiler Optimization Hints)
最佳化提示用額外資訊裝飾程式碼,幫助編譯器產生更好的碼。
- 這些 built-in 函式在 device code 一律可用。
- host code 支援與否取決於 host 編譯器。
#pragma unroll
編譯器預設會展開 trip count 已知的小迴圈;此 pragma 可控制任一迴圈的展開。必須緊接在迴圈正前方,且只作用於該迴圈。後面可選擇性接一個整數常數運算式:
| 引數情況 | 行為 |
|---|---|
| 缺省(無引數) | trip count 為常數時完全展開 |
| 求值為 0 或 1 | 不展開 |
非正整數或大於 INT_MAX |
忽略 pragma 並發出警告 |
struct MyStruct { static constexpr int value = 4; };
inline constexpr int Count = 4;
__device__ void foo(int* p1, int* p2) {
// 無引數,迴圈將完全展開
#pragma unroll
for (int i = 0; i < 12; ++i) p1[i] += p2[i] * 2;
#pragma unroll (Count + 1) // unroll 值 = 5
for (int i = 0; i < 12; ++i) p1[i] += p2[i] * 4;
#pragma unroll 1 // unroll 值 = 1,停用展開
for (int i = 0; i < 12; ++i) p1[i] += p2[i] * 8;
#pragma unroll value // unroll 值 = 4
for (int i = 0; i < 12; ++i) p1[i] += p2[i] * 16;
#pragma unroll -1 // 負值,pragma 被忽略
for (int i = 0; i < 12; ++i) p1[i] += p2[i] * 2;
}
__builtin_assume_aligned()
讓編譯器假設回傳指標至少對齊到 align bytes。
void* __builtin_assume_aligned(const void* ptr, size_t align);
void* __builtin_assume_aligned(const void* ptr, size_t align, <integral type> offset);
- 三參數版讓編譯器假設
(char*) ptr - offset至少對齊到alignbytes。 align必須是 2 的次方且為整數字面值 (integer literal)。
void* res1 = __builtin_assume_aligned(ptr, 32); // res1 至少 32-byte 對齊
void* res2 = __builtin_assume_aligned(ptr, 32, 8); // res2 = (char*)ptr - 8 至少 32-byte 對齊
建議改用 libcu++ 的 cuda::std::assume_aligned(),做為可攜且更安全的替代方案。
__builtin_assume() 與 __assume()
void __builtin_assume(bool predicate);
void __assume(bool predicate); // 僅限 Microsoft Compiler
讓編譯器假設布林引數為真。
若引數在執行期為 false,行為 undefined;若引數有副作用,行為 unspecified。
__device__ bool is_greater_than_zero(int value) { return value > 0; }
__device__ bool f(int value) {
__builtin_assume(value > 0);
return is_greater_than_zero(value); // 直接回傳 true,不再實際評估條件
}
__builtin_expect()
long __builtin_expect(long input, long expected);
- 告知編譯器
input預期等於expected,並回傳input的值。 - 通常用於提供分支預測資訊,行為類似 C++20 的
[[likely]]/[[unlikely]]屬性。
if (__builtin_expect(var, 0)) // 提示編譯器:var 很可能等於 0
doit();
__builtin_unreachable()
void __builtin_unreachable(void);
告知編譯器控制流永不會到達此呼叫點;若執行期真的到達則為 UB。可避免產生不可達分支的碼,並關閉不可達碼的編譯警告。
switch (in) {
case 1: return 4;
case 2: return 10;
default: __builtin_unreachable(); // 告知 default 永不被觸及
}
自訂 ABI 與 MMA Throughput Pragma
#pragma nv_abi
讓在 separate compilation mode 編譯的程式,藉由保留函式所用暫存器數量,達到接近 whole program compilation 的效能。
#pragma nv_abi preserve_n_data(EXPR) preserve_n_control(EXPR)
- 兩個引數皆選擇性、可任意順序,但至少要提供一個。
preserve_n_data(EXPR):限制保留的 data registers 數量。preserve_n_control(EXPR):限制保留的 control registers 數量。
放置位置與效果:
| 放置位置 | 效果 |
|---|---|
| device 函式宣告/定義前 | 修改對該函式所有呼叫的 custom ABI 屬性 |
| C++ 運算式語句中的間接呼叫前 | 只影響該次特定呼叫 |
放在 call site 時只對間接呼叫 (indirect call) 有效,對直接呼叫無效。間接呼叫僅支援自由函式 (free function);對 function reference 或 class member function 的間接呼叫不支援。此外,若函式宣告與其定義的 pragma 引數不一致,程式為 ill-formed。
#pragma nv_mma_throughput
針對矩陣乘加 (MMA) 啟用專門調校的編譯器最佳化。
- 僅在 entry-function scope(即
__global__函式)上有效。 - 效果可能沿呼叫路徑傳播到被該 entry function 呼叫的
__device__函式。
此 pragma 仍屬實驗性,其設定僅在有限的內部 NVIDIA workload 上調校過,不保證每個 kernel 都能改善效能。
除錯與診斷 (Debugging and Diagnostics)
Assertion:assert()
#define assert(expression) /* unspecified */
當 expression 等於 0 時中止 kernel 執行:
- 若在 debugger 內執行,觸發 breakpoint 供檢視 device 狀態。
- 否則,每個
expression == 0的執行緒,在透過cudaDeviceSynchronize()、cudaStreamSynchronize()或cudaEventSynchronize()與 host 同步後,向 stderr 印出訊息:
<filename>:<line number>:<function>:
block: [blockIdx.x,blockIdx.y,blockIdx.z],
thread: [threadIdx.x,threadIdx.y,threadIdx.z]
Assertion `<expression>` failed.
kernel 執行會被中止並在 host 程式引發中斷。assert() 會使 CUDA context 損毀,導致後續任何 CUDA 呼叫或 kernel 啟動失敗並回傳 cudaErrorAssert。expression 非 0 時不受影響。
- assert 僅供除錯用途,會影響效能,production code 建議停用。
- 停用方式:在 include
assert.h/<cassert>前定義NDEBUG巨集,或用編譯旗標-DNDEBUG。 expression不應有副作用,否則停用 assert 會改變程式功能。assert()在__device__與__tile__code 中皆可用。
Breakpoint:__brkpt()
void __brkpt();
從任一 device thread 呼叫即可暫停 kernel 函式的執行。
Diagnostic Pragmas
調整特定診斷訊息被觸發時的嚴重度,用警告訊息中顯示的 error number 指定對象:
#pragma nv_diag_xxx <error_number1>, <error_number2> ...
| Pragma | 作用 |
|---|---|
nv_diag_suppress |
抑制(隱藏)診斷 |
nv_diag_warning |
設為 warning(依 pragma 名稱推斷) |
nv_diag_error |
設為 error(來源:任何診斷皆可升為 error) |
nv_diag_default |
回復到任何 pragma 之前的嚴重度(受 command-line 選項修改後的正常值) |
nv_diag_once |
只觸發一次(依 pragma 名稱推斷,來源未明載行為) |
任何診斷都可被改成 error,但只有 warning 能被抑制或在改成 error 後再還原嚴重度。
#pragma nv_diag_suppress 177 // "declared but never referenced"
void foo() { int i = 0; }
#pragma nv_diag_default 177
void bar() { int i = 0; }
也可用 push/pop 儲存與還原當前診斷 pragma 狀態:
#pragma nv_diagnostic push
#pragma nv_diag_suppress 177
void foo() { int i = 0; }
#pragma nv_diagnostic pop
void bar() { int i = 0; } // 此處會再次發出警告
這些指令只影響 nvcc CUDA 前端,對 host 編譯器無效。支援診斷 pragma 時 nvcc 會定義巨集 __NVCC_DIAG_PRAGMA_SUPPORT__。
Warp Matrix Functions (WMMA) 與 Tensor Core
C++ warp matrix 運算利用 Tensor Cores 加速 D=A*B+C 形式的矩陣問題。
- 支援 compute capability 7.0 以上裝置的混合精度浮點資料。
- 需要 warp 內所有執行緒協作。
- 只有在條件對整個 warp 求值結果相同時,才允許用於條件式碼,否則執行極可能 hang。
- 所有相關函式與型別定義於 namespace
nvcuda::wmma;sub-byte 運算屬 preview,定義於nvcuda::wmma::experimental,API/資料結構可能變動。
核心 API
template<typename Use, int m, int n, int k, typename T, typename Layout=void>
class fragment;
void load_matrix_sync(fragment<...>& a, const T* mptr, unsigned ldm);
void load_matrix_sync(fragment<...>& a, const T* mptr, unsigned ldm, layout_t layout);
void store_matrix_sync(T* mptr, const fragment<...>& a, unsigned ldm, layout_t layout);
void fill_fragment(fragment<...>& a, const T& v);
void mma_sync(fragment<...>& d, const fragment<...>& a,
const fragment<...>& b, const fragment<...>& c, bool satf=false);
| 函式 | 說明 |
|---|---|
fragment |
分散於整個 warp 的矩陣片段;元素到內部儲存的映射未指定且可能隨架構改變 |
load_matrix_sync |
等所有 lane 到達後,從記憶體載入 fragment |
store_matrix_sync |
等所有 lane 到達後,把 fragment 存回記憶體 |
fill_fragment |
以常數值 v 填滿 fragment |
mma_sync |
warp 同步矩陣乘加 D=A*B+C(也支援 in-place C=A*B+C) |
fragment 模板參數
第一個模板參數 Use 指定該 fragment 在運算中的角色,決定 tile 維度:
Use |
角色 | tile 維度 |
|---|---|---|
matrix_a |
第一乘數 A | m × k |
matrix_b |
第二乘數 B | k × n |
accumulator |
來源/目的累加器 C 或 D | m × n |
- 乘數的
T可為double、float、__half、__nv_bfloat16、char、unsigned char;累加器的T可為double、float、int、__half(僅特定組合受支援)。 matrix_a/matrix_b必須指定Layout:row_major或col_major(指該方向元素在記憶體連續)。accumulator的Layout應保留預設void;只有在 load/store 時才另外指定 row/col layout。
對齊、步幅與同步規則
mptr必須是 256-bit 對齊指標,指向矩陣第一個元素。ldm(連續 row/col 間以元素計的 stride):__half須為 8 的倍數、float須為 4 的倍數(兩者皆等於 16 bytes 倍數)。- 載入 accumulator 時 layout 須指定為
mem_row_major或mem_col_major;matrix_a/matrix_b則由 fragment 的 layout 參數推得。 mptr、ldm、layout及所有模板參數對 warp 內所有執行緒必須一致,且須由所有執行緒呼叫,否則結果 undefined。
mma_sync 額外規則:satf 與各 fragment 模板參數須全 warp 一致,且 A、B、C、D 的 m、n、k 必須相符。若 satf(saturate to finite value)為 true:
| 元素結果 | 累加器內容 |
|---|---|
| +Infinity | +MAX_NORM |
| -Infinity | -MAX_NORM |
| NaN | +0 |
直接存取 fragment 元素
因元素映射未指定,個別元素一般須在 store_matrix_sync 後從記憶體存取。但若全 warp 對所有 fragment 元素一致地做 element-wise 運算,可直接用成員:
enum fragment<Use, m, n, k, T, Layout>::num_elements;
T fragment<Use, m, n, k, T, Layout>::x[num_elements];
// 將 accumulator tile 整體乘以 0.5
wmma::fragment<wmma::accumulator, 16, 16, 16, float> frag;
float alpha = 0.5f; // 全 warp 相同值
for (int t = 0; t < frag.num_elements; t++) frag.x[t] *= alpha;
WMMA 資料型別、矩陣尺寸與限制
支援的型別與 m-n-k 尺寸
| Matrix A | Matrix B | Accumulator | Matrix Size (m-n-k) | 條件 |
|---|---|---|---|---|
__half |
__half |
float / __half |
16x16x16 / 32x8x16 / 8x32x16 | CC 7.0+ |
unsigned char |
unsigned char |
int |
16x16x16 / 32x8x16 / 8x32x16 | |
signed char |
signed char |
int |
16x16x16 / 32x8x16 / 8x32x16 | |
__nv_bfloat16 |
__nv_bfloat16 |
float |
16x16x16 / 32x8x16 / 8x32x16 | CC 8.0+ |
precision::tf32 |
precision::tf32 |
float |
16x16x8 | CC 8.0+ |
double |
double |
double |
8x8x4 | CC 8.0+ |
precision::u4 |
precision::u4 |
int |
8x8x32 | experimental |
precision::s4 |
precision::s4 |
int |
8x8x32 | experimental |
precision::b1 |
precision::b1 |
int |
8x8x128 | experimental |
Alternate Floating Point(CC 8.0+)
__nv_bfloat16:替代 fp16 格式,與 f32 同範圍但精度降低(7 bits),型別在cuda_bf16.h。其 fragment 必須搭配float累加器;shape 與運算同__half。tf32:Tensor Core 特殊浮點格式,與 f32 同範圍、精度 >=10 bits,內部 layout 為實作定義。輸入矩陣須手動轉成 tf32:用 intrinsic__float_to_tf32(輸入輸出皆float型別,但輸出在數值上是 tf32)。需以precision::tf32精度 +float資料型別搭配load_matrix_sync,兩個累加器皆須float,唯一支援尺寸 16x16x8。映射為precision::tf32 -> float。
tf32 精度僅供 Tensor Core 使用;若與其他 float 運算混用,結果的精度與範圍未定義。
Double Precision(CC 8.0+)
使用 double 型別 fragment;mma_sync 以 **.rn(round to nearest even)**捨入修飾子執行。
Sub-byte Operations(preview / experimental)
存取 Tensor Core 低精度能力,位於 nvcuda::wmma::experimental,資料型別有 u4(4-bit unsigned)、s4(4-bit signed)、b1(1-bit)。元素被打包,故 num_storage_elements < num_elements:
precision::u4 -> unsigned (8 elements / 1 storage element)
precision::s4 -> int (8 elements / 1 storage element)
precision::b1 -> unsigned (32 elements / 1 storage element)
T -> T // 其餘型別
- sub-byte 的 layout 固定:
matrix_a須row_major、matrix_b須col_major。 ldm:u4/s4須為 32 的倍數、b1須為 128 的倍數(皆等於 16 bytes 倍數)。bmma_sync做位元矩陣乘加D = (A op B) + C:op為bmmaBitOp(bmmaBitOpXOR需 compute_75+;bmmaBitOpAND需 CC 8.0+)後接累加bmmaAccumulateOpPOPC(計算 set bit 數)。
precision::u4、precision::s4、以及 bmmaBitOpXOR 的 precision::b1 變體已棄用,將在 sm_90 移除。
Restrictions(fragment 是架構特定的)
- tensor core 所需特殊格式會隨 major/minor 架構不同;fragment 是不透明、架構特定的 ABI 結構,開發者不可假設參數如何映射到暫存器。
- 危險:若兩函式為不同 link-compatible 架構編譯(如 sm_70 與 sm_75,其 fragment layout 不同)卻連結在一起,互傳 fragment 會導致錯誤結果或損毀,且編譯期與執行期工具都可能偵測不到。
跨外部介面傳遞時,應先 wmma::store_matrix_sync(dst, …) 把矩陣存到記憶體,再以指標型別(如 float* dst)安全傳遞,避免 fragment layout 不一致的連結風險。weak linkage(如 inline 函式)時 linker 可能任選定義,更易隱性跨單元傳遞。
由於 sm_70 可在 sm_75 上執行,上述 sm_75 範例改用 sm_70 編譯仍能在 sm_75 正確運作;但若要與其他 sm_75 分別編譯的二進位連結,建議在應用程式中保留 sm_75 原生碼。
完整範例(16x16x16 單 warp 矩陣乘法)
#include <mma.h>
using namespace nvcuda;
__global__ void wmma_ker(half* a, half* b, float* c) {
// 宣告 fragment
wmma::fragment<wmma::matrix_a, 16, 16, 16, half, wmma::col_major> a_frag;
wmma::fragment<wmma::matrix_b, 16, 16, 16, half, wmma::row_major> b_frag;
wmma::fragment<wmma::accumulator, 16, 16, 16, float> c_frag;
wmma::fill_fragment(c_frag, 0.0f); // 輸出初始化為零
wmma::load_matrix_sync(a_frag, a, 16); // 載入輸入
wmma::load_matrix_sync(b_frag, b, 16);
wmma::mma_sync(c_frag, a_frag, b_frag, c_frag); // 矩陣乘加
wmma::store_matrix_sync(c, c_frag, 16, wmma::mem_row_major); // 存回輸出
}
考試/測驗重點
| 題型 | 關鍵答案 |
|---|---|
#pragma unroll 無引數時? |
trip count 為常數則完全展開 |
#pragma unroll 1 效果? |
停用展開(0 或 1 都不展開) |
#pragma unroll -1 效果? |
忽略 pragma 並發警告(非正或 > INT_MAX) |
#pragma unroll 放哪? |
緊接迴圈正前方,只作用該迴圈 |
__builtin_assume 條件執行期為假? |
undefined behavior;有副作用則 unspecified |
__builtin_expect 回傳什麼? |
回傳 input,提供分支預測,類似 [[likely]]/[[unlikely]] |
__assume() 限制? |
僅 Microsoft Compiler |
#pragma nv_abi 放 call site 的效果? |
只影響間接呼叫,對直接呼叫無效;對 function reference / member function 的間接呼叫不支援 |
#pragma nv_abi 放宣告/定義前? |
影響對該函式的所有呼叫;宣告與定義的 pragma 引數不一致則 ill-formed |
__builtin_assume_aligned 的 align 限制? |
必須是 2 的次方且為整數字面值 |
| 三參數對齊版假設什麼? | (char*)ptr - offset 至少 align-byte 對齊 |
| assert 對 CUDA context? | 使 context 損毀,後續呼叫回 cudaErrorAssert |
| 如何停用 assert? | 定義 NDEBUG(include 前)或編譯旗標 -DNDEBUG |
| assert 同步用哪些函式印訊息? | cudaDeviceSynchronize / cudaStreamSynchronize / cudaEventSynchronize |
__brkpt() 作用? |
任一 device thread 呼叫即暫停 kernel |
| 診斷 pragma 哪些能被抑制? | 只有 warning;任何診斷都能升成 error |
| nv_diag pragma 影響 host 編譯器嗎? | 不影響,只影響 nvcc 前端 |
| WMMA 最低 compute capability? | 7.0(bfloat16/tf32/double/AND 需 8.0) |
| WMMA 條件式碼安全條件? | 條件須對整個 warp 求值相同,否則 hang |
mptr 對齊要求? |
256-bit 對齊 |
ldm 要求? |
__half 倍數 8、float 倍數 4(皆 16 bytes 倍數) |
| matrix_a/matrix_b 的 tile 維度? | A 為 m×k、B 為 k×n、accumulator 為 m×n |
__half 支援的 m-n-k? |
16x16x16、32x8x16、8x32x16 |
double WMMA 尺寸與捨入? |
8x8x4,.rn(round to nearest even) |
| tf32 唯一支援尺寸? | 16x16x8,累加器須 float |
| satf=true 時 NaN 結果? | 累加器存 +0(±Inf → ±MAX_NORM) |
| sub-byte fragment 的 layout? | 固定:matrix_a 須 row_major、matrix_b 須 col_major |
bmmaBitOp 的 CC 門檻? |
bmmaBitOpXOR 需 compute_75+、bmmaBitOpAND 需 CC 8.0+ |
| 為何不可直接互傳 fragment? | fragment 架構特定,不同 link-compatible 架構互傳致錯誤/損毀;應 store 到記憶體再傳指標 |