ATI 流計算介紹

引言:
      隨着GPU的並行處理能力的不斷提升,GPU的特性被不斷的應用於圖形無關的應用中,並獲得了非常大的速度提升。大量的應用開始工作在GPU上,而不是利用多核CPU進行加速。類似於光線跟蹤,光子映射等開銷很大的計算都可以在GPU上達到靜實時的性能。

      本文介紹了ATI流計算的一些基礎知識,對於CAL,Brook+或者OpenCL有興趣的朋友,可以看看。

正文:

ATI流計算模型:

      ATI流計算模型包含了一套軟件系統以及ATI流處理器。下圖展示了ATI流處理模塊之間的關係:

            1 

      ATI流計算軟件爲客戶端用戶提供了一個靈活完整的接口,從而使開發人員充分利用ATI的硬件特性進行流計算。
      軟件主要分爲下面幾個模塊:

           編譯器: 類似於Brook+的編譯器,把Brook+內核的代碼編譯成爲獨立的C++文件以及IL代碼。

           流處理器的設備驅動: ATI流計算抽象模型(CAL)。

           性能分析工具: Stream KernelAnalyzer,可以及時編譯Brook+,IL代碼,並且分析程序性能。

           性能庫: AMD核心數學庫(ACML),這部分是專用於特定領域的。

     在最新一代的ATI流處理器中,編程模型應用一種通用的Shader語言。可編程的流處理器可以執行用戶指定的各種程序,這些程序被稱爲內核函數(Kernel)。這些流處理器可以以一種單指令多數據(SIMD)的形式執行一些與圖形完全無關的任務。這種編程模型被稱爲流計算,內存中存儲的大量相同類型的數據可以被分配到不同的SIMD引擎中進行處理,從而生成輸出數據。

     每一個在SIMD引擎中被處理的實例被稱作爲線程(Thread)。在每個Pass裏,可以有大量的線程被映射到一個矩形區域中,這個矩形區域被稱爲執行區域(Domain of Execution)。

     流計算處理器爲每組線程都分配到一組線程處理器(Thread Processor)中,直到所有的線程都被處理。只有之前的線程完成計算之後,後面的線程纔可以得到處理。下圖爲一個簡化的ATI流計算模型:

          2

Brook+簡介:

      Brook+爲開發者提供了一個簡便快捷的流計算開發接口。用戶可以通過Brook+在ATI的硬件進行流計算開發。Brook+的內核程序是由一種類C語言編寫的,所有非常適合傳統的C以及C++程序員。Brook+語言中兩個最關鍵的概念:

      1. 流。流就是一些類型相同的數據,他們可以被分配到不同的流處理器中進行處理。

      2. 內核。內核程序其實就是GPU硬件的行爲程序,它指定了硬件的行爲特性。

      Brook+的內核函數是被Brcc(Brook Code Compiler)編譯的,編譯後生成三個文件,其中兩個是.h和.cpp文件,就是定義了一些和函數相關的類,還有一個最關鍵的就是IL代碼。有了這些文件,這三個文件再和用戶的其他源文件一起編譯,鏈接,從而生成可執行性文件。Brook+的程序在運行時,是需要Brook runtime的。Brook+的內核程序也可以被CPU執行,還可以進行調試。

      目前,Brook+的版本是1.4版本。已經被提交到了SourceForge上,應該是沒有太多的維護了。由於Brook+還有一些限制,所以在靈活性方面並不如CUDA好。而且由於過多的封裝,效率也並不很高。唯一的好處就在於用Brook+開發比較簡便,程序員需要管理的事情不是特別多,因爲Brook+已經把很多複雜的內容都封裝起來了。對於想做一些GPGPU測試程序的朋友,這個接口還不錯。但是如果開發複雜的項目,Brook+會有一定的限制。

