GPU計算性能
單核CPU無論在PC端,還是服務器上,基本上已經退出歷史舞台,目前主流的計算平台是使用多核(multiple cores)的CPU,以及眾核(many cores)的GPU。另外處理器與內存訪問速度差距也不斷增大,為克服訪存瓶頸,主要采用兩種方法。其中多核CPU與單核CPU,都是利用Cache來掩蓋訪問系統內存的延遲,以減輕訪存帶寬的壓力,其芯片的較大面積也都貢獻給Cache。在另一端,GPU通過同時運行很多簡單的線程,不使用或者只利用相對較小的Cache,而主要通過線程間的並行(Thread Level Parallelism, TLP)來隱藏內存訪問延遲,當一部分線程因為訪存停滯的時候,另一部分線程會接着執行,使得處理單元不會空閑下來。
目前的異構計算平台,同時采用這兩種截然不同的架構,使得性能預測和優化都不太容易,面對一個給定的計算負載,應該如何分發能夠達到性能最佳?對芯片架構師而言,在面積受限的芯片上,怎樣合理部署處理單元、Register File和Cache等等也是讓人撓頭的事情。希望能夠為理解優化性能提供參考,定義了一個統一仿真模型,可以容納延展這兩種不同特點的架構設計。這個模型對應一個想象的混合計算平台,該平台由很多簡單的處理單元,以及較大的共享緩存構成,通過靈活配置一系列參數,包括處理單元個數、緩存大小以及緩存和內存的訪問延遲等等,可以觀察不同參數變化,對計算性能的影響。
為保持模型簡單,論文假設所有線程相互不共享數據,且系統內存帶寬足夠大。如下圖所示,當線程數量較少的時候,隨着線程數量增加,性能開始提升,而當線程數量到達轉折點,Cache不能夠容納所有線程的工作集時,性能反而下降。之后,隨着線程數量越來越多,由於有足夠的線程來掩蓋Cache訪問不命中帶來內存訪問延遲,性能又接着上升,直達到平台可獲得的最大性能。可以認為MC Region對應多核CPU的情形,而MT Region自然對應有超多線程的GPU,MC Region和MT Region之間的性能波谷區域,在架構設計和程序優化中,都是要努力避免的。
以下具體推導下參數曲線對應的公式,下表列出計算模型涉及的參數,左邊是平台相關的,右邊跟運算任務有關。
GPU計算
從處理單元設計和存儲層次結構兩個方面,探討GPU不同於CPU的特點,再次確認反復申明的GPU,更重視整體的Throughput,而CPU更在乎具體任務的Latency。CPU和GPU從一開始就是為不同的目標而設計,CPU雖然也可以同時執行多個線程,但其旨在高效地處理串行指令,通過許多復雜技術優化,提高指令級並行,以便可以盡快執行串行程序。GPU同時執行成千上萬個線程,犧牲單個線程性能換取整體性能最大化。下圖對CPU與GPU的抽象架構進行了比對,其中Control是控制器、Core是處理單元、Cache指的是各級緩存、DRAM就是內存。可以看到GPU設計者將更多的晶體管用作執行單元,而不是像CPU那樣用作復雜的控制邏輯和緩存。
在下面內容里,會具體討論這兩種設計面向帶來的影響,盡量不拘泥具體產商的特定GPU產品,而是希望能給出一般的指引,但是因為文本材料的優勢,以及在通用計算領域明顯的優勢地位,大概還是不能脫離Nvidia GPU的語境, AMD GPU的處理單元設計細節與Nvidia有較大不同,以后可以專文討論。
SIMT和硬件多線程
根據計算機歷史上有名的的費林分類法(Flynn's Taxonomy),如下圖所示計算機體系架構可以簡單分為四類,分別是:
- 單一指令流單一數據流計算機(SISD, Single Instruction Single Data)
- 單一指令流多數據流計算機(SIMD, Single Instruction Multiple Data)
- 多指令流單一數據流計算機(MISD, Multiple Instruction Single Data)
- 多指令流多數據流計算機(MIMD, Multiple Instruction Multiple Data)
單核CPU可以歸類為SISD,多核CPU屬於MIMD。重點關注的SIMD指的是采用一個控制器來控制多個處理單元,同時對一組數據的元素分別執行相同的操作從而實現空間上並行的技術。傳統CPU的指令擴展SSE和NEON都屬於典型的SIMD。現代GPU在SIMD基礎上發展出SIMT(Single Instruction Multiple Thread)的執行架構。傳統SIMD是一個線程調用向量處理單元(Vector ALU)執行向量指令來操作向量寄存器完成運算,而SIMT往往由一組標量處理單元(Scalar ALU)構成,每個處理單元對應一個硬件線程,所有處理單元共享指令預取/譯碼模塊,並接收同一指令共同完成SIMD類型運算,運行其上的線程,可以有自己的寄存器堆,獨立的內存訪問尋址以及執行分支。以Nvida CUDA為例來,介紹SIMT是如何運作的。下圖是有關分發CUDA的計算任務到GPU硬件上執行,展示了軟硬件視角各個層級的對應關系。
先介紹層級圖右面的GPU硬件層次,CUDA的GPU有很多SM(Streaming Multiprocesso)組成。一個SM又有很多SP(Streaming Processor)構成,SP是每個線程具體執行指令所在,SP也采用流水線設計,提高指令級並行,一般都是順序執行,很少使用分支預測、動態執行等復雜技術。
在GPU通用計算語境下,GPU設備上執行的程序被稱為Kernel,針對某個Kernel分發的所有線程,都執行相同的程序,這些線程被組織成一系列層次結構,也就是Grid和Block,如層級圖左邊所示。Grid規定各個維度Block的數量,Block規定各個維度線程的數量,尺寸大小都是在CUDA程序中分發Kernel時指定。下圖程序中vecAdd就是Kernel程序,Kernel的分發是由<<<...>>>語法定義,其中規定了要分發的Kernel程序,Grid和Block的尺寸,以及Kernel程序的參數。
在實際執行過程中,GPU會以Block為單位,把相同Block的線程分配給同一個SM進行運算,Block中的線程可以通過Shared Memory交換數據(注:Shared Memory訪問性能類似L1,與Cache由硬件控制對軟件透明不同,Shared Memory由軟件顯式移動數據),並支持相互同步操作。在硬件內部,Block進一步會被為分組成Warp,Warp是GPU硬件最小調度單位,Warp內的線程被分配給SP,按照SIMD的模式工作,也就是這些線程共享同樣的PC(程序計數器),以鎖步(Lockstep)的方式執行指令。目前支持CUDA的GPU,其Warp大小都是32,SM中SP的數量可能只有8或者16,在這種情況下,一條指令Warp需要跨幾個時鍾分批執行。再來看下GPU硬件可以支持的線程數量,以Fermi GF100為例,該GPU一共有16個SM,每個SM最多可以容納48個Warp,也就是1536個線程,整個GPU可以支持24576 個線程同時在線。可以與CPU對照下,消費級CPU一般有2~8個核,就算打開Hyperthreading,一共也就支持十幾個硬件線程同時在線。為了避免一些高延遲指令引起處理單元流水線停頓,CPU和GPU采取了完全不同的做法。
- CPU的做法是一方面窮盡所能充分挖掘指令級並行來規避,另一方面通過各級Cache來掩蓋訪問內存延遲,萬不得已CPU才會切換到別的硬件線程執行。硬件線程數量太多切換太頻繁,即使有助於整體吞吐卻惡化單個線程的延遲,對CPU設計來說也是不可接受的,所以,可以看到Hyperthread的數目一般都比較少。
- GPU的做法是另外一種思路,大規模數據並行帶來海量的可執行線程,GPU完全可以通過切換到別的線程Warp,規避指令延遲帶來處理單元的停頓。這種切換會非常頻繁,需要在很短時間完成(比如一個時鍾),所以無論每個線程執行需要的的寄存器堆,還是Block之內線程的Shared Memory,從一開始就要分配妥當,切換過程中線程上下文一直駐留,直到線程或者整個Block執行結束才能釋放。所以相比CPU,GPU的Register File大小非常驚人,而其處理單元的設計卻可以異常簡單。
GPU的Memory Hierarchy
一方面GPU通過同時運行很多簡單的線程,不使用或者只利用相對較小的Cache,而主要通過線程間的並行來隱藏內存訪問延遲。另一方面顯存帶寬對整體計算吞吐又有重要意義,直接關系到GPU性能伸縮能力。所以,如下圖所示,GPU存儲層次設計的時候,相比Latency,更重視Throughput,而且各級存儲容量相對偏小。
以Fermi GF100 GPU為例,下圖是其存儲層次結構,Fermi GPU是CUDA GPU第一次添加L1和L2的支持,其中L1和Shared Memory共享同一塊片上內存,每個SM各64K大小,可以根據要求以48K/16K或者16K/48K在L1和Shared Memory之間分配。
下面表格是幾代CUDA GPU的L1、L2和Register File大小配置。可以看到,最早的CUDA GPU,也就是G80都沒有通用的L1和L2,只有16K的Shared Memory。至於為什么添加Cache的支持,主要是考慮到對某些應用來說,可能沒有足夠的數據並行來掩藏訪存延遲,而對另外一些應用其數據重用模式不可預測,無法有效利用軟件控制的Shared Memory,總之,為了讓GPU變得更通用,能夠兼容更多的計算范式。
對GF100,RF大小總共為2048K,L1為48x16=768K,L2也是768K,RF反而比L1和L2都要大,而L1和L2差不多,其它GPU也有類似現象。
參考資料:
- Many-core vs many-thread machines: Stay away from the valley
- Cuda C Programming Guide
- CUDA Warps and Occupancy
- SIMD < SIMT < SMT: parallelism in NVIDIA GPUs
- The Top 10 Innovations in the New NVIDIA Fermi Architecture, and the Top 3 Next Challenges