這是新的系列教程,在本教程中,我們將介紹使用 FPGA 實現(xiàn)深度學(xué)習(xí)的技術(shù),深度學(xué)習(xí)是近年來人工智能領(lǐng)域的熱門話題。
在本教程中,旨在加深對深度學(xué)習(xí)和 FPGA 的理解。
用 C/C++ 編寫深度學(xué)習(xí)推理代碼
高級綜合 (HLS) 將 C/C++ 代碼轉(zhuǎn)換為硬件描述語言
FPGA 運行驗證
在上一篇文章中,我們在 MNIST 數(shù)據(jù)集上創(chuàng)建并訓(xùn)練了一個網(wǎng)絡(luò)模型。從本文開始,為了在 FPGA 上運行推理處理,我們將首先用 C++ 編寫推理處理代碼。
在這篇 C++ 實現(xiàn)的第一篇文章中,我們開始針對卷積層的 C++ 實現(xiàn)。具體內(nèi)容是(1)卷積層的實現(xiàn),(2)運算校驗(C驗證,C/RTL協(xié)同驗證)(就是HLS的流程)。
卷積層實現(xiàn)
在上一篇文章中,我解釋了卷積層是對圖像的過濾過程,但是并沒有解釋輸入輸出通道如何處理,過濾時圖像的邊緣處理等。由于本文旨在實現(xiàn)層面的理解,因此我將詳細(xì)介紹這些要點。
處理 I/O 通道
在圖像處理中,對RGB輸入圖像進(jìn)行噪聲去除等濾波處理,并頻繁地進(jìn)行RGB圖像的處理。在這種情況下,卷積過程往往是針對每個通道(R/G/B)獨立完成的,輸入的G/B通道值不影響輸出的R通道結(jié)果。
每通道獨立卷積
另一方面,在卷積層中執(zhí)行的卷積過程中,所有輸入通道的值影響每個輸出通道。因此,對于輸出圖像的每個像素(輸出通道,Y坐標(biāo),X坐標(biāo)),所有輸入通道和周圍的像素區(qū)域都會參與計算,導(dǎo)致計算量非常大。
使用所有通道的卷積
另外,如上所述每個通道獨立卷積的卷積層稱為Depthwise Convolution。
這通常用于減少計算量的網(wǎng)絡(luò)模型,例如MobileNet(https://arxiv.org/abs/1704.04861)。
圖像邊緣處理
在對圖像進(jìn)行卷積處理時,圖像邊緣的處理往往是一個問題。
由于卷積過程在計算某個像素時使用了周圍像素,因此對于沒有周圍像素的像素,例如圖像邊緣的像素,就無法獲取周圍像素。
卷積神經(jīng)網(wǎng)絡(luò)主要通過以下兩種方式處理邊緣像素。
無填充:輸出圖像減少了輸入圖像的卷積區(qū)域。
補零:將輸入圖像預(yù)先用卷積區(qū)域擴(kuò)展,用零填充該區(qū)域,對原始輸入圖像進(jìn)行卷積處理。
沒有填充的卷積的圖形表示如下所示:在這種情況下,輸出圖像將是比輸入圖像小一個濾波器尺寸的區(qū)域(橙色部分)。如果內(nèi)核大小為 3(中心像素 +/-1),則輸出圖像大小在寬度和高度上都將為 -2,因為圖像之外的 1 個像素是無法進(jìn)行卷積的區(qū)域。
無填充卷積:輸出圖像縮小
接下來,零填充的圖形表示如下所示。在這個例子中,預(yù)先在輸入圖像的外部添加了一個值為0的區(qū)域(灰色區(qū)域),進(jìn)行卷積,這樣就不會出現(xiàn)圖像縮小現(xiàn)象。如果內(nèi)核大小為 3,則帶填充的輸入圖像大小在寬度和高度上均為 +2,因為 +1 像素將添加到屏幕外部且值為零。
零填充卷積:輸出圖像大小保持不變
在我們的模型中,我們在所有卷積層中使用零填充。
C代碼
如果根據(jù)目前為止的解釋用 C 語言實現(xiàn)卷積過程,它將類似于下面的代碼。
?void?conv2d(const?float*?x,?const?float*?weight,?const?float*?bias,?int32_t?width,?int32_t?height, ?????????????int32_t?in_channels,?int32_t?out_channels,?int32_t?ksize,?float*?y)?{ ???for?(int32_t?och?=?0;?och?=?height?||?pw?0?||?pw?>=?width)?{ ?????????????????continue; ???????????????} ???????????????int64_t?pix_idx?=?(ich?*?height?+?ph)?*?width?+?pw; ???????????????int64_t?weight_idx?=?((och?*?in_channels?+?ich)?*?ksize?+?kh)?*?ksize?+?kw; ???????????????sum?+=?x[pix_idx]?*?weight[weight_idx]; ?????????????} ???????????} ?????????} ?????????//?add?bias ?????????sum?+=?bias[och]; ?????????y[(och?*?height?+?h)?*?width?+?w]?=?sum; ???????} ?????} ???} ?}
此函數(shù)的解釋如下所示:
輸入
-- x: 輸入圖像。shape=(in_channels, height, width)
-- weight: 權(quán)重因子。shape=(out_channels, in_channels, ksize, ksize)
-- bias: 偏置值。shape=(out_channels)
輸出
-- y: 輸出圖像。shape=(out_channels, height, width)
參數(shù):-- width: 輸入/輸出圖像的寬度
-- height: 輸入/輸出圖像高度
-- in_channels:輸入圖像的通道數(shù)
-- out_channels:輸出圖像的通道數(shù)
-- ksize: 內(nèi)核大小
每個輸入/輸出的內(nèi)存布局shape=(...)如表格所示,但float x[in_channels][height][width];將其視為定義為三維數(shù)組。
卷積層的處理是一個6級循環(huán)。第一個三級循環(huán)確定輸出圖像上的位置,隨后的三級循環(huán)對該位置執(zhí)行卷積操作。
零填充在第 24-26 行完成。由于實際創(chuàng)建零填充輸入圖像是低效的,所以零填充是通過在訪問圖像外部時不參與乘積之和來實現(xiàn)的。
第31行是卷積過程中的積和運算部分,這個積和運算out_channels * height * width * in_channels * ksize * ksize進(jìn)行了兩次。這個卷積過程的操作數(shù)量非常大,在很多情況下,卷積層支配著卷積神經(jīng)網(wǎng)絡(luò)的執(zhí)行時間。這就是為什么計算單元比 CPU 多的 GPU 和 FPGA 更適合處理神經(jīng)網(wǎng)絡(luò)。
第37行是偏差處理部分。到目前為止,我還沒有觸及什么是偏差處理,但正如我在這里所寫的那樣,它是一個簡單地對輸出值進(jìn)行偏移的過程。這種偏差處理在輸入通道/內(nèi)核大小 (Y,X) 循環(huán)之外,因此處理步驟的數(shù)量非常微不足道。
運算檢查
作為對上一節(jié)創(chuàng)建的函數(shù)運行的確認(rèn),conv2d我們將比較結(jié)果是否足夠接近在 PyTorch 的 C++ API (libtorch) 上執(zhí)行的卷積計算。
每個測試包括以下兩個步驟。
C. 驗證
C/RTL 協(xié)同驗證
1、C 驗證類似于正常的軟件開發(fā),gcc只是用通用的編譯器編譯源代碼并檢查結(jié)果。
2、C/RTL協(xié)同驗證是使用AMD-Xilinx提供的高階綜合工具Vitis HLS進(jìn)行驗證。對于此驗證,HLS 首先將 C 源代碼轉(zhuǎn)換為 Verilog HDL 等 RTL。然后在 Vivado 中對生成的 RTL 執(zhí)行功能仿真。
在這個邏輯仿真中,將一個類似于C驗證的數(shù)據(jù)序列輸入到創(chuàng)建的電路中,確認(rèn)輸出結(jié)果是否正確。
本節(jié)以后的內(nèi)容將以運行創(chuàng)建的源代碼的形式進(jìn)行說明。
源代碼將在后面發(fā)布。
運行環(huán)境
運行環(huán)境面向 Linux 機(jī)器。不支持 Windows/Mac 操作系統(tǒng)。此外,由于預(yù)裝gcc版本,該發(fā)行版針對 Ubuntu 18.04。難以自行準(zhǔn)備運行環(huán)境的朋友,看看就行。
需要以下工具。
Vivado ?2020.2(推薦 2019.2)
cmake >= 3.11
cmake比較麻煩,因為它需要的版本比apt等包管理器可以安裝的版本高,但是可以下載預(yù)構(gòu)建的二進(jìn)制文件(cmake--Linux-x86_64.tar.gz)。
C. 驗證
測試代碼/tests/ref/conv2d.cc的使用,我不會在本文中詳細(xì)介紹,但測試將是一個正常的隨機(jī)測試。
可以按照以下步驟構(gòu)建代碼。請將 -DVIVADO_HLS_ROOT 的值相應(yīng)地替換為安裝的 Vivado 的路徑。
$?mkdir?/build $?cd? /build $?cmake?..?-DVIVADO_HLS_ROOT=/tools/Xilinx/Vivado/2022.2 $?cmake?--build?.
使用以下命令進(jìn)行測試:如果沒有任何錯誤,那它就是成功的。
$?ctest?-V?-R?"conv2d_ref"
C/RTL 協(xié)同驗證
運行以下命令以使用 HLS 啟動 C/RTL 協(xié)同驗證。大約需要 5 分鐘。
$?ctest?-V?-R?"conv2d_hls_cosim"
當(dāng)執(zhí)行 C/RTL 協(xié)同驗證時,會自動創(chuàng)建一個 HLS 項目文件,因此可以使用它來檢查高級綜合和 RTL 仿真波形的結(jié)果。
要檢查這一點,請使用以下命令啟動 HLS:
$?vitis_hls?&
HLS 打開后,單擊“打開項目”,如下所示,導(dǎo)航到/build/tests/hls/conv2d/conv2d_hls_cosim目錄并單擊“確定”。
然后,HLS 綜合報告將顯示如下屏幕所示。
從此報告中,可以看到從 Performance Estimates 列創(chuàng)建的電路的估計性能,以及從 Utilization Estimates 看到在目標(biāo)設(shè)備上實施時的估計資源使用情況。
點擊頂部紅框包圍的區(qū)域,可以看到仿真的波形。
波形如下所示,可以看出可以通過某種方式讀取到該值,大概2000.00ns就能輸出y的第一個值。
通過這種方式,我們能夠創(chuàng)建一個邏輯電路,該邏輯電路使用 ?HLS 執(zhí)行卷積層計算,而無需特別注意 HW。
然而,由于這個電路根本沒有調(diào)整,它的設(shè)計只是實現(xiàn)功能,在后續(xù)會對此進(jìn)行優(yōu)化。
總結(jié)
在這篇文章中,用 C++ 實現(xiàn)了一個卷積層并確認(rèn)了它的運行。我們還在這個 C++ 實現(xiàn)上使用 HLS 進(jìn)行了高級綜合,并確認(rèn)它在 C/RTL 協(xié)同驗證中沒有任何問題。
原編輯::黃飛
評論
查看更多