苗瑞霞,張雪蘭,譚星浩,方華啟
(1.西安郵電大學 電子工程學院,陜西 西安 710121;2.芯來智融科技有限公司,湖北 武漢 430074)
由于不斷提高的嵌入式設備性能的需求和ARM指令集不能向后兼容的特性導致了在ARM平臺上的人工智能算法不能很好地移植和維護。文獻[1]和文獻[2]分別闡述了從數據量化的方式和進行數據量化時,采用非線性量化的方式來分析此方法對嵌入式神經網絡加速的可執行性。此結論為文章中提出采用8位定點數卷積模型來代替ARM CMSIS-NN模型中提出的16位定點數卷積模型這一操作奠定了理論基礎。同時,從現有的神經網絡加速優化的方向來看,本文主要采用了模型參數低精度量化的思想[5-8]進行設計,完善了當前ARM中不支持8位數據處理的神經網絡平臺的短板。并為RISC-V平臺的神經網絡模型發展做出補充。
嵌入式設備的芯片處理能力和內存資源都是有限且珍貴的,這一限制使得卷積這一復雜且運算量巨大的算法在嵌入式平臺的部署成為難點。并且在嵌入式平臺部署神經網絡的情景時,需要解決性能和內存資源緊缺的問題[9-11]。同時,由于目前RISC-V沒有自身專用的神經網絡函數處理庫和專用的嵌入式設備。針對以上闡述問題,本文主要采用了從軟硬件協同的角度,提出了一種面向RISC-V處理器的卷積神經網絡系統的實現方法,將模型參數量化與RISC-V硬件平臺結合的方式來更大程度上優化神經網絡在嵌入式平臺上的部署。首先提出采用針對嵌入式平臺的低精度定點量化的方法,在保持精度的同時減小了數據位寬,進而減小了神經網絡模型在嵌入式設備上部署的內存限制,采用RISC-V指令架構[12-14]來完成卷積神經網絡模型的底層實現以縮短運算的執行時間,提升整體的性能。本設計完善了神經網絡中數據低精度的處理,減輕了內存資源對神經網絡在嵌入式設備上部署的限制,同時又將神經網絡和RISC-V相結合,不必承受遭受困擾ARM許久的兼容性問題的干擾,可以用在更廣泛的高性能嵌入式設備中。
(1)寄存器操作的R類型指令
(2)用于短立即數和訪存load操作的I型指令
(3)用于訪存store操作的S型指令
(4)用于條件跳轉操作的B類型指令
(5)用于長立即數的U型指令和用于無條件跳轉的J型
(1)指令只有6種格式,所有指令都是32位位寬,b便于指令解碼。例如ARM-32、x86-32中包含許多不同格式的指令,增加解碼的成本。
(2)RISC-V指令架構含有3個寄存器操作數,與x86-32不同的是,后者讓源操作數和目的操作數共享一個字段。這就需要多使用一條move(搬運)指令,來保存目的寄存器的值,增加了代碼量。
(3)所有指令,總是在同一位置的讀寫寄存器的標識符可以大大簡化解碼操作,增加了指令的執行效率。
(4)這些格式的立即數字段總是符號擴展,符號位總是在指令中最高位。這一特征表明可能成為關鍵路徑的立即數符號擴展,可以在指令解碼之前進行,對于RISC-V的模塊化指令特點具體見表1。
CMSIS-NN其實是作為基于ARM的Cortex處理器微控制器的硬件抽象層,其本質是一個擁有眾多神經網絡功能函數接口的模型庫。該庫是高效神經網絡的內核集合。使用者可以通過各種易于使用的標準化軟件界面更快地編寫軟件而不用去了解這些功能函數的實現方法以及實現流程,只需要按照需求去調用相應的功能函數即可。

表1 RISC-V的模塊化指令集
同時其一致的API(應用程序編程接口)提高了軟件的可移植性和可重用性。通用軟件庫和接口提供一致的軟件框架。同時,作為獨立于編譯器的一層,它允許使用者選擇大部分主流的編譯器。
內核代碼主要由兩部分組成:NN-Functions和NN-Support[15,16]函數,應用程序代碼可以使用這些函數來實現機器學習的功能。NNSuppor庫包括實用功能,如數據轉換和激活函數表,服務于NN庫的函數。應用程序代碼也可以使用這些實用功能來構造更復雜的NN模塊。ARM CMSIS-NN神經網絡內核架構如圖1所示。

