Warp 函式、CUDA 巨集與裝置函式 (Warp Functions, CUDA Macros and Device Functions)

重點總覽

Warp 函式讓「同一 warp 內的 thread」彼此通訊與協作。其中 vote / match / reduce / shuffle 等同步原語皆以 *_sync 結尾並帶 mask 參數(連同 __syncwarp 統稱 warp __sync intrinsic),mask 用來在硬體執行前確保收斂__activemask() 為例外,不帶 mask、也非同步原語。本篇另含 CUDA 專屬巨集(__CUDA_ARCH__ 等)與裝置端工具函式(位址空間判斷/轉換、低階快取載入/儲存、__trap__nanosleep、DPX)。

項目 重點
__activemask() 回傳 32-bit「當下 active lane」快照;等同「會走某分支的 lane」,僅供 opportunistic 用
Warp vote __all_sync / __any_sync / __ballot_sync:對 predicate 做 reduction-and-broadcast
Warp match __match_any_sync / __match_all_sync:broadcast-and-compare,找出值相同的 lane
Warp reduce __reduce_<op>_sync:CC 8.x+ 才支援;add/min/max + and/or/xor
Warp shuffle __shfl_sync / _up / _down / _xor:不經 shared memory 交換值,width 必為 2 的次方
mask 鐵則 每個呼叫 thread 的 bit 必須=1、非呼叫 thread=0;參與者須用「相同 mask」(disjoint 例外)
為何 Volta+ 要 mask thread 可獨立排程,mask 明示參與集合並強制收斂;舊式無 mask intrinsic 已不安全
記憶體序 所有 warp *_sync intrinsic 不提供任何 memory ordering / barrier
__CUDA_ARCH__ 僅 device code 有定義;值 = compute_<version> * 10(compute_80 → 800)
位址空間 intrinsic __isGlobal/__isShared/... 判斷;__cvta_* 在 generic 與特定空間間轉換
__nanosleep(ns) 約睡 ns 奈秒,上限約 1 ms;常用於 exponential back-off mutex
DPX 3 路 min/max + fused add-min/max,含 ReLU;用於動態規劃演算法
優先用高階集合原語

原文反覆建議:warp 級操作盡量用 CUB Warp-Wide "Collective" Primitivescub::WarpScan / cub::WarpReduce)或 libcu++cuda::device::warp_shuffle() / warp_match_all(),以兼顧效率、安全與可攜性。

Warp Active Mask:__activemask()

unsigned __activemask();

回傳 32-bit 整數遮罩,第 N bit=1 表示呼叫當下第 N lane 為 active;inactive(含已退出程式)的 lane 為 0。

不可拿來判斷「哪些 lane 走某分支」

__activemask() 只是瞬間快照,僅供 opportunistic warp 程式設計。下例的結果是 non-deterministic、跨次執行可能不同:

if (pred) {
  // Invalid:at_least_one 的值不確定
  at_least_one = __activemask() > 0;
}
收斂不保證延續

__activemask() 收斂的 thread,不保證在後續指令仍收斂——除非後續是 warp 同步 intrinsic(*_sync)。編譯器可能重排指令,使 active 集合改變。

關鍵反模式:即使下一個指令就是 *_sync,把 __activemask() 的回傳值拿去當它的 mask 仍不安全——因為 active 集合可能在「取 mask」與「呼叫 *_sync」這兩步之間改變。正確做法是直接用已知的靜態 mask 呼叫 *_sync

unsigned mask = __activemask();
// 假設 mask == 0xFFFFFFFF(全部 bit 設立、所有 thread active)
int predicate = threadIdx.x % 2 == 0;        // 偶數 lane 為 1、奇數為 0
int result = __any_sync(mask, predicate);    // active 集合可能已不被保留

Warp Vote、Match 與 Reduce

Warp Vote(reduction-and-broadcast)

每個非退出 thread 輸入一個整數 predicate,與 0 比較後做歸約並把單一結果廣播回所有參與 thread。

