1. 程式人生 > >cuda-Block和Grid設定

cuda-Block和Grid設定

CUDA的Threading:Block和Grid設定

硬體基本架構  實際上在 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 都處理結束。
  為一個多處理器指定了一個或多個要執行的執行緒塊時,它會將其分成warp塊,並由SIMT單元進行排程。將塊分割為warp的方法總是相同的,每個warp都包含連續的執行緒,遞增執行緒索引,第一個warp中包含全域性執行緒過索引0-31。每發出一條指令時,SIMT單元都會選擇一個已準備好執行的warp塊,並將指令傳送到該warp塊的活動執行緒。Warp塊每次執行一條通用指令,因此在warp塊的全部32個執行緒執行同一條路徑時,可達到最高效率。如果一個warp塊的執行緒通過獨立於資料的條件分支而分散,warp塊將連續執行所使用的各分支路徑,而禁用未在此路徑上的執行緒,完成所有路徑時,執行緒重新匯聚到同一執行路徑下,其執行時間為各時間總和。分支僅在warp塊內出現,不同的warp塊總是獨立執行的--無論它們執行的是通用的程式碼路徑還是彼此無關的程式碼路徑。
  建議的數值?

  在 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) 這些數字了~
  Compute Capability 1.0/1.1 的硬體上,每個grid最多可以允許65535×65535個block。每個block最多可以允許512個thread,但是第三維上的最大值為64。而官方的建議則是一個 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 能使用的資源更少。因此,這點也就是在優化時要做取捨的了。