圖1 ARM CMSIS-NN神經網絡內核架構
該卷積神經網絡利用了輸入由圖像組成的方式約束了體系結構。與常規神經網絡不同的是,ARM CMSIS-NN的圖層具有3個維度排列的神經元:寬度、高度、深度。例如CIFAR-10[17,18]中的輸入圖像是經過激活的輸入量,并且大小為32*32*3(寬度、高度、深度)的尺寸。一個層中的神經元將只連接到它之前的一個小區域,而不是以完全連接的方式連接所有神經元。此外,CIFAR-10的最終輸出層具有尺寸1×1×10,因為到ConvNet體系結構結束時,我們將整個圖像縮減為類分數的單個矢量,并沿深度維度排列,如圖2所示。
在CIFAR-10輸入圖像的大小僅為32*32*3(32寬、32高、3色通道),因此常規神經網絡第一個隱藏層中的單個完全連接神經元將具有32*32*3=3072權重。此時權重數量還在可管理范圍內,但很明顯,此完全連接的結構不會擴展到更大的圖像。例如輸入更大的圖像200*200*3將導致具有200*200*3=120 000個權重的神經元。顯然,這樣會造成浪費和大量的參數將迅速過度擬合,而這樣的輸入僅僅是一張尺寸較大圖片的輸入帶來影響。如Alexnet[19]神經網絡中的計算量達到上億級,但是乘法運算就至少需要7億次以上。卷積層的計算公式如式(1)
(1)
式中:w是權重矩陣,b為偏置,f是激活函數,x是每一層的輸入,Y是每一層的輸出。從公式可以看出輸入與權重的乘積隨著卷積層數的增加都是呈指數型增長的,數據量的增大對嵌入式設備的綜合要求就越高,由此可見嵌入式神經網絡平臺受到自身資源因素限制。其最常見的3層神經網絡如圖2所示。
P標準擴展:封裝的單指令多數據(Packed-SIMD)指令。P擴展細分了現有的寄存器架構,提供更小數據類型上的并行計算。P指令集擴展增加了RISC-V CPU IP產品的DSP算法處理能力,其支持8、16、32位數據操作。通過添加RISC-V P指令集擴展,RISC-V CPU現在可以以更低的功耗和更高的性能運行這些各種DSP應用程序。同時,P指令集封裝的SIMD指令(單指令多數據指令)代表了一種合理復用現有寬數據通路的設計,這樣更加有利于組合指令完成復雜的運算,有了更多實現方式,以便于移植和維護。
SIMD,即Single Instruction, Multiple Data,一條指令操作多個數據。是CPU基本指令集的擴展。主要用于提供fine grain parallelism,即瑣碎數據的并行操作,同時更多用于圖像處理,圖像的數據常用的數據類型是RGB565、RGBA8888、YUV422等格式,這些格式的數據特點是一個像素點的一個分量總是用小于等于8 bit的數據表示。如果使用傳統的處理器做計算,雖然處理器的寄存器是32位或是64位的,處理這些數據卻只能用于它們的低8位,這樣對于內存的使用效率略低。如果把64位寄存器拆成8個8位寄存器就能同時完成8個操作,計算效率提升了8倍。
首先是協處理器的接口,課題中使用的蜂鳥E200處理器核的接口是借鑒的Rocket Core的協處理器接口,為了和其區分開來,名為EAI(extension accelerator interface)。其32位的EAI指令格式如圖3所示。

圖3 EAI 指令編碼格式
課題采用支持協處理器訪問存儲器資源的RISC-V處理器核是因為其可以擴大處理器處理的指令范圍。在LSU(load store unit)中有特定的協處理器接口資源,這就使得基于EAI接口的協處理器可以訪問主處理器能訪問的存儲資源。
其實現的原理是在LUS中有預留的接口資源。另外在主處理器中有控制信號,即為了防止主處理器中的存儲訪問指令和協處理器中存儲訪問指令造成競爭,主處理器有專用的控制信號阻止后續的指令繼續訪問存取器,以此來規避競爭。另外協處理采用的是基于ICB總線的Valid和Ready握手機制。所以,只要初期的LSU單元能夠接受多個存取器訪問指令,那么協處理就可以發送多個存儲器訪問指令。這樣也可以加快在數據處理中執行效率。同時,主處理器也能在協處理器沒有訪問操作的時候拉低其獨占信號,使得主處理器中的后續指令得以執行,協處理器控制模塊控制框架如圖4所示。

