1. 程式人生 > >嵌入式裝置上卷積神經網路推理時memory的優化

嵌入式裝置上卷積神經網路推理時memory的優化

以前的神經網路幾乎都是部署在雲端(伺服器上),裝置端採集到資料通過網路傳送給伺服器做inference(推理),結果再通過網路返回給裝置端。如今越來越多的神經網路部署在嵌入式裝置端上,即inference在裝置端上做。嵌入式裝置的特點是算力不強、memory小。可以通過對神經網路做量化來降load和省memory,但有時可能memory還吃緊,就需要對神經網路在memory使用上做進一步優化。本文就以一維卷積神經網路為例談談怎麼來進一步優化卷積神經網路使用的memory。

 

文章(卷積神經網路中一維卷積的計算過程 )講了卷積神經網路一維卷積的處理過程,可以看出卷積層的輸入是一個大矩陣,輸出也是一個大矩陣,儲存這些矩陣是挺耗memory的。其實做卷積計算時,每次都是從輸入中取出kernel size大小的資料與kernel做卷積運算,得到的是一個值,儲存在卷積層的輸出buffer裡,輸出也是下一層的輸入。如果輸入是卷積層kernel的大小(即是能做運算的最小size),輸出是下一層的能做運算的最小size,相對於整個輸入和輸出的size而言,能節省不少memory。下面就講講怎麼用這種思路來做CNN的memory優化。

 

假設當前層和下一層均為卷積層,stride為1,padding模式為same。當前層的輸入是一個MxN的矩陣,kernel size是P,kernel count是Q,所以kernel是一個MxP的矩陣,當前層的輸出是一個QxN矩陣。當前卷積層的輸出就是下一卷積層的輸入。下一卷積層的kernel size是J,kernel count是K,所以下一卷積層的kernel是一個QxJ的矩陣,輸出是一個KxN的矩陣。示意如下圖:

首先從輸入矩陣中取出0~(P-1)列(共P列)放進輸入buffer中,正好放滿。與第一個kernel做卷積運算就得到一個值放進輸出buffer中(0,0)位置。同樣與第二個kernel做卷積得到的值放在輸出buffer中(1,0)位置,與所有kernel做完卷積後得到是一個Q行1列的矩陣,放在輸出buffer第一列上,如下圖:

再將輸入矩陣的第1~P列取出放進輸入buffer中(即把輸入buffer中每列向前移一格,0列移出buffer,第P列放在最後1列),同上面一樣計算,得到的依舊是1列數值放進輸出buffer的第2列中。如下圖:

依次這麼做下去,當輸出buffer裡的最後一列被填滿時,就要觸發下一卷積層做卷積運算,與K個kernel卷積運算後得到的是一個K行1列的值放進下一卷積層輸出buffer的第一列中。如下圖:

由於下一卷積層的輸出buffer未滿,不能觸發後面的運算。又回到從輸入矩陣中取資料放進當前卷積層的輸入buffer中,再進行當前卷積層和下一卷積層的運算,結果放在各自的輸出buffer裡(如輸出buffer滿了就要把每列左移一格,0列移出buffer,新生成的1列資料放在最後1列)。類似的處理,直到把輸入矩陣中取到最後一列,再把各個層的輸入buffer全處理完,最後得到結果。

 

從上面的思路看出,輸入和輸出buffer從大矩陣變成kernel大小的小矩陣可以省不少memory。具體軟體實現時,有輸入層,中間各層(包括卷積層等),每一層為一個整體,要用event機制去觸發下一個要處理的層。當一層處理完後要判斷下一步是哪一層做處理(有可能是下一層,也有可能是當前層或上一層,還有可能是輸入層等),就給那一層發event,那一層收到event後就會繼續處理。示意如下圖:

軟體實現時還有很多細節要處理,尤其是當輸入層資料取完後後面各層的處理,這裡就不一一細述了。軟體除錯時先用不省memory的原始code生成每層的輸出,儲存在各自的檔案裡,用於做位元校驗。然後對省memory的code進行除錯,先從第一層開始,一層一層的除錯。優化後的程式碼每層的輸出跟優化前的輸出是完全一樣的才算除錯完