近距離看GPU計算

在前面文章中,我們交代了計算平臺相關的一些基本概念以及爲什麼以GPU爲代表的專門計算平臺能夠取代CPU成爲大規模並行計算的主要力量。在接下來的文章中,我們會近距離從軟硬件協同角度討論GPU計算如何開展。跟先前的文章類似,筆者會採用自上而下,從抽象到具體的方式來論述。希望讀者不只是對GPU計算能有所理解,而且能夠從中瞭解可以遷移到其它計算平臺的知識,此是筆者之願景,能否實現一二,還懇請各位看官不斷反饋指正,歡迎大家在後臺留言交流。在本文中,我們首先介紹下GPU及其分類,並簡單回顧下GPU繪製流水線的運作,最後又如何演化爲通用計算平臺。

一,什麼是GPU及其分類

按維基百科定義,GPU(Graphics Processing Unit,圖形處理器)是一種專門在個人電腦、工作站、遊戲機和一些移動設備(如平板電腦、智能手機等)上運行繪圖運算工作的微處理器。爲了以後討論方便,這裏先給市面的GPU按配置大致做個分類,分類之間界限比較模糊,不一定完全準確。

  1. 獨立GPU(Discrete GPU),或者獨立顯卡。是指GPU通過PCI Express或者早期的AGP、PCI等擴展接口與主板連接。所謂的“獨立”即是指顯卡內的RAM只會被該GPU專用,而不是指顯卡是否可從主板上移除。由於尺寸和重量的限制,供筆記本電腦使用的獨立GPU通常會通過非標準的接口作連接,然而由於邏輯接口相同,這些接口仍會被視爲PCIE,即使在物理上它們是不可與其他顯卡互換。一些專門的GPU互聯技術,如NVIDIA的SLI、NVLink和AMD的CrossFire等允許多個獨立GPU協同工作,可顯著增強設備的圖形處理能力。獨立GPU價格高,體積大,功耗高,但性能更強勁,而且因爲自帶顯存,消耗的系統資源也更少。

image

  1. 集成GPU(Integrated GPU), 或者集成顯卡。是集成在主板或CPU上的GPU,運行時會佔用部分的系統內存,相比起使用獨立顯卡的方案,這種方案較爲便宜,但性能也相對較低。從2009年開始,集成GPU已經從主板移至CPU了,Intel將之稱爲HD Graphics(核芯顯卡),AMD也推出了集成了CPU和GPU的APU。將GPU集成至處理器的好處是可以降低功耗,提升性能。隨着技術的成熟,目前的集成GPU已經足夠應付基本3D的需求,不過由於仍然依賴主板本身的RAM,相比獨立顯卡,訪存帶寬始終是個不小的限制。

image

以後談到GPU計算的時候,我們主要還是以高性能爲訴求。所以如果上下文沒有特別說明,我們一般都是針對獨立GPU。

二,GPU繪製流水線

在這節我們會簡單的介紹GPU的繪製流水線(Rendering Pipeline),GPU就是爲圖形繪製加速而生,知道它的來龍去脈,有助於我們理解在其基礎之上衍生的GPGPU。GPU繪製的過程,類似我們生活中拍照和寫生,是有關如何把三維空間的場景在二維的屏幕上能儘量真實的呈現出來。我們以寫生來做譬喻,針對特定場景輸入,擇一視點,取景構圖,按照透視比例通過點線面勾勒出物體的邊界和輪廓,確定明暗色調,注意遠近關係多層次描繪。

image

與採用畫筆、相機等工具不同,3D圖形程序通過調用OpenGL(ES)、Direct3D或者Vulcan API的接口函數來同GPU硬件交互。爲方便論述又不失代表性,下圖是一個相對目前GPU簡化的繪製管線,基本上相當於OpenGL(ES) 2.0或者Direct3D 9.0的規格,繪製管線主要有以下步驟構成。值得注意的是,管線分爲可編程單元以及固定功能(fixed function)單元,後者優化處理管線中不容易並行化的工作,顯然各種Shader都在可編程單元執行。