函式 回傳語意
int __all_sync(mask, predicate) mask 內所有非退出 thread 的 predicate 皆非零 → 回非零
int __any_sync(mask, predicate) mask 內至少一個 thread 的 predicate 非零 → 回非零
unsigned __ballot_sync(mask, predicate) 第 N bit=1 ⟺ 第 N thread predicate 非零 active;否則為 0

Warp Match(broadcast-and-compare)

unsigned __match_any_sync(unsigned mask, T value);
unsigned __match_all_sync(unsigned mask, T value, int* pred);

Warp Reduce(CC 8.x 以上)

T __reduce_add_sync(unsigned mask, T value);   // T: 有/無號整數
T __reduce_min_sync(unsigned mask, T value);
T __reduce_max_sync(unsigned mask, T value);
unsigned __reduce_and_sync(unsigned mask, unsigned value);
unsigned __reduce_or_sync (unsigned mask, unsigned value);
unsigned __reduce_xor_sync(unsigned mask, unsigned value);
共通限制

Vote / Match / Reduce 全部受 Warp __sync Intrinsic Constraints 約束,且不提供任何 memory ordering

Warp Shuffle 函式

T __shfl_sync     (unsigned mask, T value, int srcLane,  int width = warpSize);
T __shfl_up_sync  (unsigned mask, T value, unsigned delta, int width = warpSize);
T __shfl_down_sync(unsigned mask, T value, unsigned delta, int width = warpSize);
T __shfl_xor_sync (unsigned mask, T value, int laneMask, int width = warpSize);

不經 shared memory,在 warp 內非退出 thread 間交換 value

函式 來源 lane 計算 邊界行為
__shfl_sync 直接複製 srcLane 的值 srcLane 超出 [0, width-1] → 取 srcLane % width(同 subsection 內)
__shfl_up_sync caller - delta(值往上移) 不跨 width wrap;最低 delta 個 lane 值不變
__shfl_down_sync caller + delta(值往下移) 不跨 width wrap;最高 delta 個 lane 值不變
__shfl_xor_sync caller XOR laneMask(butterfly) width<warpSize 時可讀「較早」群組;讀「較晚」群組會取回自己的值
width 與 subsection

width 小於 warpSize 時,warp 被切成多個 subsection,各自獨立、邏輯 lane ID 從 0 起算。width 必為 2 的次方且在 [1, warpSize]:1、2、4、8、16、32;其他值結果未定義。

T 可為:

只能讀「正在參與」的 thread

若目標 thread 為 inactive(未在本次呼叫中參與),取回的值未定義。下列皆為未定義行為:lane 0 未參與卻被當 srcLane、目的 lane 不在 active 集合、width 非 2 的次方。Shuffle 不隱含 memory barrier

典型用法範例

// 廣播:全 warp 從 lane 0 取 value
value = __shfl_sync(0xFFFFFFFF, value, 0);

// 蝴蝶式 full-warp 歸約:log2(32) = 5 步
for (int i = 1; i <= 16; i *= 2)
  value += __shfl_xor_sync(0xFFFFFFFF, value, i);

// 8-thread sub-partition 的 inclusive scan:log2(8) = 3 步
for (int delta = 1; delta <= 4; delta *= 2) {
  int tmp = __shfl_up_sync(0xFFFFFFFF, value, delta, /*width=*/8);
  if ((laneId % 8 - delta) >= 0) value += tmp;  // source_lane < 0 的不變
}

Warp __sync Intrinsic Constraints(mask 規則)

所有 __shfl_*_sync__match_*_sync__reduce_*_sync__syncwarp 都靠 mask 指明哪些 lane 參與,並在硬體執行前確保收斂。mask 每個 bit 對應一個 lane ID(threadIdx.x % warpSize),intrinsic 會等到 mask 內所有非退出 thread 都抵達呼叫點。

正確執行需滿足:

