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:

  1. L1 是不能保證一直在的(可能會被換出,程式設計師不可控),所以如果對某個記憶體值多次讀取,可以放入 Shared Mem,通過程式碼來管理,可控性更強(這種情況需要人工管理同步,可能會有死鎖)。
  2. 粗暴增加 Shared Mem 可能會帶來性能下降(L1 cache 小了)

Cache Line 的大小:

由於這些值實際和硬體設計相關,未必能對應到新的硬體上。 (關於 cache line 參見這個鏈接, CPU 的,不過在這裡是通用的)

  1. L1 = 128 bytes
  2. L2 = 32 bytes
  3. 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。

  1. 由於 GPU 的 context switch 基本為0,所以只要等待執行的執行緒足夠多,那麼存儲器的延遲都會被不斷執行緒切換執行掩蓋掉。
  2. Cuda stream,非同步拷貝等等掩蓋 latency。
  3. 記憶體訪問這塊盡量用好 shared mem 和各種 cache。

Refs

  1. Exploring the GPU Architecture
  2. GPGPU origins and GPUhardware architecture
  3. An Introduction toModern GPU Architecture
  4. CUDA編程入門(二)GPU硬體基礎
  5. GPU Memory Model Overview
  6. GPU Memory
  7. Performance Programming: Bank Conflicts Memory coalescing Improved Matrix Multiply Tree summation
  8. Shared memory usage
  9. Introduction to GPGPU and CUDA Programming: Thread Divergence
  10. Lecture 3: control flow andsynchronisation
  11. What is Warp Divergence ?
  12. CUDA C++ Best Practices Guide
  13. CUDA_threads_and_block_scheduling
  14. Using Shared Memory in CUDA C/C++
  15. 「CUDA Tutorial」
  16. Benchmarking the Memory Hierarchy of Modern GPUs
  17. NVIDIA GPU的一些解析(一)
  18. CUDA微架構與指令集(4)-指令發射與warp調度
  19. NVIDIA AMPERE GA102 GPU ARCHITECTURE
  20. Lecture 5:GPU Architecture & CUDA Programming
  21. Pascal GPU memory and cache hierarchy
  22. Increasing GPU Throughput usingKernel Interleaved Thread Block Scheduling
  23. CUDA Warps and Occupancy