1. Introduction
在CNN的計算過程中,各級存儲層次(DRAM、on-chip global buffer、Regs)之間的數據傳輸很復雜,從功耗的觀點來看,當前的CNN加速器是通信主導的,最小化通信是提高CNN加速器能效的關鍵。
最大化數據復用可以減少通信,數據復用依賴於卷積數據流,當前研究設計的數據流基於直覺/啟發式的分析,不能保證是最優的。
在給定的硬件資源(On-chip memory)下,搜索出一種數據流和架構最小化通信具有實用意義。
2. Background
2.1 Convolutional Layers

圖1和圖2展示了卷積的基本計算過程,它是一個7層的循環,包含對多種數據復用方式,如輸入復用(InR,一個輸入被多個卷積核使用)、卷積窗復用(WndR,一個輸入被多個重疊的滑動窗口復用)、權重復用(WtR,一個權重被多個輸入使用)、輸出復用(OutR,在整個計算過程中,輸出駐留在片上)。
2.2 Related Work
許多CNN加速器都是通過精心設計的數據流來優化目標的,他們聲稱數據流和/或加速器的優勢,但沒有解釋為什么設計本質上是最好的。
不是使用單一的數據流,一些研究已經將多個數據流集成到一個加速器中(增加了硬件成本),並根據層的大小選擇最佳的一個。
為了找到具有特定目標的最優數據流,一種可能的方法是全面考慮所有可能的loop orders和tiling sizes(即循環步長)。這就是DSE(設計空間探索)方法。
2.3 Preliminary: Red-blue Pebble Game(紅藍卵石游戲)
這是一個理論模型,用來估計兩層存儲器之間數據傳輸的最小容量。
3.Layer-wise lower bound of off-chip communication
3.1 Relation Between Convolutions and MM

將卷積層轉化為矩陣乘的方法:
輸入圖像:被unfold成輸入矩陣,每一行表示一個滑動窗的輸入,不同行對應不同的滑動窗。
卷積核:被reshape成權重矩陣,每一列表示一個卷積核的權重,不同列對應不同的卷積核。
輸出圖像:被reshape成輸出矩陣,每一列表示一個輸出通道的輸出,不同列對應不同的輸出通道。
reshap意味着重新組織元素,沒有增刪元素,因此對於卷積核和輸出圖像的轉化是“等價的”。但是對輸入圖像的unfold操作其實是有增加元素的,因為它丟棄了卷積窗重用特性,為了表征這種“不等價性”,文中引入了R表征卷積窗的重用次數:
注意,convolution-to-MM轉換只是用於推導的一個邏輯操作。在這篇文章的數據流或架構中,它不是一個真正的操作。
3.2 Theoretical Derivation
(1)矩陣乘法(MM)的訪存下界
不經優化
逐點計算:計算輸出矩陣的每個點需要2C的訪存,一共有AB個輸出點。
經優化
逐塊計算:每次不再是只計算一個輸出點,而是計算一個xy大小的塊,這樣的話輸入矩陣中的每個點就被復用了y次,同樣權重矩陣中的每個點就被復用了x次。這種方式產生的訪存量Q=每塊的訪存量(xC+yC)乘以輸出矩陣分的塊數(AB/xy)。由均值不等式可導出一個訪存下界,即2ABC除以√S,其中S是片上存儲的總量。並且當且僅當x=y=√S時,即由兩個輸入矩陣中讀入相等的數據量時,可以達到通信最優。這種方式得到的矩陣的訪存量要比最直接的矩陣乘實現減少√S的量。對應於實際的計算流程,要把幾乎所有的片上存儲全部分配給輸出部分,用來存儲輸出中間結果的時候可以達到最小的訪存量。
(2)卷積層的訪存下界
如果從直觀上對得到的訪存量公式做解釋,分子上是一個卷積層的Batch大小、輸出圖像長寬、通道個數、卷積核大小連乘積,這和矩陣乘法2ABC類似,分母就是√RS,S是片上存儲的大小,R是卷積窗重用每個元素最多被重用的次數,與矩陣乘法的通信下界公式相比,這里其實只多了一個√R,所以卷積當中訪存下界其實是比訪存最優的矩陣乘減少一個√R的倍數,這是卷積滑動窗重用R次的概念
對應到MM:
“A”= WoHoB
“B”= Co
“C”= WkHkCi
4. Communication-optimal dataflow
4.1 Dataflow with Minimized Off-Chip Communication

