編譯器最佳化提示、診斷與 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)

最佳化提示用額外資訊裝飾程式碼,幫助編譯器產生更好的碼。

#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);
void* res1 = __builtin_assume_aligned(ptr, 32);     // res1 至少 32-byte 對齊
void* res2 = __builtin_assume_aligned(ptr, 32, 8);  // res2 = (char*)ptr - 8 至少 32-byte 對齊
Tip

建議改用 libcu++ 的 cuda::std::assume_aligned(),做為可攜且更安全的替代方案。

__builtin_assume()__assume()

void __builtin_assume(bool predicate);
void __assume(bool predicate);   // 僅限 Microsoft Compiler

讓編譯器假設布林引數為真。

Warning

若引數在執行期為 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);
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)

放置位置與效果:

放置位置 效果
device 函式宣告/定義前 修改對該函式所有呼叫的 custom ABI 屬性
C++ 運算式語句中的間接呼叫前 只影響該次特定呼叫
Warning

放在 call site 時只對間接呼叫 (indirect call) 有效,對直接呼叫無效。間接呼叫僅支援自由函式 (free function);對 function reference 或 class member function 的間接呼叫不支援。此外,若函式宣告與其定義的 pragma 引數不一致,程式為 ill-formed。

#pragma nv_mma_throughput

針對矩陣乘加 (MMA) 啟用專門調校的編譯器最佳化。

Note

此 pragma 仍屬實驗性,其設定僅在有限的內部 NVIDIA workload 上調校過,不保證每個 kernel 都能改善效能。

除錯與診斷 (Debugging and Diagnostics)

Assertion:assert()

#define assert(expression) /* unspecified */

expression 等於 0 時中止 kernel 執行

<filename>:<line number>:<function>:
block: [blockIdx.x,blockIdx.y,blockIdx.z],
thread: [threadIdx.x,threadIdx.y,threadIdx.z]
Assertion `<expression>` failed.
Warning

kernel 執行會被中止並在 host 程式引發中斷。assert() 會使 CUDA context 損毀,導致後續任何 CUDA 呼叫或 kernel 啟動失敗並回傳 cudaErrorAssertexpression 非 0 時不受影響。

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 名稱推斷,來源未明載行為)
Warning

任何診斷都可被改成 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; }  // 此處會再次發出警告
Note

這些指令只影響 nvcc CUDA 前端,對 host 編譯器無效。支援診斷 pragma 時 nvcc 會定義巨集 __NVCC_DIAG_PRAGMA_SUPPORT__

Warp Matrix Functions (WMMA) 與 Tensor Core

C++ warp matrix 運算利用 Tensor Cores 加速 D=A*B+C 形式的矩陣問題。

核心 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

對齊、步幅與同步規則

Important

  • mptr 必須是 256-bit 對齊指標,指向矩陣第一個元素。
  • ldm(連續 row/col 間以元素計的 stride):__half 須為 8 的倍數float 須為 4 的倍數(兩者皆等於 16 bytes 倍數)。
  • 載入 accumulator 時 layout 須指定為 mem_row_majormem_col_majormatrix_a/matrix_b 則由 fragment 的 layout 參數推得。
  • mptrldmlayout 及所有模板參數對 warp 內所有執行緒必須一致,且須由所有執行緒呼叫,否則結果 undefined。

mma_sync 額外規則:satf 與各 fragment 模板參數須全 warp 一致,且 A、B、C、D 的 mnk 必須相符。若 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+)

Warning

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  // 其餘型別
Warning

precision::u4precision::s4、以及 bmmaBitOpXORprecision::b1 變體已棄用,將在 sm_90 移除

Restrictions(fragment 是架構特定的)

Important

跨外部介面傳遞時,應先 wmma::store_matrix_sync(dst, …) 把矩陣存到記憶體,再以指標型別(如 float* dst)安全傳遞,避免 fragment layout 不一致的連結風險。weak linkage(如 inline 函式)時 linker 可能任選定義,更易隱性跨單元傳遞。

Note

由於 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_alignedalign 限制? 必須是 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_arow_majormatrix_bcol_major
bmmaBitOp 的 CC 門檻? bmmaBitOpXOR 需 compute_75+、bmmaBitOpAND 需 CC 8.0+
為何不可直接互傳 fragment? fragment 架構特定,不同 link-compatible 架構互傳致錯誤/損毀;應 store 到記憶體再傳指標