天天看點

深入了解FPGA加速原理——不是随便寫個C代碼去HLS一下就能加速的

深入了解FPGA加速原理——不是随便寫個C代碼去HLS一下就能加速的

不要寫代碼做無意義的操作

寫這篇文章的起因是前段時間有人在網上問了我一個HLS的問題,他寫的C代碼HLS時報錯,問我是咋回事。可惜的是由于我水準低,沒玩過HLS,是以我也不知道是咋回事。不過我掃了一眼他的代碼,再加上他的介紹,我看出來他的代碼是幹了下面這樣一件事情:

深入了解FPGA加速原理——不是随便寫個C代碼去HLS一下就能加速的
深入了解FPGA加速原理——不是随便寫個C代碼去HLS一下就能加速的

他的FPGA的DDR裡存了一副較寬的圖像,這副圖像是三張圖檔拼接起來從HDMI接口傳進來存到DDR裡的。FPGA要做的事情大概是要再把這3幅圖像拆開分别做些仿射變換之後再拼成一副尺寸不同的大圖像。他寫的要HLS的代碼所做的事情就是把存在一起的拼接圖像從DDR中讀出來再分開存入DDR中,他說這是為了下一步好做雙緩存。

我了解到這個就覺得這個把圖像從DDR裡讀出來再換個位址存進去,然後啥計算都沒有進行,這個操作完全沒有任何意義啊!然後我就跟他說,你幹嘛要這樣弄,你要想把圖像分開來存,你把從HDMI存進DDR那個地方的代碼改一下,在那時直接分開存不就可以了。但他可能沒寫過verilog代碼,不知道還可以這樣改,是以我說的話估計他也沒聽懂。

要知道上述這種把資料從DDR中讀出來啥都沒幹又換個位址存進去的操作是完全沒有意義的。要達到這個目的在最初存進DDR的時候每到1/3和2/3行的時候改一下位址就行了。把資料從DDR裡讀出來又寫進去,完全是在浪費DDR的帶寬,浪費功耗。最關鍵的是浪費了時間。假如這個項目是要做VR顯示的加速,VR不就是要盡可能的低延時麼?就算DDR帶寬足夠你浪費,功耗你也不在乎,但這一讀一寫徒增的很多延時你也不思忖一下?

要想加速得先學一下并行計算原理

是以不論你是想用FPGA還是GPU做加速,都得先學一下加速的原理,和并行計算的程式設計技巧。不是你随便寫個代碼扔到GPU編譯器編譯一下,或是HLS一下扔到FPGA裡就能加速。網上講FPGA加速原理的資料不多,但講GPU程式設計的教程有很多。我看過B站上的《nVIDIA CUDA 高度并行處理器程式設計課程》的這部視訊.

GPU的瓶頸之一在于運算單元喂不飽

看完這個視訊我們就會知道GPU裡有很多并行運算單元,隻有當這些運算單元一直都在做運算的時候才能發揮出最大效率。或許你會問,難道GPU裡的運算單元還會有閑着的時候?有的,當你的代碼寫的不好,沒做多少運算就要從DDR裡取資料的時候,運算單元就有可能會因為取資料的延時而在那裡等待。GPU能統計出運算單元的使用效率,如果你的代碼在GPU上跑的時候運算單元的使用率隻有百分之二三十,那就是比較低了。是以GPU要提高效率,也是要充分利用片上記憶體,減少DDR的通路頻率,這和cpu要優化Cache命中率是一樣的。而這個事情GPU的編譯器會做一部分,但也做不到完全智能,是以還是需要程式設計者要有一定的技巧,不是任何代碼拿去就能效率高的。不過好在現在做機器學習用GPU加速都有别人寫好的架構,裡面GPU的代碼都已經有高手寫好了,不用我們去操心這個GPU代碼該怎麼寫的問題。

FPGA加速的優勢到底在哪裡?

FPGA的DDR帶寬比不上GPU,運作的頻率也比不上GPU,裡面的運算單數量元之前好像也比不上GPU,那FPGA的優勢到底在哪裡?

深入了解FPGA加速原理——不是随便寫個C代碼去HLS一下就能加速的