(1)數據流描述
這種數據流(數據調度)可以描述如下:對batch、output channel、output row、output column 進行tiling,tiling系數分別為b、z、y、x,也就是說每次迭代計算一個大小為bxyz的輸出block的Psums(之所以稱作Psums,是因為一次迭代只計算了k個input channel的累加和)。為了計算這樣一個輸出block(綠色部分),需要在input channel維度上進行多次iterations (partial update to the output block),每次iteration取k個input channel進行計算,那么輸入數據就需要:b×(x+Wk-1)×(y+Hk-1)×k=bx'y'k(紅色部分),權重數據就需要:Wk×Hk×k×z(紅色部分)。
文章提到“For an output sub-matrix, the needed inputs and weights are read from the off-chip DRAM exactly once.” 文章說對於一個輸出子矩陣,所需的inputs和weights只需要從片外DRAM中讀取一次。但是考慮到輸出矩陣由多個這樣的子矩陣構成,這時候inputs和weights的讀取就不止一次了,具體來說,對於weights而言,因為對於輸入通道Ci的循環位於output row、output column和Batch循環的內層(外層進行一次新的循環,內層循環已經完成一輪,也就意味着原來從DRAM導入到Gbuf的數據發生了覆蓋),因此有多少個子矩陣,就會對應多少次weights(CiWkHkCo)的重復讀取,即weights被重復讀取的次數為:BWoHo/bxy;同樣地,對於inputs而言,因為對應於輸入通道Ci的循環位於輸出通道Co和Batch的循環的內層,因此inputs被重復讀取的次數為:BCo/bz ,即輸出通道數除以每次迭代計算的輸出通道數。以上是根據數據流的偽代碼推出的結果,其實這個結論也可以從文章后面給的公式推導出來。
文章還提到的一點是,“For a fixed quadruple {b, z, y, x}, k does not affect the off-chip communication. However, under a given on-chip memory capacity, smaller k results in larger output sub-matrices, and thus, less output sub-matrices. Hence,k should be the smallest value, namely, 1.” 文章說對一個固定的四重組合{b,z,x,y},k(每次迭代計算的通道數,也就是每次從DRAM導入到GBuf的通道數)這個因子不會影響片外訪存,在給定的片上memory容量下,更小的k會得到更大的輸出子矩陣(因為可以勻出更多的的容量給Psums),也就相當於更少的子矩陣數目,從上一段的分析來看就是更少的weights重復讀取次數(BWoHo/bxy),所以文中建議k=1.
如下圖所示,k的取值(下圖用in_ch表示)其實是會影響片外訪存,因為當Ci/k大於1時(只要大於1,就存在Gbuf中weights數據的前后覆蓋),weights的訪存就是固定為WoHo/xy了,而在一般的實際應用中,通道數Ci往往比較大,不適合分配這么大的片上memory去存Ci個通道,也就是說一般情況下Ci/k這個值都是大於1的,所以文章的思路就是既然這樣,干脆就讓k=1,把更多的片上memory勻給輸出子矩陣,這樣就相當於xy可以取得更大,也就減少了weights的訪存次數:WoHo/xy。在我現在的設計中,我采用的優化思路是去從算法上減少Ci,也就是通過分組卷積讓輸入通道減為Ci/group,如此一來達到讓Ci/k這個值等於1,從而使得weights的訪存降低為1次。另一方面,從inputs訪存來看,通過分組卷積使得每組卷積的輸出通道減為Co/group,這樣inputs被重復讀取的次數降低為BCo/(group×bz)。

