開篇一張圖,后面聽我編
1. 知識(shí)準(zhǔn)備
1.1 中央處理器(CPU)
中央處理器(CPU,Central Processing Unit)是一塊超大規(guī)模的集成電路,是一臺(tái)計(jì)算機(jī)的運(yùn)算核心(Core)和控制核心( Control Unit)。它的功能主要是解釋計(jì)算機(jī)指令以及處理計(jì)算機(jī)軟件中的數(shù)據(jù)。
中央處理器主要包括運(yùn)算器(算術(shù)邏輯運(yùn)算單元,ALU,Arithmetic Logic Unit)和高速緩沖存儲(chǔ)器(Cache)及實(shí)現(xiàn)它們之間聯(lián)系的數(shù)據(jù)(Data)、控制及狀態(tài)的總線(Bus)。它與內(nèi)部存儲(chǔ)器(Memory)和輸入/輸出(I/O)設(shè)備合稱為電子計(jì)算機(jī)三大核心部件。
CPU的結(jié)構(gòu)主要包括運(yùn)算器
(ALU, Arithmetic and Logic Unit)、控制單元
(CU, Control Unit)、寄存器
(Register)、高速緩存器
(Cache)和它們之間通訊的數(shù)據(jù)
、控制及狀態(tài)的總線
。
簡單來說就是:計(jì)算單元、控制單元和存儲(chǔ)單元,架構(gòu)如下圖所示:
什么?架構(gòu)記不住?來,我們換種表示方法:
嗯,大概就是這個(gè)意思。
從字面上我們也很好理解,計(jì)算單元主要執(zhí)行算術(shù)運(yùn)算、移位等操作以及地址運(yùn)算和轉(zhuǎn)換;存儲(chǔ)單元主要用于保存運(yùn)算中產(chǎn)生的數(shù)據(jù)以及指令等;控制單元?jiǎng)t對(duì)指令譯碼,并且發(fā)出為完成每條指令所要執(zhí)行的各個(gè)操作的控制信號(hào)。
所以一條指令在CPU中執(zhí)行的過程是這樣的:讀取到指令后,通過指令總線送到控制器(黃色區(qū)域)中進(jìn)行譯碼,并發(fā)出相應(yīng)的操作控制信號(hào);然后運(yùn)算器(綠色區(qū)域)按照操作指令對(duì)數(shù)據(jù)進(jìn)行計(jì)算,并通過數(shù)據(jù)總線將得到的數(shù)據(jù)存入數(shù)據(jù)緩存器(大塊橙色區(qū)域)。過程如下圖所示:
是不是有點(diǎn)兒復(fù)雜?沒關(guān)系,這張圖完全不用記住,我們只需要知道,CPU遵循的是馮諾依曼架構(gòu),其核心就是:存儲(chǔ)程序,順序執(zhí)行。
講到這里,有沒有看出問題,沒錯(cuò)——在這個(gè)結(jié)構(gòu)圖中,負(fù)責(zé)計(jì)算的綠色區(qū)域占的面積似乎太小了,而橙色區(qū)域的緩存Cache和黃色區(qū)域的控制單元占據(jù)了大量空間。
高中化學(xué)有句老生常談的話叫:結(jié)構(gòu)決定性質(zhì),放在這里也非常適用。
因?yàn)镃PU的架構(gòu)中需要大量的空間去放置存儲(chǔ)單元(橙色部分)和控制單元(黃色部分),相比之下計(jì)算單元(綠色部分)只占據(jù)了很小的一部分,所以它在大規(guī)模并行計(jì)算能力上極受限制,而更擅長于邏輯控制。
另外,因?yàn)樽裱T諾依曼架構(gòu)(存儲(chǔ)程序,順序執(zhí)行),CPU就像是個(gè)一板一眼的管家,人們吩咐的事情它總是一步一步來做。但是隨著人們對(duì)更大規(guī)模與更快處理速度的需求的增加,這位管家漸漸變得有些力不從心。
于是,大家就想,能不能把多個(gè)處理器放在同一塊芯片上,讓它們一起來做事,這樣效率不就提高了嗎?
沒錯(cuò),GPU便由此誕生了。
1.2 顯卡
顯卡(Video card,Graphics card)全稱顯示接口卡,又稱顯示適配器,是計(jì)算機(jī)最基本配置、最重要的配件之一。顯卡作為電腦主機(jī)里的一個(gè)重要組成部分,是電腦進(jìn)行數(shù)模信號(hào)轉(zhuǎn)換的設(shè)備,承擔(dān)輸出顯示圖形的任務(wù)。顯卡接在電腦主板上,它將電腦的數(shù)字信號(hào)轉(zhuǎn)換成模擬信號(hào)讓顯示器顯示出來,同時(shí)顯卡還是有圖像處理能力,可協(xié)助CPU工作,提高整體的運(yùn)行速度。對(duì)于從事專業(yè)圖形設(shè)計(jì)的人來說顯卡非常重要。 民用和軍用顯卡圖形芯片供應(yīng)商主要包括AMD(超微半導(dǎo)體)和Nvidia(英偉達(dá))2家。現(xiàn)在的top500計(jì)算機(jī),都包含顯卡計(jì)算核心。在科學(xué)計(jì)算中,顯卡被稱為顯示加速卡。
為什么GPU特別擅長處理圖像數(shù)據(jù)呢?這是因?yàn)閳D像上的每一個(gè)像素點(diǎn)都有被處理的需要,而且每個(gè)像素點(diǎn)處理的過程和方式都十分相似,也就成了GPU的天然溫床。
從架構(gòu)圖我們就能很明顯的看出,GPU的構(gòu)成相對(duì)簡單,有數(shù)量眾多的計(jì)算單元和超長的流水線,特別適合處理大量的類型統(tǒng)一的數(shù)據(jù)。
再把CPU和GPU兩者放在一張圖上看下對(duì)比,就非常一目了然了。
GPU的工作大部分都計(jì)算量大,但沒什么技術(shù)含量,而且要重復(fù)很多很多次。
但GPU無法單獨(dú)工作,必須由CPU進(jìn)行控制調(diào)用才能工作。CPU可單獨(dú)作用,處理復(fù)雜的邏輯運(yùn)算和不同的數(shù)據(jù)類型,但當(dāng)需要大量的處理類型統(tǒng)一的數(shù)據(jù)時(shí),則可調(diào)用GPU進(jìn)行并行計(jì)算。
借用知乎上某大佬的說法,就像你有個(gè)工作需要計(jì)算幾億次一百以內(nèi)加減乘除一樣,最好的辦法就是雇上幾十個(gè)小學(xué)生一起算,一人算一部分,反正這些計(jì)算也沒什么技術(shù)含量,純粹體力活而已;而CPU就像老教授,積分微分都會(huì)算,就是工資高,一個(gè)老教授資頂二十個(gè)小學(xué)生,你要是富士康你雇哪個(gè)?
注:GPU中有很多的運(yùn)算器ALU和很少的緩存cache,緩存的目的不是保存后面需要訪問的數(shù)據(jù)的,這點(diǎn)和CPU不同,而是為線程thread提高服務(wù)的。如果有很多線程需要訪問同一個(gè)相同的數(shù)據(jù),緩存會(huì)合并這些訪問,然后再去訪問dram。
可愛的你如果對(duì)CUDA硬件有更多的興趣,可移步NVIDIA中文官網(wǎng)進(jìn)一步學(xué)習(xí)。
1.3 內(nèi)存
內(nèi)存是計(jì)算機(jī)中重要的部件之一,它是與CPU進(jìn)行溝通的橋梁。計(jì)算機(jī)中所有程序的運(yùn)行都是在內(nèi)存中進(jìn)行的,因此內(nèi)存的性能對(duì)計(jì)算機(jī)的影響非常大。內(nèi)存(Memory)也被稱為內(nèi)存儲(chǔ)器,其作用是用于暫時(shí)存放CPU中的運(yùn)算數(shù)據(jù),以及與硬盤等外部存儲(chǔ)器交換的數(shù)據(jù)。只要計(jì)算機(jī)在運(yùn)行中,CPU就會(huì)把需要運(yùn)算的數(shù)據(jù)調(diào)到內(nèi)存中進(jìn)行運(yùn)算,當(dāng)運(yùn)算完成后CPU再將結(jié)果傳送出來,內(nèi)存的運(yùn)行也決定了計(jì)算機(jī)的穩(wěn)定運(yùn)行。 內(nèi)存是由內(nèi)存芯片、電路板、金手指等部分組成的。
1.4 顯存
顯存,也被叫做幀緩存,它的作用是用來存儲(chǔ)顯卡芯片處理過或者即將提取的渲染數(shù)據(jù)。如同計(jì)算機(jī)的內(nèi)存一樣,顯存是用來存儲(chǔ)要處理的圖形信息的部件。
1.5 顯卡、顯卡驅(qū)動(dòng)、CUDA之間的關(guān)系
顯卡:(GPU)主流是NVIDIA的GPU,深度學(xué)習(xí)本身需要大量計(jì)算。GPU的并行計(jì)算能力,在過去幾年里恰當(dāng)?shù)貪M足了深度學(xué)習(xí)的需求。AMD的GPU基本沒有什么支持,可以不用考慮。
驅(qū)動(dòng):沒有顯卡驅(qū)動(dòng),就不能識(shí)別GPU硬件,不能調(diào)用其計(jì)算資源。但是呢,NVIDIA在Linux上的驅(qū)動(dòng)安裝特別麻煩,尤其對(duì)于新手簡直就是噩夢。得屏蔽第三方顯卡驅(qū)動(dòng)。下面會(huì)給出教程。
CUDA:是NVIDIA推出的只能用于自家GPU的并行計(jì)算框架。只有安裝這個(gè)框架才能夠進(jìn)行復(fù)雜的并行計(jì)算。主流的深度學(xué)習(xí)框架也都是基于CUDA進(jìn)行GPU并行加速的,幾乎無一例外。還有一個(gè)叫做cudnn,是針對(duì)深度卷積神經(jīng)網(wǎng)絡(luò)的加速庫。
查看顯卡驅(qū)動(dòng)信息(以實(shí)驗(yàn)室服務(wù)器為例)
ssh ubuntu@192.168.1.158
輸入服務(wù)器密碼登陸
然后,進(jìn)入cuda
cd /usr/local/cuda-8.0/samples/1_Utilities/deviceQuery
運(yùn)行其中的可執(zhí)行文件
./deviceQuery
得到如下信息
./deviceQuery Starting...
CUDA Device Query (Runtime API) version (CUDART static linking)
Detected 4 CUDA Capable device(s)
Device 0: "GeForce GTX 1080 Ti"
CUDA Driver Version / Runtime Version 9.0 / 8.0
CUDA Capability Major/Minor version number: 6.1
Total amount of global memory: 11171 MBytes (11713708032 bytes)
(28) Multiprocessors, (128) CUDA Cores/MP: 3584 CUDA Cores
GPU Max Clock rate: 1620 MHz (1.62 GHz)
Memory Clock rate: 5505 Mhz
Memory Bus Width: 352-bit
L2 Cache Size: 2883584 bytes
Maximum Texture Dimension Size (x,y,z) 1D=(131072), 2D=(131072, 65536), 3D=(16384, 16384, 16384)
Maximum Layered 1D Texture Size, (num) layers 1D=(32768), 2048 layers
Maximum Layered 2D Texture Size, (num) layers 2D=(32768, 32768), 2048 layers
Total amount of constant memory: 65536 bytes
Total amount of shared memory per block: 49152 bytes
Total number of registers available per block: 65536
Warp size: 32
Maximum number of threads per multiprocessor: 2048
Maximum number of threads per block: 1024
Max dimension size of a thread block (x,y,z): (1024, 1024, 64)
Max dimension size of a grid size (x,y,z): (2147483647, 65535, 65535)
Maximum memory pitch: 2147483647 bytes
Texture alignment: 512 bytes
Concurrent copy and kernel execution: Yes with 2 copy engine(s)
Run time limit on kernels: No
Integrated GPU sharing Host Memory: No
Support host page-locked memory mapping: Yes
Alignment requirement for Surfaces: Yes
Device has ECC support: Disabled
Device supports Unified Addressing (UVA): Yes
Device PCI Domain ID / Bus ID / location ID: 0 / 2 / 0
Compute Mode:
< Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) >
Device 1: "GeForce GTX 1080 Ti"
CUDA Driver Version / Runtime Version 9.0 / 8.0
CUDA Capability Major/Minor version number: 6.1
Total amount of global memory: 11172 MBytes (11715084288 bytes)
(28) Multiprocessors, (128) CUDA Cores/MP: 3584 CUDA Cores
GPU Max Clock rate: 1620 MHz (1.62 GHz)
Memory Clock rate: 5505 Mhz
Memory Bus Width: 352-bit
L2 Cache Size: 2883584 bytes
Maximum Texture Dimension Size (x,y,z) 1D=(131072), 2D=(131072, 65536), 3D=(16384, 16384, 16384)
Maximum Layered 1D Texture Size, (num) layers 1D=(32768), 2048 layers
Maximum Layered 2D Texture Size, (num) layers 2D=(32768, 32768), 2048 layers
Total amount of constant memory: 65536 bytes
Total amount of shared memory per block: 49152 bytes
Total number of registers available per block: 65536
Warp size: 32
Maximum number of threads per multiprocessor: 2048
Maximum number of threads per block: 1024
Max dimension size of a thread block (x,y,z): (1024, 1024, 64)
Max dimension size of a grid size (x,y,z): (2147483647, 65535, 65535)
Maximum memory pitch: 2147483647 bytes
Texture alignment: 512 bytes
Concurrent copy and kernel execution: Yes with 2 copy engine(s)
Run time limit on kernels: No
Integrated GPU sharing Host Memory: No
Support host page-locked memory mapping: Yes
Alignment requirement for Surfaces: Yes
Device has ECC support: Disabled
Device supports Unified Addressing (UVA): Yes
Device PCI Domain ID / Bus ID / location ID: 0 / 3 / 0
Compute Mode:
< Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) >
Device 2: "GeForce GTX 1080 Ti"
CUDA Driver Version / Runtime Version 9.0 / 8.0
CUDA Capability Major/Minor version number: 6.1
Total amount of global memory: 11172 MBytes (11715084288 bytes)
(28) Multiprocessors, (128) CUDA Cores/MP: 3584 CUDA Cores
GPU Max Clock rate: 1620 MHz (1.62 GHz)
Memory Clock rate: 5505 Mhz
Memory Bus Width: 352-bit
L2 Cache Size: 2883584 bytes
Maximum Texture Dimension Size (x,y,z) 1D=(131072), 2D=(131072, 65536), 3D=(16384, 16384, 16384)
Maximum Layered 1D Texture Size, (num) layers 1D=(32768), 2048 layers
Maximum Layered 2D Texture Size, (num) layers 2D=(32768, 32768), 2048 layers
Total amount of constant memory: 65536 bytes
Total amount of shared memory per block: 49152 bytes
Total number of registers available per block: 65536
Warp size: 32
Maximum number of threads per multiprocessor: 2048
Maximum number of threads per block: 1024
Max dimension size of a thread block (x,y,z): (1024, 1024, 64)
Max dimension size of a grid size (x,y,z): (2147483647, 65535, 65535)
Maximum memory pitch: 2147483647 bytes
Texture alignment: 512 bytes
Concurrent copy and kernel execution: Yes with 2 copy engine(s)
Run time limit on kernels: No
Integrated GPU sharing Host Memory: No
Support host page-locked memory mapping: Yes
Alignment requirement for Surfaces: Yes
Device has ECC support: Disabled
Device supports Unified Addressing (UVA): Yes
Device PCI Domain ID / Bus ID / location ID: 0 / 130 / 0
Compute Mode:
< Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) >
Device 3: "GeForce GTX 1080 Ti"
CUDA Driver Version / Runtime Version 9.0 / 8.0
CUDA Capability Major/Minor version number: 6.1
Total amount of global memory: 11172 MBytes (11715084288 bytes)
(28) Multiprocessors, (128) CUDA Cores/MP: 3584 CUDA Cores
GPU Max Clock rate: 1620 MHz (1.62 GHz)
Memory Clock rate: 5505 Mhz
Memory Bus Width: 352-bit
L2 Cache Size: 2883584 bytes
Maximum Texture Dimension Size (x,y,z) 1D=(131072), 2D=(131072, 65536), 3D=(16384, 16384, 16384)
Maximum Layered 1D Texture Size, (num) layers 1D=(32768), 2048 layers
Maximum Layered 2D Texture Size, (num) layers 2D=(32768, 32768), 2048 layers
Total amount of constant memory: 65536 bytes
Total amount of shared memory per block: 49152 bytes
Total number of registers available per block: 65536
Warp size: 32
Maximum number of threads per multiprocessor: 2048
Maximum number of threads per block: 1024
Max dimension size of a thread block (x,y,z): (1024, 1024, 64)
Max dimension size of a grid size (x,y,z): (2147483647, 65535, 65535)
Maximum memory pitch: 2147483647 bytes
Texture alignment: 512 bytes
Concurrent copy and kernel execution: Yes with 2 copy engine(s)
Run time limit on kernels: No
Integrated GPU sharing Host Memory: No
Support host page-locked memory mapping: Yes
Alignment requirement for Surfaces: Yes
Device has ECC support: Disabled
Device supports Unified Addressing (UVA): Yes
Device PCI Domain ID / Bus ID / location ID: 0 / 131 / 0
Compute Mode:
< Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) >
> Peer access from GeForce GTX 1080 Ti (GPU0) -> GeForce GTX 1080 Ti (GPU1) : Yes
> Peer access from GeForce GTX 1080 Ti (GPU0) -> GeForce GTX 1080 Ti (GPU2) : No
> Peer access from GeForce GTX 1080 Ti (GPU0) -> GeForce GTX 1080 Ti (GPU3) : No
> Peer access from GeForce GTX 1080 Ti (GPU1) -> GeForce GTX 1080 Ti (GPU0) : Yes
> Peer access from GeForce GTX 1080 Ti (GPU1) -> GeForce GTX 1080 Ti (GPU2) : No
> Peer access from GeForce GTX 1080 Ti (GPU1) -> GeForce GTX 1080 Ti (GPU3) : No
> Peer access from GeForce GTX 1080 Ti (GPU2) -> GeForce GTX 1080 Ti (GPU0) : No
> Peer access from GeForce GTX 1080 Ti (GPU2) -> GeForce GTX 1080 Ti (GPU1) : No
> Peer access from GeForce GTX 1080 Ti (GPU2) -> GeForce GTX 1080 Ti (GPU3) : Yes
> Peer access from GeForce GTX 1080 Ti (GPU3) -> GeForce GTX 1080 Ti (GPU0) : No
> Peer access from GeForce GTX 1080 Ti (GPU3) -> GeForce GTX 1080 Ti (GPU1) : No
> Peer access from GeForce GTX 1080 Ti (GPU3) -> GeForce GTX 1080 Ti (GPU2) : Yes
deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 9.0, CUDA Runtime Version = 8.0, NumDevs = 4, Device0 = GeForce GTX 1080 Ti, Device1 = GeForce GTX 1080 Ti, Device2 = GeForce GTX 1080 Ti, Device3 = GeForce GTX 1080 Ti
Result = PASS
大家可以在自己PC或者工作機(jī)上嘗試一下。
再啰嗦兩句
GPU就是用很多簡單的計(jì)算單元去完成大量的計(jì)算任務(wù),純粹的人海戰(zhàn)術(shù)。這種策略基于一個(gè)前提,就是小學(xué)生A和小學(xué)生B的工作沒有什么依賴性,是互相獨(dú)立的。
但有一點(diǎn)需要強(qiáng)調(diào),雖然GPU是為了圖像處理而生的,但是我們通過前面的介紹可以發(fā)現(xiàn),它在結(jié)構(gòu)上并沒有專門為圖像服務(wù)的部件,只是對(duì)CPU的結(jié)構(gòu)進(jìn)行了優(yōu)化與調(diào)整,所以現(xiàn)在GPU不僅可以在圖像處理領(lǐng)域大顯身手,它還被用來科學(xué)計(jì)算、密碼破解、數(shù)值分析,海量數(shù)據(jù)處理(排序,Map-Reduce等),金融分析等需要大規(guī)模并行計(jì)算的領(lǐng)域。
所以GPU也可以認(rèn)為是一種較通用的芯片。
2. CUDA軟件構(gòu)架
CUDA是一種新的操作GPU計(jì)算的硬件和軟件架構(gòu),它將GPU視作一個(gè)數(shù)據(jù)并行計(jì)算設(shè)備,而且無需把這些計(jì)算映射到圖形API。操作系統(tǒng)的多任務(wù)機(jī)制可以同時(shí)管理CUDA訪問GPU和圖形程序的運(yùn)行庫,其計(jì)算特性支持利用CUDA直觀地編寫GPU核心程序。目前Tesla架構(gòu)具有在筆記本電腦、臺(tái)式機(jī)、工作站和服務(wù)器上的廣泛可用性,配以C/C++語言的編程環(huán)境和CUDA軟件,使這種架構(gòu)得以成為最優(yōu)秀的超級(jí)計(jì)算平臺(tái)。
CUDA在軟件方面組成有:一個(gè)CUDA庫、一個(gè)應(yīng)用程序編程接口(API)及其運(yùn)行庫(Runtime)、兩個(gè)較高級(jí)別的通用數(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
就會(huì)將程序編譯成GPU
能執(zhí)行的程序,并傳送到GPU
。而這個(gè)程序在CUDA
里稱做核
(kernel)。CUDA
允許程序員定義稱為核的C語言函數(shù),從而擴(kuò)展了C語言,在調(diào)用此類函數(shù)時(shí),它將由N個(gè)不同的CUDA
線程并行執(zhí)行N次,這與普通的C語言函數(shù)只執(zhí)行一次的方式不同。執(zhí)行核的每個(gè)線程都會(huì)被分配一個(gè)獨(dú)特的線程ID,可通過內(nèi)置的threadIdx變量
在內(nèi)核中訪問此ID。在 CUDA
程序中,主程序在調(diào)用任何GPU
內(nèi)核之前,必須對(duì)核進(jìn)行執(zhí)行配置,即確定線程塊數(shù)和每個(gè)線程塊中的線程數(shù)以及共享內(nèi)存大小。
3.1 線程層次結(jié)構(gòu)
在GPU中要執(zhí)行的線程,根據(jù)最有效的數(shù)據(jù)共享來創(chuàng)建塊(Block),其類型有一維、二維或三維。在同一個(gè)塊內(nèi)的線程可彼此協(xié)作,通過一些共享存儲(chǔ)器來共享數(shù)據(jù),并同步其執(zhí)行來協(xié)調(diào)存儲(chǔ)器訪問。一個(gè)塊中的所有線程都必須位于同一個(gè)處理器核心中。因而,一個(gè)處理器核心的有限存儲(chǔ)器資源制約了每個(gè)塊的線程數(shù)量。在早期的NVIDIA 架構(gòu)中,一個(gè)線程塊最多可以包含 512個(gè)線程,而在后期出現(xiàn)的一些設(shè)備中則最多可支持1024個(gè)線程。一般GPGPU
程序線程數(shù)目是很多的,所以不能把所有的線程都塞到同一個(gè)塊里。但一個(gè)內(nèi)核可由多個(gè)大小相同的線程塊同時(shí)執(zhí)行,因而線程總數(shù)應(yīng)等于每個(gè)塊的線程數(shù)乘以塊的數(shù)量。這些同樣維度和大小的塊將組織為一個(gè)一維或二維線程塊網(wǎng)格(Grid)。具體框架如下圖所示。
NOTICE:
線程(Thread)
一般通過GPU的一個(gè)核進(jìn)行處理。(可以表示成一維,二維,三維,具體下面再細(xì)說)。
線程塊(Block)
- 由多個(gè)線程組成(可以表示成一維,二維,三維,具體下面再細(xì)說)。
- 各block是并行執(zhí)行的,block間無法通信,也沒有執(zhí)行順序。
- 注意線程塊的數(shù)量限制為不超過65535(硬件限制)。
線程格(Grid)
由多個(gè)線程塊組成(可以表示成一維,二維,三維,具體下面再細(xì)說)。
線程束
在CUDA架構(gòu)中,線程束是指一個(gè)包含32個(gè)線程的集合,這個(gè)線程集合被“編織在一起”并且“步調(diào)一致”的形式執(zhí)行。在程序中的每一行,線程束中的每個(gè)線程都將在不同數(shù)據(jù)上執(zhí)行相同的命令。
從硬件上看
SP:最基本的處理單元,streaming processor,也稱為CUDA core。最后具體的指令和任務(wù)都是在SP上處理的。GPU進(jìn)行并行計(jì)算,也就是很多個(gè)SP同時(shí)做處理。
SM:多個(gè)SP加上其他的一些資源組成一個(gè)streaming multiprocessor。也叫GPU大核,其他資源如:warp scheduler,register,shared memory等。SM可以看做GPU的心臟(對(duì)比CPU核心),register和shared memory是SM的稀缺資源。CUDA將這些資源分配給所有駐留在SM中的threads。因此,這些有限的資源就使每個(gè)SM中active warps有非常嚴(yán)格的限制,也就限制了并行能力。
從軟件上看
thread:一個(gè)CUDA的并行程序會(huì)被以許多個(gè)threads來執(zhí)行。
block:數(shù)個(gè)threads會(huì)被群組成一個(gè)block,同一個(gè)block中的threads可以同步,也可以通過shared memory通信。
grid:多個(gè)blocks則會(huì)再構(gòu)成grid。
warp:GPU執(zhí)行程序時(shí)的調(diào)度單位,目前cuda的warp的大小為32,同在一個(gè)warp的線程,以不同數(shù)據(jù)資源執(zhí)行相同的指令,這就是所謂 SIMT。
3.2 存儲(chǔ)器層次結(jié)構(gòu)
CUDA設(shè)備擁有多個(gè)獨(dú)立的存儲(chǔ)空間,其中包括:全局存儲(chǔ)器、本地存儲(chǔ)器、共享存儲(chǔ)器、常量存儲(chǔ)器、紋理存儲(chǔ)器和寄存器,如圖
NOTICE:
主機(jī)(Host)
將CPU及系統(tǒng)的內(nèi)存(內(nèi)存條)稱為主機(jī)。
設(shè)備(Device)
將GPU及GPU本身的顯示內(nèi)存稱為設(shè)備。
動(dòng)態(tài)隨機(jī)存取存儲(chǔ)器(DRAM)
DRAM(Dynamic Random Access Memory),即動(dòng)態(tài)隨機(jī)存取存儲(chǔ)器,最為常見的系統(tǒng)內(nèi)存
。DRAM
只能將數(shù)據(jù)保持很短的時(shí)間。為了保持?jǐn)?shù)據(jù),DRAM使用電容
存儲(chǔ),所以必須隔一段時(shí)間刷新(refresh)一次,如果存儲(chǔ)單元沒有被刷新,存儲(chǔ)的信息就會(huì)丟失。 (關(guān)機(jī)就會(huì)丟失數(shù)據(jù))
CUDA線程可在執(zhí)行過程中訪問多個(gè)存儲(chǔ)器空間的數(shù)據(jù),如下圖所示其中:
- 每個(gè)線程都有一個(gè)私有的本地存儲(chǔ)器。
- 每個(gè)線程塊都有一個(gè)共享存儲(chǔ)器,該存儲(chǔ)器對(duì)于塊內(nèi)的所有線程都是可見的,并且與塊具有相同的生命周期。
- 所有線程都可訪問相同的全局存儲(chǔ)器。
- 此外還有兩個(gè)只讀的存儲(chǔ)器空間,可由所有線程訪問,這兩個(gè)空間是常量存儲(chǔ)器空間和紋理存儲(chǔ)器空間。全局、固定和紋理存儲(chǔ)器空間經(jīng)過優(yōu)化,適于不同的存儲(chǔ)器用途。紋理存儲(chǔ)器也為某些特殊的數(shù)據(jù)格式提供了不同的尋址模式以及數(shù)據(jù)過濾,方便Host對(duì)流數(shù)據(jù)的快速存取。
3.3 主機(jī)(Host)和設(shè)備(Device)
如下圖所示,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ī)存儲(chǔ)器和設(shè)備存儲(chǔ)器。因而,一個(gè)程序通過調(diào)用CUDA運(yùn)行庫來管理對(duì)內(nèi)核可見的全局、固定和紋理存儲(chǔ)器空間。這種管理包括設(shè)備存儲(chǔ)器的分配和取消分配,還包括主機(jī)和設(shè)備存儲(chǔ)器之間的數(shù)據(jù)傳輸。
4. CUDA軟硬件
4.1 CUDA術(shù)語
由于CUDA中存在許多概念和術(shù)語,諸如SM、block、SP等多個(gè)概念不容易理解,將其與CPU的一些概念進(jìn)行比較,如下表所示。
CPU | GPU | 層次 |
---|---|---|
算術(shù)邏輯和控制單元 | 流處理器(SM) | 硬件 |
算術(shù)單元 | 批量處理器(SP) | 硬件 |
進(jìn)程 | Block | 軟件 |
線程 | thread | 軟件 |
調(diào)度單位 | Warp | 軟件 |
4.2 硬件利用率
當(dāng)為一個(gè)GPU分配一個(gè)內(nèi)核函數(shù),我們關(guān)心的是如何才能充分利用GPU的計(jì)算能力,但由于不同的硬件有不同的計(jì)算能力,SM一次最多能容納的線程數(shù)也不盡相同,SM一次最多能容納的線程數(shù)量主要與底層硬件的計(jì)算能力有關(guān),如下表顯示了在不同的計(jì)算能力的設(shè)備上,每個(gè)線程塊上開啟不同數(shù)量的線程時(shí)設(shè)備的利用率。
計(jì)算能力 每個(gè)線 程塊的線程數(shù) | 1.0 | 1.1 | 1.2 | 1.3 | 2.0 | 2.1 | 3.0 |
---|---|---|---|---|---|---|---|
64 | 67 | 50 | 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 |
96 | 100 | 100 | 100 | 100 | 100 | 100 | 100 |
··· | ··· |
查看顯卡利用率 (以實(shí)驗(yàn)室服務(wù)器為例)
輸入以下命令
nvidia-smi
Thu Aug 23 21:06:36 2018
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 384.130 Driver Version: 384.130 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|===============================+======================+======================|
| 0 GeForce GTX 108... Off | 00000000:02:00.0 Off | N/A |
| 29% 41C P0 58W / 250W | 0MiB / 11171MiB | 0% Default |
+-------------------------------+----------------------+----------------------+
| 1 GeForce GTX 108... Off | 00000000:03:00.0 Off | N/A |
| 33% 47C P0 57W / 250W | 0MiB / 11172MiB | 0% Default |
+-------------------------------+----------------------+----------------------+
| 2 GeForce GTX 108... Off | 00000000:82:00.0 Off | N/A |
| 36% 49C P0 59W / 250W | 0MiB / 11172MiB | 0% Default |
+-------------------------------+----------------------+----------------------+
| 3 GeForce GTX 108... Off | 00000000:83:00.0 Off | N/A |
| 33% 46C P0 51W / 250W | 0MiB / 11172MiB | 1% Default |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes: GPU Memory |
| GPU PID Type Process name Usage |
|=============================================================================|
| No running processes found |
+-----------------------------------------------------------------------------+
5. 并行計(jì)算
5.1 并發(fā)性
CUDA將問題分解成線程塊的網(wǎng)格,每塊包含多個(gè)線程。快可以按任意順序執(zhí)行。不過在某個(gè)時(shí)間點(diǎn)上,只有一部分塊處于執(zhí)行中。一旦被調(diào)用到GUP包含的N個(gè)“流處理器簇(SM)”中的一個(gè)上執(zhí)行,一個(gè)塊必須從開始到結(jié)束。網(wǎng)格中的塊可以被分配到任意一個(gè)有空閑槽的SM上。起初,可以采用“輪詢調(diào)度”策略,以確保分配到每一個(gè)SM上的塊數(shù)基本相同。對(duì)絕大多數(shù)內(nèi)核程序而言,分塊的數(shù)量應(yīng)該是GPU中物理SM數(shù)量的八倍或更多倍。
以一個(gè)軍隊(duì)比喻,假設(shè)有一支由士兵(線程)組成的部隊(duì)(網(wǎng)格)。部隊(duì)被分成若干個(gè)連(塊),每個(gè)連隊(duì)由一位連長來指揮。按照32名士兵一個(gè)班(一個(gè)線程束),連隊(duì)又進(jìn)一步分成若干個(gè)班,每個(gè)班由一個(gè)班長來指揮。
要執(zhí)行某個(gè)操作,總司令(內(nèi)核程序/ 主機(jī)程序)必須提供操作名稱及相應(yīng)的數(shù)據(jù)。每個(gè)士兵(線程)只處理分配給他的問題中的一小塊。在連長(負(fù)責(zé)一個(gè)塊)或班長(負(fù)責(zé)一個(gè)束)的控制下,束與束之間的線程或者一個(gè)束內(nèi)部的線程之間,要經(jīng)常地交換數(shù)據(jù)。但是,連隊(duì)(塊)之間的協(xié)同就得由總司令(內(nèi)核函數(shù)/ 主機(jī)程序)來控制。
5.2 局部性
對(duì)于GPU程序設(shè)計(jì),程序員必須處理局部性。對(duì)于一個(gè)給定的工作,他需要事先思考需要哪些工具或零件(即存儲(chǔ)地址或數(shù)據(jù)結(jié)構(gòu)),然后一次性地把他們從硬件倉庫(全局內(nèi)存)可能把與這些數(shù)據(jù)相關(guān)的不同工作都執(zhí)行了,避免發(fā)生“取來--存回--為了下一個(gè)工作再取”。
5.3 緩存一致性
GPU與CPU在緩存上的一個(gè)重要差別就是“緩存一致性”問題。對(duì)于“緩存一致”的系統(tǒng),一個(gè)內(nèi)存的寫操作需要通知所有核的各個(gè)級(jí)別的緩存。因此,無論何時(shí),所有的處理器核看到的內(nèi)存視圖是完全一樣的。隨著處理器中核數(shù)量的增多,這個(gè)“通知”的開銷迅速增大,使得“緩存一致性”成為限制一個(gè)處理器中核數(shù)量不能太多的一重要因素。“緩存一致”系統(tǒng)中最壞的情況是,一個(gè)內(nèi)存操作會(huì)強(qiáng)迫每個(gè)核的緩存都進(jìn)行更新,進(jìn)而每個(gè)核都要對(duì)相鄰的內(nèi)存單元寫操作。
相比之下,非“緩存一致”系統(tǒng)不會(huì)自動(dòng)地更新其他核的緩存。它需要由程序員寫清楚每個(gè)處理器核輸出的各自不同的目標(biāo)區(qū)域。從程序的視角看,這支持一個(gè)核僅負(fù)責(zé)一個(gè)輸出或者一個(gè)小的輸出集。通常,CPU遵循“緩存一致性”原則,而GPU則不是。故GPU能夠擴(kuò)展到一個(gè)芯片內(nèi)具有大數(shù)量的核心(流處理器簇)。
5.4 弗林分類法
根據(jù)弗林分類法,計(jì)算機(jī)的結(jié)構(gòu)類型有:
SIMD--單指令,多數(shù)據(jù)
MIMD--多指令,多數(shù)據(jù)
SISD--單指令,單數(shù)據(jù)
MISD--多指令,單數(shù)據(jù)
5.5 分條 / 分塊
CUDA提供的簡單二維網(wǎng)格模型。對(duì)于很多問題,這樣的模型就足夠了。如果在一個(gè)塊內(nèi),你的工作是線性分布的,那么你可以很好地將其他分解成CUDA塊。由于在一個(gè)SM內(nèi),最多可以分配16個(gè)塊,而在一個(gè)GPU內(nèi)有16個(gè)(有些是32個(gè))SM,所以問題分成256個(gè)甚至更多的塊都可以。實(shí)際上,我們更傾向于把一個(gè)塊內(nèi)的元素總數(shù)限制為128、256、或者512,這樣有助于在一個(gè)典型的數(shù)據(jù)集內(nèi)劃分出更多數(shù)量的塊。
5.6 快速傅氏變換(FFT)
FFT: FFT(Fast Fourier Transformation)是離散傅氏變換(DFT)的快速算法。即為快速傅氏變換。它是根據(jù)離散傅氏變換的奇、偶、虛、實(shí)等特性,對(duì)離散傅立葉變換的算法進(jìn)行改進(jìn)獲得的。
由于不是剛需,這里不展開講。好奇的你可以點(diǎn)擊樓下時(shí)光機(jī),通過下面的教程進(jìn)行學(xué)習(xí)。
FFT(最詳細(xì)最通俗的入門手冊)
5.7 CUDA計(jì)算能力的含義
體現(xiàn)GPU計(jì)算能力的兩個(gè)重要特征:
1)CUDA核的個(gè)數(shù);
2)存儲(chǔ)器大小。
描述GPU性能的兩個(gè)重要指標(biāo): :
1)計(jì)算性能峰值;
2)存儲(chǔ)器帶寬。
參考
1.CUDA計(jì)算能力的含義
2.CUDA GPUs
6. 實(shí)踐
6.1 Ubuntu 系統(tǒng)下環(huán)境搭建
6.1.1 系統(tǒng)要求
要搭建 CUDA 環(huán)境,我們需要自己的計(jì)算機(jī)滿足以下這三個(gè)條件:
1. 有至少一顆支持 CUDA 的 GPU(我的是GeForece GT 650M)
2. 有滿足版本要求的 gcc 編譯器和鏈接工具
3. 有 NVIDIA 提供的 CUDA 工具包(點(diǎn)擊神奇的小鏈接下載)
6.1.2 準(zhǔn)備工作
下面,我們一步一步來驗(yàn)證自己的系統(tǒng)是否滿足安裝要求。
Step 1: 驗(yàn)證計(jì)算機(jī)是否擁有至少一顆支持 CUDA 的 GPU
打開終端(Ctrl + Alt + T),鍵入以下命令:
lspci | grep -i nvidia
可以看到以下內(nèi)容(結(jié)果因人而異,與具體的GPU有關(guān))
看到這個(gè)就說明至少有一顆支持 CUDA 的 GPU,可以進(jìn)入下一步了。
Step 2: 驗(yàn)證一下自己操作系統(tǒng)的版本
鍵入命令:
lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 16.04.4 LTS
Release: 16.04
Codename: xenial
更多信息請(qǐng)移步Ubuntu查看版本信息
Step 3: 驗(yàn)證 gcc 編譯器的版本
鍵入命令:
gcc --version
或者
gcc -v
得到如下信息
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Step 4: 驗(yàn)證系統(tǒng)內(nèi)核版本
鍵入命令:
uname -r
得到如下信息
對(duì)照官方提供的對(duì)各種 Linux 發(fā)行版的安裝要求進(jìn)行安裝
6.1.3 搭建 CUDA 環(huán)境
Step 1: 安裝 CUDA 工具包
在前面幾項(xiàng)驗(yàn)證都順利通過以后就來到最關(guān)鍵的一步。首先下載對(duì)應(yīng)自己系統(tǒng)版本的 CUDA 工具包(以CUDA Toolkit 9.2 為例),然后進(jìn)入到安裝包所在目錄:
sudo dpkg -i cuda-repo-ubuntu1604-9-2-local_9.2.148-1_amd64.deb
sudo apt-key add /var/cuda-repo-<version>/7fa2af80.pub
sudo apt-get update
sudo apt-get install cuda
NOTICE:
Other installation options are available in the form of meta-packages. For example, to install all the library packages, replace "cuda" with the "cuda-libraries-9-2" meta package. For more information on all the available meta packages click here.
此時(shí)靜靜地等待安裝完成。不出意外,一段時(shí)間后安裝完成了。
Step 2: 設(shè)置環(huán)境變量
首先在 PATH 變量中加入 /usr/local/cuda-9.2/bin,在Terminal中執(zhí)行:
export PATH=/usr/local/cuda-9.2/bin:$PATH
然后在 LD_LIBRARY_PATH 變量中添加 /usr/local/cuda-9.2/lib64,執(zhí)行:
export LD_LIBRARY_PATH=/usr/local/cuda-9.2/lib64:$LD_LIBRARY_PATH
Step 3: 驗(yàn)證環(huán)境搭建是否成功
首先執(zhí)行命令:
nvcc -V
關(guān)于測試...聰明的你一定想起來了,我們前面是講過怎么做的。
對(duì),沒錯(cuò),就在1.5小節(jié),話不多說,自行上翻吧。
看到通過測試,到這里,64位 Ubuntu 16.04 系統(tǒng)下 CUDA 環(huán)境搭建就完成了。
6.2 CUDA編程
6.2.1 核函數(shù)
1. 在GPU上執(zhí)行的函數(shù)通常稱為核函數(shù)。
2. 一般通過標(biāo)識(shí)符__global__
修飾,調(diào)用通過<<<參數(shù)1,參數(shù)2>>>
,用于說明內(nèi)核函數(shù)中的線程數(shù)量,以及線程是如何組織的。
3. 以線程格(Grid)的形式組織,每個(gè)線程格由若干個(gè)線程塊(block)組成,而每個(gè)線程塊又由若干個(gè)線程(thread)組成。
4.是以block
為單位執(zhí)行的。
5. 叧能在主機(jī)端代碼中調(diào)用。
6. 調(diào)用時(shí)必須聲明內(nèi)核函數(shù)的執(zhí)行參數(shù)。
7. 在編程時(shí),必須先為kernel
函數(shù)中用到的數(shù)組或變量分配好足夠的空間,再調(diào)用kernel
函數(shù),否則在GPU計(jì)算時(shí)會(huì)發(fā)生錯(cuò)誤,例如越界或報(bào)錯(cuò),甚至導(dǎo)致藍(lán)屏和死機(jī)。
看完基本知識(shí),裝好CUDA以后,就可以開始寫第一個(gè)CUDA程序了:
#include <cuda_runtime.h>
int main(){
printf("Hello world!\n");
}
慢著,這個(gè)程序和C有什么區(qū)別?用到顯卡了嗎?
答:沒有區(qū)別,沒用顯卡。如果你非要用顯卡干點(diǎn)什么事情的話,可以改成這個(gè)樣子:
/*
* @file_name HelloWorld.cu 后綴名稱.cu
*/
#include <stdio.h>
#include <cuda_runtime.h> //頭文件
//核函數(shù)聲明,前面的關(guān)鍵字__global__
__global__ void kernel( void ) {
}
int main( void ) {
//核函數(shù)的調(diào)用,注意<<<1,1>>>,第一個(gè)1,代表線程格里只有一個(gè)線程塊;第二個(gè)1,代表一個(gè)線程塊里只有一個(gè)線程。
kernel<<<1,1>>>();
printf( "Hello, World!\n" );
return 0;
}
6.2.2 dim3結(jié)構(gòu)類型
- dim3是基于uint3定義的矢量類型,相當(dāng)亍由3個(gè)unsigned int型組成的結(jié)構(gòu)體。uint3類型有三個(gè)數(shù)據(jù)成員
unsigned int x
;unsigned int y
;unsigned int z
; - 可使用于一維、二維或三維的索引來標(biāo)識(shí)線程,構(gòu)成一維、二維或三維線程塊。
-
dim3
結(jié)構(gòu)類型變量用在核函數(shù)調(diào)用的<<<,>>>中。 - 相關(guān)的幾個(gè)內(nèi)置變量
4.1.threadIdx
,顧名思義獲取線程thread
的ID索引;如果線程是一維的那么就取threadIdx.x
,二維的還可以多取到一個(gè)值threadIdx.y
,以此類推到三維threadIdx.z
。
4.2.blockIdx
,線程塊的ID索引;同樣有blockIdx.x
,blockIdx.y
,blockIdx.z
。
4.3.blockDim
,線程塊的維度,同樣有blockDim.x
,blockDim.y
,blockDim.z
。
4.4.gridDim
,線程格的維度,同樣有gridDim.x
,gridDim.y
,gridDim.z
。 - 對(duì)于一維的
block
,線程的threadID=threadIdx.x
。 - 對(duì)于大小為
(blockDim.x, blockDim.y)
的 二維block
,線程的threadID=threadIdx.x+threadIdx.y*blockDim.x
。- 對(duì)于大小為
(blockDim.x, blockDim.y, blockDim.z)
的 三維block
,線程的threadID=threadIdx.x+threadIdx.y*blockDim.x+threadIdx.z*blockDim.x*blockDim.y
。 - 對(duì)于計(jì)算線程索引偏移增量為已啟動(dòng)線程的總數(shù)。如
stride = blockDim.x * gridDim.x; threadId += stride
。
- 對(duì)于大小為
6.2.3 函數(shù)修飾符
1.__global__
,表明被修飾的函數(shù)在設(shè)備上執(zhí)行,但在主機(jī)上調(diào)用。
-
__device__
,表明被修飾的函數(shù)在設(shè)備上執(zhí)行,但只能在其他__device__
函數(shù)或者__global__
函數(shù)中調(diào)用。
6.2.4 常用的GPU內(nèi)存函數(shù)
cudaMalloc()
1. 函數(shù)原型: cudaError_t cudaMalloc (void **devPtr, size_t size)
。
2. 函數(shù)用處:與C語言中的malloc
函數(shù)一樣,只是此函數(shù)在GPU的內(nèi)存你分配內(nèi)存。
3. 注意事項(xiàng):
3.1. 可以將cudaMalloc()
分配的指針傳遞給在設(shè)備上執(zhí)行的函數(shù);
3.2. 可以在設(shè)備代碼中使用cudaMalloc()
分配的指針進(jìn)行設(shè)備內(nèi)存讀寫操作;
3.3. 可以將cudaMalloc()
分配的指針傳遞給在主機(jī)上執(zhí)行的函數(shù);
3.4. 不可以在主機(jī)代碼中使用cudaMalloc()
分配的指針進(jìn)行主機(jī)內(nèi)存讀寫操作(即不能進(jìn)行解引用)。
cudaMemcpy()
1. 函數(shù)原型:cudaError_t cudaMemcpy (void *dst, const void *src, size_t count, cudaMemcpyKind kind)
。
2. 函數(shù)作用:與c語言中的memcpy
函數(shù)一樣,只是此函數(shù)可以在主機(jī)內(nèi)存和GPU內(nèi)存之間互相拷貝數(shù)據(jù)。
3. 函數(shù)參數(shù):cudaMemcpyKind
kind
表示數(shù)據(jù)拷貝方向,如果kind
賦值為cudaMemcpyDeviceToHost
表示數(shù)據(jù)從設(shè)備內(nèi)存拷貝到主機(jī)內(nèi)存。
4. 與C中的memcpy()
一樣,以同步方式執(zhí)行,即當(dāng)函數(shù)返回時(shí),復(fù)制操作就已經(jīng)完成了,并且在輸出緩沖區(qū)中包含了復(fù)制進(jìn)去的內(nèi)容。
5. 相應(yīng)的有個(gè)異步方式執(zhí)行的函數(shù)cudaMemcpyAsync()
,這個(gè)函數(shù)詳解請(qǐng)看下面的流一節(jié)有關(guān)內(nèi)容。
cudaFree()
1. 函數(shù)原型:cudaError_t cudaFree ( void* devPtr )
。
2. 函數(shù)作用:與c語言中的free()
函數(shù)一樣,只是此函數(shù)釋放的是cudaMalloc()
分配的內(nèi)存。
下面實(shí)例用于解釋上面三個(gè)函數(shù)
#include <stdio.h>
#include <cuda_runtime.h>
__global__ void add( int a, int b, int *c ) {
*c = a + b;
}
int main( void ) {
int c;
int *dev_c;
//cudaMalloc()
cudaMalloc( (void**)&dev_c, sizeof(int) );
//核函數(shù)執(zhí)行
add<<<1,1>>>( 2, 7, dev_c );
//cudaMemcpy()
cudaMemcpy( &c, dev_c, sizeof(int),cudaMemcpyDeviceToHost ) ;
printf( "2 + 7 = %d\n", c );
//cudaFree()
cudaFree( dev_c );
return 0;
}
6.2.5 GPU內(nèi)存分類
全局內(nèi)存
通俗意義上的設(shè)備內(nèi)存。
共享內(nèi)存
1. 位置:設(shè)備內(nèi)存。
2. 形式:關(guān)鍵字__shared__
添加到變量聲明中。如__shared__ float cache[10]
。
3. 目的:對(duì)于GPU
上啟動(dòng)的每個(gè)線程塊,CUDA
C
編譯器都將創(chuàng)建該共享變量的一個(gè)副本。線程塊中的每個(gè)線程都共享這塊內(nèi)存,但線程卻無法看到也不能修改其他線程塊的變量副本。這樣使得一個(gè)線程塊中的多個(gè)線程能夠在計(jì)算上通信和協(xié)作。
常量內(nèi)存
1. 位置:設(shè)備內(nèi)存
2. 形式:關(guān)鍵字__constant__
添加到變量聲明中。如__constant__ float s[10]
;。
3. 目的:為了提升性能。常量內(nèi)存采取了不同于標(biāo)準(zhǔn)全局內(nèi)存的處理方式。在某些情況下,用常量內(nèi)存替換全局內(nèi)存能有效地減少內(nèi)存帶寬。
4. 特點(diǎn):常量內(nèi)存用于保存在核函數(shù)執(zhí)行期間不會(huì)發(fā)生變化的數(shù)據(jù)。變量的訪問限制為只讀。NVIDIA硬件提供了64KB的常量內(nèi)存。不再需要cudaMalloc()
或者cudaFree()
,而是在編譯時(shí),靜態(tài)地分配空間。
5. 要求:當(dāng)我們需要拷貝數(shù)據(jù)到常量內(nèi)存中應(yīng)該使用cudaMemcpyToSymbol()
,而cudaMemcpy()
會(huì)復(fù)制到全局內(nèi)存。
6. 性能提升的原因:
6.1. 對(duì)常量內(nèi)存的單次讀操作可以廣播到其他的“鄰近”線程。這將節(jié)約15次讀取操作。(為什么是15,因?yàn)椤班徑敝赴雮€(gè)線程束,一個(gè)線程束包含32個(gè)線程的集合。)
6.2. 常量內(nèi)存的數(shù)據(jù)將緩存起來,因此對(duì)相同地址的連續(xù)讀操作將不會(huì)產(chǎn)生額外的內(nèi)存通信量。
紋理內(nèi)存
1. 位置:設(shè)備內(nèi)存
2. 目的:能夠減少對(duì)內(nèi)存的請(qǐng)求并提供高效的內(nèi)存帶寬。是專門為那些在內(nèi)存訪問模式中存在大量空間局部性的圖形應(yīng)用程序設(shè)計(jì),意味著一個(gè)線程讀取的位置可能與鄰近線程讀取的位置“非常接近”。如下圖:
3. 紋理變量(引用)必須聲明為文件作用域內(nèi)的全局變量。
4. 形式:分為一維紋理內(nèi)存 和 二維紋理內(nèi)存。
4.1. 一維紋理內(nèi)存
4.1.1. 用texture<類型>
類型聲明,如texture<float> texIn
。
4.1.2. 通過cudaBindTexture()
綁定到紋理內(nèi)存中。
4.1.3. 通過tex1Dfetch()
來讀取紋理內(nèi)存中的數(shù)據(jù)。
4.1.4. 通過cudaUnbindTexture()
取消綁定紋理內(nèi)存。
4.2. 二維紋理內(nèi)存
4.2.1. 用texture<類型,數(shù)字>
類型聲明,如texture<float,2> texIn
。
4.2.2. 通過cudaBindTexture2D()
綁定到紋理內(nèi)存中。
4.2.3. 通過tex2D()
來讀取紋理內(nèi)存中的數(shù)據(jù)。
4.2.4. 通過cudaUnbindTexture()
取消綁定紋理內(nèi)存。
固定內(nèi)存
1. 位置:主機(jī)內(nèi)存。
2. 概念:也稱為頁鎖定內(nèi)存或者不可分頁內(nèi)存,操作系統(tǒng)將不會(huì)對(duì)這塊內(nèi)存分頁并交換到磁盤上,從而確保了該內(nèi)存始終駐留在物理內(nèi)存中。因此操作系統(tǒng)能夠安全地使某個(gè)應(yīng)用程序訪問該內(nèi)存的物理地址,因?yàn)檫@塊內(nèi)存將不會(huì)破壞或者重新定位。
3. 目的:提高訪問速度。由于GPU知道主機(jī)內(nèi)存的物理地址,因此可以通過“直接內(nèi)存訪問DMA(Direct Memory Access)技術(shù)來在GPU和主機(jī)之間復(fù)制數(shù)據(jù)。由于DMA在執(zhí)行復(fù)制時(shí)無需CPU介入。因此DMA復(fù)制過程中使用固定內(nèi)存是非常重要的。
4. 缺點(diǎn):使用固定內(nèi)存,將失去虛擬內(nèi)存的所有功能;系統(tǒng)將更快的耗盡內(nèi)存。
5. 建議:對(duì)cudaMemcpy()
函數(shù)調(diào)用中的源內(nèi)存或者目標(biāo)內(nèi)存,才使用固定內(nèi)存,并且在不再需要使用它們時(shí)立即釋放。
6. 形式:通過cudaHostAlloc()
函數(shù)來分配;通過cudaFreeHost()
釋放。
7. 只能以異步方式對(duì)固定內(nèi)存進(jìn)行復(fù)制操作。
原子性
1. 概念:如果操作的執(zhí)行過程不能分解為更小的部分,我們將滿足這種條件限制的操作稱為原子操作。
2. 形式:函數(shù)調(diào)用,如atomicAdd(addr,y)
將生成一個(gè)原子的操作序列,這個(gè)操作序列包括讀取地址addr
處的值,將y
增加到這個(gè)值,以及將結(jié)果保存回地址addr
。
6.2.6 常用線程操作函數(shù)
同步方法__syncthreads()
,這個(gè)函數(shù)的調(diào)用,將確保線程塊中的每個(gè)線程都執(zhí)行完__syscthreads()
前面的語句后,才會(huì)執(zhí)行下一條語句。
使用事件來測量性能
1. 用途:為了測量GPU在某個(gè)任務(wù)上花費(fèi)的時(shí)間。CUDA中的事件本質(zhì)上是一個(gè)GPU時(shí)間戳。由于事件是直接在GPU上實(shí)現(xiàn)的。因此不適用于對(duì)同時(shí)包含設(shè)備代碼和主機(jī)代碼的混合代碼設(shè)計(jì)。
2. 形式:首先創(chuàng)建一個(gè)事件,然后記錄事件,再計(jì)算兩個(gè)事件之差,最后銷毀事件。如:
cudaEvent_t start, stop;
cudaEventCreate( &start );
cudaEventCreate( &stop );
cudaEventRecord( start, 0 );
//do something
cudaEventRecord( stop, 0 );
float elapsedTime;
cudaEventElapsedTime( &elapsedTime,start, stop );
cudaEventDestroy( start );
cudaEventDestroy( stop );
6.2.7 流
- 扯一扯:并發(fā)重點(diǎn)在于一個(gè)極短時(shí)間段內(nèi)運(yùn)行多個(gè)不同的任務(wù);并行重點(diǎn)在于同時(shí)運(yùn)行一個(gè)任務(wù)。
- 任務(wù)并行性:是指并行執(zhí)行兩個(gè)或多個(gè)不同的任務(wù),而不是在大量數(shù)據(jù)上執(zhí)行同一個(gè)任務(wù)。
- 概念:CUDA流表示一個(gè)GPU操作隊(duì)列,并且該隊(duì)列中的操作將以指定的順序執(zhí)行。我們可以在流中添加一些操作,如核函數(shù)啟動(dòng),內(nèi)存復(fù)制以及事件的啟動(dòng)和結(jié)束等。這些操作的添加到流的順序也是它們的執(zhí)行順序。可以將每個(gè)流視為GPU上的一個(gè)任務(wù),并且這些任務(wù)可以并行執(zhí)行。
- 硬件前提:必須是支持設(shè)備重疊功能的GPU。支持設(shè)備重疊功能,即在執(zhí)行一個(gè)核函數(shù)的同時(shí),還能在設(shè)備與主機(jī)之間執(zhí)行復(fù)制操作。
- 聲明與創(chuàng)建:聲明
cudaStream_t stream
;,創(chuàng)建cudaSteamCreate(&stream)
;。 -
cudaMemcpyAsync()
:前面在cudaMemcpy()
中提到過,這是一個(gè)以異步方式執(zhí)行的函數(shù)。在調(diào)用cudaMemcpyAsync()
時(shí),只是放置一個(gè)請(qǐng)求,表示在流中執(zhí)行一次內(nèi)存復(fù)制操作,這個(gè)流是通過參數(shù)stream來指定的。當(dāng)函數(shù)返回時(shí),我們無法確保復(fù)制操作是否已經(jīng)啟動(dòng),更無法保證它是否已經(jīng)結(jié)束。我們能夠得到的保證是,復(fù)制操作肯定會(huì)當(dāng)下一個(gè)被放入流中的操作之前執(zhí)行。傳遞給此函數(shù)的主機(jī)內(nèi)存指針必須是通過cudaHostAlloc()
分配好的內(nèi)存。(流中要求固定內(nèi)存) - 流同步:通過
cudaStreamSynchronize()
來協(xié)調(diào)。 - 流銷毀:在退出應(yīng)用程序之前,需要銷毀對(duì)GPU操作進(jìn)行排隊(duì)的流,調(diào)用
cudaStreamDestroy()
。 - 針對(duì)多個(gè)流:
9.1. 記得對(duì)流進(jìn)行同步操作。
9.2. 將操作放入流的隊(duì)列時(shí),應(yīng)采用寬度優(yōu)先方式,而非深度優(yōu)先的方式,換句話說,不是首先添加第0個(gè)流的所有操作,再依次添加后面的第1,2,…個(gè)流。而是交替進(jìn)行添加,比如將a的復(fù)制操作添加到第0個(gè)流中,接著把a(bǔ)的復(fù)制操作添加到第1個(gè)流中,再繼續(xù)其他的類似交替添加的行為。
9.3. 要牢牢記住操作放入流中的隊(duì)列中的順序影響到CUDA驅(qū)動(dòng)程序調(diào)度這些操作和流以及執(zhí)行的方式。
TIPS:
- 當(dāng)線程塊的數(shù)量為GPU中處理數(shù)量的2倍時(shí),將達(dá)到最優(yōu)性能。
- 核函數(shù)執(zhí)行的第一個(gè)計(jì)算就是計(jì)算輸入數(shù)據(jù)的偏移。每個(gè)線程的起始偏移都是0到線程數(shù)量減1之間的某個(gè)值。然后,對(duì)偏移的增量為已啟動(dòng)線程的總數(shù)。
6.2.8 這是一個(gè)栗子
我們嘗試用一個(gè)程序來比較cuda/c在GPU/CPU的運(yùn)行效率,來不及了,快上車。
這是一個(gè)CUDA程序,請(qǐng)保存文件名為“文件名.cu
”,在你的PC或者服務(wù)器上運(yùn)行。
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
#include <time.h>
#define N (1024*1024)
#define M (10000)
#define THREADS_PER_BLOCK 1024
void serial_add(double *a, double *b, double *c, int n, int m)
{
for(int index=0;index<n;index++)
{
for(int j=0;j<m;j++)
{
c[index] = a[index]*a[index] + b[index]*b[index];
}
}
}
__global__ void vector_add(double *a, double *b, double *c)
{
int index = blockIdx.x * blockDim.x + threadIdx.x;
for(int j=0;j<M;j++)
{
c[index] = a[index]*a[index] + b[index]*b[index];
}
}
int main()
{
clock_t start,end;
double *a, *b, *c;
int size = N * sizeof( double );
a = (double *)malloc( size );
b = (double *)malloc( size );
c = (double *)malloc( size );
for( int i = 0; i < N; i++ )
{
a[i] = b[i] = i;
c[i] = 0;
}
start = clock();
serial_add(a, b, c, N, M);
printf( "c[%d] = %f\n",0,c[0] );
printf( "c[%d] = %f\n",N-1, c[N-1] );
end = clock();
float time1 = ((float)(end-start))/CLOCKS_PER_SEC;
printf("CPU: %f seconds\n",time1);
start = clock();
double *d_a, *d_b, *d_c;
cudaMalloc( (void **) &d_a, size );
cudaMalloc( (void **) &d_b, size );
cudaMalloc( (void **) &d_c, size );
cudaMemcpy( d_a, a, size, cudaMemcpyHostToDevice );
cudaMemcpy( d_b, b, size, cudaMemcpyHostToDevice );
vector_add<<< (N + (THREADS_PER_BLOCK-1)) / THREADS_PER_BLOCK, THREADS_PER_BLOCK >>>( d_a, d_b, d_c );
cudaMemcpy( c, d_c, size, cudaMemcpyDeviceToHost );
printf( "c[%d] = %f\n",0,c[0] );
printf( "c[%d] = %f\n",N-1, c[N-1] );
free(a);
free(b);
free(c);
cudaFree( d_a );
cudaFree( d_b );
cudaFree( d_c );
end = clock();
float time2 = ((float)(end-start))/CLOCKS_PER_SEC;
printf("CUDA: %f seconds, Speedup: %f\n",time2, time1/time2);
return 0;
}
效率對(duì)比
我們通過修改count的值并且加大循環(huán)次數(shù)來觀察變量的效率的差別。
運(yùn)行結(jié)果:
可見在數(shù)據(jù)量大的情況下效率還是相當(dāng)不錯(cuò)的。
7. GPU or FPGA
GPU優(yōu)勢
1.從峰值性能來說,GPU(10Tflops)遠(yuǎn)遠(yuǎn)高于FPGA(<1TFlops);
2.GPU相對(duì)于FPGA還有一個(gè)優(yōu)勢就是內(nèi)存接口, GPU的內(nèi)存接口(傳統(tǒng)的GDDR5,最近更是用上了HBM和HBM2)的帶寬遠(yuǎn)好于FPGA的傳統(tǒng)DDR接口(大約帶寬高4-5倍);
3.功耗方面,雖然GPU的功耗遠(yuǎn)大于FPGA的功耗,但是如果要比較功耗應(yīng)該比較在執(zhí)行效率相同時(shí)需要的功耗。如果FPGA的架構(gòu)優(yōu)化能做到很好以致于一塊FPGA的平均性能能夠接近一塊GPU,那么FPGA方案的總功耗遠(yuǎn)小于GPU,散熱問題可以大大減輕。反之,如果需要二十塊FPGA才能實(shí)現(xiàn)一塊GPU的平均性能,那么FPGA在功耗方面并沒有優(yōu)勢。
4.FPGA缺點(diǎn)有三點(diǎn):
第一,基本單元的計(jì)算能力有限。為了實(shí)現(xiàn)可重構(gòu)特性,F(xiàn)PGA 內(nèi)部有大量極細(xì)粒度的基本單元,但是每個(gè)單元的計(jì)算能力(主要依靠LUT 查找表)都遠(yuǎn)遠(yuǎn)低于CPU 和GPU 中的ALU模塊。
第二,速度和功耗相對(duì)專用定制芯片(ASIC)仍然存在不小差距。
第三,F(xiàn)PGA 價(jià)格較為昂貴,在規(guī)模放量的情況下單塊FPGA 的成本要遠(yuǎn)高于專用定制芯片。最后誰能勝出, 完全取決于FPGA架構(gòu)優(yōu)化能否彌補(bǔ)峰值性能的劣勢。
5.個(gè)人更推薦: CPU+FPGA的組合模式; 其中FPGA用于整形計(jì)算,cpu進(jìn)行浮點(diǎn)計(jì)算和調(diào)度,此組合的擁有更高的單位功耗性能和更低的時(shí)延。最后更想GPU穩(wěn)定開放,發(fā)揮其長處, 達(dá)到真正的物美價(jià)廉!
FPGA優(yōu)勢
人工智能目前仍處于早期階段,未來人工智能的主戰(zhàn)場是在推理環(huán)節(jié),遠(yuǎn)沒有爆發(fā)。未來勝負(fù)尚未可知,各家技術(shù)路線都有機(jī)會(huì)勝出。目前英偉達(dá)的GPU在訓(xùn)練場景中占據(jù)著絕對(duì)領(lǐng)導(dǎo)地位,但是在未來,專注于推理環(huán)節(jié)的FPGA必將會(huì)發(fā)揮巨大的價(jià)值。
FPGA和GPU內(nèi)都有大量的計(jì)算單元,因此它們的計(jì)算能力都很強(qiáng)。在進(jìn)行神經(jīng)網(wǎng)絡(luò)運(yùn)算的時(shí)候,兩者的速度會(huì)比CPU快很多。但是GPU由于架構(gòu)固定,硬件原生支持的指令也就固定了,而FPGA則是可編程的。其可編程性是關(guān)鍵,因?yàn)樗屲浖c終端應(yīng)用公司能夠提供與其競爭對(duì)手不同的解決方案,并且能夠靈活地針對(duì)自己所用的算法修改電路。
在平均性能方面,GPU遜于FPGA,F(xiàn)PGA可以根據(jù)特定的應(yīng)用去編程硬件,例如如果應(yīng)用里面的加法運(yùn)算非常多就可以把大量的邏輯資源去實(shí)現(xiàn)加法器,而GPU一旦設(shè)計(jì)完就不能改動(dòng)了,所以不能根據(jù)應(yīng)用去調(diào)整硬件資源。
目前機(jī)器學(xué)習(xí)大多使用SIMD架構(gòu),即只需一條指令可以平行處理大量數(shù)據(jù),因此用GPU很適合。但是有些應(yīng)用是MISD,即單一數(shù)據(jù)需要用許多條指令平行處理,這種情況下用FPGA做一個(gè)MISD的架構(gòu)就會(huì)比GPU有優(yōu)勢。 所以,對(duì)于平均性能,看的就是FPGA加速器架構(gòu)上的優(yōu)勢是否能彌補(bǔ)運(yùn)行速度上的劣勢。如果FPGA上的架構(gòu)優(yōu)化可以帶來相比GPU架構(gòu)兩到三個(gè)數(shù)量級(jí)的優(yōu)勢,那么FPGA在平均性能上會(huì)好于GPU。
在功耗能效比方面,同樣由于FPGA的靈活性,在架構(gòu)優(yōu)化到很好時(shí),一塊FPGA的平均性能能夠接近一塊GPU,那么FPGA方案的總功耗遠(yuǎn)小于GPU,散熱問題可以大大減輕。 能效比的比較也是類似,能效指的是完成程序執(zhí)行消耗的能量,而能量消耗等于功耗乘以程序的執(zhí)行時(shí)間。雖然GPU的功耗遠(yuǎn)大于FPGA的功耗,但是如果FPGA執(zhí)行相同程序需要的時(shí)間比GPU長幾十倍,那FPGA在能效比上就沒有優(yōu)勢了;反之如果FPGA上實(shí)現(xiàn)的硬件架構(gòu)優(yōu)化得很適合特定的機(jī)器學(xué)習(xí)應(yīng)用,執(zhí)行算法所需的時(shí)間僅僅是GPU的幾倍或甚至于接近GPU,那么FPGA的能效比就會(huì)比GPU強(qiáng)。
在峰值性能比方面,雖然GPU的峰值性能(10Tflops)遠(yuǎn)大于FPGA的峰值性能(<1Tflops),但針對(duì)特定的場景來講吞吐量并不比GPU差。
8. 深度學(xué)習(xí)的三種硬件方案:ASIC,F(xiàn)PGA,GPU
8.1 對(duì)深度學(xué)習(xí)硬件平臺(tái)的要求
要想明白“深度學(xué)習(xí)”需要怎樣的硬件,必須了解深度學(xué)習(xí)的工作原理。首先在表層上,我們有一個(gè)巨大的數(shù)據(jù)集,并選定了一種深度學(xué)習(xí)模型。每個(gè)模型都有一些內(nèi)部參數(shù)需要調(diào)整,以便學(xué)習(xí)數(shù)據(jù)。而這種參數(shù)調(diào)整實(shí)際上可以歸結(jié)為優(yōu)化問題,在調(diào)整這些參數(shù)時(shí),就相當(dāng)于在優(yōu)化特定的約束條件。
矩陣相乘(Matrix Multiplication)——幾乎所有的深度學(xué)習(xí)模型都包含這一運(yùn)算,它的計(jì)算十分密集。
卷積(Convolution)——這是另一個(gè)常用的運(yùn)算,占用了模型中大部分的每秒浮點(diǎn)運(yùn)算(浮點(diǎn)/秒)。
循環(huán)層(Recurrent Layers )——模型中的反饋層,并且基本上是前兩個(gè)運(yùn)算的組合。
All Reduce——這是一個(gè)在優(yōu)化前對(duì)學(xué)習(xí)到的參數(shù)進(jìn)行傳遞或解析的運(yùn)算序列。在跨硬件分布的深度學(xué)習(xí)網(wǎng)絡(luò)上執(zhí)行同步優(yōu)化時(shí)(如AlphaGo的例子),這一操作尤其有效。
除此之外,深度學(xué)習(xí)的硬件加速器需要具備數(shù)據(jù)級(jí)別和流程化的并行性、多線程和高內(nèi)存帶寬等特性。 另外,由于數(shù)據(jù)的訓(xùn)練時(shí)間很長,所以硬件架構(gòu)必須低功耗。 因此,效能功耗比(Performance per Watt)是硬件架構(gòu)的評(píng)估標(biāo)準(zhǔn)之一。
CNN在應(yīng)用中,一般采用GPU加速,請(qǐng)解釋為什么GPU可以有加速效果,主要加速算法的哪一個(gè)部分?
這里默認(rèn)gpu加速是指NVIDIA的CUDA加速。CPU是中央處理單元,gpu是圖形處理單元,gpu由上千個(gè)流處理器(core)作為運(yùn)算器。執(zhí)行采用單指令多線程(SIMT)模式。相比于單核CPU(向量機(jī))流水線式的串行操作,雖然gpu單個(gè)core計(jì)算能力很弱,但是通過大量線程進(jìn)行同時(shí)計(jì)算,在數(shù)據(jù)量很大是會(huì)活動(dòng)較為可觀的加速效果。
具體到cnn,利用gpu加速主要是在conv(卷積)過程上。conv過程同理可以像以上的向量加法一樣通過cuda實(shí)現(xiàn)并行化。具體的方法很多,不過最好的還是利用fft(快速傅里葉變換)進(jìn)行快速卷積。NVIDIA提供了cufft
庫實(shí)現(xiàn)fft
,復(fù)數(shù)乘法則可以使用cublas
庫里的對(duì)應(yīng)的level3的cublasCgemm
函數(shù)。
GPU加速的基本準(zhǔn)則就是“人多力量大”。CNN說到底主要問題就是計(jì)算量大,但是卻可以比較有效的拆分成并行問題。隨便拿一個(gè)層的filter來舉例子,假設(shè)某一層有n個(gè)filter,每一個(gè)需要對(duì)上一層輸入過來的map進(jìn)行卷積操作。那么,這個(gè)卷積操作并不需要按照線性的流程去做,每個(gè)濾波器互相之間并不影響,可以大家同時(shí)做,然后大家生成了n張新的譜之后再繼續(xù)接下來的操作。既然可以并行,那么同一時(shí)間處理單元越多,理論上速度優(yōu)勢就會(huì)越大。所以,處理問題就變得很簡單粗暴,就像NV那樣,暴力增加顯卡單元數(shù)(當(dāng)然,顯卡的架構(gòu)、內(nèi)部數(shù)據(jù)的傳輸速率、算法的優(yōu)化等等也都很重要)。
GPU主要是針對(duì)圖形顯示及渲染等技術(shù)的出眾,而其中的根本是因?yàn)樘幚砭仃囁惴芰Φ膹?qiáng)大,剛好CNN中涉及大量的卷積,也就是矩陣乘法等,所以在這方面具有優(yōu)勢。
機(jī)器學(xué)習(xí)的算法一定得經(jīng)過gpu加速嗎?
不一定。只有需要大量浮點(diǎn)數(shù)計(jì)算,例如矩陣乘法,才需要GPU加速。 用CNN對(duì)圖像進(jìn)行分類就是一個(gè)需要大量浮點(diǎn)數(shù)計(jì)算的典型案例,通常需要GPU加速
對(duì)于ASIC、FPGA、分布式計(jì)算,這里不再展開講,有興趣的小伙伴可以,自行學(xué)習(xí)。不過....說不定某天博主心情好,就會(huì)梳理一下這幾種硬件方案在端到端上應(yīng)用的區(qū)別了。
菜鳥入門教程就到這里了,聰明的你一定不滿足這個(gè)入門教程,如有興趣進(jìn)一步學(xué)習(xí)CUDA編程,可移步NVIDIA官方的課程平臺(tái)CUDA ZONE(PS:中文網(wǎng)站,英文課程)
歡迎交流 ?????
Author:He_Yu
Email:heyu.nwpu@gmail.com