圖4 示例協處理器控制模塊框架
能效分析如下:以3*3的平面卷積來看,啟動指令需要一個周期,協處理器從數據緩存中讀取到一個矩陣元素,那么采用流水線的方式去連續讀取,第一行的所有矩陣元素相加之和就需要4個周期,從第二行開始后,在本行的基礎之上,還要增加上一行的部分累加和,所以需要7個周期,每一個列累加直接從緩存中拿出計算結果即可在一個周期內計算完成,那么完成一個3*3的矩陣運算就需要22個時鐘周期,這是純C++實現的矩陣乘加性能的3~5倍。
在嵌入式平臺方面,有限的計算和內存能力使得在它們上部署NN推理模型必須進行軟件算法優化,如圖5所示,本文采用純軟件模擬和協處理器輔助實現采用8位定點量化網絡模型。為了保證對比實驗結果的準確性,采用Convolve->Relu->MaxPool->Convolve->Relu->MaxPool->Relu->MaxPool->FullConnected->Softmax的流程,其中分開了卷積部分以區分優化前后的卷積。

圖5 實現流程
在軟件層次,對ARM CMSIS-NN算法模型中替換了RISC-V的P標準拓展指令,具有單指令多數據的特性的DSP專用指令集,在SIMD(單指令多數據)指令中可以最多同時處理8個8位的數據,并且不需在ARM的模型中將8位數據符號拓展成16位,經對比,在內存的使用效率上和數據的處理效率上分別提高了2倍和8倍,同時DSP不僅支持浮點也支持定點數據運算,所以為了保證數據的正確性,還在軟件層次增加了飽和計算、舍入和移位的功能函數。
協處理器層次,因為在P標準的拓展DSP指令的大量使用下,會需要更多的乘加寄存器,并且為了保證定點計算的數據正確性,也增加了硬件方面的定點飽和,舍入和移位的實現和更多寄存器的實現,最后為P標準的DSP指令做了適配和兼容,以及后期的簡易驗證是否能夠正確執行。
CMSIS-NN庫函數大多數使用16位MAC(乘加)指令,文中采用8位數據類型指令操作來實現16位數據類型指令的算法模型。CMSIS提供了相應函數arm_q7_to_q15()執行數據轉換。數據轉換分為兩個步驟:第一步將8位數據符號擴展到16位,它使用符號擴展指令(__SXTB16)進行數據轉換;第二步重新排列數據,使輸出遵循與輸入相同的順序,如圖6所示。

圖6 CMSIS-NN中q7_t轉換至q15_t
在對數據進行優化之前采用的是16位數據運算,關鍵代碼如下所示:
while (blkCnt > 0u)
in = arm_nn_read_q7x4_ia(&pIn);
in1 = __SXTB16(__ROR(in, 8));
in2 = __SXTB16(in);
#ifndef ARM_MATH_BIG_ENDIAN
out2 = __PKHTB(in1, in2, 16);
out1 = __PKHBT(in2, in1, 16);
#else
out1 = __PKHTB(in1, in2, 16);
out2 = __PKHBT(in2, in1, 16);
#endif
write_q15x2_ia(&pDst, out1);
write_q15x2_ia(&pDst, out2);
blkCnt- -;
上述代碼中,首先是循環展開處理的第一部分。每次計算4個輸出,旋轉8位數據并將兩個q7_t值符號擴展到q15_t值,再將剩余的兩個q7_t值符號擴展到q15_t值。在獲得拓展成16位數據之后再通過__PKHTB和__PKHBT指令將in1參數的位低16位與in2參數的高16位左移16之后的結果組合在一起成一個32位操作數。以此來實現在一個操作數中具有2個真實有效的由8位拓展而來的16位數據,之后在使用相應16位的mul指令進行運算。設計中的優化省略了數據拓展這一操作。關鍵代碼如下所示:
while (blkCnt > 0u) {
in = read_q7x4_ia((q7_t **)&pIn);
write_q7x4_ia(&pDst, in);
blkCnt- -;
}
優化后的卷積數據處理過程是直接將8位輸入分開讀取至8位位寬的內存指針中,在取出相應的存儲單元地址寫入數據即可。省略數據拓展操作后即不需要__PKHTB()和__SXTB16()指令操作,提升了存儲單位的使用效率且提高了單位時間內的數據操作數量,降低了運算時間。
卷積的矩陣乘法優化,優化前矩陣乘法函數關鍵代碼:
While (colCnt)
q31_t inA11, inA12, inA21, inA22;
q31_t inB1 = arm_nn_read_q15x2_ia (& pB);
q31_t inB2 = arm_nn_read_q15x2_ia (& pB2);
pA = read_and_pad (pA, &inA11, &inA12);
pA2 = read_and_pad (pA2, & inA21, & inA22);
sum = __SMLAD (inA11, inB1, sum);
sum2 = __SMLAD (inA11, inB2, sum2);
sum3 = __SMLAD (inA21, inB1, sum3);
sum4 = __SMLAD (inA21, inB2, sum4);
inB1 = arm_nn_read_q15x2_ia (& pB);
inB2 = arm_nn_read_q15x2_ia (& pB2);
sum = __SMLAD (inA12, inB1, sum);
sum2 = __SMLAD (inA12, inB2, sum2);
sum3 = __SMLAD (inA22, inB1, sum3);
sum4 = __SMLAD (inA22, inB2, sum4);
可以看出在arm-cmsis中對于矩陣的運算每次都是需要先將存儲中的數據地址通過指針傳遞給運算指令,然后重復上面圖6的步驟,對低精度的數據進行零拓展,然后在進行數據重新排序,對于處理器來講這樣的操作就會每次都造成額外的存儲訪問,在大量數據的存儲訪問操作環境中,這對卷積運算的處理效率有著很大的影響,甚至對于處理器的冒險協調模塊是個極大的挑戰。因此為了解決以上潛在問題,以及加速在卷積模塊中對于卷積運算所需要的數據處理,化簡后關鍵代碼如下所示,矩陣乘法核如圖7所示:
while (colCnt){
q31_t inB1 = *__SIMD32 (pB)++;
q31_t inB2 = *__SIMD32 (pB2)++;
q31_t inA1 = *__SIMD32 (pA)++;
q31_t inA2 = *__SIMD32 (pA2)++;
sum = __SMAQA (inA1, inB1, sum);
sum2 = __SMAQA (inA1, inB2, sum2);
sum3 = __SMAQA (inA2, inB1, sum3);
sum4 = __SMAQA (inA2, inB2, sum4);
colCnt- -;}

