呼叫 Kernel、Compilation 與本章總結 (Calling Kernels, Compilation, and Summary)
重點總覽 (Overview)
| 主題 | 核心語法 / 概念 | 關鍵點 |
|---|---|---|
| Kernel 呼叫 | kernel<<<gridDim, blockDim>>>(args) |
<<<>>> 內為 execution configuration parameters,僅 launch grid 時使用 |
| 參數 1 (grid) | 第一個參數 = grid 內 block 數 | 用 ceiling division 確保 thread 數 ≥ n |
| 參數 2 (block) | 第二個參數 = 每個 block 的 thread 數 | 範例固定為 256,建議為 32 的倍數 |
| Transparent Scalability | block 可任意順序執行 | 同一份程式碼小 GPU 慢、大 GPU 快,自動擴展 |
| Compilation | NVCC 分離 host / device code | device code → PTX → 真正的 object code |
| 總結擴充 | function qualifiers、<<<>>>、built-in vars、runtime API |
本章涵蓋 CUDA C 對 C 的核心擴充子集 |
本章前半 (sequential vecAdd、device memory、kernel function) 已分別在 02-Heterogeneous-Data-Parallel-Computing/02-Vector-Addition-Host-Code-Device-Memory-and-Data-Transfer 與 02-Heterogeneous-Data-Parallel-Computing/03-Kernel-Functions-and-Threading 說明。本筆記專注於 最後一步:從 host 端 launch grid、編譯流程 與 全章擴充總結。
呼叫 Kernel:執行配置參數 (Calling Kernels: Execution Configuration)
實作完 kernel 後,最後一步是從 host code 呼叫 kernel 以 launch grid。CUDA C 在傳統 C 的函式呼叫語法上,加入夾在 <<< 與 >>> 之間的 execution configuration parameters。
// Launch ceil(n/256) blocks of 256 threads each
vecAddKernel<<<ceil(n/256.0), 256>>>(A_d, B_d, C_d, n);
| 配置參數位置 | 意義 | 本範例值 |
|---|---|---|
第 1 個 (<<< 後) |
grid 內 block 的數量 | ceil(n/256.0) |
| 第 2 個 | 每個 block 內 thread 數量 | 256 |
(...) 內 |
傳統 C 函式引數 | A_d, B_d, C_d, n |
- execution configuration parameters 只在 launch kernel 時使用,普通函式呼叫沒有這組參數。
- 同一個 kernel 可在 host code 不同位置用不同 thread 數呼叫。
Ceiling Division:為何要無條件進位
要確保 grid 內 thread 數足夠覆蓋所有 n 個元素,block 數必須取「期望 thread 數 / block size」的無條件進位 (ceiling):
ceil(n/256.0) // 正確:用 256.0 (浮點) 確保產生浮點商,ceil 才能正確進位
// n/256 // 錯誤:整數除法會無條件捨去,漏掉最後一個不滿的 block
256.0
若寫成整數 n/256,C 的整數除法會直接捨去小數(等同 floor)。例如 n=1000 時 1000/256 = 3,只 launch 3 個 block(768 threads),漏掉第 769~1000 個元素。用 ceil(1000/256.0) = 4 才會 launch 4 block = 1024 threads。
n=1000、blockSize=256 → 4 blocks = 1024 threads,但只有 1000 個元素。多出的 24 個 thread 由 kernel 內 if (i < n) 邊界檢查擋掉(見 02-Heterogeneous-Data-Parallel-Computing/03-Kernel-Functions-and-Threading)。ceiling division 與 if(i<n) 是一對搭檔,缺一不可。
Grid 幾何示意 (n=1000, blockSize=256)
gridDim = ceil(1000/256.0) = 4 blocks blockDim = 256 threads/block
Block 0 Block 1 Block 2 Block 3
[t0 ... t255] [t0 ... t255] [t0 ... t255] [t0 ........ t255]
i = 0..255 i = 256..511 i = 512..767 i = 768..1023
全部工作 全部工作 全部工作 i<1000 工作 │ i>=1000 閒置
(768..999) (1000..1023 被 if 擋掉)
i = blockIdx.x * blockDim.x + threadIdx.x <-- 每個 thread 的全域索引
透明擴展性 (Transparent Scalability)
<<<>>> 中的 block 數量隨資料量 n 決定,而非隨硬體決定:
| n | block size | block 數 (ceil(n/256.0)) |
|---|---|---|
| 750 | 256 | 3 |
| 4000 | 256 | 16 |
| 2,000,000 | 256 | 7813 |
關鍵性質:所有 block 操作不同資料區段,彼此獨立,可以任意順序執行。
同一份 binary,不同硬體上自動擴展:
Grid (例如 16 個 blocks)
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│B0│B1│B2│B3│B4│B5│B6│B7│B8│B9│..│..│..│..│..│15│
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
│ │
小 GPU:一次跑 1~2 個 block 大 GPU:一次跑 64~128 個 block
(序列化較多 → 較慢) (平行度高 → 較快)
Programmer 不可對 block 執行順序做任何假設。 正因為 block 之間無順序相依,runtime 才能依硬體資源自由排程 —— 同一份程式碼在小 GPU 上以較低速度執行、在大 GPU 上以較高速度執行,不需重新編譯或改寫。block 排程細節見 04-Compute-Architecture-And-Scheduling/01-GPU-Architecture-and-Block-Scheduling。
vector addition 只是教學用的最簡範例。實務上 device memory 配置 + host↔device 資料傳輸 + 釋放的 overhead,可能讓它比原本的序列 C 程式還慢 —— 因為每傳輸 / 處理 3 個 float 才做 1 次加法(arithmetic intensity 太低)。真正划算的 kernel 計算量需遠大於資料傳輸量,並讓資料跨多次 kernel 呼叫常駐 device memory 以攤平 overhead。
編譯流程 (Compilation: NVCC and PTX)
kernel 用到的 CUDA 擴充(如 __global__、<<<>>>、built-in 變數)不被傳統 C 編譯器接受,必須用能辨識這些擴充的編譯器 —— NVCC (NVIDIA C Compiler)。
CUDA C program (.cu)
host code + device code
│
┌────────▼────────┐
│ NVCC │ 依 CUDA 關鍵字分離 host / device code
└───┬─────────┬────┘
Host Code │ │ Device Code (__global__/__device__)
(straight ANSI C)│ │
┌─────────▼──┐ ┌──▼───────────────┐
│ Host C/C++ │ │ NVCC → PTX files │ (virtual binary 虛擬二進位)
│ Compiler │ └──┬───────────────┘
└─────┬──────┘ │ runtime component of NVCC
│ ┌──▼───────────────┐
傳統 CPU process │ real object files │
└──┬───────────────┘
▼
執行於 CUDA-capable GPU
| 程式碼類型 | 標記 | 編譯路徑 | 執行於 |
|---|---|---|---|
| Host code | 無 CUDA 關鍵字 | host 標準 C/C++ compiler | CPU process |
| Device code | __global__ / __device__ 等 CUDA 關鍵字 |
NVCC → PTX → runtime 再編譯成 object | CUDA GPU |
- PTX 是 virtual binary (虛擬指令集),不是最終機器碼。
- PTX 由 NVCC 的 runtime component 進一步編譯成特定 GPU 的真正 object code(這也是 transparent scalability / 跨硬體可攜性的基礎之一)。
本章總結:CUDA C 擴充 (Chapter Summary: CUDA C Extensions)
本章介紹的 CUDA C 對 C 的核心擴充,可歸納為四類:
| 類別 | 擴充內容 | 重點 |
|---|---|---|
| ① Function declarations | __global__, __device__, __host__ |
無關鍵字 → 預設 host function;__host__ __device__ 並用 → 編譯器產生兩個版本 |
| ② Kernel call / grid launch | <<< gridDim, blockDim >>> |
execution configuration parameters,只在呼叫 kernel 時用 |
| ③ Built-in (predefined) variables | threadIdx, blockIdx, blockDim |
read-only,讓 thread 區分自己並決定處理的資料範圍 |
| ④ Runtime API | cudaMalloc, cudaFree, cudaMemcpy |
host 端呼叫,代為配置 / 釋放 device global memory 與傳輸資料 |
// ① 三種 function qualifier 的呼叫關係
__global__ void k(...); // 在 device 執行;host 可呼叫(launch grid)
__device__ float d(...); // 在 device 執行;只能被 kernel/device function 呼叫
__host__ void h(...); // 在 host 執行;只能被 host function 呼叫(預設)
__host__ __device__ float f(...); // 編譯器產生 host + device 兩版,單一份原始碼
不需要寫兩次。在函式宣告同時加上 __host__ __device__,編譯系統會從同一份原始碼自動產生 host 版與 device 版 object code。許多 user library 函式都屬於這種用法。
這四類擴充是寫出第一支 CUDA 程式的最小必要集合,而非 CUDA 的完整功能。其餘特性(streams、dynamic parallelism、多維 grid…)會在後續章節隨需求引入;細節應隨時查閱 CUDA C Programming Guide。
考試/面試重點 (Exam / Test Patterns)
| 情境 / 關鍵字 | 答案 / 技巧 |
|---|---|
| vecAdd thread→data 索引映射 (Ex.1) | i = blockIdx.x * blockDim.x + threadIdx.x (選項 C) |
| n=8000、blockSize=1024、最少 block 數,grid 共幾個 thread? (Ex.4) | ceil(8000/1024)=8 blocks → 8×1024 = 8192 threads (選項 C) |
| n=1000、blockSize=256,launch 幾個 thread? | ceil(1000/256.0)=4 → 4×256 = 1024 threads(1000 做事,24 閒置) |
(N+127)/128 整數式代表什麼? (Ex.9) |
用整數除法達成 ceiling division(等同 ceil(N/128.0))算 block 數 |
| 「執行 index 計算那行 (i=...) 的 thread 數」 | = grid 全部 thread 數(在 if(i<n) 之前,所有 thread 都執行) |
「執行 C[i]=A[i]+B[i] 那行的 thread 數」 |
= n(只有通過 if(i<n) 的 thread 執行) |
寫 n/256 漏掉元素 |
整數除法捨去 → 改用 ceil(n/256.0),浮點除數不可少 |
| 可否假設 block 執行順序? | 不可;block 獨立、任意順序 → 這正是 transparent scalability 來源 |
| 同一份 code 在大小 GPU 速度不同 | transparent scalability:block 數隨 n、由硬體決定同時跑幾個 |
| device code 編譯成什麼中間檔? | PTX (virtual binary),再由 NVCC runtime 編成真正 object code |
| 沒有任何 CUDA 關鍵字的函式 | 預設為 host function |
| 想要函式同時能在 host/device 跑 | 用 __host__ __device__,編譯器產生兩版,不必寫兩次 |
Related Notes
- 02-Heterogeneous-Data-Parallel-Computing/01-Data-Parallelism-and-CUDA-Program-Structure
- 02-Heterogeneous-Data-Parallel-Computing/02-Vector-Addition-Host-Code-Device-Memory-and-Data-Transfer
- 02-Heterogeneous-Data-Parallel-Computing/03-Kernel-Functions-and-Threading
- 03-Multidimensional-Grids-And-Data/01-Multidimensional-Grid-Organization
- 04-Compute-Architecture-And-Scheduling/01-GPU-Architecture-and-Block-Scheduling
- 06-Performance-Considerations/03-Thread-Coarsening