呼叫 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 的核心擴充子集
Important

本章前半 (sequential vecAdd、device memory、kernel function) 已分別在 02-Heterogeneous-Data-Parallel-Computing/02-Vector-Addition-Host-Code-Device-Memory-and-Data-Transfer02-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

Ceiling Division:為何要無條件進位

要確保 grid 內 thread 數足夠覆蓋所有 n 個元素,block 數必須取「期望 thread 數 / block size」的無條件進位 (ceiling):

numBlocks=nblockSize=ceil(n / 256.0)gridThreads=ceil(n/256.0)×256n
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。

ceiling division 會 launch「多餘」的 thread

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
   (序列化較多 → 較慢)                  (平行度高 → 較快)
Transparent Scalability 的程式設計守則

Programmer 不可對 block 執行順序做任何假設。 正因為 block 之間無順序相依,runtime 才能依硬體資源自由排程 —— 同一份程式碼在小 GPU 上以較低速度執行、在大 GPU 上以較高速度執行,不需重新編譯或改寫。block 排程細節見 04-Compute-Architecture-And-Scheduling/01-GPU-Architecture-and-Block-Scheduling

vecAdd 的實務效益

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

本章總結: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 兩版,單一份原始碼
回應「同一函式要寫兩次」的迷思 (Exercise 10)

不需要寫兩次。在函式宣告同時加上 __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)=44×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__,編譯器產生兩版,不必寫兩次