GPU 硬體層次和調度方式
- 2020 年 12 月 21 日
- AI
工作原因,需要了解一下 GPU 的硬體和 CUDA 的對應關係和調度方法。由於不是專職優化 GPU 程式碼,所以就是個大概了解。
TL;DR
- Perfect solution for {數據並行}
- 硬體設計思路:
1. 高吞吐,低響應
2. 無需 cache (目前實際硬體有),無需複雜的指令調度(多個執行緒走的都是一樣的指令)
3. 節約硬體空間(一次 fetch/decode/dispatch 就可以支援很多個執行緒,也沒有 branch prediction/亂序等等優化需求,硬體空間全省)
4. 可以塞更多的 ALU,增加算力。
- 上下文切換可以認為是 0(每個 cycle 都可以切換,暫存器、指令都是同時分配很多個執行緒的,也就是說執行緒上下文都準備好了,沒有切換開銷)。
- 調度基本單位是 warp (目前是一個 warp 是 32 threads),基本硬體調度是 SM。
- 每個 SM 會跑一個 warp,同時等待 N 個 warp 隨時可以切換(就看暫存器 shared mem 大小),這 N + 1 個 warp 都是 active 的。
- 如果程式設計良好,active warp 達到了硬體最大支援就可以認為程式的 occupancy 比較高(如果程式已經可以撐滿記憶體的頻寬,此時的 occupancy 也就足夠了)。
- 多用 shared mem 作為快取管理可以提升速度,不過會有 bank conflict。
- Thread Divergence 如果 if else 邏輯複雜需要考慮下。
A. 記憶體模型

記憶體特性

- 由於 shared mem 的性能良好,所以可以考慮將 global mem 的數據先載入到 shared mem 來提升性能。
- 在 CUDA 11 裡面提供了一個新的方法:async-copy,可以提升程式的整體吞吐量。
訪問路徑
- 由於 texture memory 是針對 2d 數組做了 texture cache 優化的, 所以對 global mem 2d 的存取模式可以通過 texture memory 來獲得更好的性能
- Global memory 的 read-only 會通過 texture 和 L2 cache
- Local memory 會通過 L1 和 L2 cache
- Texture memory 是只讀的 device memory(就是一般意義上的顯示記憶體),會通過 texture 和 L2 cache
- Shared memory 不會通過任何 cache(因為硬體上,它就是L1 cache,只是名字上叫 memory)
新的硬體(應該是Maxwell之後) L1 cache 和 texture cache 合併,所以沒有必要再區分了。而 Global Memory 的讀寫只經過 L2 cache。
Cache 這塊實際上會有更多細分和變化,可以參考這個。
兩個補充概念
1. Bank conflict

shared memory 會通過 bank(現在硬體:32 個, 4-byte 1區分) 管理, 同一 bank 內的存取會有 bank conflicts,無法並發。所以在記憶體讀取的時候需要考慮怎樣安排計算順序,讓不同 thread 可以操作不同 bank 的數據,增加並行度。
無 bank confilict 的特殊情況:
- broadcast: 所有threads 都訪問同一個 bank 的同一個 word。
- multicast: 集中訪問幾個 bank 中的同一個 word。

下面有是不同嚴重情況下的 bank conflict 的延遲損耗(由於一個 Warp 的調度粒度是 32 threads, 所以,最多也就是 32-way bank conflict 了):

上圖可見,極限情況下,同一個 warp 內的執行緒都訪問同一個 bank 的不同地址,會有 30 倍的性能衰減 (也就是 32-way bank conflict)。
2. Half warp
由於 bank conflict 會引入 half warp 這個概念,不過新的硬體已經不需要考慮了,並行單元都是 full warp。(可以參見 這個 SO 回答 , 這裡作者也提到了可能衰減回 half-warp 的可能性)
Cache
舊硬體是沒有cache的,後續應該是為了增加局部性的性能,有了 L1,L2 cache。
L1 和 Shared Mem 共享硬體空間(可以理解為 shared mem 是程式設計師自己管理的 L1 cache),可以設置大小 trade off:
- L1 是不能保證一直在的(可能會被換出,程式設計師不可控),所以如果對某個記憶體值多次讀取,可以放入 Shared Mem,通過程式碼來管理,可控性更強(這種情況需要人工管理同步,可能會有死鎖)。
- 粗暴增加 Shared Mem 可能會帶來性能下降(L1 cache 小了)
Cache Line 的大小:
由於這些值實際和硬體設計相關,未必能對應到新的硬體上。 (關於 cache line 參見這個鏈接, CPU 的,不過在這裡是通用的)
- L1 = 128 bytes
- L2 = 32 bytes
- Texture = 32 bytes
所以對於數據如果出現 misalign 的訪問,性能也會掉(和 CPU 一樣的邏輯),GPU 默認會做 256 bytes 的 align。
程式碼與存儲的映射關係