中文的比較FPGA和GPU的文章都似乎沒把這個問題講的太明白,這篇英文的講的還不錯,谷歌翻譯一下就能看,文章一開始是這樣說的:

Raw Compute Power: Xilinx research shows that the Tesla P40 (40 INT8 TOP/s) with Ultrascale+TM XCVU13P FPGA (38.3 INT8 TOP/s) has almost the same compute power. When it comes to on-chip memory, which is essential to reduce the latency in deep learning applications, FPGAs result in significantly higher computer capability. The high amount of on-chip cache memory reduces the memory bottlenecks associated with external memory access as well as the power and costs of a high memory bandwidth solution.

意思就是Tesla P40和那款FPGA在原始算力上是差不多的。但對于片上記憶體這一項,FPGA則有着顯著更高的計算容量,而片上記憶體在深度學習等應用中對于減少延時是至關重要的。大量的片上cache緩存減少了外部記憶體讀取帶來的記憶體瓶頸,也減少了高記憶體帶寬解決方案所需要的功耗和成本。

要知道通路外部存儲,比如讀寫DDR,是非常耗能的,可能資料在DDR和晶片之間跑來跑去的能耗比晶片本身做計算的能耗還要高。FPGA片上記憶體的大容量和靈活配置能力能減少對外部DDR的讀寫,這自然能緩解記憶體瓶頸,也就是減少延時并降低功耗。那FPGA具體是如何做到這點的呢?

FPGA最擅長實時流水線運算

啥叫實時呢?實時的意思就是我的運算速度和資料來的速度是一樣的,不比它快,也不比它慢。快了也沒用,資料都沒來你也算不了。慢了就不能叫實時了。比如1920×1080×60幀的視訊流,像素時鐘頻率約是150MHz,FPGA在對它進行處理的時候運算時鐘頻率就是150MHz,用的就是像素時鐘。一個時鐘周期來一個像素處理一個像素,來一行處理一行。

流水線的意思是,第一階段的處理和第二階段的處理不是第一全部完成了再第二,而是大部分重疊在一起的:

深入了解FPGA加速原理——不是随便寫個C代碼去HLS一下就能加速的

如上圖所示,CPU做圖像處理一般要等到一幀圖像傳到電腦記憶體之後才開始,如果要算兩次卷積,那就隻能先算第一個,再算第二個。

FPGA做運算就不是這樣。首先FPGA能在圖像一邊從HDMI或傳感器晶片傳過來的時候一邊就開始做處理。而CPU一般是等一幀圖像傳完了之後才開始處理的(嵌入式處理器能不能做到一邊傳一邊處理我也不知道)。比如說每秒60幀的圖像從傳感器傳出來一幀要1/60秒時間,CPU隻能等着1/60秒完了之後才開始處理,而FPGA則是可以從圖像開始傳的0時刻開始,等圖像1/60秒傳完時也幾乎處理完了,能達到最高的實時性。

如上圖所示,CPU做一次卷積的時間是要小于FPGA的,但FPGA能充分利用圖像正在傳輸的時間,還能并行流水線的進行第二次卷積,是以FPGA運算的延時最小,能達到最高的實時性。當然條件是把兩次卷積并行起來需要兩倍的運算單元,是以如果能夠不惜代價的增加運算單元,也就是裸堆FPGA晶片,是可以把大量計算全都并行起來達到實時速度的。

最高效的計算方式應該是怎樣的?

現在大家應該都知道了,計算的瓶頸往往不在于運算單元數量的不夠或是速度不夠高,而是在記憶體帶寬上,也就是資料讀寫來不及,運算單元喂不飽。那最高效的計算方式自然就是能充分利用片上高速記憶體,讓運算單元等待資料的情況從不發生,同時盡可能的減少了對外部DDR的讀寫,這也同時降低了功耗,是以既要速度快又要功耗低這兩者其實并不沖突。