image

  • 頂點數據輸入。3D程序需要的三維場景可以通過3ds Max、Maya等專業軟件來建模,生成的模型可以有成千上萬個三角面片網格構成,其中不僅規定頂點的位置、顏色、紋理座標和法向量等等屬性也包括它們之間的連接信息。模型導入3D程序以後,就可以成爲3D程序的頂點數據流,頂點數據爲爲後面的Vertex Shader等階段提供待處理的數據。

  • Vertex Shader(頂點着色器)。Vertex Shader的主要功能是對頂點屬性進行變換,包括頂點位置的座標轉換,從局部座標統一到世界座標並切換到視點座標以至裁剪座標。以前也在Vertex Shader進行光照顏色計算,但是由於不夠真實,目前一般移到Fragment Shader階段才發生。Vertex Shader的輸入輸出都是頂點。

  • Primitive Setup(圖元裝配)和Rasterization(光柵化)。圖元裝配是將變換後的的頂點根據連接信息組裝成指定的圖元。圖元裝配階段會進行裁剪(clip)和背面剔除(backface culling)相關的優化,可以減少不必要的工作量。另外還需要透視除法(Perspective Division)達到透視效果,然後通過視口變化(Viewport Transformation)最終得到頂點的屏幕座標。在光柵化階段,基本圖元被轉換爲一組二維的片元(fragment),片元表示將來可以被渲染到屏幕上的像素,它包含有位置,顏色,紋理座標等信息,這些屬性是由圖元的相關頂點信息進行插值計算得到的。這些片元接着被送到Fragment Shader處理。

  • Fragment Shader(片元着色器)。片元着色器用來決定屏幕上潛在像素的最終顏色。在這個階段會依據紋理座標進行紋理採樣、計算光照以及處理陰影等等,是繪製管線產生高級效果所在。

  • 測試合成。測試合成是繪製管線的最後一個步驟。主要測試有裁剪測試(Scissor Test)、模板測試(Stencil Test)以及深度測試(Depth Test),深度測試就是確認進入的片元有沒有被Framebuffer(幀緩存)同樣位置的像素遮擋。通過最終測試的片元會進入合成階段,就是進入的片元和Framebuffer已有的像素進行混合,根據顏色的Alpha值取代現有像素或混合產生半透明的效果。Alpha表示的是物體的透明度。測試合成階段不是可編程的,但是我們依舊可以通過3D API提供的接口函數進行動態配置,並進一步定製測試和混合的方式。

image

上面的步驟針對接口函數其中一個繪製命令,而一幀畫面一般需要很多個繪製命令才能完成,等一幀內容結束以後,該Framebuffer就會作爲新的Front Buffer交由顯示設備顯示,而先前顯示的Front Buffer會變成Back Buffer又開始下一幀的繪製之旅。這就是GPU的雙緩存(Double Buffering)策略。在上層應用程序可以通過3D API的接口函數調用GPU功能,在底層GPU驅動將這些接口函數轉化爲各種GPU私有的命令執行,它們可以完成繪製,狀態寄存器設置以及同步等任務。CPU和GPU通過Ring Buffer(環形緩存)來傳遞和接受這些命令,CPU承擔Ring Buffer生產者的角色,而GPU扮演消費者的角色,因爲Ring Buffer大小有限,CPU和GPU需要同步。如果CPU老是要等GPU,我們說這個程序是GPU Bound,我們可能需要去優化Shader程序,減少三角面片數量來提高性能。相反如果GPU老是要等CPU,我們就認定這個程序是CPU Bound, 應用程序可以考慮把一些CPU預處理移交給GPU解決,比如利用GPU繪製管線支持的Geometry Shader(幾何着色器)和Tessellation Shaders(細分曲面着色器)來生成額外頂點和圖元,而不是等待CPU輸入等等。

image

三,GPU計算的演進之旅