文章對於這種數據流更好的原因做了以下幾點歸納:首先,它充分運用到了OutR,因為Psums在整個計算過程中都駐留在片上,直到所有計算(partial update)完成,才會被一次寫回到DRAM。其次,它充分運用了WndR,在每個x'×y'區域,每個input被R個滑窗重用。然后,它也考慮到了InR,因為輸入被z個卷積核中的權重所復用,但這種復用不是充分的,因為z<Co。最后,也考慮了WtR,因為一個權重被bx'y'個inputs復用,同樣地WtR也是不充分的,因為一個權重只能被這一小塊輸入復用,其他塊計算時需要重新導入一遍。總而言之,這種數據流充分運用了OutR和WndR,也以一種平衡但非充分的方式結合了InR和WtR(InR和WtR可以結合下文圖8理解,其實就是計算陣列橫向和縱向的相互復用)。
(2)驗證提出的數據流達到下邊界
按照前面的數據流描述,輸出圖像一共被分為(BWoHoCo)/(bxyz)個blocks,對於每個block,需要WkHkCiz個weights和bx'y'Ci個inputs數據。因此DRAM的訪存為:
可以從第二個等式看出,DRAM訪存等於:block的數目乘以卷積核的總大小(WkHkCiCo),再加上對於輸出通道的遍歷次數(Co/z)乘以輸入圖像的總大小。也就是說,卷積核被重復訪問了(block的數目)次,輸入圖像被重復訪問了(Co/z)次,這與前面的分析是一致的。
然后文章針對上式做了一個近似,近似的條件為R≈WkHk/(D^2),x'≈Dx,y'≈Dy,其中D表示步長,x'和y'是輸入子矩陣的大小,x和y是輸出子矩陣的大小,文章提到當x>>1和y>>1時后面2個式子成立,原因在於:x=(x'-Wk)/D+1,y=(y'-Hk)/D+1,所以x'=Dx+Wk-D,y'=Dy+Hk-D(卷積光暈現象),例如卷積核尺寸為3,D=1,x=224,那么x'=1*224+3-1=226,兩者近似相等。上式近似后的結果如下:
進一步地,寫入DRAM的數據量為:BWoHoCo,這一數據量與分塊系數{b,z,y,x}無關,因為該數據流的部分和一直都是駐留在片上,直到計算完成才一次寫回到DRAM中。由此文章提出了2條最優通信的tiling參數原則:
第一條:為了平衡inputs和weights的loading,令bxy=u≈Rz,也就是bxy/R≈z,其中R是前文提到的卷積窗復用系數。
從后文mapping部分可以看到,之所以讓bxy/R≈z,是因為導入的inputs數據(bxy)可以被使用R個pass,而導入的weights(z)在每個pass都需要導入一次。
第二條:uz=bxyz≈S&k=1,即將大部分的片上memory分配給Psums。
bxyz就是輸出block的大小
運用第一條原則,並將寫DRAM數據量考慮進來,得到下式:
運用第二條原則,uz≈S,並且當WkHkCi/√RS>>1,此時可以忽略寫DRAM的部分,則:
這正是3.2中的理論下界。這個結論背后的基本原則是:使用最少的輸入產生最多的輸出,這意味着數據重用達到了最大化。
4.2 Workload and Storage Mapping with Minimized On-Chip Communication
按照ASPLOS 2017斯坦福的論文TETRIS所說,數據流問題可以分解為2個子問題,第一個是mapping 問題,即如何將一組卷積計算映射到PE陣列(圖7偽代碼的紅色部分),第二個是ordering問題,即如何buffer數據從而最大化片上數據復用(圖7偽代碼的綠色部分)。前面的討論其實都是圍繞第二個問題,即ordering問題,優化DRAM和on-chip memory之間的通信,下面文章進一步討論了第一個問題,即mapping問題。
最小化GBuf通信
文章首先提出,優化off-chip通信和on-chip通信之間有很大的區別。當最小化芯片off-chip通信時,由於問題的規模可以是任意的,但是硬件資源(片上memory)是固定的,所以tiling是必要的,並且工作負載是通過一系列連續的iterations完成的。然而,對於一次迭代,由於輸出子矩陣大小受on-chip memory容量的限制,因此可以設計PE數組大小和Reg容量,使硬件資源能夠一次處理迭代的工作負載。這種差異導致了不同的下界,加載的輸入和權重(在GBuf中)可以被正好讀取一次。這無疑是最小可能的GBuf通信。

