吳 煥,吳俊敏
(中國(guó)科學(xué)技術(shù)大學(xué) 計(jì)算機(jī)科學(xué)與技術(shù)學(xué)院,安徽 合肥 230000)
過去幾年,ILSVRC(ImageNet large scale visual re-cognition competition)[1]中陸續(xù)涌現(xiàn)出一批經(jīng)典的網(wǎng)絡(luò)結(jié)構(gòu)[2-7]。這些模型往往都具備預(yù)測(cè)精度高、計(jì)算量大以及內(nèi)存占用率高等特點(diǎn)。為解決卷積神經(jīng)網(wǎng)絡(luò)的硬件需求和計(jì)算資源不匹配的問題,本文基于深度學(xué)習(xí)框架Caffe[8],對(duì)卷積操作提出了一種新的加速策略。目前,一種主流的加速方法是使用GPU通用計(jì)算。GPU憑借其高效的并行計(jì)算能力,能夠快速處理大量數(shù)據(jù)。這在很大程度上緩解了神經(jīng)網(wǎng)絡(luò)計(jì)算量過大的問題,也促使了單機(jī)多卡、多機(jī)多卡的技術(shù)革新。然而,并不是所有硬件都支持GPU通用計(jì)算,比如arm等嵌入式設(shè)備以及Macbook等個(gè)人電腦。因此,如何在不支持GPU通用計(jì)算的硬件設(shè)備上加速卷積神經(jīng)網(wǎng)絡(luò)的前向推理是目前急需解決的問題。有研究結(jié)果表明[9],卷積層是卷積神經(jīng)網(wǎng)絡(luò)中耗時(shí)比例最高的部分,優(yōu)化卷積操作可以在很大程度上提升神經(jīng)網(wǎng)絡(luò)的整體性能。Caffe卷積包含兩個(gè)主要操作,一是im2col,二是gemm。本文通過增強(qiáng)這兩個(gè)操作的訪存連續(xù)性,從而加速卷積神經(jīng)網(wǎng)絡(luò)的前向推理速度。
隨著深度學(xué)習(xí)技術(shù)不斷發(fā)展,加速卷積神經(jīng)網(wǎng)絡(luò)的需求與日俱增。過去幾年陸續(xù)出現(xiàn)各種加速策略,總的來(lái)說有如下幾種。根據(jù)卷積定律,時(shí)域中的卷積操作等效于頻域中逐點(diǎn)相乘的復(fù)數(shù)乘法。該定律不僅適用于一維信號(hào),對(duì)二維圖像同樣有效。為此,研究人員[10,11]提出用快速傅立葉變換(FFT)實(shí)現(xiàn)二維圖像的卷積操作。理論上,卷積核尺寸越大,加速比就越加明顯。然而,目前主流的卷積神經(jīng)網(wǎng)絡(luò)均采用小尺寸卷積核,因此無(wú)法保證該方法總是優(yōu)于傳統(tǒng)的實(shí)現(xiàn)方式。此外,在轉(zhuǎn)換到頻域之前,F(xiàn)FT卷積還要求對(duì)輸入圖像以及卷積核同時(shí)補(bǔ)零,進(jìn)一步增大了內(nèi)存開銷。
卷積神經(jīng)網(wǎng)絡(luò)存在大量冗余[12]。不僅在層與層之間存在冗余連接[13],模型參數(shù)的比特位也存在冗余[14]。出于訓(xùn)練目的,傳統(tǒng)實(shí)現(xiàn)通常采用單精度浮點(diǎn)數(shù)。而在推理階段,可以將其替換成低精度定點(diǎn)數(shù)。有研究結(jié)果表明,在前向推理階段將浮點(diǎn)數(shù)量化成定點(diǎn)數(shù)不會(huì)影響模型的預(yù)測(cè)準(zhǔn)確率。結(jié)合單指令多數(shù)據(jù)指令集(X86上SSE4或者ARM上Neon),還可以同時(shí)處理多個(gè)8位定點(diǎn)。研究人員在此基礎(chǔ)上進(jìn)一步提出了二元權(quán)重模型[15-17]。在專用硬件的支持下,卷積中的乘加運(yùn)算全都可以用加法實(shí)現(xiàn)。
在大型網(wǎng)絡(luò)模型中,卷積層通常有上百個(gè)卷積核。雖然權(quán)值共享的特性已經(jīng)在很大程度上減少了模型參數(shù),但是網(wǎng)絡(luò)尺寸依然很大。有實(shí)驗(yàn)結(jié)果表明[18,19],在不影響模型準(zhǔn)確率的前提下,全秩卷積核可以分解成一系列基本的卷積核。模型參數(shù)不僅更少,卷積速度也更快。在此基礎(chǔ)上,還有針對(duì)非線性單元和深層網(wǎng)絡(luò)模型的相關(guān)工作[20]。
上述工作從各個(gè)角度提出了不同的加速策略。比如,快速傅立葉變換卷積、定點(diǎn)模型參數(shù)以及卷積核的低秩分解。與之不同的是,本文從訪存連續(xù)性來(lái)加速卷積操作。
卷積操作本質(zhì)上是求輸入和卷積核的點(diǎn)積。簡(jiǎn)單來(lái)說,就是將卷積核從左到右,從上到下以一定的步幅滑動(dòng)。每滑動(dòng)一次,卷積核與所在位置的圖像塊做點(diǎn)對(duì)點(diǎn)的乘加運(yùn)算。由于就地實(shí)現(xiàn)卷積的速度很慢,Caffe卷積以矩陣乘法的形式實(shí)現(xiàn)。結(jié)合MKL、OpenBLAS等高效的基本線性代數(shù)庫(kù),運(yùn)算速度能夠大幅提升。Caffe卷積包括兩個(gè)主要的操作,一是im2col,二是gemm。Im2col全稱為image to columns,負(fù)責(zé)將圖像塊展開成列向量。Gemm(general matrix-matrix multiplication)負(fù)責(zé)矩陣之間的乘法運(yùn)算。
為了更直觀地介紹Caffe卷積的工作原理,圖1展示了一個(gè)簡(jiǎn)單示例。Memory一行展示了輸入圖像和卷積核在內(nèi)存中的存儲(chǔ)情況,Convolution一行是卷積操作的宏觀表示,CaffeConvolution一行則是卷積在Caffe中的具體實(shí)現(xiàn)。數(shù)據(jù)塊尺寸通常表示成[n,c,h,w]的四維形式,n表示輸入或卷積核的數(shù)量,c表示通道數(shù),h表示高度,w表示寬度。圖1所涉及的數(shù)據(jù)塊尺寸如下:輸入(input): [1, 2, 3, 3];卷積核(kernel): [2, 2, 2, 2];輸出(result): [1, 2, 2, 2];補(bǔ)零(pad): [0, 0];步幅(stride): [1, 1]。在im2col的作用下,輸入(input)展開成矩陣data_col。每個(gè)圖像塊對(duì)應(yīng)一個(gè)列向量,其中第一列(i1,i2,i4,i5,i10,i11,i13,i14)是第一個(gè)圖像塊,第二列(i2,i3,i5,i6,i11,i12,i14,i15)是第二個(gè)圖像塊,并以此類推。接著,以kernel和data_col為實(shí)參,調(diào)用矩陣乘法函數(shù)gemm,得到卷積結(jié)果(result)。至此,一次卷積操作完成。
通過轉(zhuǎn)置操作改變輸入圖像的數(shù)據(jù)排列,可以同時(shí)提高im2col和gemm的訪存效率。如圖2所示,在輸入圖像(input)中,每個(gè)通道既可以表示成二維形式,也可以表示成一維的行向量。若以二維形式表示,數(shù)據(jù)按照寬度、高度、通道的順序存儲(chǔ),(i1,i2, …,i9)是第一個(gè)通道,(i10,i11, …,i18)是第二個(gè)通道。Im2col操作負(fù)責(zé)將所有圖像塊展開,其中(i1,i2,i4,i5,i10,i11,i13,i14)是第一個(gè)圖像塊。在轉(zhuǎn)置之前,要將第一個(gè)圖像塊展開,每次只能連續(xù)拷貝兩個(gè)元素,即(i1,i2), (i4,i5), (i10,i11), (i13,i14)。而轉(zhuǎn)置之后,每次就可以連續(xù)拷貝4個(gè)元素,即(i1,i10,i2,i11),(i4,i13,i5,i14)。輸入圖像往往有幾十甚至上百個(gè)通道,因此,對(duì)于轉(zhuǎn)置后的輸入(transposedinput)而言,每次就可以連續(xù)拷貝上百個(gè)數(shù)據(jù),即(i1,i10, …,i2,i11, …),(i4,i13, …,i5,i14, …),而轉(zhuǎn)置前的輸入(input)每次依然只能連續(xù)拷貝兩個(gè)元素。除此之外,轉(zhuǎn)置輸入圖像還順帶提升了gemm的訪存效率。由圖1可知,im2col后是卷積核矩陣(kernel)和展開結(jié)果(data_col)的矩陣乘法,也就是每個(gè)卷積核分別與每個(gè)圖像塊做點(diǎn)對(duì)點(diǎn)的乘加運(yùn)算。在圖1的data_col中,讀取一個(gè)圖像塊相當(dāng)于讀取一個(gè)列向量。而在圖2的data_col中,讀取一個(gè)圖像塊相當(dāng)于讀取一個(gè)行向量。在以行優(yōu)先存儲(chǔ)的體系結(jié)構(gòu)中,行向量的讀取效率更高,因此矩陣乘法的執(zhí)行速度也更快。
算法1是Caffe im2col的具體實(shí)現(xiàn),下面結(jié)合圖1進(jìn)行解釋。第1行對(duì)應(yīng)data_col的行數(shù),第2行和第3行負(fù)責(zé)處理data_col的一行。根據(jù)for循環(huán)提供的索引,可以計(jì)算data_col目前所在位置的偏移dst_offset,并反推出input的偏移src_offset。若dst_address落在補(bǔ)零區(qū)(某些卷積層要求在輸入圖像周圍補(bǔ)零,本文將這塊區(qū)域稱為補(bǔ)零區(qū)),給目標(biāo)地址dst_address賦0。否則,將源地址src_address的數(shù)據(jù)拷貝到目標(biāo)地址dst_address。最終,輸入(input)被展開成矩陣data_col,每個(gè)圖像塊對(duì)應(yīng)data_col中的一個(gè)列向量。
算法1:Caffe im2col
輸入:輸入圖像(input)以及卷積操作需要的各種超
圖1 Caffe卷積的實(shí)現(xiàn)細(xì)節(jié)
圖2 改進(jìn)后的im2col
參,比如補(bǔ)零(pad)、步幅(stride)等
輸出:輸入圖像的展開結(jié)果(data_col)
(1)for卷積核的長(zhǎng)度do
(2)for卷積輸出中單個(gè)通道的高度do
(3)for卷積輸出中單個(gè)通道的寬度do
(4) 根據(jù)索引計(jì)算dst_offset和src_offset
(5) src_address = base_address(input) + src_offset
(6) dst_address = base_address(data_col) + dst_offset
(7)if((不需要為輸入圖像補(bǔ)零)or(需要補(bǔ)零and當(dāng)前位置在非補(bǔ)零區(qū)))then
(8) data_col[dst_address]=input[src_address]
(9)else
(10) data_col [dst_address]=0
算法2是算法1的改進(jìn)版本,下面結(jié)合圖2進(jìn)行解釋。與算法1相比,算法2的輸入多了一塊事先開辟的空間transposedinput,這塊空間用來(lái)存儲(chǔ)轉(zhuǎn)置后的輸入。因此,空間開銷由原先的n*c*h*w變?yōu)?*n*c*h*w。值得注意的是,這塊空間一旦分配完畢就可以一直使用,因此其開銷不計(jì)入測(cè)量時(shí)間內(nèi)。根據(jù)3.1小節(jié),首先轉(zhuǎn)置input并存入transposedinput。接著將data_col中的所有元素置0,這么做可以去除循環(huán)中的條件分支語(yǔ)句。最外面兩層循環(huán)對(duì)應(yīng)data_col的行數(shù),最內(nèi)層循環(huán)負(fù)責(zé)處理data_col的一行。根據(jù)for循環(huán)提供的索引,可以計(jì)算data_col的偏移dst_offset,并反推transposed_input中的偏移src_offset。之后,便如圖2所示,批量拷貝源地址src_address處的數(shù)據(jù)到dst_address中。每次批量拷貝的數(shù)據(jù)量為kernel.cols*kernel.channels,即(i1,i10, …,i2,i11, …),(i4,i13, …,i5,i14, …)。算法2不僅能去除for循環(huán)中的條件分支語(yǔ)句,更重要的是通過轉(zhuǎn)置提升了im2col和gemm的訪存效率。
算法2: Optimized im2col
輸入: 輸入圖像(input)以及卷積操作需要的各種超參,比如補(bǔ)零(pad)、步幅(stride)等。并事先開辟一塊空間(transposed input),用以存儲(chǔ)轉(zhuǎn)置后的輸入
輸出:輸入圖像的展開結(jié)果(data_col)
(1) transposed_input = transpose (input)
(2) memset (data_col, 0)
(3)for卷積輸出中單個(gè)通道的高度do
(4)for卷積輸出中單個(gè)通道的寬度do
(5)for卷積核的高度do
(6) 根據(jù)索引計(jì)算dst_offset和src_offset
(7) src_address = base_address (transposed_input) + src_offset
(8) dst_address = base_address (data_col) + dst_offset
(9) memcpy (dst_address, src_address, kernel.cols * kernel.channels)
本節(jié)總共設(shè)置了3個(gè)實(shí)驗(yàn),測(cè)試環(huán)境是Intel Core i5,OS X EI Capitan 10.11.4,BLAS庫(kù)是Intel的MKL。默認(rèn)情況下,Caffe采用gcc的-o2編譯開關(guān)。為了更充分地利用編譯器,改進(jìn)前后的對(duì)比實(shí)驗(yàn)均采用-o3選項(xiàng)。第一個(gè)實(shí)驗(yàn)測(cè)試im2col、gemm以及總的卷積耗時(shí)。相比于im2col,gemm的計(jì)算時(shí)間更長(zhǎng),優(yōu)化效果也更大。第二個(gè)實(shí)驗(yàn)測(cè)試通道數(shù)和輸入圖像尺寸對(duì)性能的影響。實(shí)驗(yàn)結(jié)果表明,卷積加速比最高可超過100%,平均加速比在40%左右。最后一個(gè)實(shí)驗(yàn)測(cè)試卷積核尺寸對(duì)性能的影響。
本實(shí)驗(yàn)總共測(cè)試7種不同的通道數(shù),測(cè)試內(nèi)容包括im2col,gemm以及卷積各自的計(jì)算時(shí)長(zhǎng)。出于制表目的,所有結(jié)果均四舍五入到最近的整數(shù)。如表1所示,相比于im2col,gemm的計(jì)算時(shí)間更長(zhǎng),優(yōu)化效果也更加明顯。容易看出,矩陣乘是Caffe卷積中非常耗時(shí)的一個(gè)操作,可以從兩個(gè)方向進(jìn)行優(yōu)化。一是具體實(shí)現(xiàn),二是訪存效率。優(yōu)化矩陣乘法的實(shí)現(xiàn)可以考慮使用x86上的SSE4或者ARM上的NEON。這些指令又稱為SIMD指令,即一條指令可以同時(shí)處理多個(gè)數(shù)據(jù)。由于Caffe直接調(diào)用BLAS庫(kù),因此這部分優(yōu)化工作由BLAS庫(kù)的提供商負(fù)責(zé)。再者就是提升矩陣乘的訪存效率,也正是本文所做的工作。若圖像塊以列向量的方式存儲(chǔ)(如圖1所示),每讀取一個(gè)圖像塊都需要跨行讀取,訪存效率很低。而若以行向量的方式存儲(chǔ)(如圖2所示),就能連續(xù)讀取所有圖像塊,大大提升了矩陣乘法的執(zhí)行效率。
卷積層之間的輸入尺寸和通道數(shù)往往不盡相同,為測(cè)試優(yōu)化方法在不同卷積層上的加速效果,本實(shí)驗(yàn)測(cè)試了4種輸入尺寸以及7種通道數(shù)。實(shí)驗(yàn)所用的配置如下:輸入(input): [10,c,h,w];卷積核(kernel): [64,c, 3, 3];補(bǔ)零(pad): [1,1];步幅:(stride): [1,1]。沒有固定的字母代表實(shí)驗(yàn)變量,其中c為通道數(shù),h為高度,w為寬度。如圖3和表2所示,128×128的加速效果最為明顯,平均加速比高達(dá)93.18%。對(duì)于其它3種尺寸,加速比均在40%上下浮動(dòng)。
除了輸入圖像,卷積層中的卷積核也不大相同,最常用的是1×1和3×3兩種卷積核。出于完整性考慮,本實(shí)驗(yàn)加入了其它幾種不常用的尺寸,總共測(cè)試6種卷積核。
表1 im2col,gemm以及卷積耗時(shí)
圖3 不同輸入尺寸和通道數(shù)的加速比
Input SizeAverage Speedup32×3250.00%64×6443.51%128×12893.18%224×22430.64%
需要注意兩點(diǎn),一是由于Caffe在處理1×1卷積核時(shí)沒有用到im2col,因此本實(shí)驗(yàn)不測(cè)試這個(gè)尺寸。二是當(dāng)輸入圖像的尺寸過小時(shí),卷積時(shí)間很短,因此在4.3節(jié)沒有測(cè)試本實(shí)驗(yàn)使用的幾種輸入尺寸,而是在本節(jié)直接給出運(yùn)算時(shí)間,時(shí)間單位是微秒。如表3所示,虛線兩邊分別是改進(jìn)前后的卷積時(shí)長(zhǎng)。不論是哪種尺寸的卷積核,卷積速度均有不同程度的提升。
表3 不同卷積核的加速效果
為了加速卷積神經(jīng)網(wǎng)絡(luò)的前向推理速度,本文基于深度學(xué)習(xí)框架Caffe,對(duì)卷積操作做了兩點(diǎn)優(yōu)化。首先是優(yōu)化im2col的拷貝速度。在展開輸入圖像的過程中,Caffe im2col每次都只將輸入(input)的一個(gè)元素拷貝到data_col中。經(jīng)過優(yōu)化,每次就可以連續(xù)拷貝上百個(gè)元素。接著是矩陣乘法的訪存連續(xù)性。在Caffe的原始實(shí)現(xiàn)中,每讀取一個(gè)圖像塊都相當(dāng)于讀取一個(gè)列向量,訪存效率很低。經(jīng)過優(yōu)化,可以以行向量的形式連續(xù)讀取所有圖像塊,這在很大程度上提升了矩陣乘法的執(zhí)行效率。實(shí)驗(yàn)結(jié)果表明,應(yīng)用這兩個(gè)優(yōu)化點(diǎn),卷積操作的平均加速比在40%左右。