【知乎】【Shader篇】GPU架構(gòu)與其邏輯管線
【Shader篇】GPU架構(gòu)與其邏輯管線

vvyDev

圖形程序
關(guān)注他
172 人贊同了該文章
目錄
收起
1、引言
2、GPU架構(gòu)(上)
2.1、PCIe總線
2.2、Giga Thread Engine
2.3、GPCs(Graphics Processing Cluster)
2.4、Poly Morph Engine
2.5、SM(Streaming Multiprocessors)
3、GPU邏輯管線
3.1、小結(jié)
4、GPU架構(gòu)(下)
SIMD
SIMT
co-issue
Shader指令執(zhí)行機(jī)制
GPU存儲(chǔ)器
總結(jié)
前言:本篇文章作為Shader系列的第一篇,我覺(jué)得還是從GPU架構(gòu)開(kāi)始,重新認(rèn)識(shí)Shader是如何被解析執(zhí)行的,然后把渲染管線與GPU架構(gòu)關(guān)聯(lián)起來(lái),做到知其所以然。
1、引言
通過(guò)分析GPU架構(gòu),我們可以學(xué)習(xí)到Shader是如何在GPU這個(gè)黑盒子里運(yùn)行處理的,然后運(yùn)用相關(guān)特性來(lái)優(yōu)化Shader代碼,甚至使用Debugger工具來(lái)定位Shader瓶頸。CPU核心里大部分部件都是分支預(yù)測(cè)、各級(jí)緩存,而計(jì)算單元相對(duì)GPU來(lái)說(shuō)很少,所以CPU擅長(zhǎng)處理各種邏輯復(fù)雜的代碼。而GPU把各種邏輯處理單元簡(jiǎn)化了,省下芯片空間來(lái)增加幾千個(gè)核心,擁有極強(qiáng)的并行運(yùn)算能力。
打個(gè)比喻,CPU擁有復(fù)雜的行政部門(mén),擁有各式管理層,能夠高效調(diào)用工具人處理事務(wù);而GPU卻是精簡(jiǎn)管理層,增加大量工具人。GPU只需要一個(gè)管理員去發(fā)出一條指令,然后指派32個(gè)工具人同時(shí)執(zhí)行該指令,從而瞬間完成32批任務(wù)。所以GPU擅長(zhǎng)處理大量并行的事務(wù)。
2、GPU架構(gòu)(上)
縱觀GPU架構(gòu)發(fā)展,從Fermi到現(xiàn)在的Ampere、Hopper,總體的架構(gòu)并沒(méi)有改變,所以這里以2018年的NVidia Turing架構(gòu)為例進(jìn)行解析。下圖先放其總體架構(gòu):
對(duì)應(yīng)著后續(xù)解釋渲染光線流程的順序,我從最外層往里進(jìn)行說(shuō)明。
2.1、PCIe總線
硬件上對(duì)應(yīng)的是顯卡插槽,和主板相連,從而可以和CPU通信。我們?cè)谡{(diào)用圖形API發(fā)起DrawCall時(shí),這些指令數(shù)據(jù)最終就會(huì)經(jīng)過(guò)PCIe總線,到達(dá)GPU顯存,而這些數(shù)據(jù)的傳輸交由GPU上的DMA專(zhuān)門(mén)負(fù)責(zé)的。所以CPU只需要發(fā)送相關(guān)指令給DMA,剩下的就無(wú)需插手,達(dá)到解耦。
2.2、Giga Thread Engine
獲取到數(shù)據(jù)之后,該部件將這些線程塊合理地分配給某個(gè)GPC,讓GPC去處理該線程塊對(duì)應(yīng)的數(shù)據(jù),輸出結(jié)果。如果我們寫(xiě)ComputeShader,我們會(huì)將其分為一組一組(比如模糊一張圖片,8x8像素為一組),程序員可以對(duì)線程塊大小、任務(wù)大小進(jìn)行規(guī)劃,GPU就會(huì)將每個(gè)任務(wù)塊分配給GPC。
2.3、GPCs(Graphics Processing Cluster)
一個(gè)GPU有多個(gè)GPC,每個(gè)GPC各自處理自己的任務(wù)。進(jìn)入到某個(gè)GPC內(nèi)部,每個(gè)GPC擁有一個(gè)光柵化引擎(Raster Engine)和多個(gè)SM。SM可以處理VertexShader和PixelShader以及ComputeShader(還有RT Core和Tensor Core,這里不涉及了),在執(zhí)行完VertexShader之后,數(shù)據(jù)交由光柵化引擎進(jìn)行光柵化,然后再將得到的片元重新分配給GPC讓其SM執(zhí)行PixelShader,渲染出最終結(jié)果。
2.4、Poly Morph Engine
每個(gè)SM都對(duì)應(yīng)一個(gè)Poly Morph Engine,該部件組成:VertexFetch、曲面細(xì)分、裁剪空間、Attribute Setup。它負(fù)責(zé)將分配給某個(gè)SM的數(shù)據(jù)通過(guò)三角形索引(triangle indices)取出三角形的數(shù)據(jù)(vertex data),然后交由SM執(zhí)行Shader處理頂點(diǎn)。
2.5、SM(Streaming Multiprocessors)
我們先來(lái)認(rèn)識(shí)一下SM,流多處理器,它可以一次并行處理多條數(shù)據(jù)流。GPC負(fù)載均衡地將任務(wù)塊分配給某SM,SM得到該任務(wù)塊后,將其以32個(gè)為一組分成一個(gè)個(gè)線程束(Warp),不足32則補(bǔ)空,然后SM每次調(diào)度某個(gè)線程束以32個(gè)線程來(lái)執(zhí)行。
SM里面有個(gè)線程束調(diào)度器(Warp Scheduler),負(fù)責(zé)安排哪個(gè)Warp進(jìn)行入32個(gè)核心進(jìn)行執(zhí)行。
到這里為止,我先結(jié)合以上內(nèi)容,總結(jié)GPU的邏輯管線流程,然后在?GPU架構(gòu)(下)?繼續(xù)分析SM內(nèi)容詳細(xì)(硬核)的架構(gòu)。
3、GPU邏輯管線
參考NVIDIA官網(wǎng)資料:Life of a triangle - NVIDIA's logical pipeline
1. 程序通過(guò)圖形API發(fā)出DrawCall指令,指令會(huì)被推送到驅(qū)動(dòng)程序,驅(qū)動(dòng)會(huì)檢查指令的合法性,然后會(huì)把指令放到GPU可以讀取的Pushbuffer中,顯然這個(gè)Pushbuffer在系統(tǒng)內(nèi)存,GPU可以通過(guò)PCIe共享訪問(wèn)的;
2. 經(jīng)過(guò)一段時(shí)間或者顯示調(diào)用flush之后,這些Command就會(huì)發(fā)送到GPU,GPU通過(guò)PCIe Host Interface接收,然后交由Front End處理;
3. 圖元分配器處理IBO,將組好的三角形批次分配給GPCs(之所以這里需要組好三角形,是因?yàn)閂S執(zhí)行完后需要給GPC里的光柵化引擎處理,下文提及);
4. 在GPC中,每個(gè)SM中的Poly Morph Engine負(fù)責(zé)通過(guò)三角形索引(Vertex Fetch)取出三角形的數(shù)據(jù)(vertex data);
5. 獲取到頂點(diǎn)數(shù)據(jù)后,將其劃分為32線程為一組的Warps,至此,SM得到了一組Warp(每個(gè)Warp對(duì)應(yīng)32個(gè)頂點(diǎn),交由SM里32個(gè)線程并行執(zhí)行VS處理);
6. SM里的Warp Scheduler負(fù)責(zé)調(diào)度Warp以lock-step運(yùn)行該Warp里綁定的Shader的每條指令;
7. 這些指令的執(zhí)行有些是立馬完成的,有些不是,比如讀取紋理;
8. 為了解決7的延遲問(wèn)題,通過(guò)切換Warp來(lái)提高core利用率。比如當(dāng)前指令在讀取紋理,那么SM就切換另外一個(gè)Warp來(lái)執(zhí)行,只要有足夠多的Warp來(lái)填滿(mǎn)該延遲,那么該SM的利用率就高;
9. 完成Vertex-Shader后,通過(guò)L1、L2緩存?zhèn)鬟f數(shù)據(jù),將結(jié)果交由Viewport Transform處理,進(jìn)行齊次裁剪空間下對(duì)超出范圍的三角形裁剪,等待下一步光柵化;
10. 此時(shí)得到的結(jié)果將離開(kāi)當(dāng)前GPC,然后根據(jù)三角形的包圍盒所占屏幕Tile,通過(guò)Work Distribution Crossbar重新分配給1個(gè)或多個(gè)GPC;
11. SM里的PolyMorph Engine獲取到數(shù)據(jù),Attribute Setup將處理需要插值的數(shù)值(比如在VS中輸出的各種結(jié)果),以提供給后續(xù)PS使用;
12. Raster Engine光柵化,進(jìn)行背面剔除,z-cull,early-z;
13. 再次組成一個(gè)個(gè)Warp,此時(shí)的Warp是以8個(gè)2x2的像素塊,共32像素對(duì)應(yīng)32線程的形式劃分。2x2的像素塊的形式有助于計(jì)算相鄰像素之間的求導(dǎo),比如通過(guò)變化率得知mip級(jí)數(shù);
14. 和VS階段一樣,通過(guò)Warp Scheduler調(diào)度Warps,以lock-step方式執(zhí)行每個(gè)Warp,直到完成PixelShader;
15. ROP (render output unit)渲染輸出單元將進(jìn)行深度測(cè)試,將結(jié)果blend到RT/framebuffer;這些取值測(cè)試、寫(xiě)入都是原子操作,結(jié)果的輸出也會(huì)壓縮以?xún)?yōu)化帶寬消耗。
3.1、小結(jié)
從圖形API調(diào)用到GPU端,從GPC分配到拉取頂點(diǎn)數(shù)據(jù),進(jìn)行SM的Warp調(diào)度進(jìn)行VertexShader并行執(zhí)行。然后繼續(xù)由Viewport Transform到Raster Engine,再次進(jìn)行GPC分配,并行執(zhí)行PixelShader。其中最大的特性是SM通過(guò)Warp調(diào)度器對(duì)SM的Warps列表,每次派發(fā)一個(gè)Warp。每個(gè)Warp的大小為32,剛好對(duì)應(yīng)SM 32個(gè)線程。所以該SM內(nèi)如果是高效執(zhí)行的話,應(yīng)該每時(shí)每刻都會(huì)有一個(gè)Warp里32任務(wù),正在被SM的32個(gè)線程并發(fā)執(zhí)行。SM的架構(gòu)細(xì)節(jié)在下小節(jié)會(huì)進(jìn)行更新詳細(xì)的分析。
4、GPU架構(gòu)(下)
先大概看一下SM包含的模塊,熟悉一下:
Instruction Cache:指令緩存,緩存了該SM里Warps的指令;
Warp Scheduler:線程束調(diào)度器;
Dispatch Unit:指令分發(fā)器,根據(jù)Warp Scheduler的調(diào)度向核心發(fā)送該Warp的指令;
Register File:寄存器,編譯好的機(jī)器碼如ADD r1 r2 r3,這些r開(kāi)頭的就是一個(gè)個(gè)寄存器,給Core提供計(jì)算參數(shù)或者存儲(chǔ)輸出結(jié)果,上圖的SM中有3萬(wàn)多個(gè)32bit的寄存器,Warp中每個(gè)任務(wù)都會(huì)分配私有的寄存器;
Core:計(jì)算核心,負(fù)責(zé)浮點(diǎn)數(shù)和整數(shù)的計(jì)算;
SFU:Special Function Units,執(zhí)行特殊數(shù)學(xué)計(jì)算(sin、cos、log等);
LD/ST:Load/Store,訪存單元,加載和存儲(chǔ)數(shù)據(jù);
L1 Cache:一級(jí)緩存,片上內(nèi)存,即該內(nèi)存是位于芯片內(nèi)部的,速度很快;
Shared Memory:共享內(nèi)存,片上內(nèi)存;
Tex與Texture Cache:紋理單元用于采樣紋理,紋理緩存;
PolyMorph Engine:多邊形引擎,用于處理頂點(diǎn)數(shù)據(jù)拉取、Viewport Transform等。
前文提到GPU相比于CPU不同的地方,是各種控制器分支預(yù)測(cè)的精簡(jiǎn),這里將體現(xiàn)出來(lái):
SIMD
單指令多數(shù)據(jù),該功能對(duì)應(yīng)于Core模塊。在Shader里,我們經(jīng)常處理矩陣、Position、Color,Vector等多維數(shù)據(jù)。如果以CPU指令執(zhí)行需要每個(gè)維度都執(zhí)行一次該指令,但是GPU提供了SMID,如SMID_ADD,它只需要執(zhí)行一次指令就能計(jì)算完整個(gè)向量。
SIMT
單指令多線程,該功能對(duì)應(yīng)SM的Warp Scheduler、Dispatch Unit模塊。SM存在著眾多的Warp,而Warp是邏輯層面上的概念。每個(gè)Warp里面都含有32個(gè)任務(wù),它們對(duì)應(yīng)同一個(gè)Shader,并且是同步執(zhí)行的。這樣的好處是Warp Scheduler只需要對(duì)Warp進(jìn)行調(diào)度,一條指令的派發(fā)就能驅(qū)動(dòng)Warp對(duì)應(yīng)的32個(gè)線程運(yùn)行起來(lái)。
co-issue
可以將1維和3維數(shù)據(jù)、2維和2維數(shù)據(jù)合并成4維,這樣就可以以SIMD形式運(yùn)算。
Shader指令執(zhí)行機(jī)制
SM的特點(diǎn):。
SM可以接收多個(gè)任務(wù)塊(也稱(chēng)為線程塊Block或者Work Group),每個(gè)任務(wù)塊綁定一個(gè)Shader,然后SM會(huì)將任務(wù)塊以32大小劃分為Warp。SM管理多個(gè)Warp,然后在適當(dāng)?shù)臅r(shí)候調(diào)度某個(gè)Warp來(lái)執(zhí)行。此時(shí),SM以SIMT形式驅(qū)動(dòng)計(jì)算單元以lock-step方式執(zhí)行該Warp的指令。
舉個(gè)例子,渲染一個(gè)模型,首先會(huì)分成多個(gè)任務(wù)塊,假設(shè)其中一個(gè)任務(wù)塊42個(gè)三角形,42*3=126個(gè)頂點(diǎn),將這個(gè)任務(wù)塊分配給某個(gè)SM之后,這個(gè)SM會(huì)以32為一組劃分為Warp,即126/32=4個(gè)Warp,這4個(gè)Warp對(duì)應(yīng)相同Shader,Warp內(nèi)的32個(gè)頂點(diǎn)執(zhí)行是同步的,而Warp與Warp之間雖然Shader是一樣的,但執(zhí)行的進(jìn)度是獨(dú)立的。除非在代碼中,我們主動(dòng)調(diào)用GroupMemoryBarrierWithGroupSync()來(lái)強(qiáng)制任務(wù)塊內(nèi)的線程同步。這時(shí)候,這4個(gè)Warp就會(huì)強(qiáng)制同步到同一個(gè)代碼點(diǎn),但這種操作對(duì)性能的影響比較大。
1、Warp Scheduler的任務(wù)就是協(xié)調(diào)眾多的Warp去執(zhí)行,這樣有什么好處呢?
如上圖的Warp1,當(dāng)它在執(zhí)行時(shí)遇到不能馬上執(zhí)行完的指令(比如紋理采樣,或者需要到GlobalMemory加載數(shù)據(jù)等),SM為了不讓Core空閑(Stall),會(huì)切換Warp2、Warp3、Warp...n來(lái)執(zhí)行。所以只要Warp夠多,那么總能找到需要處理的任務(wù),來(lái)填補(bǔ)Warp1的空擋時(shí)間。這里的術(shù)語(yǔ)叫?延遲隱藏(Latency Hiding)。從GlobalMemory的訪存有幾百個(gè)周期延遲,所以要有足夠多的Warp才能保證延遲隱藏的效果。Warp的數(shù)量受限如下圖:
SM的CTA(Block) 任務(wù)塊上限;
SM的Warp slots上限;
SM總線程數(shù)上限;
SM寄存器上限;
Shared Memory上限;
CTA(Block) 的Dimension也會(huì)間接影響;
2、32線程同步執(zhí)行Warp的32個(gè)任務(wù),優(yōu)缺點(diǎn)
優(yōu)點(diǎn):管理上分而治之,以32為一組;適合天然需要并行處理的任務(wù),比如圖像渲染,存在大量的頂點(diǎn)和像素,而這些頂點(diǎn)和像素的處理過(guò)程是一樣的,即Shader一樣。
缺點(diǎn)是分支處理,如下圖:
上圖列舉8個(gè)線程同步執(zhí)行的情況,當(dāng)該Warp存在if-else分支時(shí),執(zhí)行底層會(huì)有個(gè)變量exec數(shù)組,存儲(chǔ)這每個(gè)線程當(dāng)前的mask狀態(tài),如果分支為false不執(zhí)行,那么就會(huì)標(biāo)記該線程masked out,但仍舊需要跟著其他為true的線程同步“執(zhí)行”,浪費(fèi)時(shí)間。最差的情況就是32個(gè)線程中,只有一個(gè)為true,但是其他31個(gè)都需要同步等待,此時(shí)是利用率僅有1/32。同理,for循環(huán)的次數(shù)如果不一樣也存在著相同的問(wèn)題,當(dāng)某些線程的循環(huán)次數(shù)較少,或者提前break,即使已經(jīng)完成了也需要等待其他線程。
GPU存儲(chǔ)器
上文提到SM的Warp數(shù)量關(guān)系到延遲隱藏的效果,而Warp的數(shù)量又受限于寄存器、SharedMemory等因素,所以我們有必要學(xué)習(xí)一下GPU的存儲(chǔ)器。
分離式架構(gòu):CPU與GPU都有單獨(dú)的內(nèi)存和緩存,通過(guò)PCIe通信。
耦合式架構(gòu):GPU共享系統(tǒng)內(nèi)存和緩存,通俗講就是我們說(shuō)的集顯,常用于手機(jī),所以手游更考慮性能更考慮帶寬消耗,所以移動(dòng)端使用tbdr來(lái)渲染,充分利用片上內(nèi)存來(lái)降低帶寬消耗。
分離式架構(gòu)中GPU存儲(chǔ)管理:
因?yàn)樗俣瓤斓木彺?寄存器很貴,而顯存的速度,相比于核心的運(yùn)算速度相差了幾百個(gè)數(shù)量級(jí)(比如計(jì)算只需要1個(gè)時(shí)鐘,加載數(shù)據(jù)卻需要400個(gè)時(shí)鐘,嚴(yán)重拖后腿行為了),所以需要一套分等級(jí)的緩存體系。以下是GPU存儲(chǔ)器的訪問(wèn)速度:
存儲(chǔ)類(lèi)型寄存器(幾萬(wàn)個(gè)/SM)共享內(nèi)存L1緩存L2緩存紋理、常量(顯存)全局內(nèi)存(顯存)訪問(wèn)周期11~321~3232~64400~600400~600
1、寄存器是最快的,我們寫(xiě)Shader,定義的變量、數(shù)組、結(jié)構(gòu)體就是存放于此。每個(gè)SM都有自己一定數(shù)量的寄存器,SM為每個(gè)Warp分配其需要的私有的寄存器。也正是因?yàn)槊烤€程私有寄存器,所以GPU的Warp線程切換是0成本的,不像CPU有額外的線程上下文切換的消耗。假設(shè)一共就1000個(gè)寄存器,一個(gè)Shader使用10個(gè)寄存器,那么最多可以分配給1000/(32*10)=3個(gè)Warp;但如果一個(gè)Shader使用了11個(gè)寄存器,那么最多1000/(32*11)=2個(gè)Warp。所以說(shuō)寄存器數(shù)量能夠影響該SM的Warp數(shù)量上限。
但是如果實(shí)在不夠寄存器分配了,那么還會(huì)有LocalMemory,將溢出的寄存器存儲(chǔ)到LocalMemory,而其本質(zhì)為全局內(nèi)存,延遲很高,使用L2緩存。
2、共享內(nèi)存:SM內(nèi)所有Warp共享的內(nèi)存,一般可用于頻繁操作的數(shù)據(jù)臨時(shí)存放,處理完成后再寫(xiě)入到GlobalMemory。我們?cè)赟hader里定義如:groupshared uint MaxLuminance;
3、紋理與常量都有其對(duì)應(yīng)的紋理、常量緩存,典型的是紋理周?chē)臄?shù)據(jù)也許不是內(nèi)存連續(xù)的,但會(huì)以周?chē)袼剡M(jìn)行緩存。
總結(jié)
結(jié)合GPU架構(gòu)與其邏輯管線,分析了SM的執(zhí)行流程。SM是GPU執(zhí)行器的核心部件,負(fù)責(zé)執(zhí)行Shader程序。在SM中,Shader以lock-step方式并行執(zhí)行,通過(guò)大量Warp切換來(lái)隱藏指令的延遲和提高GPU的效率。后續(xù)文章將使用相關(guān)debugger工具進(jìn)行Shader Profiler,分析Shader指令延遲(Stall)的原因,進(jìn)一步優(yōu)化Shader性能。另外,了解CUDA編程非常有助于理解。
參考:
https://www.cnblogs.com/timlly/p/11471507.html
Life of a triangle - NVIDIA's logical pipeline
編輯于 2023-05-12 13:40?IP 屬地廣東
gpu渲染
渲染
shader
贊同 172
分享

發(fā)布一條帶圖評(píng)論吧
2 條評(píng)論
默認(rèn)
最新

cyberlink
講的不錯(cuò)
05-31?·?IP 屬地陜西
回復(fù)喜歡

link
能否介紹下d3d和opengl到顯卡驅(qū)動(dòng)的封裝的過(guò)程等內(nèi)容呢?這樣好和程序編譯串起來(lái)
05-20?·?IP 屬地上海
【知乎】【Shader篇】GPU架構(gòu)與其邏輯管線的評(píng)論 (共 條)
