3天上手,30天精通!——深度學習FPGA加速器設計
機器之心專欄
作者:Pooterko
本文的目標是幫助對於深度學習硬體加速器設計感興趣的朋友快速上手基於 FPGA 的深度學習加速器設計。
準備
以下是閱讀本文的基礎,請做好下列基礎準備後再上手加速器設計:
C 語言設計:熟練掌握 C 語言語法。
計算機體系結構知識:參考書《計算機組成與設計》,不需要熟讀全書,但要對一些加速器設計相關的基礎概念有比較清晰的理解和認識,如流水線、數據並行等。
高層次綜合
利用高層次綜合工具,開發者只需要編寫高級語言的代碼完成程序功能,就能將高級語言編寫的代碼綜合成相同功能的 RTL 級實現 (基於 Verilog 或 VHDL)。開發者還可以通過添加一些 pragma 的方式來指示和調整高層次綜合工具生成的硬體模塊的架構。整體而言,利用高層次綜合工具進行 FPGA 硬體開發的過程,應該是利用軟體語言的表達來描述硬體模塊的過程。目前,高層次綜合的代碼都是基於 C/C /OpenCL 的,所以對於沒有硬體設計基礎的朋友來說,利用高層次綜合工具可以大幅度地降低學習難度,縮短開發周期,加快設計迭代速度。
3 天入門實例
我們需要使用一個簡單的實例來進行入門學習。既然目標是讓大家快速上手深度學習加速器的硬體設計,那麼我們的實戰示例就選擇使用目前最火爆、最具代表性的深度學習演算法——卷積神經網路(CNN)。
我們選取卷積神經網路前向計算中耗時最長的卷積操作作為我們加速設計的目標。接下來,需完成以下步驟:
1. 準備工作(1 天)下載 Xilinx Vivado HLS 或 Xilinx SDx 工具鏈,利用官方 User guide 熟悉軟體工具的使用,包括:新建工程、配置工程參數、綜合流程等。
2. 軟體實現(1 天)實現卷積層的軟體版本 (C 語言版本),並封裝成一個頂層函數。綜合實現,結合高層次綜合工具的 report 和 analyze 工具分析理解所生成的硬體架構和預計性能。卷積運算的流程如下圖所示:
整個卷積層的輸入是 CHin 張輸入特徵圖,輸出是 CHout 張輸出特徵圖,每張輸出特徵圖的大小為 R×C。由於每一個「輸入-輸出」特徵圖對都有一個特定的卷積核用於卷積計算,所以總共有 CHout×CHin 個卷積核,每個卷積核的大小為 K×K。在進行卷積計算的過程中,每個卷積核滑過各自的輸入特徵圖,並使用當前滑過的窗口中的輸入特徵與卷積核內的權重完成卷積計算 (對應位置相乘,所有乘積累加),卷積的結果會累加到對應位置的輸出特徵上。因此,卷積計算的演算法流程如下式所示:
相應地,卷積層前向計算的軟體版本代碼如下所示:
本質上來說,卷積層前向計算的流程就是一個嵌套的 6 重循環,而在循環的最內層進行的是乘累加運算。在我們的示例中,我們用如下代碼放入 HLS 工具中進行綜合分析:
其中,數據類型我們指定為單精度浮點 (float),網路層參數如下:
設置硬體周期為 10ns,在 Vivado HLS 2018.3 中綜合得到該模塊運行延遲和資源開銷報告,其中延遲報告為 251376 個時鐘周期(具體數字可能略有差異)。
3. HLS 優化(1 天)在實現了卷積層的軟體版本後,我們可以嘗試對該代碼進行硬體並行優化,這裡我們用一個簡單的加速設計來幫助大家理解 HLS 的優化方法。從上面的卷積流程分析,我們不難發現:卷積計算過程中,不同通道的輸入/輸出特徵圖在參與計算的過程中沒有數據依賴關係,因而是可以並行處理的。在我們這個簡單的小例子中,我們計劃將輸入通道 (Input Channel Loop) 和輸出通道 (Output Channel Loop) 這兩個維度進行並行加速優化。因此,我們想要實現的加速器核心模塊示意圖如下:
我們使用 CHin 個並行的乘法器來並行處理不同通道的輸入特徵與其對應權重的乘法,這些並行乘法的乘積累加到一起,即為一個輸出通道的的卷積結果,這裡,我們把該模塊稱為一個處理單元 (Processing Element,簡稱 PE,即上圖中藍色框部分)。一個 PE 只負責一個輸出通道的卷積計算,我們可以把 PE 複製多份,形成上圖的結構,來並行處理所有輸出通道的卷積計算。總結來說,這個加速器調用一次可以並行處理包含 CHin 個輸入特徵點和 CHout 個輸出特徵點的卷積計算,而其中所有輸入特徵點都屬於不同的輸入通道,輸出特徵點也分屬於不同的輸出通道。要完成整個卷積層 6 重循環的計算,我們需要重複多次調用這個加速器。
現在我們就需要使用 HLS 來將上文設計的加速器描述出來,主要進行的代碼改動包括以下三部分:
循環重構:由於我們的加速器是在輸入通道和輸出通道兩個維度進行並行化,完成卷積計算需重複調用加速器多次,因此,我們需要將輸入通道循環和輸出通道循環放在最內層循環中。由於最內層的乘累加操作滿足結合律,卷積的 6 重循環的順序可以直接調整,而無需其它改動。
數組劃分:從上面加速器設計我們可以看出,我們需要並行地訪問輸入特徵 (In)、輸出特徵 (Out) 和權重 (W),而對應的並行度分別為 CHin、CHout 和 CHout×CHin。因此,在 FPGA 實現的時候,我們需要將這些數據劃分到多個 RAM 塊中以滿足並行訪問的需求。我們可以使用 Array Partition 來完成數組劃分,該 pragma 的具體語法請參考 Xilinx 官方文檔。
循環展開:為了描述出我們的加速器在輸入通道和輸出通道兩個維度進行了並行優化,我們需要使用 pragma UNROLL 來將這兩個循環完全展開,pragma UNROLL 的具體語法請參考 Xilinx 官方文檔。
綜合上述改動後,我們的代碼如下所示:
在 HLS 工具中重新綜合,我們可以發現延遲降到了 36876 個時鐘周期 (具體數字可能略有差異),有了 6.82 倍的加速效果。但當我們仔細看 HLS 的綜合報告時可以發現,雖然我們實現了一個並行加速器,可是這個加速器調用並沒有流水化起來:
因此,我們可以嘗試進一步優化提升性能:使用 pragma PIPELINE 將加速器設計流水化起來,該 pragma 的具體語法請參考 Xilinx 官方文檔。在使用 pragma PIPELINE 以後,之前的 pragma UNROLL 可以去掉以精簡代碼,這是由於 HLS 工具會自動將需要流水化的循環內部的所有子循環展開,這個優化會在 HLS 工具的 Console 里顯示。因此,我們的代碼調整如下:
在 HLS 工具中重新綜合,發現延遲降到了 29993 個時鐘周期 (具體數字可能略有差異),性能進一步提升了 22.95%。HLS 的綜合報告里也顯示加速器調用也已經流水化了:
從上面的綜合報告我們可以發現一個細節,雖然整個循環已經流水化了,但是 Initiation Interval 卻仍然不是理想情況的 1,即:不能做到每個周期都開始一個新的 Iteration 的計算。那麼問題出在哪裡呢?還有沒有進一步優化的空間呢?
通過分析代碼和 HLS 工具的 Warning 我們可以發現,問題出在 Out 這個數組上。在我們的實現中,Out 數組在內層循環的一個 Iteration 中參與了自加運算 ( =),即:先被讀,後被寫。如下圖所示,在執行 Iteration 0 的時候 (r=c=kr=kc=0),Out[0][0][0] 先被讀,然後被寫;然而在接下來執行計算 Iteration 1(r=c=kr=0, kc=1) 的時候,仍然是 Out[0][0][0] 先被讀,然後被寫。因此,如果我們使用 pragma PIPELINE 進行性能優化,相鄰的這兩個 Iteration 都需要操作 Out[0][0][0] 這個位置的數據,從而產生寫後讀 (RAW) 的數據依賴,即:程序必須等待 Iteration 0 對 Out[0][0][0] 的寫操作完成後,才能開始執行 Iteration 1。為了保證程序結果的正確性,HLS 工具不會將這部分循環完全流水化,進而導致性能的下降。
通過觀察我們可以發現:Out 數組的在程序中的訪問位置,只和 r、c 這兩個循環變數相關,而和 kr、kc 無關。我們可以利用這一點解決 RAW 數據依賴的問題。為了能將內層循環的計算完全流水化,我們決定將 Kernel Row 和 Kernel Column 兩重循環移到外層。如下圖所示,將循環流水化時,相鄰 Iteration 所訪問的 Out 數組的數據位置是不同的,不存在數據依賴,可以流水執行;而有 RAW 數據依賴的 Iteration 將在很遠的時間點發生 (R×C 個 Iteration 之後),此時對於該位置的寫操作早已完成,讀操作可以正常進行。這樣一來,我們實現了一個完全流水化的硬體架構,提升了加速器的整體處理性能。
按照上述優化調整後的代碼如下:
在 HLS 工具中重新綜合,發現延遲降到了僅僅 1782 個時鐘周期 (具體數字可能略有差異),相對於原始無優化版本的加速達到了 141.06 倍!HLS 的綜合報告里顯示加速器調用也已經完全流水化了:
綜上,我們就完成了一個高效的卷積運算加速器,而基本上利用 HLS 工具設計 FPGA 硬體加速器的入門也就完成了。總結來說,使用 HLS 設計 FPGA 加速器的一般化設計流程如下:
熟悉並理解目標演算法,通過軟體運行目標演算法,分析性能瓶頸所在;
實現加速目標的軟體版本,分析其中可以並行、流水化的部分,並構想可行的硬體架構;
通過代碼重構,加 pragma 等方法在 HLS 工具中描述目標架構,此過程需注意保證改寫的代碼功能性上與原代碼嚴格保持一致;
調整硬體參數配置,最大化利用硬體資源 (計算資源如 DSP、存儲資源如 BRAM) 來最大化加速器的性能。
30 天精通學習
在完成了上面的 3 天入門實例後,大家可以進一步學習和實踐 FPGA 加速器的設計,這一部分我們推薦大家利用 3 到 4 周的時間對相關知識進行詳細、系統的學習。高層次綜合的相關知識的學習我們推薦學習 Xilinx 公司推出的《Parallel Programming for FPGAs (中文版)》,該教程的下載地址是 https://github.com/xupsh/pp4fpgas-cn。大家要仔細閱讀這本書,並配合 https://github.com/xupsh/pp4fpgas-cn-hls 中的代碼實例理解高層次綜合的代碼風格和 pragma 的使用方法。關於高層次綜合的 pragma 請參照 Xilinx 官方的 pragma 詳解加深理解和記憶。開發板方面,我們推薦使用 Xilinx Pynq-Z2 進行上板實踐。
結語
硬體加速器設計是一個長期的、需要大量經驗積累的工作。本文僅為讀者提供了一個快速入門上手設計的分享,想要設計高效的硬體加速器的讀者還需要多關注前沿領域、多閱讀頂級學術論文、多上手設計實踐,在發掘潛在加速需求的同時提升自身設計加速器架構的能力。本文側重入門知識分享,後續會考慮出進階版實例、論文分享、設計總結等,希望讀者能夠多多反饋意見。
致謝
感謝北京大學高能效計算與應用中心羅國傑教授和 Xilinx 大中華區教育與創新生態高級經理陸佳華先生對本文的校訂和支持。
CECA 系統結構組簡介
北京大學高能效計算與應用中心 (Center for Energy-Efficient Computing and Applications,簡稱 CECA) 成立於 2010 年。該中心是北京大學在「985 工程」中建設的開展國際先進水平的高能效計算與應用研究的科研機構。該中心既是北京大學計算機系統結構學科的重要組成部分,又是一個交叉研究機構。CECA 系統結構組的主要研究方向包括:面向深度學習等應用的加速器系統架構設計,面向邊緣計算等新興應用的高能效系統研究,和基於新型存儲器的高能效存儲系統結構研究。
本文為機器之心專欄,轉載請聯繫本公眾號獲得授權。
------------------------------------------------
※結合符號主義和DL:DeepMind提出端到端神經網路架構PrediNet
※現實版「柯南變聲器」來了,搜狗變聲讓你聲音隨心變
TAG:機器之心 |