前面扯了很多,不過大多都是在講 CUDA 在軟體層面的東西;接下來,雖然 Heresy 自己也不熟,不過還是來研究一下硬體的部分吧∼畢竟要最佳化的時候,好像還是要大概知道一下相關的東西的。這部分主要參考資料是:
在研究硬體架構前,可能須要先回去看《nVidia CUDA 簡介》,稍微回顧一下在 CUDA 中 thread、thread block、block grid 的意義:一個 CUDA 的平行化的程式會被以許多個 thread 來執行,數個 thread 會被群組成一個 block,而多個 thread block 則會再構成 grid。也就是一個 kernel 程式會有一個 grid,grid 底下有數個 block,每個 block 都是一群 thread 的群組。而在同一個 block 中的 thread 可以透過 shared memory 來溝通,也可以同步。
硬體基本架構
實際上在 nVidia 的 GPU 裡,最基本的處理單元是所謂的 SP(Streaming Processor),而一顆 nVidia 的 GPU 裡,會有非常多的 SP 可以同時做計算;而數個 SP 會在附加一些其他單元,一起組成一個 SM(Streaming Multiprocessor)。幾個 SM 則會在組成所謂的 TPC(Texture Processing Clusters)。
在 G80/G92 的架構下,總共會有 128 個 SP,以 8 個 SP 為一組,組成 16 個 SM,再以兩個 SM 為一個 TPC,共分成 8 個 TPC 來運作。而在新一代的 GT200 裡,SP 則是增加到 240 個,還是以 8 個 SP 組成一個 SM,但是改成以 3 個 SM 組成一個 TPC,共 10 組 TPC。下面則是提供了兩種不同表示方式的示意圖。(可參考《NVIDIA G92終極狀態!!》、《NVIDIA D10U繪圖核心》)
對應到 CUDA
而在 CUDA 中,應該是沒有 TPC 的那一層架構,而是只要根據 GPU 的 SM、SP 的數量和資源來調整就可以了。
如果把 CUDA 的 Grid – Block – Thread 架構對應到實際的硬體上的話,會類似對應成 GPU – Streaming Multiprocessor – Streaming Processor;一整個 Grid 會直接丟給 GPU 來執行,而 Block 大致就是對應到 SM,thread 則大致對應到 SP。當然,這個講法並不是很精確,只是一個簡單的比喻而已。
SM 中的 Warp 和 Block
CUDA 的 device 實際在執行的時候,會以 Block 為單位,把一個個的 block 分配給 SM 進行運算;而 block 中的 thread,又會以「warp」為單位,把 thread 來做分組計算。目前 CUDA 的 warp 大小都是 32,也就是 32 個 thread 會被群組成一個 warp 來一起執行;同一個 warp 裡的 thread,會以不同的資料,執行同樣的指令。此外,在 Compute Capability 1.2 的硬體中,還加入了 warp vote 的功能,可以快速的進行 warp 內的簡單統計。
基本上 warp 分組的動作是由 SM 自動進行的,會以連續的方式來做分組。比如說如果有一個 block 裡有 128 個 thread 的話,就會被分成四組 warp,第 0-31 個 thread 會是 warp 1、32-63 是 warp 2、64-95 是 warp 3、96-127 是 warp 4。
而如果 block 裡面的 thread 數量不是 32 的倍數,那他會把剩下的 thread 獨立成一個 warp;比如說 thread 數目是 66 的話,就會有三個 warp:0-31、32-63、64-65。由於最後一個 warp 裡只剩下兩個 thread,所以其實在計算時,就相當於浪費了 30 個 thread 的計算能力;這點是在設定 block 中 thread 數量一定要注意的事!
一個 SM 一次只會執行一個 block 裡的一個 warp,但是 SM 不見得會一次就把這個 warp 的所有指令都執行完;當遇到正在執行的 warp 需要等待的時候(例如存取 global memory 就會要等好一段時間),就切換到別的 warp 來繼續做運算,藉此避免為了等待而浪費時間。所以理論上效率最好的狀況,就是在 SM 中有夠多的 warp 可以切換,讓在執行的時候,不會有「所有 warp 都要等待」的情形發生;因為當所有的 warp 都要等待時,就會變成 SM 無事可做的狀況了∼
下圖就是一個 warp 排程的例子。一開始是先執行 thread block 1 的 warp1,而當他執行到第六行指令的時候,因為需要等待,所以就會先切到 thread block 的 warp2 來執行;一直等到存取結束,且剛好有一個 warp 結束時,才繼續執行 TB1 warp1 的第七行指令。
實際上,warp 也是 CUDA 中,每一個 SM 執行的最小單位;如果 GPU 有 16 組 SM 的話,也就代表他真正在執行的 thread 數目會是 32*16 個。不過由於 CUDA 是要透過 warp 的切換來隱藏 thread 的延遲、等待,來達到大量平行化的目的,所以會用所謂的 active thread 這個名詞來代表一個 SM 裡同時可以處理的 thread 數目。
而在 block 的方面,一個 SM 可以同時處理多個 thread block,當其中有 block 的所有 thread 都處理完後,他就會再去找其他還沒處理的 block 來處理。假設有 16 個 SM、64 個 block、每個 SM 可以同時處理三個 block 的話,那一開始執行時,device 就會同時處理 48 個 block;而剩下的 16 個 block 則會等 SM 有處理完 block 後,再進到 SM 中處理,直到所有 block 都處理結束。
建議的數值?
在 Compute Capability 1.0/1.1 中,每個 SM 最多可以同時管理 768 個 thread(768 active threads)或 8 個 block(8 active blocks);而每一個 warp 的大小,則是 32 個 thread,也就是一個 SM 最多可以有 768 / 32 = 24 個 warp(24 active warps)。到了 Compute Capability 1.2 的話,則是 active warp 則是變為 32,所以 active thread 也增加到 1024。
在這裡,先以 Compute Capability 1.0/1.1 的數字來做計算。根據上面的數據,如果一個 block 裡有 128 個 thread 的話,那一個 SM 可以容納 6 個 block;如果一個 block 有 256 個 thread 的話,那 SM 就只能容納 3 個 block。不過如果一個 block 只有 64 個 thread 的話,SM 可以容納的 block 不會是 12 個,而是他本身的數量限制的 8 個。
因此在 Compute Capability 1.0/1.1 的硬體上,決定 block 大小的時候,最好讓裡面的 thread 數目是 warp 數量(32)的倍數(可以的話,是 64 的倍數會更好);而在一個 SM 裡,最好也要同時存在複數個 block。如果再希望能滿足最多 24 個 warp 的情形下,block 裡的 thread 數目似乎會是 96(一個 SM 中有 8 個 block)、128(一個 SM 中有 6 個 block)、192(一個 SM 中有 4 個 block)、256(一個 SM 中有 3 個 block) 這些數字了∼
而官方的建議則是一個 block 裡至少要有 64 個 thread,192 或 256 個也是通常比較合適的數字(請參考 Programming Guide)。
但是是否這些數字就是最合適的呢?其實也不盡然。因為實際上,一個 SM 可以允許的 block 數量,還要另外考慮到他所用到 SM 的資源:shared memory、registers 等。在 G80 中,每個 SM 有 16KB 的 shared memory 和 8192 個 register。而在同一個 SM 裡的 block 和 thread,則要共用這些資源;如果資源不夠多個 block 使用的話,那 CUDA 就會減少 Block 的量,來讓資源夠用。在這種情形下,也會因此讓 SM 的 thread 數量變少,而不到最多的 768 個。
比如說如果一個 thread 要用到 16 個 register 的話(在 kernel 中宣告的變數),那一個 SM 的 8192 個 register 實際上只能讓 512 個 thread 來使用;而如果一個 thread 要用 32 個 register,那一個 SM 就只能有 256 個 thread 了∼而 shared memory 由於是 thread block 共用的,因此變成是要看一個 block 要用多少的 shread memory、一個 SM 的 16KB 能分給多少個 block 了。
所以雖然說當一個 SM 裡的 thread 越多時,越能隱藏 latency,但是也會讓每個 thread 能使用的資源更少。因此,這點也就是在最佳化時要做取捨的了。
😀
Very useful article.
I am currently studying parallel computing using CUDA.
Thank you.