為什麼FPGA比GPU的能耗更低呢?上面講的是原因之一,還有一個原因是FPGA是硬體可程式設計,是以它的資料通路是最直接的。比如說用FPGA算兩個數相加,那直接把兩個數從記憶體讀進來送到加法器那去加就行了。但在GPU中還要進行指令譯碼,知道是做加法之後還要把這兩個數往加法器那裡送,這中間資料要經過一些選通器才會被正确的送到加法器那而不是别的運算單元那裡。譯碼,資料經過選通,這些都會産生額外的功耗。而FPGA每次要做的運算都是固定的,是以不需要指令譯碼,也基本不需要資料的選通。

最高效的方式往往隻有一種,低效的方式有無數種

兩點之間直線最短,是以從A到B除非你會瞬間穿越,否則就是走直線這一條路徑是最短的。而如果你想繞彎則可以有無數種繞法。同樣的道理,進行一種運算的最高效方法在某種運算平台或架構下往往也隻有一種。就拿3x3的圖像卷積為例,怎樣算才最高效呢?

3×3的卷積每次需要同時取三行圖像中的三個像素值,也就是做第1行的卷積時需要取第0,1,2行的資料,第二行時需要取1,2,3行,再是2,3,4行。算了三行資料,就要讀取9次行資料,那麼在最低效的情況下,一副圖檔卷積完,圖像資料實際上是要被讀取三遍的。那要是5x5的卷積就是要讀取5遍了。這個讀記憶體的操作就太多了,自然會成為瓶頸。

深入了解FPGA加速原理——不是随便寫個C代碼去HLS一下就能加速的

那最高效的方法是什麼呢?自然是隻需要讀取一遍圖像就把不管3x3還是5x5的卷積都算完。實作的方法就是用片上記憶體把前h-1(h為算子高度)行的圖像資料都緩存着,這樣第h行資料來的時候就可以直接從片上記憶體中把前幾行的資料讀出來,不用都從外部DDR中讀取。而能完美實作這種運算操作的就隻有FPGA了,CPU Cache小,肯定是不行的。GPU行不行我不清楚,就算計算一層卷積GPU效率也可以很高,那要接着再算一層呢?

再回到上面那幅圖,CPU算兩次卷積是先讀一次圖像,算出結果存回去,算完一遍後再把剛才存的結果讀出來進行第二遍計算。也就是說CPU在進行連續圖像處理的時候必須把中間結果存回DDR去再讀出來。GPU估計也得這樣,但FPGA如果片上記憶體足則可以不這樣。FPGA可以把第一層卷積的結果也緩存在片上記憶體中,然後流水線式的進行第二層卷積。以3×3的卷積為例,當第一層算到出第三行結果的時候,就可以和之前緩存的兩行結果開始進行第二層卷積了。那麼第二層卷積的第一行結果就能在原始資料讀到第五行的時候出來,如下圖所示:

是以說如果FPGA片上記憶體和運算單元夠足,數量也管夠,它是可以實作圖像隻傳輸或讀取一遍,就把好幾層的卷積都算出來,中間結果和圖像都無需再存到DDR中然後再讀出來。既能實作最高的實時性,(16層3x3卷積隻需延時32行,才十幾分之一幀的時間),也能實作最小的能耗。

卷積代碼有現成的,無需HLS

都說FPGA程式設計難,是以現在有HLS,但其實FPGA如果隻做資料流運算,代碼寫起來就沒有那麼多,也沒有那麼複雜。資料流運算的代碼比較單純,沒有寫狀态機,寫CPU那麼難寫。而且資料流運算的模式往往也是簡單重複的,比如要做多個卷積核的很多層卷積,那這不都是卷積麼?寫一個卷積代碼出來然後複制粘貼,像搭積木一樣把它們搭起來不就行了?需要寫很多代碼麼?需要HLS麼?

下面這個庫“FPGA Image Process.v”檔案夾中的GrayOperator3x3.v就是一個對灰階圖像進行3×3卷積的執行個體代碼。代碼中的ArrayXX就是3×3的圖像資料陣列,給這個子產品增加一組或多組權重輸入,讓各組權重乘以對應的ArrayXX再把結果加起來就完成若幹組卷積了。卷積的結果可以輸出到别處,也可以再複制一個這個子產品,把結果當作PixelData輸入給這個子產品,就能進行下一層的卷積了。就這麼簡單。

深入了解FPGA加速原理——不是随便寫個C代碼去HLS一下就能加速的

繼續閱讀