圖7 q7_t操作數輸出2×2矩陣乘法核
從上述代碼中可以看出,相較于之前的操作我們簡化了處理操作,通過將低精度的8 bit數據的地址直接通過指針的形式傳遞給RISCV-P拓展的SIMD運算指令,然后進行乘加運算。將其和圖6所示的計算過程相比,顯而易見的是單位操作數據量翻了一倍,同時節省了大量的存儲訪問指令,減輕了對片上存儲的壓力,降低了運算的復雜度,同時增加了算法的效率并增加了計算的吞吐量。圖7用圖形化的方式對矩陣乘法內核內循環的執行進行展示,在填充計算兩個空間相鄰輸出像素所需的兩個im2col緩沖區后,矩陣乘法內循環如圖7所示。在循環的每一次迭代中,從兩個im2col[20]緩沖器中的每一個(圖中的指針pBuffer1和pBuffer2)和從兩個權重庫(指針pWeight1和pWeight2)加載到寄存器中,所需的負載操作總數為4個。這樣,就有足夠的元素來在4個不同的累加器上設置4個_SMAQA指令操作數。因此,在矩陣乘法內核內循環的一次運行中,我們可以計算4個__SMAQA指令,它們對應于8位的MAC操作,而代價是4個負載指令,極大簡化了矩陣乘法函數。
同時,為了最大程度上的優化卷積運算因大量的數據而對存儲造成的壓力,課題中實現了im2col模塊,事實上,在卷積層中是通過計算濾波器權重與輸入要素圖中的一個小的接受區域之間的點積來提取新的特征圖。但是基于CPU實現的卷積分解為輸入重新排序和擴展(即im2col,圖像轉列)和矩陣乘法運算。im2col其實是將類似圖像的輸入轉換為代表每個卷積過濾器所需數據的列。但是要實現im2col的最大困難之一是內存占用量的增加,這就與意圖減少內存占用的初心違背,因為輸入圖像在im2col輸出矩陣中部分數據是重復的。為了緩解內存占用問題,并且保留im2col的性能優勢,為卷積實現了部分im2col內核。內核只會擴展有限數量的列,對于點積后矩陣的數據加以特征值的提取,因此足以獲取矩陣乘法內核,最大程度地減低內存占用,以及獲取輸入圖像的完整特征值。在此基礎上最大提升了存儲訪問的性能,同時保持了內存的最小開銷。im2col過程如圖8所示。

圖8 3×3內核的2D圖像上的im2col示例
另外發現隨著輸入的圖像格式的變化,對于卷積運算的效率也是有一定的影響。當輸入的數據批量是1,那么im2col其實就是平面卷積,在神經網絡中最常見的數據輸入格式有兩種,Channel-Width-Height(CHW),即首先是通道。Height-Width Channel(HWC),即最后一個通道。因此,在蜂鳥E200開發板上分別執行了HWC和CHW格式的數據來比較兩者im2col執行時間,結果顯示HWC格式具有更好的im2col性能。因此在后面的性能測試中,采用的也是基于HWC格式的數據輸入例如圖9所示的數據布局實驗結果。

