原文链接: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,依次处理输入数组,得到对应的输出矩阵。
例子:例子