首先感谢夕阳叹大神提供的思路,大家先可以去https://blog.youkuaiyun.com/jxt1234and2010/article/details/71056736看看,基本把实现的流程都说了一遍,我照着思路实现了一下,同时参考了一下CnnDroid。
这个项目断断续续写了快4个月了,最近忙起来了,可能没什么时间完善了,先用blog记录一下思路,要有机会再完善了。
目前整个项目只实现了卷积,池化,全连接,concat,flat(和tensorflow的flat逻辑不一样),softmax,卷积部分实现了常规卷积,GEMM卷积(mat4级别的),winograd卷积。整个项目没能达到夕阳叹大神实现版本的效率,能力有限也不知道怎么优化了,希望能有OpenGL的大神能review下我写的shader,是不是实现上有问题,感激不尽。
大致的benchmark,个人时间有限就只与NCNN对比了一下运行squeezenet
ncnn没时间编译了,直接用的ncnn-mobile
cpu | ncnn(4线程) | mine |
高通660 | 60ms | 70ms |
高通710 | - | 35ms |
高通835 | - | 40ms |
测试标准都是除去前10次后,然后前向运行100次耗时取均值。测试手机为小米note3,注意不同rom对opengl效率有影响,最好用最新的开发版或正式版。
基本实现流程:
1.输入层:
输入层使用RGBA16F格式的 GL_TEXTURE_2D_ARRAY 存储,纹理深度为1,纹理宽高同输入矩阵的宽高维度,通常输入矩阵的channel为1或者3,所以将输入矩阵传入纹理时,需要对其不足的通道补零。
2.卷积层
卷积层的输出纹理为RGBA16F的 GL_TEXTURE_2D_ARRAY,纹理深度为输出 ceil(channel/4),纹理高宽同输出高宽维度。
卷积层的kernel也使用纹理存储,纹理深度同输入纹理。最开始我是按照夕阳叹的思路用的ssbo,但是效率很慢,原因未知。换成纹理后效率有所提升。就我测试,基本运算有一半以上的耗时花在了从纹理读取数据上了,这部分不知道有没有优化空间。
kernel的维度为 n x (kernel_area + 1)x d。 n为kernel的数量,kernel_area 为kernel的截面积,d为输入纹理的深度,因为kernel的channel需要与输入channel相同。每一个kernel均存储为纹理上的1行,纹理每行的最后一列的第一个值用于存储改行kernel的bias。
常规卷积的逻辑为每个计算器计算1个输出,所以计算器坐标同输出坐标。
GEMM卷积为mat4级别的,所以一个计算器会同时计算4个输出值。
winograd卷积的kernel纹理时存储GgGt矩阵,存储逻辑同上。winograd实现时是将输出转变为nxn个2x2的输出,所以一个计算器也会计算4个输出值
3.池化层
池化层的输出纹理为RGBA16F的 GL_TEXTURE_2D_ARRAY,纹理深度为输出 ceil(channel/4),纹理高宽同输出高宽维度。
池化层的计算器坐标与输出纹理一一对应,每个坐标的计算器只进行对应坐标的池化运算。
4.concat层
没有判断输入channel是否为4对齐,现在就实现了2个输入,并且输入channel均为4对齐的情况。通用的concat有时间再补上吧。
5.全连接层
全连接为了直接接卷积或池化的输出,输出纹理的格式设置为 1 x 1 x d, d为全连接层的神经元数量,输出纹理的深度就为ceil(d/4)。同样每个神经元的kernel也用纹理的1行存储,最后一列用于存储bias。
6.flat层
这层主要是为了方便读取最终结果,opengl通过framebuffer读取数据时,只能读取一个深度纹理上的数据。所以通过flat将纹理所有深度上的数据,移动到一个深度的纹理上。
7.softmax层
没什么东西了,直接实现的softmax公式。
目前在ARM Mail GPU上还有bug,cifa10的10分类模型能跑,squeezeNet就输出0,原因还没找到,手边也没有这种GPU的手机,应该也没机会修复了。
顺便记录一下ARM opengl编程遇到的一点小坑:
1.arm 下 opengles 的计算器localsize 最大值为128,而高通的为1024
2.arm 下shader定义image时必须设置精度(也许也可以直接在全局设置,我没试),而高通不需要
layout(binding = 0, rgba16f) readonly uniform highp image2DArray input_image;
纹理精度设置为highp或lowp,效率没变化。
3.shader的shared变量空间比高通的小(具体多少没查到,也没手机试),高通的手机为1000 个 float。
里面包括一个cifar10 10分类和squeezeNet 1000分类的模型。
cifar10的权重提取自CnnDroid
squeezenet的权重提取自squeezenet