?? ?作者 陳巍 博士:存算一體/GPU架構(gòu)和AI專家,高級職稱。中關(guān)村云計算產(chǎn)業(yè)聯(lián)盟,中國光學(xué)工程學(xué)會專家,國際計算機學(xué)會(ACM)會員,中國計算機學(xué)會(CCF)專業(yè)會員。
作者 耿云川 博士:資深SoC設(shè)計專家,軟硬件協(xié)同設(shè)計專家,擅長人工智能加速芯片設(shè)計。
流式多處理器(Stream Multi-processor,SM)是構(gòu)建整個 GPU的核心模塊(執(zhí)行整個 Kernel Grid),一個流式多處理器上一般同時運行多個線程塊。每個流式多處理器可以視為具有較小結(jié)構(gòu)的CPU,支持指令并行(多發(fā)射)。流式多處理器是線程塊的運行載體,但一般不支持亂序執(zhí)行。每個流式多處理器上的單個Warp以SIMD方式執(zhí)行相同指令。
圖 3-1 流式多處理器在GPU架構(gòu)中的位置(以NVIDIA Tesla架構(gòu)為例,修改自NVIDIA)
3.1 整體微架構(gòu)
圖 3-3是流式多處理器(SM,AMD稱之為計算單元)微架構(gòu)(根據(jù)公開文獻和專利信息綜合獲得)。
流式多處理器按照流水線可以分為SIMT前端和SIMD后端。整個流水線處理劃分為六個階段,包括取指、譯碼、發(fā)射、操作數(shù)傳送、執(zhí)行與寫回。
圖 3-2 GPGPU的流式多處理器結(jié)構(gòu)劃分
SIMD即單指令多數(shù)據(jù),采用一個控制器來控制多組計算單元(或處理器),同時對一組數(shù)據(jù)(向量)中的每一個數(shù)據(jù)分別執(zhí)行相同的操作從而實現(xiàn)空間并行性計算的技術(shù)。
SIMT即單指令多線程,多個線程對不同的數(shù)據(jù)集執(zhí)行相同指令。SIMT的的優(yōu)勢在于無須把數(shù)據(jù)整理為合適的矢量長度,并且SIMT允許每個線程有不同的邏輯分支。
按照軟件級別,SIMT層面,流式多處理器由線程塊組成,每個線程塊由多個線程束組成;SIMD層面,每個線程束內(nèi)部在同一時間執(zhí)行相同指令,對應(yīng)不同數(shù)據(jù),由統(tǒng)一的線程束調(diào)度器(Warp scheduler)調(diào)度。
一般意義上的CUDA核,對應(yīng)于流處理器(SP),以計算單元和分發(fā)端口為主組成。
線程塊調(diào)度程序?qū)⒕€程塊分派給 SIMT 前端,線程在流式多處理器上以Warp為單位并行執(zhí)行。
圖 3-3 GPGPU的流式多處理器微架構(gòu)
流式多處理器中的主要模塊包括:
取指單元(I-Fetch):負責將指令請求發(fā)送到指令緩存。并將程序計數(shù)器 (PC)指向下一條指令。
指令緩存(I-Cache):如來自取指單元的請求在指令緩存中被命中,則將指令傳送給譯碼單元,否則把請求保存在未命中狀態(tài)保持寄存器(MSHR)中。
譯碼單元(Decode):將指令解碼并轉(zhuǎn)發(fā)至I-Buffer。該單元還將源和目標寄存器信息轉(zhuǎn)發(fā)到記分牌,并將指令類型、目標地址(用于分支)和其他控制流相關(guān)信息轉(zhuǎn)發(fā)到 SIMT 堆棧。
SIMT 堆棧(SIMT Stack):SIMT堆棧負責管理控制流相關(guān)的指令和提供下一程序計數(shù)器相關(guān)的信息。
記分牌(Scoreboard):用于支持指令級并行。并行執(zhí)行多條獨立指令時,由記分牌跟蹤掛起的寄存器寫入狀態(tài)避免重復(fù)寫入。
指令緩沖(I-Buffer):保存所有Warp中解碼后的指令信息。Warp 的循環(huán)調(diào)度策略決定了指令發(fā)射到執(zhí)行和寫回階段的順序。
后端執(zhí)行單元:后端執(zhí)行單元包括CUDA核心(相當于ALU)、特殊功能函數(shù)、LD/ST單元、張量核心(Tensor core)。特殊功能單元的數(shù)量通常比較少,計算相對復(fù)雜且執(zhí)行速度較慢。(例如,正弦、余弦、倒數(shù)、平方根)。
共享存儲:除了寄存器文件,流式多處理器也有共享存儲,用于保存線程塊不同線程經(jīng)常使用的公共數(shù)據(jù),以減少對全局內(nèi)存的訪問頻率。
3.2 取指與譯碼
圖 3-4 GPU執(zhí)行流程(修改自 GPGPU-Sim)
取指-譯碼-執(zhí)行,是處理器運行指令所遵循的一般周期性操作。
取指一般是指按照當前存儲在程序計數(shù)器(Program Counter,PC)中的存儲地址,取出下一條指令,并存儲到指令寄存器中的過程。在取指操作結(jié)束時,PC 指向?qū)⒃谙乱粋€周期讀取的下一條指令。
譯碼一般是指將存儲在指令寄存器中的指令解釋為傳輸給執(zhí)行單元的一系列控制信號。
圖 3-5 取指譯碼結(jié)構(gòu)
在GPGPU中,譯碼之后要對指令進行調(diào)度,以保證后繼執(zhí)行單元的充分利用。這一調(diào)度通過線程束調(diào)度器(Warp Scheduler)實現(xiàn)。
線程束是為了提高效率打包的線程集合(NVIDIA稱之為Warps,AMD稱為Wavefronts)。在每一個循環(huán)中的調(diào)度單位是Warp,同一個Warp內(nèi)每個線程在同一時刻執(zhí)行相同命令。
取指與譯碼操作過程如下:
取指模塊(I-Fetch)根據(jù)PC指向的指令,從內(nèi)存中獲取到相應(yīng)的指令塊。需要注意的是,在GPGPU中,一般沒有CPU中常見的亂序執(zhí)行。
圖 3-5 取指模塊
指令緩存(I-Cache)讀取固定數(shù)量的字節(jié)(對齊),并將指令位存儲到寄存器中。
對I-Cache的請求會導(dǎo)致命中、未命中或保留失?。≧eservation fail)。保留失敗發(fā)生于未命中保持寄存器 (MSHR) 已滿或指令緩存中沒有可替換的區(qū)塊。不管命中或者未命中,循環(huán)取指都會移向下一Warp。
在命中的情況下,獲取的指令被發(fā)送到譯碼階段。在未命中的情況下,指令緩存將生成請求。當接收到未命中響應(yīng)時,新的指令塊被加載到指令緩存中,然后Warp再次訪問指令緩存。
指令緩沖(I-Buffer)用于從I-Cache中獲取指令后對譯碼后的指令進行緩沖。最近獲取的指令被譯碼器譯碼并存儲在 I-Buffer 中的相應(yīng)條目中,等待發(fā)射。
每個 Warp 都至少對應(yīng)兩個 I-Buffer。每個 I-Buffer 條目都有一個有效位(Valid)、就緒位(Ready)和一個存于此 Warp 的已解碼的指令。有效位表示在 I-Buffer 中的該已解碼的指令還未發(fā)射,而就緒位則表示該Warp的已解碼的指令已準備好發(fā)射到執(zhí)行流水線。
圖 3-4 指令緩沖
當Warp內(nèi)的I-Buffer 為空時,Warp以循環(huán)順序訪問指令緩存。(默認情況下,會獲取兩條連續(xù)的指令)這時對應(yīng)指令在I-Buffer中的有效位被激活,直到該Warp的所有提取的指令都被發(fā)送到執(zhí)行流水線。
當所有線程都已執(zhí)行,且沒有任何未完成的存儲或?qū)Ρ镜丶拇嫫鞯膾炱饘懭?,則 Warp 完成執(zhí)行且不再取指。當線程塊中的所有Warp都執(zhí)行完成且沒有掛起的操作,標記線程塊完成。所有線程塊完成標記為內(nèi)核已完成。
相對于CPU,GPU的前端一般沒有亂序發(fā)射,每個核心的尺寸就可以更小,算力更密集。
3.3 發(fā)射
發(fā)射是指令就緒后,從指令緩沖進入到執(zhí)行單元的過程。
在(譯碼后的)指令發(fā)射階段,指令循環(huán)仲裁選擇一個Warp,將I-Buffer中的發(fā)射到流水線的后級,且每個周期可從同一Warp發(fā)射多條指令。
所發(fā)射的有效指令應(yīng)符合以下條件:
在Warp里未被設(shè)置為屏障等待狀態(tài);
在I-Buffer中已被設(shè)置為有效指令(有效位被置為1);
已通過計分板(Scoreboard)檢查;
指令流水線的操作數(shù)訪問階段處于有效狀態(tài)。
在GPU中,不同的線程束的不同指令,經(jīng)由SIMT堆棧和線程束調(diào)度,選擇合適的就緒的指令發(fā)射。
在發(fā)射階段,存儲相關(guān)指令(Load、Store等)被發(fā)送至存儲流水線進行相關(guān)存儲操作。其他指令被發(fā)送至后級SP(流處理器)進行相關(guān)計算。
3.3.1 SIMT堆棧
SIMT堆棧用于在Warp前處理SIMT架構(gòu)的分支分化的執(zhí)行。一般采用后支配堆棧重收斂機制來減少分支分化對計算效率的負面影響。
SIMT 堆棧的條目代表不同的分化級別,每個條目存儲新分支的目標 PC、后繼的直接主要再收斂 PC 和分布到該分支的線程的活動掩碼。在每個新的分化分支,一個新條目被推到棧頂;而當 Warp 到達其再收斂點時,棧頂條目則被彈出。每個 Warp 的 SIMT 堆棧在該 Warp 的每個指令發(fā)出后更新。
線程束分化
從功能角度來看,雖然SIMT架構(gòu)下每個線程獨立執(zhí)行,但在實際的計算過程中會遇到一些分支的處理,即有些線程執(zhí)行一個分支,而另外的線程則執(zhí)行其他分支。如果在同一個Warp內(nèi)不同的線程執(zhí)行不同的分支,就會造成線程束分化,導(dǎo)致后繼SIMD計算的效率降低。因此應(yīng)盡量避免線程束的分化。
圖 3-6 線程束分化與重聚合
SIMT堆棧功能
SIMT堆棧模塊可有效改善線程束分化引起的GPGPU執(zhí)行單元利用率下降的問題。
SIMT堆棧重點解決:
控制流嵌套問題(Nested Control Flow)
在控制流嵌套中,一個分支嚴重地依賴另一個分支,這極大影響了線程的獨立性。
如何跳過計算過程(Skip Computation)
由于線程束分支的存在,導(dǎo)致同一個Warp內(nèi)的有些線程并不必要執(zhí)行某些計算指令。
3) SIMT掩碼
SIMT堆棧中使用了SIMT掩碼(SIMT Mask)來處理線程束分化問題,以下例來說明掩碼如何控制整個Warp的執(zhí)行。
4)?SIMT 掩碼引起的死鎖問題
SIMT 掩碼可以解決Warp內(nèi)分支執(zhí)行問題,通過串行執(zhí)行完畢分支之后,線程在Reconverge Point(重合點)又重新聚合在一起以便最大提高其并行能力。
但對于一個程序來說,如果出現(xiàn)分支就表明每個分支的指令和處理是不一致的,容易使一些共享數(shù)據(jù)失去一致性。如果在同一個Warp內(nèi)如果存在分支,則線程之間可能不能夠交互或者進行數(shù)據(jù)交換,在一些實際算法中可能使用鎖定(Lock)機制來進行數(shù)據(jù)交換。但掩碼恰恰可能因為調(diào)度失衡,造成鎖定一直不能被解除,造成死鎖問題。
5) GPGPU解決死鎖的方法
圖 3-8 V100 Warp調(diào)度對比圖[2]
解決死鎖的方法如下:
NVIDIA為V100 中Warp內(nèi)的每個線程都分配了一個PC指針和堆棧,將PC指針的顆粒度細化到每一個線程中去,保障數(shù)據(jù)交換避免死鎖。(圖3-5)
為避免細粒度的PC指針和堆棧與GPU的SIMT執(zhí)行模型產(chǎn)生沖突,硬件仍以Warp為單位來進行線程調(diào)度。
使用了Schedule Optimizer(調(diào)度優(yōu)化器)硬件模塊來決定哪些線程可以在一個Warp內(nèi)進行調(diào)度,將相同的指令重新進行組織排布到一個Warp內(nèi),并執(zhí)行SIMD模型,以保證利用效率最大化[2]。
3.3.2 線程束調(diào)度與記分牌
進行線程束(Warp)調(diào)度的目的是充分利用內(nèi)存等待時間,選擇合適的線程束來發(fā)射,提升執(zhí)行單元計算效率。
在理想的計算情況下,GPU內(nèi)每個Warp內(nèi)的線程訪問內(nèi)存延遲都相等,那么可以通過在Warp內(nèi)不斷切換線程來隱藏內(nèi)存訪問的延遲。
GPU將不同類型的指令分配給不同的單元執(zhí)行,LD/ST硬件單元用于讀取內(nèi)存,而執(zhí)行計算指令可能使用INT32或者FP32硬件單元,且不同硬件單元的執(zhí)行周期數(shù)一般不同。這樣,在同一個Warp內(nèi),執(zhí)行的內(nèi)存讀取指令可以采用異步執(zhí)行的方式,即在讀取內(nèi)存等待期間,下一刻切換線程其他指令做并行執(zhí)行,使得GPU可以一邊進行讀取內(nèi)存指令,一邊執(zhí)行計算指令動作,通過循環(huán)調(diào)用(Round Robin)隱藏內(nèi)存延遲問題,提升計算效率。
在理想狀態(tài)下,可以通過這種循環(huán)調(diào)用方式完全隱藏掉內(nèi)存延遲。但在實際計算流程中,內(nèi)存延遲還取決于內(nèi)核訪問的內(nèi)存位置,以及每個線程對內(nèi)存的訪問數(shù)量。
內(nèi)存延遲問題影響著Warp調(diào)度,需要通過合理的Warp調(diào)度來隱藏掉內(nèi)存延遲問題。
1) 指令順序調(diào)整的原因
在同一個Warp的單個線程中,調(diào)整發(fā)送到ALU將要執(zhí)行的指令順序,可以隱藏掉一部分內(nèi)存延遲問題。例如讀取指令和加法指令使用的是不同的硬件單元,在第一個時鐘周期執(zhí)行內(nèi)存讀取指令之后,下一個時鐘周期不必等待讀取內(nèi)存指令,而是可以直接執(zhí)行加法指令,從而實現(xiàn)一邊計算一邊讀取,來提高整個運行效率。
但在實際情況中,后一個指令有可能是依賴于前一個指令的讀取結(jié)果。要解決該問題就需要GPU提前對指令之間的依賴關(guān)系進行預(yù)測,解析出指令之間的獨立性和依賴關(guān)系。
圖 3-11動態(tài)線程束示例(來源:WILSON W. L. FUNG等)
2) 記分牌與指令順序調(diào)整的方法
GPU在這里參考了CPU設(shè)計,為了解析指令之間的獨立性,采用順序記分牌(In-Order Scoreboard)。
對于單線程束情況,
每個寄存器在記分牌中都對應(yīng)一個標志位,用于表示該寄存器是否已被寫入數(shù)據(jù),如果置1則表示該寄存器已經(jīng)被寫入。
此時如果另外一個指令想要讀或者寫該寄存器,則會處于一直等待狀態(tài),一直到該寄存器的標志位被清零(表明之前寫寄存器操作完成)。這樣就可以阻止Read-After-Write和Write-After-Write的問題。
當順序記分牌和順序指令(In-Order Instruction)結(jié)合時,能避免Write-After-Read的問題。
圖 3-11數(shù)據(jù)沖突與流水線結(jié)構(gòu)相關(guān)
對于多線程束情況,將上述方法應(yīng)用到GPU時,還需要解決兩個問題:
由于有大量寄存器GPU,在每組寄存器中增加一個標志位將需要占用更多額外的寄存器。
在GPU中,一般會有很多個線程同時執(zhí)行同一指令,一旦其執(zhí)行的指令被打斷,會有很多線程同時訪問Scoreboard造成讀取阻塞。
對于多線程束情況,可通過動態(tài)記分牌解決上面的兩個問題:
圖 3-9 記分牌Entry流程
為每個Warp創(chuàng)建幾個入口(Entry),每個入口與一個即將被寫但操作尚未完成的寄存器相對應(yīng)。記分牌在指令進入指令緩沖區(qū)(Instruction buffer,I-Buffer)和寫操作完成結(jié)果存入Register File時能被訪問(圖3-6)
當一個指令從內(nèi)存中讀取出來放入到I-Buffer時,將該指令中的源寄存器和目的寄存器與Entry做比較,看是否有其他指令集已經(jīng)對該寄存器在做寫操作,如果有則返回一個bit Vector,與該寄存器一起寫入到I-Buffer中。如果該指令集的寫操作完成了,將會刷新I-Buffer中的該指令集寄存器的bit Vector,將bit Vector清除掉。
如果一個指令做寫操作,并需要將該寄存器放入Entry中,但是此Entry已經(jīng)滿了,那么該指令將會一直等待,或者被丟棄過一定時鐘周期后被重新獲取再次查看Entry是否滿[3]。
編輯:黃飛
?
評論
查看更多