正確:if (threadIdx.x < 4)   __all_sync(0b1111,      pred);   // 0,1,2,3 都參與
正確:disjoint               __syncwarp(tid<16 ? 0xFFFF : 0xFFFF0000);
錯誤:active 卻沒設           __all_sync(0b0000011,   pred);   // 2,3 active 但漏設
錯誤:設了卻非 active         __all_sync(0b1111111,   pred);   // 4,5,6 非 active
錯誤:參與者 mask 重疊不一致  __all_sync(tid==0 ? 1 : 0xFFFFFFFF, pred);
何時會 hang / 未定義

呼叫 thread 不在 mask 中;mask 內某非退出 thread 既不退出也不在同一程式點以同一 mask 呼叫;或條件式中各非退出 thread 的條件未一致 evaluate——皆可能造成 kernel hang 或未定義行為。

為何 Volta 之後 mask 是必須的

本節來源只陳述:mask 用來在硬體執行前確保收斂,且效率最佳是全員參與(mask = 0xFFFFFFFF)。以下因果屬補充背景(來自 Independent Thread Scheduling / CUDA 9 棄用脈絡,非本節原文):Volta 起 thread 可獨立排程、不再保證 lock-step 收斂,mask 明確列出「應參與的 lane 集合」並強制收斂;這也是舊式無 mask 的 __shfl/__any/__ballot*_sync 版取代的原因。

CUDA-Specific Macros

__CUDA_ARCH__

代表「正在編譯的目標虛擬架構」,其值可能與實際裝置 compute capability 不同。只在 device code__device____host__ __device____global__)中有定義,可用來分流架構特化路徑、或區分 host/device 程式碼。

__CUDA_ARCH__ 四大約束

  1. 下列實體的型別簽章不得依賴 __CUDA_ARCH__ 是否定義或其值:__global__ 函式/模板、__device____constant__ 變數、texture/surface。
  2. __global__ 函式模板的具現化不得依賴 __CUDA_ARCH__;相同 template 引數的具現化必須在所有 device 程式與 host 程式中都存在。
  3. 分離編譯(separate compilation)下,具外部連結的函式/變數「是否定義」不得依賴 __CUDA_ARCH__
  4. 分離編譯下,header 中的 weak/template 函式不得依賴 __CUDA_ARCH__,否則不同 object 為不同架構編譯時可能在 link 時衝突(只取其中一版)。編譯器不保證會對這些誤用發出診斷。

__CUDA_ARCH_SPECIFIC____CUDA_ARCH_FAMILY_SPECIFIC__

用來識別具「架構特定」與「家族特定」特性的 GPU,同樣只在 device code 定義,分別對應 nvcc 的 compute_<version>acompute_<version>f

nvcc 旗標 __CUDA_ARCH__ _SPECIFIC__ _FAMILY_SPECIFIC__
compute_100a / sm_100a 1000 1000 1000
compute_100f / sm_103f 1000 未定義 1000
-arch=sm_100 1000 未定義 未定義
-arch=sm_100a 1000 1000 與未定義並存 未定義
-arch=sm_100a 為何 _SPECIFIC__ 會有兩種結果

-arch=sm_100a 等同 --generate-code arch=sm_100a,compute_100,compute_100a,會做多趟(multi-pass)編譯:對 compute_100a 那趟 __CUDA_ARCH_SPECIFIC__ == 1000,對 compute_100 那趟則未定義,兩種路徑並存。

CUDA Feature Testing Macros

當 CUDA 前端編譯器支援某特性時定義對應巨集:

巨集 意義 / 啟用方式
__CUDACC_DEVICE_ATOMIC_BUILTINS__ 支援 device atomic 編譯器 builtins
__NVCC_DIAG_PRAGMA_SUPPORT__ 支援診斷控制 pragma
__CUDACC_EXTENDED_LAMBDA__ 支援 extended lambda(--expt-extended-lambda / --extended-lambda
__CUDACC_RELAXED_CONSTEXPR__ 支援 relaxed constexpr(--expt-relaxed-constexpr

__nv_pure__ 屬性

標記 pure 函式(對參數無 side effect、可讀但不改全域變數),host/device 皆可用。編譯器將其轉成 GNU pure 屬性或 MSVC noalias 屬性。

__device__ __nv_pure__ int add(int a, int b) { return a + b; }

CUDA-Specific Functions

位址空間判斷(Address Space Predicate)

__device__ unsigned __isGlobal(const void* ptr);
__device__ unsigned __isShared(const void* ptr);
__device__ unsigned __isConstant(const void* ptr);
__device__ unsigned __isGridConstant(const void* ptr);  // __grid_constant__ kernel 參數
__device__ unsigned __isLocal(const void* ptr);

ptr 為指向該位址空間物件的 generic 位址則回 1,否則回 0;對 NULL 指標行為未指定。

可攜替代

libcu++ 提供 cuda::device::is_address_from() / cuda::device::is_object_from(),可作為 __isGlobal/__isShared 等 intrinsic 的可攜且更安全替代。

位址空間轉換(Address Space Conversion)

當編譯器無法判定指標的位址空間(跨 translation unit、或與 PTX 互動)時使用:

generic → 特定(回 size_t 特定 → generic(回 void* 對應 PTX
__cvta_generic_to_global __cvta_global_to_generic cvta.to.global / cvta.global
__cvta_generic_to_shared __cvta_shared_to_generic cvta.to.shared / cvta.shared
__cvta_generic_to_constant __cvta_constant_to_generic cvta.to.const / cvta.const
__cvta_generic_to_local __cvta_local_to_generic cvta.to.local / cvta.local
省暫存器的最佳化

shared / local / constant 空間位址範圍小於 32-bit,可把 64-bit 位址截斷成 32-bit 整數儲存(少佔暫存器,且 32-bit 運算更快);要還原時 zero-extend 回 64-bit 再呼叫對應的 __cvta_*_to_generic

低階載入/儲存(快取運算子)

函式群 用途
__ldg read-only L1/Tex 快取載入
__ldcg / __ldca / __ldcs / __ldlu / __ldcv 依 PTX ISA 指定快取運算子的載入
__stwb / __stcg / __stcs / __stwt 依 PTX ISA 指定快取運算子的儲存

皆支援所有 C++ 基本型別、CUDA 向量型別(x3 分量除外)與擴充浮點型別(__half__half2__nv_bfloat16__nv_bfloat162)。

__trap()__nanosleep()

void __trap();                                 // 中止 kernel、向 host 觸發中斷
__device__ void __nanosleep(unsigned nanoseconds);  // 約睡 ns 奈秒,上限約 1 ms
__trap() 會毀掉 context

任何 device thread 呼叫 __trap() 會中止 kernel 執行並在 host 端觸發中斷,導致 CUDA context 損毀,後續所有 CUDA 呼叫與 kernel 啟動皆失敗。可攜替代:libcu++ cuda::std::terminate()

// __nanosleep 經典用例:exponential back-off mutex
__device__ void mutex_lock(unsigned* mutex) {
  unsigned ns = 8;
  while (atomicCAS(mutex, 0, 1) == 1) {
    __nanosleep(ns);
    if (ns < 256) ns *= 2;     // 退避時間倍增,上限 256ns
  }
}

DPX(Dynamic Programming eXtension)指令

對最多三個 16-bit 或 32-bit 有/無號整數做 min/max 與 fused add-min/max,並可選 ReLU(夾到 0)。

類別 代表函式 語意
三路比較 __vimax3_s32 / __vimin3_u16x2 max(a,b,c) / min(a,b,c)
二路 + ReLU __vimax_s32_relu / __vimin_s16x2_relu max(a,b,0) / max(min(a,b),0)
三路 + ReLU __vimax3_s32_relu max(a,b,c,0) / max(min(a,b,c),0)
二路 + 回報誰較大/小 __vibmax_* / __vibmin_*bool* pred 回極值並把比較結果寫入 pred
Fused add-min/max __viaddmax_s32 / __viaddmin_u32 max(a+b,c) / min(a+b,c)(可再 +ReLU)
__vimax3_s32_relu(-15, 8, 5);      // max(-15, 8, 5, 0) = 8
__vimax3_s32_relu(-15, -2, -4);    // 全負 → ReLU 後 = 0
__viaddmax_s32_relu(-5, 6, -2);    // max(-5+6, -2, 0) = max(1,-2,0) = 1
__vimax3_u16x2(0x00050002, 0x00070004, 0x00020006); // 逐 16-bit → 0x00070006
硬體加速視 CC 而定

DPX 依 compute capability 決定為硬體加速或軟體模擬(詳見 Arithmetic Instructions 與 CUDA Math API)。s16x2 / u16x2 後綴表示一次處理兩個 packed 的 16-bit 值。常用於 Smith-Waterman、Needleman-Wunsch(基因序列)與 Floyd-Warshall(路徑最佳化)等動態規劃演算法。

考試/測驗重點

題型 關鍵答案
__activemask() 能否判斷哪些 lane 走某分支? 不能;只是 active lane 的瞬間快照,供 opportunistic 用
收斂在 __activemask() 後保證持續嗎? 不保證,除非後續是 warp 同步 intrinsic(*_sync
__all_sync vs __any_sync all:全部非零才回非零;any:至少一個非零就回非零
__ballot_sync 第 N bit 何時=1? 第 N thread predicate 非零 active
__match_all_sync 何時回 0? mask 內非退出 thread 的值不全相同時回 0,且 *pred=false
__match_any_sync vs __match_all_sync any:回「與自己同值」的 lane 遮罩;all:全同回 mask、否則回 0 並設 *pred
Warp reduce 需要哪個 compute capability? CC 8.x 以上
__reduce_and/or/xor_sync 接受什麼型別? unsigned(bitwise 歸約)
__shfl_up_sync 來源 lane 怎麼算? caller - delta,不 wrap,最低 delta 個 lane 不變
__shfl_xor_sync 實作什麼模式? butterfly(蝴蝶)定址,用於 tree reduction / broadcast
shuffle 的 width 限制? 必為 2 的次方且 ∈ [1, 32]:1/2/4/8/16/32
讀到 inactive thread 的 shuffle 值會如何? 未定義
warp *_sync 提供 memory ordering 嗎? 不提供,也不隱含 memory barrier
mask 規則:呼叫/非呼叫 thread 的 bit? 呼叫者 bit=1、非呼叫者 bit=0,退出 thread 被忽略
不同 mask 可並發呼叫嗎? 可,只要彼此 disjoint,即使 divergent 也合法
為何 Volta+ 要帶 mask? thread 獨立排程,mask 明示參與集合並在硬體執行前強制收斂
__CUDA_ARCH__ 在哪裡有定義?值怎麼算? 只在 device code;值 = compute_<version> * 10(compute_80→800)
compute_80,code=sm_90__CUDA_ARCH__=? 800(取 compute 的虛擬架構,非 sm_90)
compute_100f / sm_103f_SPECIFIC___FAMILY_SPECIFIC__ __CUDA_ARCH_SPECIFIC__ 未定義;__CUDA_ARCH_FAMILY_SPECIFIC__ = 1000
__isShared 回傳什麼? ptr 指向 shared 物件的 generic 位址回 1,否則 0
__cvta_generic_to_shared 回傳型別? size_t(特定空間位址),對應 PTX cvta.to.shared
__ldg 走哪條快取? read-only L1/Tex
__trap() 的副作用? 中止 kernel、觸發 host 中斷、損毀 CUDA context
__nanosleep 最大睡眠時間? 約 1 毫秒
DPX 的 ReLU 後綴語意? 對結果夾到 0,即 max(..., 0)
DPX 典型應用? Smith-Waterman、Needleman-Wunsch、Floyd-Warshall 等動態規劃