圖9給出了一個詳細的例子:對2個PE的mapping。對一個PE,需要xs'ys'k個inputs(進一步對bx'y'k按照PE陣列一列上的PE數目進行划分)和zskWkHk個weights(進一步對z按照PE陣列一行上PE數目進行划分)。為了在xs'ys'運用WndR(卷積窗復用),xs'ys'個inputs(對於1個PE)被load到Regs。因為weigts不存在WndR,因此可以只load zs 個weights(對於一個PE)。在一次迭代中,如果將一次完整的outputs更新操作(xsyszs這個輸出小塊的所有點更新一次)稱為一次pass(第i次pass計算所有outputs的第i個Psums),那么在每次pass,1個PE使用xs'ys'個inputs和zs個weights產生xsyszs個Psums(也就是一次累加)。因為這里1個PE對應一個MAC 單元,一個周期只能完成1次乘累加操作,因此一個pass對應xsyszs個周期(計算一個輸出點的Psums花費1個周期)。載入的inputs數據可以被使用WkHk個pass,因為Ifmap平面的每個數據點其實都會被kernel平面的每個weights掃一遍(除了邊界數據點外)。但是載入的weights只能被使用1個pass,因此PE需要在每個pass都導入zs個weights到Regs。如此,完成一次迭代,即產生完整的xsyszs累加和,需要kWkHk個pass(即需要這么多次Psum更新操作),其實也就是累加鏈長。值得注意的是,這個累加和僅僅對於當前迭代來說是完整的,但是對於實際的卷積計算而言還不不是完整的,因為一次迭代只累加了k個通道,一共有Ci個通道數據需要累加。
結合PE陣列考慮,同1行的PE共享載入的inputs數據,一行中每個PE算zs個輸出通道,一行的PE一共計算z個輸出通道(輸出並行)。同一列的PE共享載入的weights數據,一列中每個PE算xsys個outputs數據,一列的PE一共計算bxy個數據(其實也就是說卷積核在輸入圖像的不同區域范圍xs'ys'是共享的,得益於多個xs'ys'的並行計算,也就是卷積窗並行)。因此,GBuf中的每個weight正好只被讀取了一次,GBuf中的每個input數據平均被讀取了(xs'ys'/xsys)次,大於1是因為卷積光暈現象。當然也可以設計一種復雜的數據傳輸網絡避免由於卷積光暈現象導致的額外讀取,但是會使得硬件復雜、讀取模式不規則。這里對GBuf的讀取次數分析時基於當前的iteration而言的,如果考慮所有的iterations,那么weights(CoCiWkHk)被讀取的次數為:(BWoHo)/(bxy),也就是分的sub-matrix數目,因為計算每個sub-matrix都要再導入一遍weights數據。考慮所有的iterations,那么inputs(BCiWiHi)被讀取的次數為:(BCo)/(bz),因為每次導入到Gbuf中的數據只夠b個batch的z個輸出通道使用。此處的分析可以結合圖7的數據流偽代碼考慮
文章中用來存儲inputs和weights數據的存儲器使用的是global Regs,而不是PE內部的寄存器,例如在圖9中,每一行PE共享xs'ys'的GRegs.在實際中為了避免大的fanouts和長線,文章對PE陣列進行分組,每組PE共享一批GRegs。文章選擇PE的局部寄存器LRegs對Psum進行存儲,一共需要bxyz個寄存器,這樣做的好處在於:如果將Psums存儲在GBuf中,那么每次update都要從Gbuf中讀取到Reg中,更新完再寫回GBuf,總而造成大量的數據搬運,導致高能耗。而將Psums保留到PE內部,可以最小化Gbuf和Regs間的通信。

如上圖所示,每個PE計算zs個輸出通道的xs×ys區域,因此一個PE在輸出sub-matrix中貢獻zs×xs×ys這樣一個block,p×q個PE的輸出應該覆蓋reshape后的輸出sub-matrix(bxy×z)。通過采用上述workload和storage的mapping方式,GBuf的容量可以得到減少:每次不需要導入KWkHkz個weights和bx'y'k個inputs,只需要存儲一行一列數據即可,即weights存儲z個(Reshaped weight sub-matrx的一行),inputs存儲bx'y'個(Reshaped input sub-matrix的一列),其實也就是一次pass所需的數據量。這樣其實就是流水線工作,一旦Gbuf中的數據被導入GRegs,GBuf就被用來為接下來的pass預取數據。
最小化Reg通信
Psums存儲在PE的LRegs中,因為每個MAC操作對應一次Reg寫操作,所以總的Reg寫次數為:
將Psums保持在LRegs自然地達到下邊界。