圖9 CHW和HWC數據布局的實驗結果
SPIKE,RISC-V ISA模擬器,內部實現了多個RISC-V harts的功能模型。為了便于對項目維護并且進行后續的版本控制以及查看API何時擴展或呈現不兼容,SPIKE遵守版本控制方案,當進行向后不兼容的API更改時,將主要版本號遞增;添加新API時,次要版本號遞增;當以向后兼容的方式修復Bug時,修補程序版本號將遞增。同時SPIKE的主要公共API是RISC-VISA。目前C++與SPIKE內部接口的一個接口不被視為公共API,并且將在不增加主要版本號的情況下對此接口進行向后不兼容的更改。
本設計中所采用的開源ARM CMSIS-NN模型,使用軟件語言C++將模塊功能以代碼來實現。功能仿真測試通過開源的RISC-V拓展指令集自測試用例,測試處理器是否符合指令集架構的功能級仿真器SPIKE。該測試程序是由RISC-V架構開發者為了檢測處理器是否符合指令集架構中的定義而編寫的測試程序,但是由于本設計中對硬件方面的優化做出的改動為使用了RISC-V架構協處理器,并且自行設計拓展了RISC-V的DSP專用指令。原生SPIKE仿真器不支持DSP的拓展指令,因此在做仿真測試前需要在SPIKE功能仿真器上適配DSP功能。
為了量化卷積運算協處理器的加速效果,在此對軟件實現的卷積運算和協處理器提供一定量相同的隨機輸入數據,并且在SPIKE的仿真器上模擬設置和在蜂鳥E203 FPGA開發板上相同的16 MHZ的運行頻率來保證相同的輸入環境和相同的運行頻率,然后對比兩者分別所需要的時間。
實驗選取一個卷積神經網絡(CNN)示例的演示,卷積、ReLU激活、池化和完全連接的功能。實驗中使用的CNN基于BVLC Caffe的CIFAR-10示例。神經網絡由3個卷積層組成,散布有ReLU激活層和最大池化層,最后是一個完全連接地層。網絡的輸入是32×32像素的顏色圖片,將被分為10個輸出類別之一。設置32.3 KB的存儲權重,以及40 KB的激活權重,同時在實驗開始前需要調試好軟件環境。
實驗準備需要安裝工具Ubuntu Linux 18.04 LTS,以及使用sudo apt-get install make zip命令解壓縮python2.7 pythonpip,還有相應環境設置,如設置項目路徑等。同時還需要使用兩條硬件線將GPIO與BTN連接,硬件連線如圖10所示。

圖10 硬件連線
實驗測試參數設定結果見表2。

表2 卷積網絡輸入各參量
實驗條件控制:輸入數據為隨機,并且對比確認純軟件實現算法和協處理器實現算法的輸入相同,輸出結果對比結果完全相同,在此實驗環境下得出的兩種前景下的消耗時間見表3。

表3 測試結果數據對比
經軟件模擬RISC-V處理器,利用RISC-V處理器中可用的數字信號處理(DSP)擴展和集群的并行性,分別在開啟DSP拓展模式和未開啟DSP拓展模式下通過測試所得數據,如表4所示在q7_t數據類型上優化后的卷積神經網絡算法加速比為11.968 101 93,此數據在蜂鳥E203上采用8 M大小RAM測得。

表4 采用hbird-e-sdk使用riscv-cifar10測試所得數據
本文針對當前嵌入式神經網絡平臺逐漸提高的性能要求和部署神經網絡模型時受到寄存器資源限制這一現狀,設計了一款面向RISC-V的神經網絡嵌入式平臺模型,且采用8位點量化數據對ARM CMSIS-NN定開源的卷積神經網絡模型進行優化。分析了采用8位定點量化數據的必要性、將神經網絡部署在嵌入式設備上的必要性、ARM CMSIS-NN模型中的有待優化之處。實驗結果表明,RISC-V的拓展指令集在測試平臺可以驗證通過。優化后的神經網絡卷積模型在蜂鳥E203開發板上實現邏輯功能,并完成了SPIKE仿真分析和示例測試。由測試結果可知,性能加Cortex-M3提升了11.968倍。目前該模型已經用于蜂鳥E203嵌入式設備,下一步將面向特定的模型進行優化,對嵌入式神經網絡的全連接層、池化層進一步研究。