本篇文章不講具體的主題和代碼細(xì)節(jié),就是隨便聊聊高性能計(jì)算和性能優(yōu)化,想到哪說到哪。文章分為4個(gè)部分,第一個(gè)部分聊聊并行算法,第二個(gè)部分系統(tǒng)地說一下性能優(yōu)化的方法論,第三個(gè)部分介紹一下性能分析,第四個(gè)部分介紹一下小結(jié)和感悟。說的東西不一定準(zhǔn)確,如果有錯(cuò)誤的地方,也麻煩各位批評(píng)指正。
1. 并行算法
目前單核處理器性能已經(jīng)碰到了瓶頸,想通過單核上的優(yōu)化去顯著提高算力已經(jīng)是一個(gè)非常困難的事情了。但是,現(xiàn)在對(duì)算力的需求卻日益劇增,科學(xué)與工業(yè)領(lǐng)域需要更多的算力進(jìn)行仿真模擬,游戲渲染需要更多的算力滿足人的娛樂需求,人工智能領(lǐng)域需要更多的算力進(jìn)行模型訓(xùn)練和推理服務(wù)。因而,對(duì)算力的巨大需求促使了英偉達(dá)的股價(jià)近十年內(nèi)一輪又一輪地暴漲以及目前異構(gòu)加速器遍地開花。所有人都知道這是塊肥肉,大家都想吃上一口。而從最底層角度而言,所有的一切都源于一件事情,并行算法可以將單核的任務(wù)劃分到多核異構(gòu)設(shè)備上從而實(shí)現(xiàn)加速。這個(gè)事情保證了,在一個(gè)可以并行的算法上,計(jì)算核心越多,理論上,你的代碼就能跑得越快,人類社會(huì)的發(fā)展也能越快。
不過,說實(shí)話,我一直覺得并行算法是一個(gè)非常難的課題,并行算法的思維是非常反人類的。對(duì)于一堆的事情,如果有相關(guān)性的話,人總是習(xí)慣于列個(gè)計(jì)劃,比如,要聚會(huì)了,一堆人在一起。大家先一起做飯,再一起吃飯,最后一起洗碗。干完一件事,再干下一件事。而并行算法在干一個(gè)什么事情呢,就是非得讓一些人在做飯,一些人在吃飯,一些人在洗碗,然后加一些亂七八糟的同步保證做飯的時(shí)候不會(huì)拿臟盤子盛飯,洗碗的時(shí)候不會(huì)把別人碗里沒吃的米飯倒了。正常人去看這個(gè)思維,覺得這人腦子多少是有點(diǎn)問題的。但是計(jì)算機(jī)里面經(jīng)常要這么干,導(dǎo)致人去理解并行算法的時(shí)候就會(huì)變得非常困難。比如scan算法,給定一串?dāng)?shù),求出這些數(shù)中,每一個(gè)數(shù)前面所有數(shù)的和。公式表達(dá)是這樣子,sum[i]=sum[i-1]+num[i]。這個(gè)計(jì)算過程有著很強(qiáng)的數(shù)據(jù)依賴,怎么在GPU里面去做?稍微一想就覺得費(fèi)勁。再比如排序,給一堆數(shù),怎么在GPU上開一堆線程把這些數(shù)排好?又比如圖里面的寬度優(yōu)先遍歷和最短路徑,怎么樣在GPU上跑起來?學(xué)計(jì)算機(jī)的同學(xué)學(xué)習(xí)了數(shù)據(jù)結(jié)構(gòu),各種樹和圖的算法。但是這些玩意怎么在GPU上跑起來,又得再開一門課來介紹。嘮嘮叨叨說這么多,其實(shí)都是想說并行算法的困難,這一點(diǎn)在圖計(jì)算領(lǐng)域尤其突出,而對(duì)于BLAS這樣的線性代數(shù)計(jì)算庫(kù)而言,又顯得相對(duì)容易,畢竟把一個(gè)矩陣拆分成多行或者多列,這個(gè)事情理解起來基本沒什么難度。當(dāng)然對(duì)于一些其他的線性代數(shù)計(jì)算庫(kù),也存在著較多的數(shù)據(jù)依賴,導(dǎo)致并行較為困難的情況。
2. 性能優(yōu)化方法論
這一節(jié)聊聊性能優(yōu)化方法論。當(dāng)不同的人談?wù)撔阅軆?yōu)化的時(shí)候,腦子里面想的東西還不一定是同一個(gè)事。當(dāng)搞網(wǎng)絡(luò)的人談性能優(yōu)化,想的可能是怎么降低網(wǎng)絡(luò)延時(shí),想的是網(wǎng)絡(luò)協(xié)議和socket相關(guān)的東西。當(dāng)搞數(shù)據(jù)庫(kù)的人談性能優(yōu)化,想的可能是怎么減少查詢數(shù)據(jù)庫(kù)的耗時(shí),想的可能是多級(jí)索引,盡可能地減少對(duì)磁盤的訪問。當(dāng)搞HPC的人談性能優(yōu)化,估計(jì)腦子里立馬就涌現(xiàn)出cache、分塊、SIMD相關(guān)的概念。所以這里還是得說明白,這篇文章里面講的性能優(yōu)化是HPC相關(guān)從業(yè)者腦子里的那種性能優(yōu)化。
我讀過一些論文,看過一些博客。對(duì)于不同問題的性能優(yōu)化,不用的人可能有不同的方法論術(shù)語。在深度學(xué)習(xí)訓(xùn)練的時(shí)候,有的時(shí)候先分IO瓶頸、CPU瓶頸、GPU瓶頸。有的時(shí)候又分為通信瓶頸、IO瓶頸、訪存瓶頸、計(jì)算瓶頸。林林總總,都有道理,都是在不同的角度去解析實(shí)際的問題。我有的時(shí)候想著,是不是能夠有一個(gè)統(tǒng)一的東西去盡可能地說明所有的問題。自然科學(xué)領(lǐng)域有一個(gè)詞叫做第一性原理,從一個(gè)最基礎(chǔ)的原理和規(guī)律開始,再不斷擴(kuò)展到其他事物。最近有一些搞深度學(xué)習(xí)理論的科學(xué)家用到這個(gè)詞,深度學(xué)習(xí)的第一性原理,想要通過一些數(shù)學(xué)手段來解讀深度學(xué)習(xí),從而指導(dǎo)模型的優(yōu)化,并往通用人工智能努力。我想了想,如果HPC也要整一個(gè)第一性原理,我覺得應(yīng)該就是訪存優(yōu)化。所有的技巧和努力都是在試圖跨過一道墻,也就是內(nèi)存墻,memory wall。之前oneflow的一篇博客提到了這個(gè),把內(nèi)存墻稱為AI算力的阿喀琉斯之踵。但我覺得AI可以去掉,內(nèi)存墻是算力的阿喀琉斯之踵。
OneFlow:AI算力的阿喀琉斯之踵:內(nèi)存墻 https://zhuanlan.zhihu.com/p/363041668
對(duì)于現(xiàn)代的計(jì)算機(jī)而言,相比于訪存,計(jì)算已經(jīng)足夠快了。為了讓訪存盡可能地快一點(diǎn),延時(shí)盡可能地少一點(diǎn),科研人員絞盡腦汁,因而有了多層cache,有了TLB,有了現(xiàn)代計(jì)算機(jī)架構(gòu)。近些年來又有人在折騰存算一體,也是挺有意思的一個(gè)領(lǐng)域。當(dāng)然,前面是硬件的角度,軟件的角度來說,那就是HPC。我覺得高性能計(jì)算這個(gè)領(lǐng)域本身的存在就是通過軟件的方式來減少memory wall的影響。我們?cè)囅胍幌拢绻麤]有了memory wall,訪存足夠地快,所有的計(jì)算單元都能在瞬間拿到數(shù)據(jù)并進(jìn)行計(jì)算,那么pipeline可以全部跑滿,計(jì)算單元一直保持在100%的利用率,那么HPC這個(gè)領(lǐng)域就沒有什么可以研究的東西了。
當(dāng)我們假定了訪存優(yōu)化是第一性原理之后,其實(shí),從某種角度而言,其他的東西也可以被涵蓋到訪存優(yōu)化這個(gè)大目錄下面。IO優(yōu)化本質(zhì)上就是對(duì)最底層的存儲(chǔ)結(jié)構(gòu)-訪存磁盤數(shù)據(jù)的優(yōu)化。通信優(yōu)化本質(zhì)上就是盡可能地加快不同計(jì)算節(jié)點(diǎn)訪問其他計(jì)算節(jié)點(diǎn)存儲(chǔ)單元的速度。而計(jì)算優(yōu)化,當(dāng)訪存已經(jīng)優(yōu)化地足夠好了之后,計(jì)算其實(shí)就基本上已經(jīng)沒有什么可以優(yōu)化的,無非是將循環(huán)展開,將地址對(duì)齊,將SIMD單元打滿。說完了訪存優(yōu)化這個(gè)第一性原理之后,接下來擴(kuò)展,說些具體的優(yōu)化技巧。當(dāng)我們?cè)谡f訪存優(yōu)化的時(shí)候,我們具體需要做些什么。總的來說,就是三板斧。一是減少數(shù)據(jù)搬運(yùn),二是減少數(shù)據(jù)訪存延時(shí),三是保證負(fù)載均衡。
2.1.?減少數(shù)據(jù)搬運(yùn)
現(xiàn)代計(jì)算架構(gòu)都是多級(jí)存儲(chǔ),需要一級(jí)一級(jí)地將數(shù)據(jù)往計(jì)算單元上搬。如何減少數(shù)據(jù)搬運(yùn),最主要的手段就是分塊,或者說tiling。之前在我的博客里面詳細(xì)地介紹了GEMM中的三級(jí)分塊策略,具體可以看看下面鏈接。
深入淺出GPU優(yōu)化系列:GEMM優(yōu)化(一)
為什么是三級(jí)分塊,不是四級(jí)或者兩級(jí)。因?yàn)镹V的GPU內(nèi)存結(jié)構(gòu)是三級(jí)的,global mem->shared mem,shared mem->register。通過三級(jí)分塊可以盡可能地提高數(shù)據(jù)的復(fù)用性。也就是盡可能地將數(shù)據(jù)放到更近的存儲(chǔ)結(jié)構(gòu)上進(jìn)行多次利用。而如果存儲(chǔ)結(jié)構(gòu)是四級(jí)的話,那就需要再多一次分塊。再舉個(gè)例子,對(duì)于稀疏矩陣的計(jì)算而言,常常會(huì)使用不同的存儲(chǔ)結(jié)構(gòu),本質(zhì)上也是為了減少對(duì)于內(nèi)存的訪問,壓縮效率越高,對(duì)于內(nèi)存的訪問就越少。具體可以看下面鏈接。
稀疏矩陣存儲(chǔ)格式總結(jié)+存儲(chǔ)效率對(duì)比:COO,CSR,DIA,ELL,HYB - Bin的專欄 - 博客園 https://www.cnblogs.com/xbinworld/p/4273506.html
而sparse里面五花八門的各種算法,核心也是根據(jù)數(shù)據(jù)排布的不同特性,盡可能地將數(shù)據(jù)放到shared mem或者其他更近的存儲(chǔ)結(jié)構(gòu)上cache住,從而獲得加速。又比如之前幫一個(gè)老哥優(yōu)化depthwise卷積,最后效果比pytorch快。
里面有個(gè)核心的技巧就是讓一個(gè)block計(jì)算多行,這樣的話,w可以在shared memory先放著,從而減少對(duì)global mem中w的數(shù)據(jù)的重復(fù)搬運(yùn)。再比如我們一直在強(qiáng)調(diào)cache命中率,要盡可能地提高cache命中率,為什么提高cache命中率可以提高性能。原因就是:如果cache不命中,那么就要到更下層的存儲(chǔ)結(jié)構(gòu)上去搬運(yùn)數(shù)據(jù),這個(gè)開銷就立馬上去了。所以我們說,要盡可能地保證數(shù)據(jù)連續(xù)訪問,其中最主要的一個(gè)原因就是提高cache命中率,從而避免不必要的數(shù)據(jù)搬運(yùn)。當(dāng)然,盡可能地保證數(shù)據(jù)連續(xù)訪問,還有一個(gè)原因是為了讓DMA搬運(yùn)數(shù)據(jù)的時(shí)候更加高效。這個(gè)可以歸納為減少數(shù)據(jù)訪存延時(shí)。接下來介紹一下減少數(shù)據(jù)訪存延時(shí)。 2.2.?減少數(shù)據(jù)訪存延時(shí) 這個(gè)部分跟減少數(shù)據(jù)搬運(yùn)的區(qū)別在于,減少數(shù)據(jù)搬運(yùn)是減少數(shù)據(jù)搬運(yùn)的次數(shù),但減少數(shù)據(jù)訪存延時(shí)指的是當(dāng)數(shù)據(jù)搬運(yùn)的次數(shù)已經(jīng)確定了之后,怎么讓每一次數(shù)據(jù)搬運(yùn)盡可能地更快。這個(gè)部分跟硬件綁定在一起,沒有辦法撇開硬件單獨(dú)去說這個(gè)事情。總的來說有這么幾個(gè)點(diǎn)。 首先是減少bank沖突,如何減少shared memory上的bank沖突,如何減少register上的bank沖突,這需要對(duì)于硬件的深入理解以及如何通過合理的數(shù)據(jù)排布來避免bank沖突。這個(gè)部分直接在GEMM里面也做過詳細(xì)的說明; 深入淺出GPU優(yōu)化系列:GEMM優(yōu)化(三) 其次是軟流水,有的時(shí)候叫double buffer,有的時(shí)候叫ping pong操作,我覺得跟預(yù)取也差不多,其思想都是一樣的,就是訪存和計(jì)算錯(cuò)開,讓流水更加順暢,減少計(jì)算等待訪存導(dǎo)致的空泡。這個(gè)部分也已經(jīng)在GEMM的博客上說得很詳細(xì)了。而NV則是把這一套思想放到了硬件,SIMT架構(gòu)和CUDA的這套軟硬件一體的方式,做得實(shí)在是太漂亮了,每當(dāng)我仔細(xì)地揣摩NV的這套架構(gòu)時(shí),我都會(huì)暗暗說一句,MD,真tm牛逼。通過warp切換來掩蓋訪存的開銷,再配合上標(biāo)量計(jì)算。可以最低程度地減少開發(fā)者成本,一個(gè)初學(xué)者,依葫蘆畫瓢寫個(gè)簡(jiǎn)單的CUDA kernel,很容易就能達(dá)到百分之七八十的硬件性能。再詳細(xì)地說一下這個(gè)東西,當(dāng)數(shù)據(jù)訪存的時(shí)候,就讓warp stall,而后再選一個(gè)warp進(jìn)行計(jì)算,通過這種方式交錯(cuò)開計(jì)算和訪存,讓訪存單元一直忙碌,帶寬打滿。這個(gè)操作跟雙緩沖或者說ping pong操作里面的思路是一樣,缺點(diǎn)也非常一致。雙緩沖需要雙倍的存儲(chǔ)空間來存儲(chǔ)額外的數(shù)據(jù),而SIMT架構(gòu)也需要大量的register file從而保證warp被選中后能立馬接著工作。也因此,對(duì)于NV的GPU,只有極少數(shù)情況需要開發(fā)者手寫double buffer進(jìn)行流水排布,比如GEMM相關(guān)的kernel需要。而且在GEMM里面,如果選擇的BM和BN比較大的話,開雙緩沖可能性能反而更差,因?yàn)樾枰嗟挠布Y源,導(dǎo)致實(shí)際工作的warp較少,warp切換難以掩蓋訪存的延時(shí)。BM和BN比較小的話,活動(dòng)的warp比較多,訪存的延時(shí)會(huì)較好地掩蓋掉,反而代碼跑得更快。除了GEMM的其他kernel,基本上不用去考慮軟流水的事情。而其他的一些硬件,因?yàn)橛布軜?gòu)相對(duì)來說比較落后,哪怕連非常簡(jiǎn)單的kernel,可能都需要開發(fā)者精心地設(shè)計(jì)pingpong操作才能獲得比較好的性能。 最后的技巧其實(shí)跟前面的軟流水是一個(gè)道理,就是切分更多的塊,啟動(dòng)更多的warp來掩蓋訪存延時(shí)。舉個(gè)例子,以sgemv為例,給定矩陣A[M,N],x[N]計(jì)算Ax。比如[50,1024]*100。M比較小,而N相對(duì)來說比較大的情況。如果還是按照之前的方式,讓一個(gè)warp負(fù)責(zé)一行的計(jì)算,那么只有50個(gè)warp在工作,而NV的卡上有幾十個(gè)SM,warp太小,那性能會(huì)非常地差。這個(gè)時(shí)候可以讓8個(gè)block來負(fù)責(zé)一行的數(shù)據(jù),每個(gè)block128線程,負(fù)責(zé)128個(gè)元素。這樣的話,更多的warp可以更好地掩蓋訪存開銷,性能自然可以上去。再擴(kuò)展一下,M很大,N又很小的情況,則可以讓一個(gè)線程負(fù)責(zé)一行的計(jì)算,這個(gè)過程就跟elementwise的優(yōu)化比較接近了。讓多個(gè)block負(fù)責(zé)一行從而切分更多的數(shù)據(jù)塊,有的時(shí)候叫做XX2D算法。之前斯坦福和google在SC20上發(fā)的一篇叫做《Sparse GPU Kernels for Deep Learning》論文里面最重要的一個(gè)優(yōu)化就是在SPMM里面,讓多個(gè)block負(fù)責(zé)一行的計(jì)算。
2.3.?保證負(fù)載均衡 關(guān)于負(fù)載均衡的話題,主要是在sparse里面談的比較多,核心都是保證兩個(gè),block/warp之前負(fù)載均衡,thread之間負(fù)載均衡。要么讓一個(gè)block負(fù)責(zé)一個(gè)大的和一個(gè)小的,保證每個(gè)block負(fù)責(zé)的大數(shù)據(jù)和小數(shù)據(jù)加起來差不多。要么在nnz維度上進(jìn)行切分,天然地保證每個(gè)block上的負(fù)載是均衡的,但這個(gè)時(shí)候需要引入額外的開銷來進(jìn)行判斷數(shù)據(jù)是數(shù)據(jù)哪一行。思想都是樸素且易于理解的。在這里面,我主要想說的是硬件層面的負(fù)載均衡。我們?cè)O(shè)想一個(gè)場(chǎng)景,如果有100個(gè)block,block的負(fù)載是均勻的。GPU上有50個(gè)SM,按常理,每個(gè)SM負(fù)責(zé)2個(gè)block,那SM之間肯定是負(fù)載均衡的。那有沒有可能是每個(gè)SM需要負(fù)責(zé)4個(gè)block,25個(gè)block在忙碌,25個(gè)block啥也不干?正常人都會(huì)說,不可能吧。那為什么不可能,這個(gè)分配具體是什么樣的呢?只有清楚地了解了這個(gè)硬件運(yùn)行機(jī)制才能清楚地說明白為什么不可能。這個(gè)機(jī)制其實(shí)就是在硬件層面如何進(jìn)行block索引號(hào)和SM索引號(hào)的映射,只有清楚這個(gè),才能保證軟件層面的負(fù)載均衡在硬件層面也是負(fù)載均衡的。比如在V100上,這個(gè)映射機(jī)制是下面這個(gè)樣子的。
block索引號(hào)和sm索引號(hào)映射關(guān)系
這一節(jié)介紹了性能優(yōu)化的核心,也就是訪存優(yōu)化。隨后又介紹了訪存優(yōu)化的三板斧,也就是減少數(shù)據(jù)搬運(yùn)、減少數(shù)據(jù)訪存延時(shí)、保證負(fù)載均衡。并通過大量的case來說明為什么這三者能夠有效地提高訪存性能。但遇到實(shí)際問題的時(shí)候,還是應(yīng)該case by case地進(jìn)行分析,切勿一上來就說我要分塊,我要避免哪哪哪的bank沖突,我要做負(fù)載均衡。一定要做足夠多的profiling工作之后,深刻地了解性能瓶頸之后,再著手進(jìn)行優(yōu)化。 3. 性能分析
本節(jié)介紹性能分析,也就是profiling。這個(gè)部分實(shí)在是太過于重要,所以必須單獨(dú)拎出來放在一節(jié)講。在分析任何具體的問題時(shí),都必須做充足的profiling。其實(shí)當(dāng)我們談優(yōu)化的時(shí)候,需要做的工作,就是profiling找到性能瓶頸,對(duì)性能瓶頸優(yōu)化,再profiling找到性能瓶頸,再對(duì)性能瓶頸優(yōu)化。不斷重復(fù),直到接近硬件瓶頸或者達(dá)到想要的目標(biāo)即可。
profiling可以簡(jiǎn)單地分為粗粒度和細(xì)粒度。粗粒度主要是判斷瓶頸是不是在GPU上,具體又是哪個(gè)kernel,典型代表就是nsight system工具,會(huì)顯示出整個(gè)程序的timeline。可以從timeline上直接清晰明了地看到瓶頸是在CPU還是GPU,如果是GPU,那又是在GPU的哪個(gè)kernel上。細(xì)粒度主要是判斷kernel或者一個(gè)函數(shù)里面的性能瓶頸在哪。
關(guān)于粗粒度的部分,其實(shí)需要說的并不多。主要是細(xì)粒度的部分需要好好嘮一嘮。怎么判斷一個(gè)kernel的性能瓶頸在哪里,這個(gè)事情其實(shí)并沒有那么簡(jiǎn)單。需要非常豐富的經(jīng)驗(yàn)才能做到真正游刃有余。上一節(jié)說到了性能優(yōu)化的核心在于訪存優(yōu)化,性能分析里面最重要的也是對(duì)于訪存的分析。像NV的卡,主要是分為四層,DRAM、L2 cache、L1 cache + shared memory、register。我們需要盡可能地保證每一層的數(shù)據(jù)搬運(yùn)效率都足夠地高,盡可能地把帶寬打滿。至于如何評(píng)估數(shù)據(jù)搬運(yùn)效率,可以詳細(xì)地看看nsight compute的使用教程。
如果發(fā)現(xiàn)實(shí)際帶寬比較差,數(shù)據(jù)搬運(yùn)效率比較低,這個(gè)時(shí)候就要去思考,是不是可以有辦法,通過分塊的一些技巧來減少數(shù)據(jù)搬運(yùn)。如果數(shù)據(jù)搬運(yùn)不能夠再減少了的話,是否可以通過一些方式來提高數(shù)據(jù)的搬運(yùn)效率,比如向量化訪存、合并訪問來提高對(duì)DRAM的訪存性能、避免bank沖突來提高對(duì)shared memory的訪存性能、調(diào)整分塊大小來讓更多的warp跑起來從而減少訪存的延時(shí),如果不是SIMT架構(gòu),就需要精細(xì)地設(shè)計(jì)各級(jí)訪存的pipeline,讓訪存操作盡可能地ping pong起來,從而讓訪存流水盡可能地連續(xù)起來不要被打斷。理論大概是這樣,但是每一個(gè)問題都有著不同的處理方式,每一個(gè)問題可能都是不同的瓶頸。總之就是萬變不離其宗,準(zhǔn)確地評(píng)估每一級(jí)存儲(chǔ)的訪存效率然后盡可能地提高每一級(jí)的訪存效率,盡可能地把訪存流水打滿,不要有空泡。
大概地說了一下profiling,然后再提一下MicroBenchmark。很多時(shí)候,硬件廠商給出的性能分析工具不可能覆蓋所有的東西,也不可能詳細(xì)地告訴開發(fā)者相應(yīng)的細(xì)節(jié),尤其是一代新硬件出來之后。比如說訪問global memory需要多少個(gè)cycle,訪問L2 cache需要多少個(gè)cycle,訪問shared memory需要多少個(gè)cycle,訪問寄存器時(shí)寄存器號(hào)和bank索引號(hào)的映射關(guān)系。這些東西都需要進(jìn)行詳細(xì)的microbenchmark才能讓我們更加了解硬件從而指導(dǎo)優(yōu)化。至于怎么指導(dǎo)優(yōu)化,這又可以另外開一個(gè)話題詳細(xì)地說。舉個(gè)例子,做矩陣乘法的優(yōu)化時(shí),可以大概地評(píng)估從shared memory訪存需要多少個(gè)cycle,然后再相應(yīng)地計(jì)算出往里面加多少條計(jì)算指令差不多可以掩蓋shared mem訪存的開銷。當(dāng)然這部分跟warp切換也有關(guān)系,不同的參數(shù)選擇會(huì)導(dǎo)致不同的warp活躍數(shù),warp切換的話,會(huì)產(chǎn)生不同的影響。再比如我們需要知道指令cache的大小,這樣的話,對(duì)于計(jì)算密集型的kernel,可以大概確定分塊的大小以及循環(huán)展開的次數(shù),這個(gè)時(shí)候說一下GEMM里面為啥很多介紹都是使用128*8的shared memory塊和8*8的寄存器塊,因?yàn)檫@個(gè)數(shù)字所需要的空間開銷,可以使得每個(gè)SM上跑大概4個(gè)左右的block,用上雙緩沖能掩蓋訪存開銷,并且計(jì)算的部分循環(huán)展開到8,使用的指令數(shù)差不多剛好可以在指令cache中放下。當(dāng)然這個(gè)數(shù)據(jù)針對(duì)不同的硬件又有所不同,所以每一代硬件都需要單獨(dú)進(jìn)行處理。關(guān)于這部分的內(nèi)容有很多的MicroBenchmark和CostModel相關(guān)的論文,大家有興趣可以去查一下。
4. 小結(jié)和感悟
4.1. 經(jīng)驗(yàn)or完善的知識(shí)體系
我從讀研究生開始做高性能計(jì)算,到現(xiàn)在也有些時(shí)間了,寫過一些kernel,涉及的領(lǐng)域挺多,從圖計(jì)算到科學(xué)計(jì)算,從科學(xué)計(jì)算再到深度學(xué)習(xí)。接觸過的硬件也有一些,主要是NV和AMD的GPU,其他國(guó)產(chǎn)硬件也接觸過兩三款。一直圍繞著性能優(yōu)化,做一些計(jì)算庫(kù),發(fā)揮硬件算力。這個(gè)過程里,也跟別人交流過許多。大家普遍會(huì)覺得高性能計(jì)算是一個(gè)非常注重經(jīng)驗(yàn)的領(lǐng)域,很多東西都是case by case的方式。每一個(gè)問題都需要進(jìn)行具體的分析,一個(gè)新手入門時(shí),遇到性能問題總是容易束手無策,常常會(huì)有疑問,“這個(gè)kernel性能應(yīng)該怎么提高?”“為什么別人的代碼比我快好幾倍?”有的時(shí)候苦思冥想都很難找到關(guān)鍵的點(diǎn),很難提高代碼的性能,長(zhǎng)久下去就喪失信心,不再愿意做這個(gè)方向。這些情況大多數(shù)都是因?yàn)榻?jīng)驗(yàn)不足,但所有人都是從新手村里出來的。有的時(shí)候想想為什么會(huì)出現(xiàn)這個(gè)原因,我覺得,主要還是因?yàn)檫@個(gè)領(lǐng)域目前關(guān)注的人還不是很多,而且中國(guó)關(guān)于這方面的學(xué)科建設(shè)并不成熟,在本科期間,只有極少數(shù)的人接觸過CUDA編程或者高性能計(jì)算,一般只有研究生才能接觸到,而且說實(shí)話,目前國(guó)內(nèi)做這方面工作的高校并不多。所以每一年培養(yǎng)出來的合格的人才其實(shí)非常少,而且只在少數(shù)top高校能夠找到合適的人。也因?yàn)楦愕娜松伲嚓P(guān)的文檔和資料非常少。在網(wǎng)上可以很容易找到某個(gè)深度學(xué)習(xí)算法的解析,但是很難找到詳細(xì)的中文文檔來告訴大家怎么分析性能怎么提高性能、怎么一步步地達(dá)到硬件極限。這也是為什么我之前寫了一系列深入淺出GPU優(yōu)化的博客,就是希望幫助更多的人順利入門。不過也因?yàn)槟壳芭囵B(yǎng)的人少,需求又逐漸多了起來,所以工作薪酬方面都比較nice,甚至常常有坑多人少的情況。扯多了繞回來,總之高性能計(jì)算是一個(gè)比較吃經(jīng)驗(yàn)的領(lǐng)域,什么叫做經(jīng)驗(yàn),經(jīng)驗(yàn)就是有著完善的知識(shí)體系,真正去做了很多實(shí)踐,形成系統(tǒng)的方法論,這就是經(jīng)驗(yàn)。
4.2. 通用代碼or針對(duì)性優(yōu)化
這些年隨著硬件產(chǎn)品的不斷涌現(xiàn),對(duì)計(jì)算庫(kù)的需求也越來越多。針對(duì)不同的硬件架構(gòu)、不同的算子、不同的數(shù)據(jù)排布都要針對(duì)性進(jìn)行優(yōu)化,這個(gè)工作量非常巨大。所以工業(yè)界和學(xué)術(shù)界都在思考著如何減少計(jì)算庫(kù)開發(fā)的人力成本,如何讓代碼在更多的硬件設(shè)備上跑起來且性能還OK,如何實(shí)現(xiàn)性能可移植可擴(kuò)展。目前TVM、XLA等相關(guān)的深度學(xué)習(xí)編譯器在這方面做出了突出的工作。采用了Halide的思想將一系列成熟的優(yōu)化技巧封裝到schedule中,再通過代碼生成的方式,就能達(dá)到不錯(cuò)的性能。再加上圖優(yōu)化,通過一系列的fusion就能優(yōu)化整張圖的計(jì)算耗時(shí)。總得來說,非常牛逼,也解決了一些工業(yè)界的問題,于是掀起了一股研究的浪潮。細(xì)細(xì)去想為什么TVM這樣的深度學(xué)習(xí)編譯器能夠成功,在某些程度上在于深度學(xué)習(xí)里面需要的算子相對(duì)簡(jiǎn)單,大部分還是GEMM、reduce、elementwise這樣的訪存模式,而這些東西在高性能領(lǐng)域研究地足夠透徹,并且現(xiàn)在主流的硬件已經(jīng)做得足夠牛逼,所以相對(duì)簡(jiǎn)單的優(yōu)化策略就能生成性能比較好的kernel。但如果真的要在工業(yè)場(chǎng)景落地,還是比較依賴于硬件廠商經(jīng)過細(xì)致調(diào)優(yōu)的計(jì)算庫(kù),當(dāng)然這兩者也不是對(duì)立關(guān)系,實(shí)際上是相輔相成的,相互配合使用,在不同的應(yīng)用場(chǎng)景中發(fā)揮作用。話再說回來,如果真的能夠?qū)崿F(xiàn)一套通用代碼來實(shí)現(xiàn)多種硬件設(shè)備,且保證性能OK,生產(chǎn)環(huán)境能用起來用的好,在科學(xué)計(jì)算和深度學(xué)習(xí)等多個(gè)領(lǐng)域都能work。這會(huì)是一個(gè)極其牛逼的工作。但是這里面需要花費(fèi)巨大的人力成本和時(shí)間成本,而且需要一定程度上的理論突破。個(gè)人覺得,還是學(xué)術(shù)界來主導(dǎo),然后一些天才型的開發(fā)者掌握合適的方向,再坐幾年冷板凳沒準(zhǔn)會(huì)有一些成果出來。然后再由工業(yè)界不斷地迭代幾輪,達(dá)到成熟。
4.3. 正確地評(píng)估和認(rèn)識(shí)
這里想說的倒不是profiling那些東西,而是說一下我的一些感悟。就是看待別人的一些工作時(shí),盡可能地理智客觀,如果有必要,最好動(dòng)動(dòng)手。有的事情,看著比較難,比如說寫一個(gè)kernel性能超過官方庫(kù)啥啥的,其實(shí)如果針對(duì)特定的數(shù)據(jù),特定的硬件,特定的庫(kù)版本,如果還不到硬件極限的話,要超越官方庫(kù)是一個(gè)相對(duì)簡(jiǎn)單的事情,無非是多一些hard code,根據(jù)數(shù)據(jù)特性和硬件特性,總是能把性能提上去。但有一些官方庫(kù)在特定的硬件上已經(jīng)做了非常好的優(yōu)化,盡量就不要再另外花時(shí)間去想著超越官方庫(kù)了,意義不大。有的事情看著比較簡(jiǎn)單,實(shí)際上會(huì)有各種阻力,中間可能會(huì)遇到各種困難。比如做計(jì)算庫(kù),很多時(shí)候功能可能比較簡(jiǎn)單,就像blas,但實(shí)際上要針對(duì)各種硬件,針對(duì)各種數(shù)據(jù)排布都能有一個(gè)比較好的性能,這個(gè)是非常困難的,里面所需要的精力和耗時(shí)也不是外行人能夠搞清楚的。總結(jié)一下,就是盡可能地跟專業(yè)的團(tuán)隊(duì)做專業(yè)的事情,如果對(duì)其他領(lǐng)域不是那么那么清楚,沒有真正寫過相關(guān)代碼,沒有踩過相關(guān)的坑,那不要亂下結(jié)論,不要總是拍腦子想事情。這一點(diǎn)其實(shí)非常重要,整個(gè)人類社會(huì)出現(xiàn)外行領(lǐng)導(dǎo)內(nèi)行的情況非常多,其實(shí)倒不可怕,最可怕的是外行覺得自己是內(nèi)行,總是瞎指揮。那小到幾人的團(tuán)隊(duì),大到一個(gè)國(guó)家,都容易出現(xiàn)問題。 ?
編輯:黃飛
?
評(píng)論
查看更多