小海思芯片上运行ncnn深度学习框架的尝试
【摘要】随着硬件设备的发展,越来越多的底层芯片开始具备运行深度学习模型的能力。本文详细介绍了在海思的3516ev200芯片上使用腾讯的ncnn框架编译和运行模型的流程,希望对你有所帮助,同时也希望能探讨端侧算法更多的可能性。
1. ncnn深度学习框架
ncnn是腾讯在2017年开源的嵌入式深度学习框架,可以跑在多个不同的嵌入式设备上。参考的网址为(https://github.com/Tencent/ncnn)。从其介绍和代码目录看,其不仅支持当前火热的手机设备移动端包括Android与IOS,且对其进行了相关代码指令集的优化,还提供了运行在树莓派和jetson等开发板的能力。
当然,ncnn并不是唯一一个嵌入式深度学习框架。近年来涌现出了不少嵌入式深度学习框架,包括谷歌的Tensorflow Lite,OPEN AI LAB的Tengine等。与其他框架相比,ncnn具有以下两个优点:1、不依赖于第三方库函数,编译调整较为方便;2、提供了多个嵌入式平台的编译脚本(甚至包括了海思的交叉编译链)提供参考。
2. 编译过程
整个编译过程比较简单,由于3516ev200使用的是himix100的交叉编译工具链,故只需在它提供的himix100.toolchain.cmake上修改少量的编译参数就好。
对于编译其HiLinux版本的ncnn库,只需要修改下图中的几个参数:
1) 由于3516ev200芯片对应的是ARM系统,需要将aarch64修改为ARM
2) 注意修改交叉编译工具的相关地址,在服务器中3516ev200的交叉编译默认安装路径为/opt/hisi-linux/x86-arm/arm-himix100-linux/bin/arm-himix100-linux-gcc。
3) 修改CXX的编译参数为"-std=c++11 -mcpu=cortex-a7 -mfloat-abi=softfp -mfpu=neon-vfpv4 -fno-aggressive-loop-optimizations ${CMAKE_CXX_FLAGS}"。其中后面几个参数是3516ev200海思芯片SDK建议的编译参数,进行了包括neon的相关优化;去掉-fopenmp则是因为芯片是单核900MHz的,多线程不起优化作用甚至引起了报错。
接下来,按照官网提供的cmake,make与make install三部曲后就可以得到最终交叉编译出来的库了。编译出来的库有2.1M的大小。
3. 使用过程
交叉编译好后就可以拿着libncnn.a的库使用了。在ncnn的库目录下example有相关的样例代码。
ncnn提供了模型转换工具的,源码目录在tools下。可以在CMakeLists.txt里控制编译哪些工具。从目录上看其支持caffe、mxnet、tensorflow等模型的转换的。这里尝试了caffe模型与mxnet模型的转换使用。
1) 对于caffe模型:先使用caffe2ncnn [prototxt] [caffemodel] [param] [bin]命令进行格式转换,其中前两者是caffe模型与网络参数,后两者就是ncnn的模型与网络参数了。
对于mxnet模型:使用mxnet2ncnn [model.json] [model.params] [param] [bin]命令进行格式转换,其中前两者是mxnet模型与网络参数,后两者就是ncnn的模型与网络参数了。
至此就可以使用ncnn的模型进行推理了。参考example目录下的squeezenet.cpp文件,使用方法也非常简单。
具体来说,只要载入了模型与网络参数文件,进行简单的输入处理,就能通过网络节点的名称输入图像数据和得到输出节点的推理结果了。样例代码中的data与prob分别对应的squeezenet的输入输出节点。后续使用自己的模型时需要注意修改输入输出节点名称,避免产生以下错误:
此外,ncnn除了通过模型路径传参外,还支持从头文件中加载模型和模型参数的方式。换句话说,ncnn提供了命令ncnn2mem [param] [bin] [id.h] [mem.h]的方式把模型直接转为头文件。后两者就是转换出来的头文件,把load_model的入参改为头文件中对应的变量名即可。
使用这种方式,能在编译时直接隐藏模型文件的详细信息,不再需要外部模型输入。但通过这种方法,编译出的可执行程序大小也会相应地增加(多了模型的大小)。对于3516ev200来说,只建议小模型通过这种方式直接编译为可执行程序。
2) 在1)中的ncnn模型都为f32的浮点型模型,对于芯片环境来说还是太大,所以一般都会使用到ncnn的另外一个转换工具,将相关的浮点模型转换为int8的量化模型。
ncnn2table --image [imagepath] --param [param] --bin [bin] --output [table]命令是用提取量化模型时的相关参数,在执行命令时在后面还可以添加mean, norm, size, thread等参数。ncnn是使用了KL散度通过量化前后分布的相似性确定最佳量化参数的,所以imagepath路径里的图片建议有上千张的图像数据确定分布参数。最后通过命令ncnn2int8 [inparam] [inbin] [outparam] [outbin] [table]得到最终的量化模型。
以mobilenet模型为例,量化前的模型为16M而量化后为4M。可以看出量化的方法对于空间内存有限的3516ev200芯片而言具有很大意义。
下面是在3516ev200芯片上跑出来的一些主流模型的性能结果。
下图是squeezenet-f32与squeezenet-int8的推理结果,由于代码中的实现index从0开始计数的,故得到的index为456时对应synset_words.txt中的类别为457,从结果可以看到识别率还是准确的。
下面一组图反映了不同模型在float32与int8模式下的差别,对于同一个模型,int8的模型大小大致是float32模型的1/4,推理时间相对于float32减小了大约25%,占用的内存大小则有明显下降,故在内存有限的芯片上对模型进行量化是十分必要的。
4. 使用心得
除了正常的使用流程外,在使用过程中还注意到几个地方。
1) ncnn提供了一种通过控制算子层的方法裁剪库的大小的方法。即使用过程中把模型不需要的算子层去掉能减小库的体积。具体方法是,先打开使用模型的param文件,查看模型有哪些算子。例如squeezenet的算子图如下
在src目录下的CMakeLists.txt文件里,把不需要的算子层去掉(如关上ArgMax层)。注意其中一些算子(或像图像resize一类的操作)可能隐藏的依赖于其它算子。经过裁剪后库的大小可以缩减到1.3M。
2) 裁剪过程中注意到convolution层编译出来的体积较其它层大。查看了算子发现是ncnn优化了arm里的卷积操作,不仅引入了neon汇编级优化,还对不同的卷积核大小用不同的代码实现了优化。所以下一步的裁剪计划是,查看模型的卷积核尝试把不需要用到尺寸大小代码关闭。
以上就是对ncnn框架在3516ev200芯片上的一些使用分析,希望能对大家有所帮助。在端侧算力发展的时代里,期望算法能在端边云的联动中发生神奇的化学反应,迎来新一轮的人工智能算法的发展。
- 点赞
- 收藏
- 关注作者
评论(0)