先碎碎念一下…以目前來講,CUDA 還算是一個很新的技術,在網路上可以找到的教學文件,相對也就很少。目前 nVidia 官方的文件,也只有一份 Programming Guide 和 SDK 裡附的 Sample,連份 tutorial 都沒有,對於初學者來說,要上手其實還滿麻煩的。在 nVidia 的論壇中,其實有比較多的資料,但是畢竟是以論壇的形式存在的,沒有整理過,要找資料可以,要拿來學習並不是很容易。
Heresy 對於 CUDA 也是剛開始玩,也只能參考 Programming Guide 和 sample code 來摸索;希望以後,能找到更多更好的教學文件可以做參考。
GPGPU
在近年來,顯示卡的運算單元(GPU)的速度越來越快,在某些方面的應用上,甚至已經大幅的超越 CPU 了!右圖就大致說明了目前 GPU 和 CPU 的速度演進∼很明顯的可以看出來,單就浮點數的計算來說,GPU 的成長速度已經算是 CPU 的數倍了!(電晶體數目也大增、耗電量也大增… @@)
再加上 shader 程式的出現、演進,GPU 的可控制性也大幅增加!因此,將 GPU 用在非傳統的 3D 圖形顯示方面的應用,也越來越多了…一般,會把這樣的應用叫做 GPGPU(General-purpose computing on graphics processing units);而相關的應用,不管是數量和方向,都相當多了!有興趣的人可以參考維基百科。
原則上,適用於 GPGPU 的問題,大多是用在可以把一個問題大量的拆解成多個相同、且彼此不相關的小問題的情況;在這種情況下,用 GPGPU 的方法,就可以把這些一樣的小問題,丟給顯示卡的 GPU 來大量平行化的處理,藉此達到加速的效果。
更明確的說,由於 GPU 的架構設計,GPU 最適合拿來做的計算是「資料平行化的計算(data-parallel computations)」;也就是不同的資料,要用同樣的程式來做計算。而在這種情況下,就可以透過把每一組資料的計算,都當成一個 thread 來計算,以此平行化來加速計算。其中,個別的計算量也要較高,如此才能避免平行話的負擔大於平行化的加速。
傳統的 GPGPU 的開發方法,都是透過 OpenGL 或 Direct3D 這一類現有的圖形函式庫,以編寫 shading language 的方法,控制 shader 來想辦法做到自己想要的計算;不過這樣的缺點,就是必須要遵循著 Redner 的固定流程來進行。
CUDA
而 nVidia 所提出的 CUDA(Compute Unified Device Architecture) 也是一種 GPGPU 的技術;不過,透過 CUDA 來進行 GPGPU 的程式開發,是透過他的 C 語言的函式庫和一些 CUDA 的延伸來編寫,因此不用用到 OpenGL 或 Direct3D。以 nVidia 的官方說法來說,是說入門的門檻會相對的降低;此外也因為不用使用圖形函式庫,而不會被傳統的 render pipeline 綁住使得在程式設計上更方便。
CUDA 的架構圖大概如下:
其中,CUDA 大概分為 Library、runtime、Driver 三個部分;而我們在開發程式的時候,可以透過這三個部分,來使用 GPU 的計算能力。
而由於 CUDA 是個新的技術,目前只能用在 nVidia G80 核心的顯示卡上,也就是 GeForce 8 系列,以及最新的 Quadro FX 了∼不過,nVidia 也承諾現在用 CUDA 寫的程式,在將來新的顯示卡上也會可以正常運作。
Programming Model
在 CUDA 的程式架構裡,程式執行的區域會分成兩部分:
- Host
- Device
其中,「host」指的就是 CPU,而「device」就是 GPU 了∼
在 CUDA 的程式架構中,主程式還是由 CPU 來執行;而當遇到了資料平行化處理的部分,就會將要在 GPU 跑的程式編譯成 device 能執行的程式,再丟給 device 執行了。而這個程式在 CUDA 裡把他叫做「kernel」。
而實際在運作時,CUDA 會產生許多在 device 上執行的 thread,每一個 thread 都會去執行 kernel 這個程式;雖然程式都是同一份,但是會因為其 index 的不同,而取得不同的資料來計算。
在 device 中要執行的 thread 中,要根據「最有效率的資料共用」來建置 thread 的 block;其中,thread block 的型式可以是一維、二維或三維的。而在同一個 block 裡的 thread,有部分的記憶體(shared memory)是共用的;所以在校能的調整上,可能須要考慮到這一點。
而由於 thread block 的最大大小是有限制的,所以不能把所有的 thread 都塞到同一個 block 裡(一般的 GPGPU 程式 thread 數目都會很多);這時候,可以用同樣維度和大小的 thread block(當然,也要是同一個 kernel),來組成一個 grid 做批次處理。這個 grid 可以是一維或二維陣列的形式。
所以,實際上 CUDA 在執行一段 kernel 程式的時候,必須要指定他的 grid 和 block 的相關資訊(維度和大小);而會產生的 thread 數目,就會由這兩者的資訊來決定。
Memory Model
在 CUDA 中,要讓 thread 可以使用的變數,都必須要先把資料放置到 device 的記憶體裡;而 device 的記憶體,又分為 DRAM 和 chip 上的記憶體兩種。在實際使用上,分成下面幾種記憶體的類型:
- registers
Read-write per-thread- local memory
Read-write per-thread- shared memory
Read-write per-block- global memory
Read-write per-grid- constant memory
Read-only per-grid- texture memory
Read-only per-grid
可以發現,registers 和 local memory 是以 thread 為單位來使用的;而 shared memory 則是存在於 block,讓每一個 thread 共用。上面這三種,都是在 chip 上的記憶體,相較之下,速度會比屬於 DRAM 的記憶體來的快。
Grid 中的 global, constant, texture 這三種 memory 可以讓不同的 block 中的 thread 一起使用。此外,這三種記憶體則是屬於 DRAM 上的記憶體,可以在同一個程式的不同 kernel 中,持續的存在、使用;而他們之間的差異,在於最佳化的方式不同。像 constant 和 texture 因為對於 thread 是唯獨的,所以實際上會有快取的機制,可以用來加速讀取。
上面的文章大致對 CUDA 的架構和程式的基本概念做了一些說明,內容大概對映到 nVidia 的《CUDA Programming Guide 1.0》(PDF 文件)的第一章和第二章,不過省略了不少東西;而本文的圖片也都取自於該文件。第三張硬體實作的部分應該也會跳過,接下來就開始寫 CUDA 程式了!
參考文件:
- nVidia CUDA Homepage
- CUDA Programming Guide 1.0
- CUDA @ Wikipedia
原始發表於:nVidia CUDA 簡介