計算架構與排程 練習題 (Practice - GPU Architecture, Block Scheduling, Synchronization and Transparent Scalability)
Related Concepts
- 04-Compute-Architecture-And-Scheduling/01-GPU-Architecture-and-Block-Scheduling — GPU 架構、Block 排程與 Synchronization (Transparent Scalability)
- 04-Compute-Architecture-And-Scheduling/02-Warps-SIMD-and-Control-Divergence — Warps、SIMD 硬體、Control Divergence 與 Warp Scheduling
- 04-Compute-Architecture-And-Scheduling/03-Resource-Partitioning-and-Occupancy — Resource Partitioning、Occupancy 與 Device Properties 查詢
| 關鍵字 / 題型 | 答案重點 |
|---|---|
| 「某 GPU 有幾個 core?」 | SM 數 × 每 SM core 數;A100 = 108 × 64 = 6912 |
| Thread 如何 assign 到 SM | block-by-block;同 block 全部同時落在同一個 SM |
__syncthreads() 放 if-then-else |
兩個分支各一個 = 兩個不同 barrier → undefined / deadlock |
| 為何同一 binary 可跑不同速度 | Transparent scalability:無跨 block 同步 → block 可任意順序執行 |
| warp n 含哪些 thread | 32·n ~ 32·(n+1)−1(row-major 線性化後) |
| block size 非 32 倍數 | 最後一個 warp 以 inactive thread 補滿 32 格 |
| SIMD efficiency | active threads / 32 × 100% |
if(i<n) boundary check 哪些 warp 分歧 |
只有跨越資料邊界的那個 warp |
| Zero-overhead scheduling | warp 狀態常駐硬體暫存器,切換無 save/restore、無 idle cycle |
| Oversubscription (A100) | 2048 threads / 64 cores = 32×,用來容忍長延遲 |
| Occupancy 定義 | (assigned warps)/(max warps per SM) = (assigned threads)/(max threads per SM) |
| block size 32 → occupancy | 撞 32 block-slot 上限 → 1024/2048 = 50% |
| block size 768 → occupancy | 不整除 → 2 blocks = 1536/2048 = 75% |
| full occupancy 的 register 上限 | 65,536 / 2048 = 32 regs/thread |
| Performance cliff | 資源用量小增(31→33 reg)→ blocks/SM 驟降 → occupancy 100%→75% |
| 查 SM 數 / warp 大小 | multiProcessorCount / warpSize(透過 cudaGetDeviceProperties) |
Question 1 - SM 與 Core 的組織 [recall]
情境:請描述一個現代 CUDA-capable GPU 的高階組織,並說明 Ampere A100 共有多少個 core,怎麼算出來的。
GPU 組織成一陣列的 streaming multiprocessors (SMs);每個 SM 內有多個 streaming processors / CUDA cores,它們共用 control logic 與 on-chip memory resources。
A100:108 SMs × 64 cores/SM = 6912 cores。考題公式:core 數 = SM 數 × 每 SM core 數。
Question 2 - Block 如何指派到 SM [recall]
情境:當 kernel 啟動且 grid 的 block 數遠多於硬體能同時容納的數量時,CUDA runtime 如何把 thread 指派到 SM?指派的基本單位是什麼?
以 block-by-block 為單位指派:同一 block 的所有 thread 同時被指派到同一個 SM。一個 SM 可同時容納多個 block,但有上限。
block 數通常遠多於能同時執行者 → runtime 維護一個待執行 block 清單,當已指派的 block 完成、釋放資源後,才把新的 block 補進 SM。這個「同 block 同時在同一 SM」的保證,正是 __syncthreads() 與 shared memory 能運作的前提。
Question 3 - __syncthreads() 的使用規則 [recall]
情境:某 kernel 在
if-then-else的 then 路徑與 else 路徑中各放了一個__syncthreads(),由threadIdx.x的奇偶決定走哪條路徑。這段程式合法嗎?會發生什麼事?
不合法。if-then-else 兩個分支裡的 __syncthreads() 是兩個不同的 barrier。由於 block 內並非所有 thread 都會到達同一個 barrier(偶數走 then-barrier、奇數走 else-barrier),違反「barrier 必須由 block 內每個 thread 執行」的規則 → undefined behavior 或 deadlock。
規則:放在 if 內時,block 內所有 thread 要嘛都走含 __syncthreads() 的路徑、要嘛都不走。
Question 4 - Transparent Scalability [recall]
情境:為什麼同一份未修改的 CUDA binary,能在低階手機 GPU 與高階桌機 GPU 上以差異極大的速度執行?這個性質叫什麼?它的代價是什麼?
這叫 transparent scalability。因為 CUDA 不允許跨 block 做 barrier synchronization,沒有任何 block 需要等待別的 block,runtime 就能以任意順序、任意分波執行 block:低階裝置每波執行少量 block,高階裝置每波執行大量 block。
代價:不同 block 的 thread 無法互相同步。波數公式:waves = ⌈grid_blocks / blocks_per_wave⌉。
Question 5 - Warp 切分公式與 padding [recall]
情境:一個一維 block 含 48 個 thread。它會被切成幾個 warp?warp n 包含哪些
threadIdx?不足的部分如何處理?
48 threads → ⌈48/32⌉ = 2 個 warp。warp n 含 thread 32·n 到 32·(n+1)−1:warp 0 = thread 0–31,warp 1 = thread 32–47。
因為 48 不是 32 的倍數,最後一個 warp 以 16 個 inactive thread 補滿 32 格。warp 是 SM 中 thread 排程的基本單位(目前所有 CUDA device warp size = 32)。
Question 6 - 多維 Block 的 warp 線性化 [recall]
情境:一個 8×8(x、y 各 8)的二維 block 共 64 個 thread,會被切成幾個 warp?第一個 warp 從哪個 thread 開始、到哪個 thread 結束?
先將多維 thread 投影成 row-major 線性順序(z 大者在後、同 z 內 y 大者在後、同 y 內 x 遞增),再每 32 個切一個 warp。
64 threads → 2 個 warp。Warp 0 = T(0,0) .. T(3,7)(y=0,1,2,3 各 8 個 = 32),Warp 1 = T(4,0) .. T(7,7)。記法 T(y,x),y 為 threadIdx.y、x 為 threadIdx.x。
Question 7 - SIMD / SIMT 與 Processing Block [recall]
情境:解釋為什麼一個 warp 的執行行為被稱為 SIMD(或 NVIDIA 的 SIMT)。Ampere A100 的 SM 有 64 個 core,這些 core 是如何分組的?
SM 把 core 分組成 processing block,每組共用一個 instruction fetch/dispatch unit。同一 warp 的 thread 指派到同一 processing block,硬體取一條指令、同時對 warp 內所有 thread 執行(同指令、不同資料,如 add r1,r2,r3 各 core 的 r2/r3 不同)→ SIMD / SIMT (Single-Instruction, Multiple-Thread)。
A100 SM 64 cores → 4 個 processing block × 16 cores。好處:控制硬體成本被眾多執行單元攤提,晶片面積可多用於算術吞吐。
Question 8 - Zero-overhead Scheduling 與 Oversubscription [recall]
情境:A100 的一個 SM 只有 64 個 core,卻可同時被指派最多 2048 個 thread。為何要這樣「超量訂閱」?warp 切換為何是「零開銷」的?
Oversubscription = 2048 / 64 = 32×,目的是 latency tolerance(延遲容忍):當某 warp 卡在長延遲操作(如 global memory access),硬體可立即換上另一個就緒 warp 執行,把延遲「藏」起來。駐留 warp 越多,越可能隨時找到就緒的 warp。
Zero-overhead scheduling:GPU 把所有 resident warp 的狀態(PC、registers)常駐於硬體暫存器,切換 warp 不需 save/restore、不引入任何 idle cycle(CPU context-switch 則要存/載狀態 → 有開銷)。
Question 9 - 32-thread Block 能否省略 __syncthreads() [recall]
情境:一位程式設計師說:「如果每個 block 只啟動 32 個 thread(剛好一個 warp),就可以省略所有
__syncthreads(),反正同一 warp 一定 lockstep。」這是好主意嗎?
不是好主意 / 不安全。warp 內 thread 是否同步是硬體實作行為,程式正確性絕不可依賴這個假設。尤其從 Volta 架構起的 independent thread scheduling,divergent 的多個 pass 可交錯執行,warp 內不再保證 lockstep。
若確實需要 warp 內同步,應明確使用 __syncwarp()(warp 層級),而非省略同步。
Question 10 - 裝置屬性查詢 API [recall]
情境:你想寫一個能在多種 GPU 上自適應、並避開效能很差的 integrated GPU 的程式。要呼叫哪些 API?哪些
cudaDeviceProp欄位能查到 SM 數量與 warp 大小?
先用 cudaGetDeviceCount(&devCount) 取得 device 數量,再逐一用 cudaGetDeviceProperties(&devProp, i) 查詢每個 device 的屬性後挑選合適者(PC 常含 integrated GPU,CUDA 效能差)。
關鍵欄位:multiProcessorCount(SM 數)、warpSize(warp 大小)、maxThreadsPerBlock、maxThreadsDim[0..2]、maxGridSize[0..2]、regsPerBlock、clockRate。裝置資源量由 compute capability 決定(A100 = 8.0)。
Question 11 - Vector Add 的 Grid 大小與分歧 Warp [application]
情境:對長度 2000 的向量做加法,每個 thread 算一個輸出元素,block size = 512。(a) grid 中共有幾個 thread?(b) 因
if(i<n)邊界檢查,會有幾個 warp 發生 control divergence?
(a) ⌈2000/512⌉ = 4 個 block → 4 × 512 = 2048 threads。
(b) 邊界落在 i = 2000,位於最後一個 block(global thread 1536–2047)內。涵蓋 i = 1984–2015 的那個 warp 同時含 active(i<2000)與 inactive(i≥2000)thread → 僅 1 個 warp 分歧。其餘 warp 要嘛全 active 要嘛全 inactive,不分歧。
Question 12 - Occupancy 計算(block-slot 與整除性) [application]
情境:某 GPU 每 SM 上限為 2048 threads、32 blocks、65,536 registers。在 register 不受限的前提下,下列兩種 block size 的 occupancy 各是多少?(a) 32 threads/block;(b) 768 threads/block。
(a) 填滿 2048 threads 需 2048/32 = 64 個 block,但撞到 32 block-slot 上限 → 只能放 32 × 32 = 1024 threads → occupancy = 1024/2048 = 50%。(要 ≥64 threads/block 才可能 100%。)
(b) 2048/768 = 2.67 → 只能放 2 blocks = 1536 threads,剩 512 slot 閒置(block 上限與 thread 上限都沒到)→ occupancy = 1536/2048 = 75%。
Question 13 - Register 限制下的 Occupancy [application]
情境:GPU 每 SM 有 65,536 registers、最多 2048 threads。某 kernel 每個 thread 使用 64 個 register。不論 block size 設多少,這個 kernel 最高能達到多少 occupancy?另外,要達 full occupancy,每 thread 至多能用幾個 register?
register 限制下可駐 thread 數 = 65,536 / 64 = 1024 threads → occupancy ≤ 1024/2048 = 50%,且與 block size 無關(register 是限制因子)。
要 full occupancy:regs/thread ≤ 65,536 / 2048 = 32 registers/thread。編譯器可能用 register spilling 降低每 thread register 需求以提升 occupancy,但會增加記憶體存取、可能反而變慢。
Question 14 - Control Divergence 的影響隨資料規模變化 [analysis]
情境:同樣用 block size 64 處理向量加法。比較「向量長度 100」與「向量長度 10,000」兩種情況下,邊界檢查造成的 control divergence 對效能的相對影響,並解釋為何不同。
兩種情況都只有一個 warp(跨越資料尾端者)分歧,但該 warp 佔總 warp 數的比例決定影響大小:
- 長度 100:
⌈100/64⌉=2blocks,約 4 個 warp,1/4 分歧 → 影響顯著(即使該 warp 時間翻倍,整體可能 +25%)。 - 長度 10,000:約 313 個 warp,1/313 分歧 → 影響 < 1%。
結論:divergence 的相對成本 ≈divergent_warps / total_warps,隨資料規模增大而被攤薄。優化時應優先關注大規模下仍顯著的 divergence,而非邊界處的少數 warp。
Question 15 - Performance Cliff:Register 用量的權衡 [analysis]
情境:一個 kernel 原本每 thread 用 31 registers、block size 512,在每 SM(2048 threads、65,536 registers)上跑出 100% occupancy。程式設計師多宣告兩個自動變數,使每 thread 變成 33 registers。請分析 occupancy 會如何變化,並說明這個「performance cliff」的成因與啟示。
- 原始 31 reg:4 blocks/SM × 512 = 2048 threads,用
2048×31 = 63,488 ≤ 65,536registers → 100% occupancy。 - 改成 33 reg:4 blocks 需
2048×33 = 67,584 > 65,536registers,塞不下 → runtime 降為 3 blocks = 1536 threads(用 50,688 registers)→ occupancy =1536/2048 =75%。
成因:資源用量只小幅增加(+2 registers),卻跨過 register 邊界,使每 SM 的 block 數從 4 驟降為 3 → 平行度與 latency hiding 能力陡降,即 performance cliff (Ryoo et al., 2008)。啟示:優化時必須警覺資源邊界,並用 CUDA Occupancy Calculator 評估;同時注意高 occupancy 只是手段,記憶體頻寬等仍可能才是真正瓶頸。
| 主題 | 一句話重點 | 關鍵公式 / 數字 |
|---|---|---|
| SM / Cores | SM 陣列;每 SM 多個 core 共用 control + memory | A100 = 108 SM × 64 = 6912 cores |
| Block scheduling | block-by-block 指派;同 block 同時在同一 SM;runtime 維護待執行清單 | block slot 有上限 |
__syncthreads() |
block 內每個 thread 都須執行;if-else 各放一個 = 兩 barrier → deadlock | warp 內同步用 __syncwarp() |
| Transparent scalability | 無跨 block 同步 → block 可任意順序 → 同 binary 多速度 | waves = ⌈blocks / blocks_per_wave⌉ |
| Warp 切分 | row-major 線性化後每 32 個一 warp;不足補 inactive | warp n = thread 32n ~ 32(n+1)−1 |
| SIMD / SIMT | 一 warp 一指令多資料;core 分組成 processing block 共用 fetch/dispatch | A100 SM = 4 PB × 16 cores |
| Control divergence | 同 warp 走不同路徑 → 多 pass,inactive thread 浪費資源 | SIMD efficiency = active/32;影響 ≈ divergent/total warps |
| Warp scheduling | 卡延遲的 warp 換下、就緒 warp 換上 → latency tolerance | zero-overhead:狀態常駐暫存器 |
| Oversubscription | 指派遠多於 core 數的 thread 以提高就緒 warp 機率 | A100 = 2048/64 = 32× |
| Occupancy | assigned / max threads(或 warps);越高越能隱藏延遲 | block 32 → 50%;768 → 75% |
| Register / Performance cliff | 每 thread register × threads ≤ SM 上限;小增資源可使 occupancy 驟降 | full occ ≤ 32 regs/thread;31→33 → 100%→75% |
| Device query | runtime 查詢硬體資源以自適應、避開 integrated GPU | cudaGetDeviceProperties → multiProcessorCount/warpSize |