CUDA是一種新的操作GPU計(jì)算的硬件和軟件架構(gòu),它將GPU視作一個數(shù)據(jù)并行計(jì)算設(shè)備,而且無需把這些計(jì)算映射到圖形API。
1. 硬件架構(gòu)
1.1 GPU困境
雖然GPU通過圖形應(yīng)用程序的算法存在如下幾個特征:算法密集、高度并行、控制簡單、分多個階段執(zhí)行以及前饋(Feed
Forward)流水線等,能夠在高度密集型的并行計(jì)算上獲得較高的性能和速度,但在2007年以前GPU要實(shí)現(xiàn)這樣的應(yīng)用還是存在許多困難的:
- GPU只能通過一個圖形的API來編程,這不僅加重了學(xué)習(xí)負(fù)擔(dān)更造成那些非圖像應(yīng)用程序處理這些 API 的額外開銷。
- 由于DRAM內(nèi)存帶寬,一些程序會遇到瓶頸。
- 無法在 DRAM 上進(jìn)行通用寫操作。
所以NVIDIA于2006年11月在G80系列中引入的Tesla統(tǒng)一圖形和計(jì)算架構(gòu)擴(kuò)展了GPU,使其超越了圖形領(lǐng)域。通過擴(kuò)展處理器和存儲器分區(qū)的數(shù)量,其強(qiáng)大的多線程處理器陣列已經(jīng)成為高效的統(tǒng)一計(jì)算平臺,同時適用于圖形和通用并行計(jì)算應(yīng)用程序。從G80系列開始NVIDIA加入了對CUDA的支持。
1.2 芯片結(jié)構(gòu)
具有Tesla架構(gòu)的GPU是具有芯片共享存儲器的一組SIMT(單指令多線程)多處理器。它以一個可伸縮的多線程流處理器(Streaming Multiprocessors,SMs)陣列為中心實(shí)現(xiàn)了MIMD(多指令多數(shù)據(jù))的異步并行機(jī)制,其中每個多處理器包含多個標(biāo)量處理器(Scalar Processor,SP),為了管理運(yùn)行各種不同程序的數(shù)百個線程,SIMT架構(gòu)的多處理器會將各線程映射到一個標(biāo)量處理器核心,各標(biāo)量線程使用自己的指令地址和寄存器狀態(tài)獨(dú)立執(zhí)行。
如上圖所示,每個多處理器(Multiprocessor)都有一個屬于以下四種類型之一的芯片存儲器:
- 每個處理器上有一組本地 32 位寄存器(Registers);
- 并行數(shù)據(jù)緩存或共享存儲器(Shared Memory),由所有標(biāo)量處理器核心共享,共享存儲器空間就位于此處;
- 只讀固定緩存(Constant Cache),由所有標(biāo)量處理器核心共享,可加速從固定存儲器空間進(jìn)行的讀取操作(這是設(shè)備存儲器的一個只讀區(qū)域);
- 一個只讀紋理緩存(Texture Cache),由所有標(biāo)量處理器核心共享,加速從紋理存儲器空間進(jìn)行的讀取操作(這是設(shè)備存儲器的一個只讀區(qū)域),每個多處理器都會通過實(shí)現(xiàn)不同尋址模型和數(shù)據(jù)過濾的紋理單元訪問紋理緩存。
多處理器 SIMT單元以32個并行線程為一組來創(chuàng)建、管理、調(diào)度和執(zhí)行線程,這樣的線程組稱為 warp 塊(束),即以線程束為調(diào)度單位,但只有所有32個線程都在諸如內(nèi)存讀取這樣的操作時,它們就會被掛起,如圖7所示的狀態(tài)變化。當(dāng)主機(jī)CPU上的CUDA程序調(diào)用內(nèi)核網(wǎng)格時,網(wǎng)格的塊將被枚舉并分發(fā)到具有可用執(zhí)行容量的多處理器;SIMT單元會選擇一個已準(zhǔn)備好執(zhí)行的 warp 塊,并將下一條指令發(fā)送到該 warp塊的活動線程。一個線程塊的線程在一個多處理器上并發(fā)執(zhí)行,在線程塊終止時,將在空閑多處理器上啟動新塊。
CPU五種狀態(tài)的轉(zhuǎn)換.png
2. 軟件架構(gòu)
CUDA是一種新的操作GPU計(jì)算的硬件和軟件架構(gòu),它將GPU視作一個數(shù)據(jù)并行計(jì)算設(shè)備,而且無需把這些計(jì)算映射到圖形API。操作系統(tǒng)的多任務(wù)機(jī)制可以同時管理CUDA訪問GPU和圖形程序的運(yùn)行庫,其計(jì)算特性支持利用CUDA直觀地編寫GPU核心程序。目前Tesla架構(gòu)具有在筆記本電腦、臺式機(jī)、工作站和服務(wù)器上的廣泛可用性,配以C/C++語言的編程環(huán)境和CUDA軟件,使這種架構(gòu)得以成為最優(yōu)秀的超級計(jì)算平臺。
CUDA在軟件方面組成有:一個CUDA庫、一個應(yīng)用程序編程接口(API)及其運(yùn)行庫(Runtime)、兩個較高級別的通用數(shù)學(xué)庫,即CUFFT和CUBLAS。CUDA改進(jìn)了DRAM的讀寫靈活性,使得GPU與CPU的機(jī)制相吻合。另一方面,CUDA提供了片上(on-chip)共享內(nèi)存,使得線程之間可以共享數(shù)據(jù)。應(yīng)用程序可以利用共享內(nèi)存來減少DRAM的數(shù)據(jù)傳送,更少的依賴DRAM的內(nèi)存帶寬。
3. 編程模型
CUDA程序構(gòu)架分為兩部分:Host和Device。一般而言,Host指的是CPU,Device指的是GPU。在CUDA程序構(gòu)架中,主程序還是由CPU 來執(zhí)行,而當(dāng)遇到數(shù)據(jù)并行處理的部分,CUDA 就會將程序編譯成 GPU能執(zhí)行的程序,并傳送到GPU。而這個程序在CUDA里稱做核(kernel)。CUDA允許程序員定義稱為核的C語言函數(shù),從而擴(kuò)展了C語言,在調(diào)用此類函數(shù)時,它將由N個不同的CUDA線程并行執(zhí)行N次,這與普通的C語言函數(shù)只執(zhí)行一次的方式不同。執(zhí)行核的每個線程都會被分配一個獨(dú)特的線程ID,可通過內(nèi)置的threadIdx變量在內(nèi)核中訪問此ID。在 CUDA 程序中,主程序在調(diào)用任何 GPU內(nèi)核之前,必須對核進(jìn)行執(zhí)行配置,即確定線程塊數(shù)和每個線程塊中的線程數(shù)以及共享內(nèi)存大小。
3.1 線程層次結(jié)構(gòu)
在GPU中要執(zhí)行的線程,根據(jù)最有效的數(shù)據(jù)共享來創(chuàng)建塊(Block),其類型有一維、二維或三維。在同一個塊內(nèi)的線程可彼此協(xié)作,通過一些共享存儲器來共享數(shù)據(jù),并同步其執(zhí)行來協(xié)調(diào)存儲器訪問。一個塊中的所有線程都必須位于同一個處理器核心中。因而,一個處理器核心的有限存儲器資源制約了每個塊的線程數(shù)量。在早起的NVIDIA 架構(gòu)中,一個線程塊最多可以包含 512個線程,而在后期出現(xiàn)的一些設(shè)備中則最多可支持1024個線程。一般 GPGPU程序線程數(shù)目是很多的,所以不能把所有的線程都塞到同一個塊里。但一個內(nèi)核可由多個大小相同的線程塊同時執(zhí)行,因而線程總數(shù)應(yīng)等于每個塊的線程數(shù)乘以塊的數(shù)量。這些同樣維度和大小的塊將組織為一個一維或二維線程塊網(wǎng)格(Grid)。具體框架如圖9所示。

