原文鏈接:GPU-based Acceleration of Deep Convolutional Neural Networks on Mobile Platforms
github地址:CNNDroid
1.前置知識 移動GPU架構
現在的移動GPU一般由多個平行計算單元SC(shader core)組成。每個SC又由多個平行算數邏輯單元ALU(arithmetic and logic unit)組成。
例如,Samsung Exynos 5433芯片架構如上圖。芯片由ARM Cortex-A53/A57 CPU 和 Mali T-760 GPU構成。每個T-760 GPU中的SC包含兩個128-bit的ALU。每個128-bit的ALU能夠執行SIMD(single instruction multiple data)計算。例如,每個128-bit的ALU能夠同時執行兩個64-bit,或4個32-bit,或8個16-bit的計算。而PowerVR GPU的每個SC由多個16-bit和32-bit的ALU構成。
移動GPU和電腦上的有很大不同。其有很大的面積限制,因此移動GPU都被設計爲只有較少的核心。然而,較少的核心讓並行線程管理更加簡單。例如,在某些移動GPU的每個線程中都有其自己的程序計數器,因此branch divergence不是問題。另一個主要不同是,電腦上CPU 和 GPU 都有單獨的內存,而移動芯片CPU和GPU共用同一塊主內存。因而,不會有在CPU和GPU間複製數據的限制。
2.CNN on Mobile GPU
以下均基於圖片識別任務的cnn網絡
優化效果見下圖
2.1. GPU-Based Basic Parallel Acceleration
basic parallel acceleration方法是仍然按照序列計算每幅圖片,但在對每幅圖片卷積時採用並行計算。當存在ReLU層時,它的計算就被嵌入到兩次卷積操作之間,而沒有明顯的耗時。這是通過利用CPU的空閒達到的。當GPU在卷積計算第i幅圖片時,第(i-1)幅圖的ReLU層就會被CPU計算。因此CPU與GPU同時處於工作狀態。
2.2. GPU-Based Basic SIMD Acceleration
在前面的GPU架構中有提到,每個GPU的SC都包含多個SIMD ALU。每個ALU都能在一個時鐘週期中處理多個計算操作。例如128-bit的SIMD ALU能同時執行4個32-bit的浮點計算。我們使用這個特性加速性能。
通常的卷積核尺寸都是單數,不能被4整除。因此,爲了能完全利用SIMD單元,我們對輸入的卷積層做了如下操作。因爲輸入矩陣的channel數能被4整除(RGBA),所以我們重新排列矩陣的維度,將channel移動到最低維,而height和width移動到更高維度(即[W,H,C]->[C,W,H])。考慮到我們進行的圖片的維度變換,因此在計算時需要沿着chanel軸進行計算。(我的理解是同時對4個channel進行卷積)
2.3. GPU-Based Advanced SIMD Acceleration
在上個方法中,儘管讀取數據時通過4維的向量,但每個線程只有一個元素在輸出時計算。爲了減少線程數,以減少幀與卷積核加載到GPU緩存的次數。我們每次在每個線程中同時計算多個元素(4個或8個)。
流程:
1. 解析net.txt 文件,生成多個卷積網絡計算圖。每一個網絡層生成對應的層對象,放入一個ArrayList中
root_directory: "/sdcard/Cifar10/"
allocated_ram: 20
execution_mode: "parallel"
auto_tuning: "off"
layer {
type: "Convolution"
name: "conv1"
parameters_file: "model_param_conv1.msg"
pad: 2
stride: 1
group: 1
}
layer {
type: "Pooling"
name: "pool1"
pool: "max"
kernel_size: 3
pad: 0
stride: 2
}
layer {
type: "ReLU"
name: "ReLU1"
}
layer {
type: "Convolution"
name: "conv2"
parameters_file: "model_param_conv2.msg"
pad: 2
stride: 1
group: 1
}
2. 在生成Convolution層和FullyConnected層時,會通過其中的parameters_file找到對應的權重參數文件,並將其賦值到層對應的權重矩陣中
float[][][][] inputBatch = new float[1][3][mWidthPixel][mHeightPixel];
ImageView img = (ImageView) findViewById(R.id.imageView);
TextView text = (TextView) findViewById(R.id.textView);
// 讀取圖像源文件 並縮放到預定大小(32)
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.airplane);
Bitmap bmp1 = Bitmap.createScaledBitmap(bmp, imgSize, imgSize, true);
Bitmap bmp2 = Bitmap.createScaledBitmap(bmp, mWidthPixel, mHeightPixel, false);
img.setImageBitmap(bmp1);
for (int j = 0; j < mWidthPixel; ++j) {
for (int k = 0; k < mHeightPixel; ++k) {
// 讀取圖像的pixel值,並歸一化 means矩陣由網絡初始化時讀取mean.msg生成
// 注意生成的矩陣順序爲 [batch, channel, width, height]
int color = bmp2.getPixel(j, k);
inputBatch[0][0][k][j] = (float) (android.graphics.Color.blue(color)) - mean[0][j][k];
inputBatch[0][1][k][j] = (float) (android.graphics.Color.blue(color)) - mean[1][j][k];
inputBatch[0][2][k][j] = (float) (android.graphics.Color.blue(color)) - mean[2][j][k];
}
}
// 調用網絡計算
float[][] output = (float[][]) myConv.compute(inputBatch);
3. 循環存儲網絡層的ArrayList,依次處理輸入數組,得到對應的輸出矩陣。
例子:例子