文章只對存Psum的LRegs的寫次數進行了分析,缺少對於存Inputs和Weights的GRegs的讀次數的分析,GRegs的訪存跟PE具體如何使用xs'ys'個inputs和zs個weights產生xsyszs個Psums的具體操作過程有關,按照前文作者對其數據流優越性的描述提到的“More importantly, it also takes into account InR (an input is reused by weights in z kernels)and WtR (a weight is reused by b×x'×y' inputs) at the same time.”推測其計算方式為:例如對於第1個pass,先取1號weight,與1-9號inputs做MAC(9個clock),再接着取2-zs號weight重復上述運算過程。所以1個pass inputs就被重復讀取了zs次,一共WkHk個pass就被重復讀取了WkHkzs次,而weights就只被讀取了一次。
5. Communication-optimal CNN accelerator architecture

文章給出的架構例子具有64KB的Psums,p×q=16×16個PEs。考慮16bit的算術單元,那么就有32768個Psum條目,每個PE具有128個。如前所述,對PE陣列進行分組,每組PE(pg×qg=4×4 PEs)共享一系列的GRegs,所有GReg行存儲相同的weights,所有GReg列存儲所有的inputs。
GBufs
根據之前的設計原則,bxy≈Rz,bxyz=S=32768,如果R=1(也就是沒有WndR),bxy≈z≈181。因為181是z的近似值,因此設置z=256,也就是WGbuf的大小。如果R變大,典型地,考慮最大的R=9(Wk=Hk=3,D=1),最大的bxy=181×3=543.考慮卷積光暈效應(bx'y'),設置IGBuf=1024。
GRegs

為了適應不同的z(每次迭代計算的輸出通道數)值,這里采用了一種MUX邏輯,從而避免復雜的片上網絡。對於存儲權重的部分寄存器而言,使用了類似Round Robin的連接方式,如果這部分寄存器沒有被完全用滿,則只需要控制多路選擇器的輸入即可,而不會涉及沒有使用的部分。例如,如果z=64(所以zs=64/16=4),那么MUXes的選擇信號就從0-3,4×16剛好等於64。
同樣,對於存儲輸入的寄存器,每一行PE使用一組寄存器,每個GReg具有64個條目,每個GReg segment 從IGBuf導入xs'ys'個inputs,每個GReg segment 具有一個64-1的MUX提供輸入給一行PE單元,所以這個input MUX的選擇信號為從0到xs'ys'-1。這樣做的好處是,只需要控制多路選擇器的輸入即可將相應的滑窗數據分配給PE。
總結
1、文章對卷積加速器的通信下界作了理論推導,並結合MM作了很直觀的類比,很有實踐指導意義;
2、文章提出了對於卷積的Ordering方法,總體思路是盡量將片上buffer分配給輸出進行分塊卷積運算(psum駐留PE的LRegs),並以盡可能少的輸入(Inputs和Weights)獲得盡量多的輸出,我目前的架構設計與其比較類似(輸出分塊、Psums駐留PE的LRegs),但“以盡可能少的輸入獲得盡量多的輸出”這點還考慮不夠
3、文章提出的對於一次卷積Iteration的Mapping方法,在PE陣列的橫向運用卷積核並行從而達到InR,縱向運用卷積窗並行從而達到WtR,我目前的架構設計因為只有一列PE,只考慮到了卷積核並行運算,后續可以考慮卷積窗並行,這樣一方面可以減少Input buffer的大小,另一方面可以增加輸出block的大小,也就是“以盡可能少的輸入獲得盡量多的輸出”,並且提高並行度
4、文章的Mapping方法對於Inputs的重復讀取次數較多,對Weights的讀取次數只有1次,運用在BWN中是不夠合理的,后續可以考慮改進