12

派大星 · 5月24日

端侧推理引擎Tengine初识:安卓平台交叉编译并跑通MobileNetV1

前阵子看到Tengine为OpenCV4.3版本贡献了ARM CPU底层汇编代码,加速深度学习计算。最近也看到Tengine的不少同学在做相关PR。可能有小伙伴不了解Tengine。根据ARM官网也有介绍Tengine,其介绍如下。
Tengine 是OPEN AI LAB 针对前端智能设备开发的软件开发包,核心部分是一个轻量级,模块化,高性能的AI 推断引擎,并支持用DLA、GPU、xPU作为硬件加速计算资源异构加速。

微信公众号:NeuralTalk
作者: https://github.com/ysh329
Project: https://github.com/ysh329/awesome-embedded-ai

话不多说,还是先试试怎么样。

首先找到github上Tengine主页:github.com/OAID/Tengine和文档页面,其实就是wiki。同时,再去release页面,可以看到有提供android的armv7和v8预先编译好的二进制代码包和源码,对应下面五个文件:

  1. andoird_arm32_ndk16_api22_prebuilt.tar:应该是针对Android armv7,基于NDK16预编译的库,查了下api22的Android API Level对应Android的版本是安卓5.1。下同;
  2. andoird_arm64_ndk16_api22_prebuilt.tar
  3. convert_model_to_tm
  4. Source code(zip)
  5. Source code(tar.gz)

不知道其中的convert_model_to_tm是干啥的,先不管。后来在wiki的Tengine快速上手指南(中文版)的模型转换小节 看到,这个工具是模型转换工具,tm即Tengine模型格式tmfile。扯得有点远了,继续回过神来。

1. 交叉编译Tengine安卓库

虽然release页面有提供预编译好的库,但是还是想亲身感受一下Tengine的编译过程。

先把Tengine代码clone下来再说,顺带这个过程看看当前release页面,看到最新(这篇文章是2020年5月23日写的,之后可能会有新release,大家参考)的release版本是 v1.12.0,即 04b6791,接着刚刚克隆的代码接着操作:

git clone https://github.com/OAID/Tengine.git

# 下面进入Tengine目录并切换到最新的<release-commit-id>
cd Tengine
git checkout 04b6791

切到代码目录下,一眼就瞄到这个build.sh,很是亲切,一顿无脑执行./build.sh,根据屏幕输出的log信息,发现自动找到我在~/.bashrc里设置的NDK环境变量,还是不要无脑执行,ctrl+c暂停掉后,打开瞧一瞧,哇,写的真好,对我来说真的通俗易懂!build.sh支持android的armv7、armv8、armv8.2、himix200、hisiv500、arm-linux的armv7和v8等等编译。

注:我这里的环境是基于自己平时开发的docker环境,已经装好了CMake、NDK等工具。本文大量参考Tengine这篇wiki的安卓指导: Tengine快速上手指南Tengine使用教程

因为这次上手android,我只编译android的v7和v8之后的exit掉顺带修改编译线程数为30(我服务器cpu核数,大家可以根据自己的情况做修改),可能不到1分钟吧编译完成,得到两个目录build-android-aarch6build-android-armv7

瞅一眼项目代码的样子,这里我根据README.md里提到的组成Tengine的5个模块,结合我蹩脚的翻译,再配合我的理解简单介绍一下:

  • build-android-aarch64/:这个是刚编译的android-armv8的产物;
  • build-android-armv7/:同上;
  • build.sh*:编译脚本。通俗易懂看了就明白,可以无脑用起来;
  • cmake/:项目CMakeLists.txt的辅助文件;
  • core/:提供框架基本组件和功能;
  • driver/:这个没看太明白,贴一下原文:the adapter of real H/W and provides service to device executor by HAL API. It is possible for single driver to create multiple devices;
  • examples/:看了下有不少例子如mobilenet_ssdfaster_rcnn
  • executor/:实现了对graph和operator的执行,特别对多核的A72做了更深度的优化;
  • operator/:定义算子如卷积、激活池化等操作
  • pytengine/:我估计是Python API;
  • serializer/:加载保存模型,这个模块也自定义扩展其功能来增加对其它模型的支持,据说已经支持NCNN模型,常规模型结构支持了Caffe/ONNX/Tensorflow/MXNet,前文在release页面也准备好了编译好的模型转换工具convert_model_to_tm;
  • tests/:不同模块对应的单元测试,如conv等等;
    toolchains/:不同平台编译时不可或缺的文件如android、hisiv的toolchain。

2. Tengine的API

既然库很快编译好了,可以先看下将要跑模型所用到的调用代码,比较懒,懒得看文档了直接翻代码,example目录下有个classification.cpp,摘录调用Tengine的API关键代码如下:

<div class="highlight"><pre>`// 下面的somenet应该就是类似
    // 其他框架的Predictor或Executor的东西
    tengine::Net somenet;
    tengine::Tensor input_tensor;
    tengine::Tensor output_tensor;

    // 根据传入的Tengine的模型路径加载模型
    somenet.load_model(NULL, &#34;tengine&#34;, _model_file);

    // 定义输入Tensor的宽度和高度,通道数默认为3
    // 并传入输入数据
    input_tensor.create(img_w, img_h, 3);
    get_input_data(_image_file, (float* )input_tensor.data, img_h, img_w, _channel_mean, scale);

    // 绑定输入
    // 查了include下tengine_cpp_api.h的input_tensor
    // input_tensor有多种实现,下面的实现
    // 前两个参数分别为int node_index, int tensor_index
    // 同时根据代码注释看出绑定输入即执行推理
    somenet.input_tensor(0, 0, input_tensor);

    // 获取计算结果
    // 同样是查tengin_cpp_api.h的extract_tensor说明
    // 下面实现的方法支持多输出获取
    // 前两个参数分别是int node_index, int tensor_index
    somenet.extract_tensor(0, 0, output_tensor);`</pre></div>

看起来是没有显式释放somenet的过程的,应该是资源自动释放,挺好!不需要用户来手动维护资源。

3. 编译产物

因为想跑一个分类模型,编译产物,以armv7为例也就是刚编译出的build-android-armv7目录(如果是armv8则是build-android-aarch64目录),重点内容在build下的install目录,其内容如下(因为内容较多,我这里只保留重要的):

<div class="highlight"><pre>`.
|-- benchmark
|   |-- bench_mobilenet
|   `-- bench_sqz
|-- examples
|   |-- classification
|   |-- faster_rcnn
|   `--  mobilenet_ssd
|-- include
|   |-- cpu_device.h
|   |-- net.h
|   |-- tengine_c_api.h
|   |-- tengine_c_compat.h
|   |-- tengine_cpp_api.h
|   `-- tengine_operations.h
|-- lib
|   |-- libc++_shared.so
|   `-- libtengine.so
`-- tests
    `-- bin
        |-- test_tm
        `-- tm_mssd`</pre></div>

因为是跑Caffe的MobileNetV1这个1000类别的分类模型,会用到编译产物里的如下几个文件(我也是比较懒,先操作出了问题后看文档,可能是大多开发者的通病):

  1. install/example/classification这个文件。同时也是在后面发现这个文件会动态链接libc++_shared.solibtengine.so,即在后文安卓ADB shell环境执行前,需要将存放这两个动态库的文件导入到LD_LIBRARY_PATH中,否则会报相关错误;
  2. install/lib/libc++_shared.so,编译的是什么版本对应的就是v7或者v8的;
    install/lib/libtengine.so,同上;
  3. cat.jpg,输入模型的图片,来自Tengine/tests/images/cat.jpg
  4. install/examples/synset_words.txt,包含分类1000类的各个类别的名字,用于将分类结果的序号“翻译”为实际所指类别;
  5. 还需要Tengine的专属的tmfile格式的模型:下面即将会讲到。

    4. Caffe模型转换

将Caffe训练好的MobileNetV1分类模型拿出来,使用刚下载好的模型转换工具。做如下操作:

<div class="highlight"><pre>`./convert_model_to_tm -f caffe -p ../models/MobileNet-Caffe/mobilenet_deploy.prototxt -m ../models/MobileNet-Caffe/mobilenet.caffemodel -o ./caffe_mobilenetv1.tmfile
The input file specified is using deprecated params: ../models/MobileNet-Caffe/mobilenet_deploy.prototxt
Please upgrade the input file by using caffe tools(upgrade_net_proto_text/upgrade_net_proto_binary).
Create graph failed
errno: 22

# 发现是因为Caffe的模型格式&lt;1.0造成的,
# 需要用Caffe的工具对老模型升级Prototxt和Caffemodel。
# 我下载了caffe的docker容器:docker pull bvlc/caffe:cpu
# 在容器里做的模型升级,进入容器后作类似如下的操作
# 升级prototxt
$CAFFE_ROOT/build/tools/upgrade_net_proto_text MODEL.prototxt MODEL.new.prototxt
# 升级caffemodel
$CAFFE_ROOT/build/tools/upgrade_net_proto_binary MODEL.caffemodel MODEL.new.caffemodel

# 基于升级后的Caffe模型重新转换
./convert_model_to_tm -f caffe -p ../models/MobileNet-Caffe/mobilenet_deploy.prototxt.new.prototxt -m ../models/MobileNet-Caffe/mobilenet.caffemodel.new.caffemodel -o ./caffe_mobilenetv1.tmfile
Create tengine model file done: ./caffe_mobilenetv1.tmfile`</pre></div>

根据执行log最后一行可以看到转换模型成功!来,试试看。

后来发现,Tengine也提供了一些常见模型,是在百度云上的,即有原始模型也有转换好的Tengine的tmfile格式的模型文件

5. ADB Shell环境测试

在集成到APP前,先在ADB shell环境试一下,将刚编译好的库 + 模型依次上传到手机,我写了如下脚本:

<div class="highlight"><pre>`# 假设当前在&lt;Tengine&gt;项目根目录下
# 且转好的模型caffe_mobilenetv1.tmfile也在该目录下
# serial_num是手机device的序列码,可以通过命令adb devices查看