CAL簡介:

     ATI CAL(Compute Abstraction Layer)是一個硬件驅動接口,程序員通過CAL可以訪問硬件所提供的幾乎所有特性,可以控制硬件非常底層的東西。相對於Brook+來說,要底層一些,沒有什麼限制,靈活的多。事實上Brook+就是基於CAL實現的。CAL具有以下特性:

       可以生成設備相關代碼(ISA)。

       設備管理。

       資源管理。

       內核的讀取與執行。

       多核GPU的支持。

       與3D圖形接口的交互(Interoperability)。(關於這種交互,我專門有介紹過:http://blog.csdn.net/codeboycjy/archive/2009/11/28/4896835.aspx

       CAL可以支持用IL編寫的內核函數,也同樣可以接受用硬件高度相關的代碼(ISA)來編寫Kernel。

     CAL的優點在於,它是很底層的接口,用戶可以非常靈活的控制GPU的很多模塊。但是CAL也有一些缺點,CAL的內核編寫是非常繁瑣的,因爲IL是一種類似於彙編的語言,所以在開發過程中,是很難調試的。而且也不適合快速開發,對於C程序員來說,可能上手也不是特別快。但是其實如果適應了IL的開發,這些困難也可能並不很大。

OpenCL簡介:

     目前來說,如果想用A卡進行流計算,無非就是用Brook+,CAL或者OpenCL了。Brook+實際上沒有後續支持了,所以並不是最理想的接口。而CAL雖然AMD一直都在更新,但是由於開發難度相對來說大一些,可能也不適合初學者。如果開發者不想用pixel shader進行流計算的話,就只能用OpenCL了。好在這個接口是有Khronos提出的,是一種開放的接口,可以跨平臺工作,具有很大潛力。AMD還是比較重視這個接口的,早在9月份初,就已經實現了CPU的solution了。在10月中旬左右,AMD再次發佈了基於GPU的OpenCL。

     用戶可以通過下載ATI Stream 2.0 Bate 4.0來體驗一下AMD的流計算功能。當然,必須要有一塊顯卡支持纔可以。如果有HD Radeon 5000系列最好,但是如果沒有的話,4000系列也是同樣支持的。

     OpenCL的接口的優點在於,代碼不僅可以運行在A卡上,還可以運行在N卡上,Nvidia也發佈了OpenCL的solution了。而且還可以在x86架構上運行。由於接口的開放性,以後可能會有更多的處理器支持OpenCL接口。那麼用戶的OpenCL代碼理論上來說,同樣是可以跑在新的平臺下的。OpenCL的內核編寫也是用類C語言進行開發的,所以也非常簡單易用。

Stream Kernel Analyzer:

     對於用IL或者Brook+開發的內核程序,用戶可以用這個軟件進行性能測試。可以在這裏下載到:http://developer.amd.com/gpu/ska/Pages/default.aspx

     對於用IL開發程序的朋友來說,可能這個軟件就更實用一些了。因爲他可以檢查一些語法錯誤,而且報告出的性能更接近與實際的。下圖就是這個軟件了:

          3

流處理器的硬件功能:

        4

      上圖爲ATI流處理器的簡易模型。不同型號的GPU會有不同的性能參數(例如SIMD引擎的數量),但是基本都是一樣的模型。

      一個流處理器裏面包含很多SIMD引擎。每個SIMD引擎又包含了很多Thread處理器,每個線程處理器可以對於獨立的數據進行內核所規定的操作。線程處理器還不算是最小的處理單元,一個線程處理器裏面還包含了一定數量的流計算核心,這些核心纔是最基本的進行處理的單元,他們可以進行整數,單精度浮點數,雙精度浮點數等操作。在同一時間內,一個SIMD引擎中的所有的線程處理器都執行相同的指令集,不同的SIMD引擎是可以執行不同的指令的。

       5

      一個線程處理器中可以同時最多處理五條指令。我們看到上圖中,一個線程處理器中有五個Stream core。其中一個是可以計算超越函數的,剩下四個可以同時計算當精度浮點數。雙精度浮點數的處理是通過把四個stream core合起來纔可以處理的,所以相對來說要慢一些。除了stream core,每個線程處理器實際還包含一個流控制器,他可以處理一些條件分支的情況。

     不同型號的GPU的細節參數都是不同的。例如,在ATI Radeon 3870 GPU(RV670)這款GPU裏面一共包含了4個SIMD引擎,每個SIMD引擎裏面有16個線程處理器,並且每個處理器裏面有5個stream core。一共是320個物理處理核心單元。

線程處理:

     但同一個cycle中,每個SIMD引擎中的所有線程處理器都必須執行同一指令。爲了隱藏內存訪問所帶來的延遲,線程在發送了內存訪問命令之後會被立刻切換。GPU的流處理器裏面的Cache並沒有CPU多,對於內存訪問的優化是通過線程之間的切換來進行的。

     在一個線程處理器中,每4個cycle中,實際可以對於線程處理器指定四條指令。例如,還是剛纔那個3870的例子中,16個線程處理器執行同樣的指令,每個線程處理器中可以一次執行四條指令(因爲每個線程處理器中有4個stream core)。實際上,從外部看,3870的SIMD引擎可以同時處理64條指令的。被同時執行的所有線程的集合被稱爲wavefront。這裏面我們可以理解爲3870的wavefront的大小是64的,注意,不是16。

     wavefront的大小是根據GPU的型號不同而可變的。例如,HD 2600 和 HD 2400 的 wavefront的大小分別是32和16,而AMD FireStream 9170的wave front的大小就是64了。

流控制:

     流控制實際上是通過把所有的可能涉及的指令都執行結合起來實現的。舉個例子,看下面僞代碼:

      if( condition )
      {
          // cost T1
          perform operation 1
      }else
      {
          // cost T2
          perform operation 2
      }

     在一個wavefront裏面,如果所有的線程都執行行爲1,那麼行爲2的指令將不會被執行。反之亦然。但是如果有一部分執行了行爲1,有一部分執行了行爲2,即使是1個線程,那麼硬件的做法實際上是讓所有線程執行行爲1,然後在讓所有線程執行行爲2。那麼總的時間就是T1+T2。

     再舉個例子,在一個內核函數的循環裏面,每次循環cost T,一個wavefront的線程都只循環一兩次,除了一個例外線程循環了100次。那麼實際的執行時間就是100*T。這個道理和竹筒裝水是一樣的。

     所以我們在進行內核程序開發的時候,一定要對於這種分支很明顯的情況保持高度敏感。

內存模型:

     在ATI流計算模型中,有三種內存模型:

        host端的內存:這部分內存就是host程序的數據內存等。他可以被host端訪問,但是不能被GPU kernel訪問。

        PCIe內存:這部分的內存可以被host端或者GPU端訪問,但是要做好同步工作。在CAL裏面要用calCtxIsEventDone函數,Brook+和OpenCL都已經把這些內容透明瞭,用戶可以無視。

        Local內存:這裏的局部是相對於GPU來說的,那麼很顯然,這種內存是可以被GPU訪問的,但是不能被host端訪問。

     有三種方式可以拷貝內存到流處理器內存(局部內存)中:

       通過 calResMap

       通過 calCtxMemCopy

       通過一些自定義內核函數從PCIe內存中拷貝。

流處理器的分配:

      高效的流處理器分配機制可以很好的隱藏內存訪問所帶來的延遲。上面提到GPU是通過線程間切換隱藏內存訪問的,這裏我們具體舉個例子來看一下。

      7

     這裏我們假設有四個線程。在最開始的時候,T0在運行,然後在第20個cycle的時候,該線程申請內存讀取。其實,線程沒有自己去讀取內存,而是發送一條指令給DMA。然後T0就被suspend起了,這個thread processor就去執行T1。然後T1也會被suspend,切換T2。以此類推。在第70個cycle的時候,線程T0請求的內存被返回,所以這是T0就可以繼續進行操作了。那麼在T3結束後,T0會繼續。從thread processor角度講,在任何一個時間內,都在工作,沒有idle狀態,所以利用率很高。而從線程角度講,由於線程的相互切換,內存訪問的延遲就被隱藏起來了。

     訪問全局的內存的cycle的數量級在200左右,而訪問shared memory或者register就會在幾個cycle內搞定。這個差距是非常大的。所以爲了能夠有效的隱藏全局訪問的延遲,我們需要讓計算更密集。就是說在儘可能少訪問內存的情況下,多進行計算操作。另外還要有足夠的線程數量,上面的例子是個最簡單的例子,訪問全局的延遲遠遠要大於四個指令讀取的命令。一定要儘可能的保證線程數量的足夠,這樣才能最好的利用GPU的硬件特性。

     當然,這裏絕對不是說爲了更好的利用硬件,要增加一些無關的計算,以及一些無用的線程。線程當然是越少越快,但是如果少到一定的程度,甚至比stream core的數量還要少,GPU的利用率是非常低的。

     OK。就介紹這麼多吧。希望能夠對於想學習Stream computing的朋友有一點點的幫助。

發佈了25 篇原創文章 · 獲贊 7 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章