函式與變數限定詞 (Function and Variable Annotations)
重點總覽
CUDA C/C++ 在標準 C++ 之上加了一組限定詞 (annotation),用來標示函式在哪個情境執行、變數放在哪段記憶體、以及如何啟動 kernel。核心分三類:執行空間限定詞(__global__ / __device__ / __host__)、記憶體空間限定詞(__device__ / __constant__ / __shared__ / __managed__),以及啟動相關限定詞(__launch_bounds__ / __maxnreg__ / __cluster_dims__)。再配合內建型別(向量型別、dim3)與內建變數(threadIdx 等)構成 kernel 的基本語彙。
| 項目 | 重點 |
|---|---|
__global__ |
kernel 進入點;host 端可呼叫、在 device(SIMT) 執行;回傳 void、需 <<<>>>、不可遞迴、非 class member |
__device__ / __host__ |
分別只在 device / host 執行;__host__ __device__ 兩邊都編譯,用 __CUDA_ARCH__ 分流 |
__device__ 變數 |
放 device 全域記憶體;host 端用 cudaMemcpyToSymbol 等存取 |
__constant__ |
device 端唯讀,只能由 host 改 |
__shared__ |
block 內共享;static 尺寸或 extern __shared__ 動態尺寸 |
__managed__ |
host/device 自動共用;位址非常數運算式、不可作 decltype 非括號引數 |
__restrict__ |
承諾指標不別名 (no aliasing),放開優化;會增加暫存器壓力 |
__grid_constant__ |
__global__ 參數共用單一位址、唯讀,省去 per-thread copy |
__launch_bounds__ |
提示 maxThreadsPerBlock 等,引導暫存器配置與占用率 |
| 內建變數 | gridDim/blockDim 為 dim3,blockIdx/threadIdx 為 uint3,warpSize 為 int |
Function Execution Space Specifiers
執行空間限定詞標示函式在 host、SIMT(傳統 device)、或 tile 情境執行與被呼叫:
| 限定詞 | 執行於 | 可被呼叫自 |
|---|---|---|
__host__(或不寫) |
Host | Host |
__device__ |
SIMT | SIMT |
__global__ |
SIMT | Host + SIMT |
__tile__ |
Tile | Tile |
__tile_global__ |
Tile | Host |
__global__ / __tile_global__ 的限制
- 必須回傳 void。
- 不可為 class / struct / union 的成員。
- 必須提供 execution configuration(即
<<<...>>>,見 Kernel Configuration)。 - 不支援遞迴。
- 另見
__global__函式參數的額外限制(見__grid_constant__參數)。
__global__ / __tile_global__ 的呼叫是非同步的:在 device 完成執行前就先返回 host thread。
多重執行空間與 __CUDA_ARCH__
宣告多個執行空間(如 __host__ __device__)的函式會為每個情境各編譯一份。用 __CUDA_ARCH__ 巨集區分 host / device 程式路徑:
__host__ __device__ void func() {
#if defined(__CUDA_ARCH__)
// Device 端程式路徑
#else
// Host 端程式路徑
#endif
}
__CUDA_ARCH__ 在 host 端是未定義的,因此凡是要在 host 程式中用到的值(例如 threads per block)都不能依賴它。Variable Memory Space Specifiers
記憶體空間限定詞標示變數在 device 上的儲存位置:
| 限定詞 | 位置 | 可存取者 | 生命週期 | 唯一實例 |
|---|---|---|---|---|
__device__ |
device 全域記憶體 | grid 內 threads / Runtime API | Program / CUDA context | 每個 device 一份 |
__tile__ |
device 全域記憶體 | tile blocks / Runtime API(tile code 可直接以名稱存取) | Program / CUDA context | 每個 device 一份 |
__constant__ |
device constant 記憶體 | grid 內 threads / Runtime API | Program / CUDA context | 每個 device 一份 |
__managed__ |
host 與 device(自動) | host/device threads | Program | 每個 program 一份 |
__shared__ |
device(SM 上) | 同一 block 的 threads | Block | 每個 block 一份 |
| 無限定詞 | device(暫存器) | 單一 thread | 單一 thread | 單一 thread |
__device__、__tile__、__constant__變數可從 host 用cudaGetSymbolAddress()、cudaGetSymbolSize()、cudaMemcpyToSymbol()、cudaMemcpyFromSymbol()存取。__constant__在 device 端唯讀,只能由 host 透過 Runtime API 修改。
__device__ float device_var = 4.0f; // 位於 device 記憶體
__constant__ float constant_var = 4.0f; // 位於 constant 記憶體
int main() {
float* device_ptr;
cudaGetSymbolAddress((void**)&device_ptr, device_var); // 取得符號位址
size_t symbol_size;
cudaGetSymbolSize(&symbol_size, device_var); // 取得大小 (4 bytes)
float host_var;
cudaMemcpyFromSymbol(&host_var, device_var, sizeof(host_var)); // device -> host
host_var = 3.0f;
cudaMemcpyToSymbol(device_var, &host_var, sizeof(host_var)); // host -> device
}
__shared__ 記憶體
- 尺寸分兩種:static(編譯期決定)或 dynamic(kernel 啟動時決定)。
- 動態尺寸變數必須宣告為 external array 或 pointer。
- 靜態尺寸變數不可在宣告處初始化。
extern __shared__ char dynamic_smem[]; // 動態 shared memory
__global__ void kernel() {
__shared__ int smem_var1[4]; // static 尺寸
auto smem_var2 = (int*)dynamic_smem; // dynamic 尺寸
}
int main() {
size_t shared_memory_size = 16;
kernel<<<1, 1, shared_memory_size>>>(); // 第三個參數指定動態 shared bytes
cudaDeviceSynchronize();
}
__managed__ 記憶體
__managed__ 變數的主要限制
- 其位址不是常數運算式(不能當 template 非型別參數)。
- 不可為 reference 型別
T&。 - 當 CUDA runtime 可能尚未在有效狀態時(static/dynamic 初始化或解構、
exit()後、__attribute__((constructor))等),不可使用其位址或值。 - 不可作為
decltype()的非括號 id-expression 引數(decltype(global_var)為錯誤,decltype((global_var))為合法)。 - 一致性 (coherence/consistency) 行為與動態配置的 managed memory 相同。
__tile__ 記憶體
- 與
__device__類似:配置於 device 全域記憶體、可由 Runtime API 存取;差別是__tile__變數可由 tile code 直接以名稱存取。 - 同一變數不可同時標記
__device__與__tile__。 __device__變數不可在__tile__/__tile_global__code 以名稱直接存取;__tile__變數也不可在__device__/__global__code 以名稱直接存取。但兩者都位於 device global memory,可透過指標在任一情境存取。__tile__變數不可含 pointer 或 reference 型別的子物件。
Inlining、__restrict__ 與 __grid_constant__
Inlining Specifiers(限 __host__ / __device__)
| 限定詞 | 作用 |
|---|---|
__noinline__ |
指示 nvcc 不要 inline |
__forceinline__ |
強制在單一 translation unit 內 inline |
__inline_hint__ |
啟用 LTO(Link-Time Optimization)時跨 TU 的積極 inline |
__tile__ 函式上會被忽略。__restrict__ 指標
- 解決 pointer aliasing(多個指標指向重疊記憶體)抑制優化(如重排、共同子運算式消除)的問題。
- 是程式設計者的承諾:指標生命週期內,該記憶體只透過此指標存取,讓編譯器更積極優化。
- 所有指標參數都必須加
__restrict__優化才有效。
__device__ void f(const float* __restrict__ a,
const float* __restrict__ b,
float* __restrict__ c);
另外,標了 __restrict__ 的 __global__ const 指標會編成唯讀快取載入(PTX ld.global.nc,等同 __ldg())。
__grid_constant__ 參數
替 __global__ 函式參數加上 __grid_constant__,可阻止編譯器建立per-thread copy,改讓 grid 內所有 thread 透過單一位址存取,提升效能。
- 具 kernel 的生命週期;對單一 kernel 私有,其他 grid(含 sub-grid)看不到。
- 所有 thread 看到相同位址。
- 唯讀:修改該物件或其子物件(含 mutable 成員)為 undefined behavior。
- 參數須為 const-qualified 非 reference 型別;在
__tile_global__參數上會被忽略。 - 所有函式宣告(含 function template 特化與實例化)對
__grid_constant__參數的標註必須與主宣告一致,否則編譯錯誤。
Annotation Summary(Table 41)
| Annotation | 用於 __host__ / __device__ |
用於 __global__ |
|---|---|---|
__noinline__ / __forceinline__ / __inline_hint__ |
Function | × |
__restrict__ |
Pointer Parameter | Pointer Parameter |
__grid_constant__ |
× | Parameter |
__launch_bounds__ |
× | Function |
__maxnreg__ |
× | Function |
__cluster_dims__ |
× | Function |
Built-in Types and Variables
Host Compiler 型別擴充
- 128-bit 整數
__int128:Linux 上 host 編譯器定義__SIZEOF_INT128__時支援。 - 128-bit 浮點
__float128/_Float128:在 compute capability 10.0 以上的 GPU 可用;Linux x86 上 host 編譯器定義__SIZEOF_FLOAT128__或__FLOAT128__時支援。__float128常數運算式可能以較低精度處理。 _Complex型別只在 host 程式支援。- 128-bit 整數與浮點型別不支援 tile code。
Built-in Variables(device-only)
| 變數 | 型別 | 內容 |
|---|---|---|
gridDim |
dim3 |
grid 維度(block 數量) |
blockDim |
dim3 |
block 維度(thread 數量) |
blockIdx |
uint3 |
block 在 grid 內的索引 |
threadIdx |
uint3 |
thread 在 block 內的索引 |
warpSize |
int |
warp 內的 thread 數(執行期值,常見為 32) |
dim3與uint3都是含x/y/z三個 unsigned 的 trivial struct;C++11 起dim3各分量預設為 1。- 上述變數不支援 tile code;tile 中改用
cuda::tiles::bid()與cuda::tiles::num_blocks()。
Built-in Vector Types
CUDA 提供從基本整數/浮點衍生的向量型別(host 與 device 皆可用),命名規則為 <base>1~<base>4,例如 char1..char4、int1..int4、float1..float4、double1..double4 等。
- 分量透過
.x/.y/.z/.w存取(第一到第四個)。 - 每種型別都有工廠函式
make_<type_name>(),例如make_int4(...)。 - 非 nvcc 編譯 host 程式時,需
#include <cuda_runtime.h>才能用這些型別與函式。
int sum(int4 v) { return v.x + v.y + v.z + v.w; }
int4 add_one(int x, int y, int z, int w) {
return make_int4(x + 1, y + 1, z + 1, w + 1);
}
對齊重點(Table 43 摘要):
| 型別 | Size | Alignment |
|---|---|---|
char1 |
1 | 1 |
int4 / uint4 |
16 | 16 |
float4 |
16 | 16 |
double2 |
16 | 16 |
longlong2 |
16 | 16 |
int3 / float3 |
12 | 4 |
long4、ulong4、longlong4、ulonglong4、double4 在 CUDA 13 已 deprecated,未來版本可能移除;改用對齊明確的 ..._16a / ..._32a 變體。
另外 long 在 Windows 64-bit(LLP64)為 4 bytes,在 Linux 64-bit(LP64)為 8 bytes,故相關型別大小/對齊會隨平台不同。
Kernel Configuration
每次呼叫 __global__ / __tile_global__ 函式都必須提供 execution configuration,語法為在函式名與引數列之間插入 <<<grid_dim, block_dim, dynamic_smem_bytes, stream>>>:
| 欄位 | 型別 | 說明 |
|---|---|---|
grid_dim |
dim3 |
grid 維度;x*y*z = 啟動的 block 數 |
block_dim |
dim3 |
block 維度;x*y*z = 每 block thread 數(__tile_global__ 時須為 1) |
dynamic_smem_bytes |
size_t |
選用,預設 0;per-block 動態配置的 shared bytes(供 extern __shared__) |
stream |
cudaStream_t |
選用,預設 NULL;關聯的 stream |
__global__ void kernel(float* parameter);
kernel<<<grid_dim, block_dim, dynamic_smem_bytes>>>(parameter);
- execution configuration 的引數會先於實際函式引數被求值。
- 若
grid_dim/block_dim超過 device 上限,或dynamic_smem_bytes超過扣除靜態配置後可用的 shared memory,呼叫會失敗。
Thread Block Cluster(__cluster_dims__)
- compute capability 9.0 以上可指定編譯期 cluster 維度,語法
__cluster_dims__([x, [y, [z]]])。
__global__ void __cluster_dims__(2, 1, 1) kernel(float* parameter); // X=2, Y=Z=1
- 無引數的
__cluster_dims__()表示 kernel 以 grid cluster 啟動,cluster 維度延到啟動時指定;啟動時若沒指定會產生 launch-time error。 - 不可用於
__tile_global__kernel。 - 也可在執行期用
cudaLaunchKernelEx(搭配cudaLaunchConfig_t與cudaLaunchAttributeClusterDimension)指定;grid 維度應為 cluster 大小的整數倍。
Launch Bounds(__launch_bounds__)
__global__ void
__launch_bounds__(maxThreadsPerBlock, minBlocksPerMultiprocessor, maxBlocksPerCluster)
MyKernel(...) { ... }
| 參數 | 必要性 | PTX directive | 意義 |
|---|---|---|---|
maxThreadsPerBlock |
必要 | .maxntid |
應用程式啟動 kernel 時的最大 threads per block |
minBlocksPerMultiprocessor |
選用 | .minnctapersm |
期望每個 SM 的最少 resident block 數 |
maxBlocksPerCluster |
選用 | .maxclusterrank |
每個 cluster 的最大 block 數 |
- 編譯器據此推導暫存器上限 L,確保
minBlocksPerMultiprocessor個 block(各maxThreadsPerBlockthreads)能常駐 SM;初始暫存器數超過 L 就壓低(可能增加 local memory / 指令數)。 - 若初始暫存器數低於 L:只指定
maxThreadsPerBlock(未指定minBlocksPerMultiprocessor)時,編譯器用它決定 n↔n+1 個常駐 block 轉換的暫存器門檻;若同時指定minBlocksPerMultiprocessor與maxThreadsPerBlock,編譯器可能反而把用量提高到 L,以減少指令數、改善單執行緒指令的延遲隱藏。 - kernel 若以超過
maxThreadsPerBlock的 threads/block 或超過maxBlocksPerCluster的 blocks/cluster 啟動,會啟動失敗。
__launch_bounds__(maxThreadsPerBlock);否則可能出現「too many resources requested for launch」錯誤。雙引數版可在分析後改善效能。__CUDA_ARCH__ 在 device 端切換;但因 host 端 __CUDA_ARCH__ 未定義,host 設定 threads/block 要用不依賴它的常數,或執行期依 cudaGetDeviceProperties 的 compute capability 決定。--resource-usage 可回報暫存器使用量。Maximum Registers per Thread(__maxnreg__)
__global__ void __maxnreg__(maxNumberRegistersPerThread) MyKernel(...) { ... }
- 指定 block 內單一 thread 可配置的最大暫存器數;編成
.maxnregPTX directive。 __launch_bounds__()與__maxnreg__()不可同時套在同一 kernel。--maxrregcount <N>可控制整個檔案所有__global__函式的暫存器用量,但對含__maxnreg__的 kernel 會被忽略。
考試/測驗重點
| 題型 | 關鍵答案 |
|---|---|
__global__ 函式回傳型別? |
必須是 void |
__global__ 可否為 class member / 遞迴? |
都不行;也必須有 <<<>>> |
__global__ 呼叫是同步還是非同步? |
非同步,device 完成前就返回 host |
如何在 __host__ __device__ 內分流 host/device? |
用 #if defined(__CUDA_ARCH__) |
__CUDA_ARCH__ 在 host 程式中? |
未定義,故 host 端不可依賴它設值 |
__constant__ 在 device 端的特性? |
唯讀,只能由 host 經 Runtime API 修改 |
從 host 存取 __device__/__constant__ 用哪些 API? |
cudaGetSymbolAddress/Size、cudaMemcpyTo/FromSymbol |
動態 __shared__ 怎麼宣告與配置? |
extern __shared__ 陣列/指標;大小由 <<<,,bytes>>> 給 |
static __shared__ 變數可否在宣告處初始化? |
不行 |
__managed__ 位址是常數運算式嗎? |
不是;也不能當 template 非型別參數 |
decltype(managed_var) vs decltype((managed_var))? |
前者錯誤(非括號 id-expr),後者合法 |
__restrict__ 承諾什麼?副作用? |
指標不別名;增加暫存器壓力、可能降 occupancy |
__restrict__ const __global__ 指標被編成什麼? |
唯讀快取載入(ld.global.nc / __ldg()) |
__grid_constant__ 的三大特性? |
全 thread 共用單一位址、唯讀、kernel 生命週期 |
gridDim/blockDim 與 blockIdx/threadIdx 的型別? |
前兩者 dim3,後兩者 uint3 |
warpSize 型別與常見值? |
int,執行期值,常見 32 |
dim3 各分量預設值? |
1(C++11 起) |
| 向量型別第四分量用哪個欄位?工廠函式? | .w;make_<type>() |
| CUDA 13 哪些向量型別被 deprecated? | long4/ulong4/longlong4/ulonglong4/double4 |
__launch_bounds__ 第一個參數編成哪個 PTX directive? |
.maxntid(maxThreadsPerBlock) |
__launch_bounds__ 與 __maxnreg__ 能否並用? |
不能 |
__cluster_dims__ 需要哪個 compute capability? |
9.0 以上;不可用於 __tile_global__ |
| execution config 的四個欄位順序? | <<<grid_dim, block_dim, dynamic_smem_bytes, stream>>> |
int3/float3 的 size 與 alignment? |
size 12、alignment 4(非 16);char3 為 size 3、alignment 1 |
__tile_global__ kernel 的 block_dim 必須是? |
1(由編譯器決定排程多少 thread) |
| execution config 引數與函式引數誰先求值? | execution config 引數先於實際函式引數求值 |
__maxnreg__ 的 PTX directive?--maxrregcount 影響? |
.maxnreg;含 __maxnreg__ 的 kernel 會忽略 --maxrregcount |
Related Notes
- 05-Technical-Appendices/05-Cpp-Language-Restrictions
- 05-Technical-Appendices/07-Synchronization-and-Atomic-Functions
- 02-Programming-GPUs/04-CUDA-Cpp-Errors-and-Specifiers
- 01-Introduction-to-CUDA/04-GPU-Memory-Hierarchy
- 05-Technical-Appendices/01-Compute-Capabilities
- 05-Technical-Appendices/09-Compiler-Hints-and-Warp-Matrix
- 05-Technical-Appendices/Practice-Technical-Appendices
- 00-Dashboard/Exam-Traps