B. 硬體架構
從大到小的順序,其中 SP 是基本單位:
Processor Cluster -> Steaming Multiprocessor(SM) -> Stream Processor(SP / CUDA Core)
GA102 結構(30系列遊戲卡)
- 7 x GPC
- 42 x TPC ( 6 / GPC)
- 84 SM(2/TPC)
- { 10752 x CUDA Core (128/SM), 336 x Tensor Core(4/SM), 74 x Ray Tracing Core(1/SM), 256KB registers, [128 KB L1/Shared mem, Texture Cache]}
- 6144 KB L2 cache
3090 實際硬體是 41 TPC,所以對應的硬體 sped 都是少了一個TPC的數值。
GA102 SM
每個 SM 分成四個 block,INT32 和 FP32 可以並行處理。
具體可以參看:

C. 編程模型
軟硬體概念對應
- Thread -> CUDA Core
- Block -> SM
- Grid -> Device
執行緒間的 context switching 很小,會提前預分配好很多執行緒的上下文。最簡單粗暴的優化就是堆執行緒。只要等待執行的執行緒夠多,Tensor Core 就可以不斷的運行。
調度單位 Warp
目前硬體是 32 threads / warp, warp 是執行緒調度的基本單位,無論如何,每次必調度32執行緒執行,即使只執行16執行緒,另外的16執行緒也會佔用硬體。
同一個 warp 裡面執行的指令是一樣的,所以也會共用 instruction fetch/dispatch (這就是所謂的 SIMT 模式),同一個 warp 裡面的執行緒稱作 lanes。
與 Warp 相關的一些優化,可以參見這個(新的編譯器已經自動優化這個場景了,看看思路就可以了)
- Thread Divergence: 由於 warp 內是完全一致的指令,所以如果 warp 裡面有 if A else B 的分支會順序執行(所有需要執行 A 內容的執行緒完成指令後,再跑需要執行 B 相關的指令,最後再執行後續的指令)。如果一個 warp 中實際 if else 的分叉比較多的話,就需要增加調度,增加等待概率(極限情況是 32 threads 的邏輯路徑全不一樣,那麼在 warp 級別上來看就會有 32 倍的性能丟失)。
Warp 數量
Warp 中如果出現有等待(比如讀取 global mem),會調入其他 warp 執行,所以只要等待執行的 warp 足夠,就可以隱藏掉數據存取的開銷。如果計算密集和訪存密集的程式放在一個SM上,就可以互相隱藏,獲得更好的吞吐量。
D. Performance
原則指南:Hide latency with computation。
- 由於 GPU 的 context switch 基本為0,所以只要等待執行的執行緒足夠多,那麼存儲器的延遲都會被不斷執行緒切換執行掩蓋掉。
- Cuda stream,非同步拷貝等等掩蓋 latency。
- 記憶體訪問這塊盡量用好 shared mem 和各種 cache。
Refs
- Exploring the GPU Architecture
- GPGPU origins and GPUhardware architecture
- An Introduction toModern GPU Architecture
- CUDA編程入門(二)GPU硬體基礎
- GPU Memory Model Overview
- GPU Memory
- Performance Programming: Bank Conflicts Memory coalescing Improved Matrix Multiply Tree summation
- Shared memory usage
- Introduction to GPGPU and CUDA Programming: Thread Divergence
- Lecture 3: control flow andsynchronisation
- What is Warp Divergence ?
- CUDA C++ Best Practices Guide
- CUDA_threads_and_block_scheduling
- Using Shared Memory in CUDA C/C++
- 「CUDA Tutorial」
- Benchmarking the Memory Hierarchy of Modern GPUs
- NVIDIA GPU的一些解析(一)
- CUDA微架構與指令集(4)-指令發射與warp調度
- NVIDIA AMPERE GA102 GPU ARCHITECTURE
- Lecture 5:GPU Architecture & CUDA Programming
- Pascal GPU memory and cache hierarchy
- Increasing GPU Throughput usingKernel Interleaved Thread Block Scheduling
- CUDA Warps and Occupancy