閱讀873 返回首頁    go 技術社區[雲棲]


揭開神經網絡加速器的神秘麵紗之DianNao

1 前言

1.1 挖坑(寫在前麵的廢話,可以直接忽略)

最近開始負責組裏神經網絡加速器IP的開發,暫時還是基於FPGA實現,因此閱讀了一些經典的神經網絡加速器實現的論文,包括基於ASIC的寒武紀係列,TPU係列,以及一些基於FPGA的,打算寫一個專題,專門介紹各個神經網絡芯片的實現細節。

1.2 廣告(不適者可以直接忽略)

在具體介紹之前,先表明一下個人觀點:基於神經網絡的專用加速芯片應該是一個大趨勢,但是在業務量還沒穩定之前,FPGA還是有很大發展潛力的。最後,硬件隻是整個神經網絡加速器係統中最簡單的一個環境,算法和軟件才是最複雜的,所以也歡迎在這個領域有想法的兄弟部門一起來合作,我們提高底層FPGA和FPGA上的實現能力,算法大神們提供算法解決方案,軟件大神們提供係統解決方案,一起把這個事情做起來。

2 DianNao

2.1 背景

DianNao是中科院計算所陳老師寒武紀係列的開山之作,奠定了陳老師在這個領域裏的地位;文章主要內容從今天的視角來看,還是比較簡單的,因此今天我們先來聊聊DianNao。

首先要說明的是,DianNao絕不是神經網絡加速器的第一個實現,在DianNao這片文章的相關工作部分就介紹了很多已有的設計,但是DianNao和之前這些實現比起來有一個最大的不同:之前文章的重點都集中在如何高效實現計算部分。這很容易理解,神經網絡加速器本來不就是用來加速神經網絡計算的嘛。但是當你的模型參數越來越大時,你沒有辦法把所有參數都保存在片內了,這個時候訪存必然會成為你的瓶頸,因此DianNao這篇文章選擇高效實現訪存作為切入點!

2.2 整體架構

幾乎所有的神經網絡加速芯片都有如下共性的模塊:

  1. 一個計算單元,主要完成深度學習中矩陣乘/卷積操作;這個計算單元的實現大致分為兩種: 1. 以TPU為代表的脈動陣列(systoic array); 2. 點乘器(dot-product)。

  2. 片上存儲單元,存儲每一層的輸入filter map/輸出filter map及權值(weights):目前的設計中,輸入 filter map和輸出 filter map一般都是共享一塊片上存儲資源;權值(weights)可以很大,因此有時候不能全部存儲在片上;

其實神經網絡加速芯片這兩個共性的模塊一點也不難理解,神經網絡加速芯片不就是為了完成每個網絡層的計算嘛,因此需要一個高吞吐量的計算單元;為了能喂飽這個計算單元,就必須想辦法高效的給其喂數據,因此就有了片上存儲單元。具體到DianNao這個芯片,我們來看看其內部實現:

111.png

對應上麵這個架構圖,我們來具體分析一下這塊芯片。首先來看一下計算單元。

1 計算單元NFU

DianNao中計算單元稱之為NFU(Neuron Function Unit),這是一個典型的點乘器(dot-product)方案,可以看到這個NFU分了三個階段,分別為: 負責做乘法的NFU-1;負責做累加的NFU-2;及負責做激活函數的NFU-3。上麵這個架構圖看起來可能不太直觀,我單獨把這個模塊功能分解一下,重新畫了一副圖:

222.png

上圖是DianNao整體架構圖中紅框內容展開,一個這個結構計算輸出output feature map中一個點.不同的是,在DianNao裏,一共有16個輸入I0 - I16 和16個權值W0 - W16做點積,這裏為了方便,隻畫了8個input的點積。注意在NFU-2最後的階段有一個寄存器R(圖中紅框裏的字母R表示),這是用來存儲中間結果的,隻有當一個卷積/全鏈接層中一個點計算完後,才會進行激活操作,而中間階段部分和會臨時存在寄存器R中;另外最後一個加法器還有一個輸出是不經過NFU-3,直接輸出的(圖中藍色虛線表示),這是因為有一些層計算沒有激活這個操作,典型的如池化層;對應均值池化,隻需要設置好對應權值,NFU-2的輸出就是池化的輸出結果,如:對於一個kernel大小為2x2的均值池化層,隻需要把權值設置為1/4,NFU-2的輸出就是池化的結果。最後需要說明,為了支持最大值池化,這裏的加法器其實除了需要支持加法功能,還必須支持比較功能。

在DianNao的設計中,上述這個結構一共有16個,因此可以同時計算16個輸出output feature map中的點。具體深度學習中各個層怎麼映射到上述這個結構中的,後麵會有詳細介紹。

2 片上存儲單元

在DianNao整體架構中,除了NFU模塊,另外一個重要的模塊就是三塊片上存儲單元了:

  1. NBin: 保存每一層的輸入數據;位寬Tn表示Tn個半精度浮點數,因為DianNao采用的是半精度fp16數據計算,因此位寬Tn表示Tn個fp16;
  2. NBout: 保存每一層的輸出數據;位寬同樣用Tn表示;
  3. SB: 保存模型的權值,位寬為Tn X Tn