隨着真實感繪製進一步發展,對圖形性能要求愈來愈高,GPU發展出前所未有的浮點計算能力以及可編程性。這種遠超CPU的計算吞吐和內存帶寬使得GPU不只是在圖形領域獨領風騷,也開始涉足其它非圖形並行計算應用。最早通過使用3D API OpenGL或者DirectX接口函數,很多數據並行算法被移植到GPU,性能也獲得很好提升,但是這種利用模式面臨不少問題,下面具體看看一步步是如何解決的。

  1. CUDA的發明。之前的GPGPU實現需要並行算法程序員很熟悉圖形API和GPU硬件,算法輸入輸出需要定義爲圖形繪製的元素,比如頂點座標,紋理,幀緩存等,而實際算法又必須用着色程序(Shader Program)來表達,極大增加了通用並行算法在GPU上移植開發的複雜度,另外受限圖形API的表達能力,很多並行問題沒辦法有效發揮GPU的潛力。2006年,Nvidia破天荒地推出CUDA,作爲GPU通用計算的軟件平臺和編程模型,它將GPU視爲一個數據並行計算的設備,可以對所進行的計算分配和管理。在CUDA框架中,這些計算不像過去那樣必須映射到圖形API,因此對於開發者來說,基於CUDA的開發門檻大大降低了。CUDA編程語言基於標準的C語言,一般用戶也很容易上手開發CUDA的應用程序。

  2. 統一可編程單元。早些時候的GPU繪製管線都是固定功能的,不存在可編程部分。後來出現了可編程的Vertex和Fragment處理,極大地豐富了繪製效果,但是Vetex和Fragment的處理單元還是分離的,很容易造成負載不均衡,性能的伸縮性也不好。伴隨着Direct3D 10(Shader Model 4.0)出現,GPU開始用統一的處理單元運行Vertex、Fragment以及Geomerty Shader。對通用並行計算而言,配合CUDA框架,只要增加GPU可編程處理器數量配置,這種統一處理方式就能夠最大限度地擴展性能,影響非常深遠。

image

  1. 浮點計算的標準化。GPU的可編程處理單元是面向浮點運算,但是浮點數的支持之前幾乎每個GPU廠商都有自己的解決方案,精度、舍入的處理都不一致,導致計算的準確度存在明顯差異。比如繪製管線傾向於把溢出(overflow),下溢(underflow)和非規格化浮點數(denorms)截斷爲可表示有意義的最大值或者最小值。現在GPU增加了對特殊數值(Special Values)Infinity和NaN的支持,計算過程的精度和準確度也向IEEE 754標準要求靠攏,比如下圖演示的FMA。浮點計算除支持半精度和單精度以外,雙精度的支持也不可或缺。另外除了浮點數,GPU也開始支持各種各樣的整形運算。這些數據類型的支持對GPU通用計算的重要意義不言而喻。

image

  1. 隨機存取數據。傳統的GPU架構只有非常有限的尋址能力,如通過提供紋理座標給紋理處理單元讀取紋理數據,Fragment Shader把像素最終的顏色值輸出到對應的幀緩存位置,這些讀寫過程用戶沒有辦法顯式控制,非常限制通用計算的數據交互能力。現在的的GPU增加了額外的存取單元,在指令集中增加統一尋址存取指令,很大程度拓展了GPU通用計算應用空間。

  2. 存儲支持ECC。隨着製程工藝不斷進步,器件尺寸縮小,DRAM和SRAM的永久性故障(Hard Error)和瞬時間失效(Soft Error)錯誤都會增加,尤其後者在電容儲存電荷量較小的情況下,問題會越來越嚴重。GPU當然也不能倖免,從顯存,到多級cache以至寄存器文件(Register File)都暴露在這一風險之下。對圖形應用來說,這一問題並不需要太多擔心,人們根本意識不到屏幕上幾百萬個像素中個別顏色值中一位或幾位bit出現了翻轉,哪怕發生更嚴重的錯誤,人類的視覺機制都有機會自我補償糾正。但在高性能計算領域,差之毫釐,謬以千里,這些存儲失效的問題都是不能承受之重。所以現在GPU廠商至少會針對HPC產品在整個存儲器層次結構添加ECC(Error Correcting Code)支持,數據中心和服務器的客戶也纔敢放心購買使用。

image

有了以上一些改進和其他措施,終於GPU作爲通用計算平臺慢慢脫離原始階段,開始成熟起來,成爲大規模並行計算市場的主力軍。在下篇文章中,我們會具體分析GPU不同於CPU的架構特點,重點介紹計算線程的硬件調度模式,敬請關注。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章