adb -s $serial_num shell mkdir -p /data/local/tmp/tengine

adb -s $serial_num push ./tests/images/cat.jpg /data/local/tmp/tengine/
adb -s $serial_num push ./build-android-armv7/examples/classification /data/local/tmp/tengine/
adb -s $serial_num push ./build-android-armv7/install/lib/libtengine.so /data/local/tmp/tengine/
adb -s $serial_num push ./build-android-armv7/install/lib/libc++_shared.so /data/local/tmp/tengine/
adb -s $serial_num push ./caffe_mobilenetv1.tmfile /data/local/tmp/tengine/
adb -s $serial_num push ./build-android-armv7/install/examples/synset_words.txt /data/local/tmp/tengine/

adb -s $serial_num shell chmod +x /data/local/tmp/tengine/classification
adb -s $serial_num shell &#34;export LD_LIBRARY_PATH=/data/local/tmp//tengine/;
                          /data/local/tmp/tengine/classification \
                          -m /data/local/tmp/tengine/caffe_mobilenetv1.tmfile \
                          -i /data/local/tmp/tengine/cat.jpg \
                          -l /data/local/tmp/tengine/synset_words.txt \
                          -g 224,224 -s 0.017 -w 104.007,116.669,122.679 -r 100&#34;

# 其他参数,依据前文的classification.cpp代码里的pint_usage
# 用法如下
# [-m model_file],模型路径
# [-l label_file],输出类别序号转类名映射表
# [-i image_file],输入图片路径,[-g img_h,img_w],输入图片宽高
# [-s scale] [-w mean[0],mean[1],mean[2]],图片预处理操作相关的属性设置,我上面根据默认
# [-r repeat_count],循环的运行次数`</pre></div>

最后的参数-r表示跑100次,会打印出每次的执行时间。来试试看!

<div class="highlight"><pre>`tengine library version: 1.12.0-github

Model name : /data/local/tmp/tengine/caffe_mobilenetv1.tmfile
tengine model file : /data/local/tmp/tengine/caffe_mobilenetv1.tmfile
label file : /data/local/tmp/tengine/synset_words.txt
image file : /data/local/tmp/tengine/cat.jpg
img_h, imag_w, scale, mean[3] : 224 224 0.017 104.007 116.669 122.679
Cost 98.888 ms
Repeat [1] min 98.888 ms, max 98.888 ms, avg 98.888 ms
0.2920 - &#34;n02123159 tiger cat&#34;
0.1459 - &#34;n02119022 red fox, Vulpes vulpes&#34;
0.1364 - &#34;n02119789 kit fox, Vulpes macrotis&#34;
0.0806 - &#34;n02113023 Pembroke, Pembroke Welsh corgi&#34;
0.0318 - &#34;n02123045 tabby, tabby cat&#34;
--------------------------------------
ALL TEST DONE

上面是执行的结果。可以看到将cat.jpg成功分类为tiger cat,因为只跑了一次性能会有波动且第一次有加载耗时会比较慢,如果是做benchmark,多跑几次如10次20次先预热起来,之后的性能就稳定且很快了。

此外,据说Tengine有开源版和商业版,本次只是试验了开源版本,感觉上手非常容易,尤其是编译速度很快,模型转换也很方便,也提供了转好模型。也期待商业版本的性能!

6. 参考

  1. OAID/Tengine: Tengine is a lite, high performance, modular inference engine for embedded device
  2. Tengine快速上手指南(中文版)
  3. Tengine源码编译
  4. OPEN AI LAB发布嵌入式前端深度学习框架Tengine - 中文社区论区 - 中文社区 - Arm Community
  5. Android API Level对应Android版本一览表_移动开发_ 红壶吃猬队的博客-CSDN博客



作者文章推荐:

欢迎关注公众号,关注模型压缩、低比特量化、移动端推理加速优化、部署。
嵌入式AI.jpg
获取更多嵌入式AI文章内容,请关注嵌入式AI专栏.Tengine更多技术干货请关注Tengine-边缘AI推理框架专栏。
12 阅读 434
推荐阅读
1 条评论
关注数
12218
内容数
190
嵌入式端AI,包括AI算法在推理框架Tengine,MNN,NCNN,PaddlePaddle及相关芯片上的实现。欢迎加入微信交流群,微信号:gg15319381845(备注:嵌入式)
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
Arm中国学堂公众号
关注Arm中国学堂
实时获取免费 Arm 教学资源信息
Arm中国招聘公众号
关注Arm中国招聘
实时获取 Arm 中国职位信息