對於每一層,整個數據流為: NFU從NBin讀取input feature map,從SB讀取weight,然後計算,最終得到的結果存在NBout中。這裏有個問題,那就是每次計算完一層,都需要把NBout buffer裏數據copy會NBin,對於現在的設計,基本都會將NBin和NBout合並成一個buffer,利用地址區分輸入和輸出。

3 其他模塊

  1. DMA引擎:可以看到每個片上存儲單元都有一個DMA引擎,可以獨立的和主存DDR交換數據;
  2. Control Processor(CP): 這個是整個芯片的控製邏輯,DianNao采用了比較簡單的指令係統,利用一個簡單指令譯碼單元,可以將指令轉換成控製單元裏麵信號的高低。

4 總結

可以看到,整個DianNao的硬件結構還是比較簡單的,至於一個神經網絡模型到底是怎麼樣在這個簡單的硬件上執行起來的,就要靠軟件在處理輸入數據,去控製各個模塊的工作,因此,同硬件相比,軟件的工作量更大!

3 具體案例

在這部分,我將簡單介紹一下,各個層是如何在DianNao裏工作的,重點介紹全鏈接層;這裏其實有一套簡單的指令係統,但是為了介紹的更具體一些,我就直接舉例子來說明了,對指令係統感興趣的可以自己去讀論文。

3.1 全鏈接層+激活層(sigmoid)

1 參數介紹

  1. 全鏈接層的輸入是8192個神經元,輸出是256個神經元;
  2. 片上存儲單元的位寬Tn為16;因此NBin位寬為16個FP16,為16x16-bit;NBout也為16x16-bit;SB為256x16-bit;
  3. 片上存儲單元NBin的深度為64;因此NBin大小為2KB( 64項 X 256 bit)。NBout也為64項,因此NBout大小也為2KB;SB也為64項,因此SB大小為32KB,

2 全鏈接層介紹

全鏈接層的本質是一個向量和一個矩陣相乘,得到一個向量。具體到這個case,那就是一個 1 x 8192的向量 I (公式1)和一個8192 x 256 的矩陣W(公式2)相乘,得到一個 1 x 256 的向量O(公式3)。

111

3 具體實現

  1. 數據重用 (reuse)

對於每一個輸出矩陣O中的點,其都需要8192個I作為輸入,因此輸入向量I中每個點,在計算不同的輸出點時,是可以重用的,這樣可以減少對DDR的訪問;對於權值O,不具備這個特性

  1. 數據傳輸

因為NBin一次隻能容納 64 x 16個輸入,而總共有8192個輸入,因此必須分成8個tile;對於SB,總共有8192x256個權值,但是一次隻能容納 64x256個,因此需要分為128個tile。

input feature map第一個tile在NBin中排列如下:

222

weight第一個tile在SB中排列如下:

333

  1. 具體計算過程
  2. load input filter map第一個tile到NBin;
  3. load weight第一個tile到SB;
  4. read NBin一項,這16個input 同時輸入給16個點乘器(每個點乘器輸入為16個input 和16個weight),每個點乘器的input feature輸入相同
  5. read SB一項256個weight分別輸入給16個點乘器(16個點乘器,每個需要16個weight作為輸入),每個點乘器的weight均不同;
  6. 計算這16個輸出filter map的部分和,並和臨時寄存器R相加,並將結果保存在臨時寄存器R中;
  7. 重複上述操作64次,可以得到輸出16個filter map點的部分和;這時候其實對於輸出,隻計算了1024個輸入點的部分和,因此這個結果還隻是一個中間結果。這時如果要繼續計算這16個output點,那就必須更新NBin,達不到input reuse目地,因此我們選擇把這個中間結果寫入到NBout中;然後開始計算下一組output點的部分和。
  8. load下一組weight進入SB(這裏其實用到了double buffer,在第一組weight計算同時,第二組weight以及在load了),重複利用NBin數據。重複上述步驟,經過16次後,在NBout中得到256個輸出點前1024個輸入點的加權和。
  9. 同時更新NBin和SB內容;NBin load下一個input feature map的下一個tile。這時注意一定要從NBout中read出來對應output點的中間結果,存到臨時寄存器R中。
  10. 上述步驟重複8次,inpute feature map的8個tile均計算完成,得到最終的全鏈接層輸出。注意在計算得到最終結果的那輪NFU運算裏,才會觸發NFU-3這個pipeline,執行激活函數,其他時間裏,NFU-3階段均被pypass。

3.2 卷積層+激活層(sigmoid)

最簡單的,卷積運算可以轉換乘矩陣乘,後續過程就類似全鏈接層的運算了。

最後更新:2017-07-03 16:02:04

  上一篇:go  B站高性能微服務架構
  下一篇:go  Gitlab 官方對整個數據刪除事件的詳細說明