核函數(shù)只能在主機(jī)端調(diào)用,其調(diào)用形式為:Kernel<<<Dg,Db, Ns, S>>>(paramlist)
Dg:用于定義整個grid的維度和尺寸,即一個grid有多少個block。為dim3類型。Dim3
Dg(Dg.x, Dg.y,
1)表示grid中每行有Dg.x個block,每列有Dg.y個block,第三維恒為1(目前一個核函數(shù)只有一個grid)。整個grid中共有Dg.x*Dg.y個block,其中Dg.x和Dg.y最大值為65535。Db:用于定義一個block的維度和尺寸,即一個block有多少個thread。為dim3類型。Dim3
Db(Db.x, Db.y,
Db.z)表示整個block中每行有Db.x個thread,每列有Db.y個thread,高度為Db.z。Db.x和Db.y最大值為512,Db.z最大值為62。
一個block中共有Db.x*Db.y*Db.z個thread。計(jì)算能力為1.0,1.1的硬件該乘積的最大值為768,計(jì)算能力為1.2,1.3的硬件支持的最大值為1024。Ns:是一個可選參數(shù),用于設(shè)置每個block除了靜態(tài)分配的shared
Memory以外,最多能動態(tài)分配的shared
memory大小,單位為byte。不需要動態(tài)分配時該值為0或省略不寫。-
S:是一個cudaStream_t類型的可選參數(shù),初始值為零,表示該核函數(shù)處在哪個流之中。
如下是一個CUDA簡單的求和程序:
CUDA求和程序.png
3.2 存儲器層次結(jié)構(gòu)
CUDA設(shè)備擁有多個獨(dú)立的存儲空間,其中包括:全局存儲器、本地存儲器、共享存儲器、常量存儲器、紋理存儲器和寄存器,如圖
11所示。
CUDA線程可在執(zhí)行過程中訪問多個存儲器空間的數(shù)據(jù),如圖 12所示其中:
- 每個線程都有一個私有的本地存儲器。
- 每個線程塊都有一個共享存儲器,該存儲器對于塊內(nèi)的所有線程都是可見的,并且與塊具有相同的生命周期。
- 所有線程都可訪問相同的全局存儲器。
- 此外還有兩個只讀的存儲器空間,可由所有線程訪問,這兩個空間是常量存儲器空間和紋理存儲器空間。全局、固定和紋理存儲器空間經(jīng)過優(yōu)化,適于不同的存儲器用途。紋理存儲器也為某些特殊的數(shù)據(jù)格式提供了不同的尋址模式以及數(shù)據(jù)過濾,方便
Host對流數(shù)據(jù)的快速存取。
存儲器的應(yīng)用層次.png
3.3 主機(jī)(Host)和設(shè)備(Device)
如圖 13所示,CUDA假設(shè)線程可在物理上獨(dú)立的設(shè)備上執(zhí)行,此類設(shè)備作為運(yùn)行C語言程序的主機(jī)的協(xié)處理器操作。內(nèi)核在GPU上執(zhí)行,而C語言程序的其他部分在CPU上執(zhí)行(即串行代碼在主機(jī)上執(zhí)行,而并行代碼在設(shè)備上執(zhí)行)。此外,CUDA還假設(shè)主機(jī)和設(shè)備均維護(hù)自己的DRAM,分別稱為主機(jī)存儲器和設(shè)備存儲器。因而,一個程序通過調(diào)用CUDA運(yùn)行庫來管理對內(nèi)核可見的全局、固定和紋理存儲器空間。這種管理包括設(shè)備存儲器的分配和取消分配,還包括主機(jī)和設(shè)備存儲器之間的數(shù)據(jù)傳輸。
- CUDA軟硬件
4.1 CUDA術(shù)語
由于CUDA中存在許多概念和術(shù)語,諸如SM、block、SP等多個概念不容易理解,將其與CPU的一些概念進(jìn)行比較,如下表所示。
CPU | GPU | 層次 |
---|---|---|
算術(shù)邏輯和控制單元 | 流處理器(SM) | 硬件 |
算術(shù)單元 | 批量處理器(SP) | 硬件 |
進(jìn)程 | Block | 軟件 |
線程 | thread | 軟件 |
調(diào)度單位 | Warp | 軟件 |
4.2 硬件利用率
當(dāng)為一個GPU分配一個內(nèi)核函數(shù),我們關(guān)心的是如何才能充分利用GPU的計(jì)算能力,但由于不同的硬件有不同的計(jì)算能力,SM一次最多能容納的線程數(shù)也不盡相同,SM一次最多能容納的線程數(shù)量主要與底層硬件的計(jì)算能力有關(guān),如下表顯示了在不同的計(jì)算能力的設(shè)備上,每個線程塊上開啟不同數(shù)量的線程時設(shè)備的利用率。
計(jì)算能力 每個線 程塊的線程數(shù) | 1.0 | 1.1 | 1.2 | 1.3 | 2.0 | 2.1 | 3.0 |
---|---|---|---|---|---|---|---|
64 | 67 | 67 | 50 | 50 | 33 | 33 | 50 |
96 | 100 | 100 | 75 | 75 | 50 | 50 | 75 |
128 | 100 | 100 | 100 | 100 | 67 | 67 | 100 |
192 | 100 | 100 | 94 | 94 | 100 | 100 | 94 |
256 | 100 | 100 | 100 | 100 | 100 | 100 | 100 |
…… | …… |