29

zhangxiaolong · 2021年04月19日

探游·R329·AI部署实战(二)移植搭建AI环境

前言

hello呀\~我又来啦\~这一期拖了好久啦\~由于周末抽时间做的,时间不稳定,抱歉呐\~

上次做了R329的第一期,也就是解决了配置R329开发板并跑起来的问题;
这一期能解决的问题是:你能用R329上的AIPU跑个resnet50啥的,你能用R329的CPU跑起来NCNN,跑个resnet50,mobilenet啥的,并对比性能数据。

ps. 关于自定义模型如何在AIPU上跑,如YOLO\_XX,KWS\_XX等,就是下一节的内容啦\~当然按照惯例,我会先教你们把环境搭建好;
    • -

这一期大概要解决的问题如下:

  1. ”我要怎么样才能在R329上跑模型的呀?不要多了,能跑你们官方的demo就行,我主要想对整体的开发流程有个主观印象。”
  2. ”我想跟NCNN跑的数据做对比,我该如何在R329上移植NCNN并跑benchmark的呀?要怎么准备环境、怎么跑的呀?“
  3. “我要跑自定义模型该如何操作的呀?”
  4. ”我的模型贼复杂,有前后处理balabala的,我想一部分在CPU上做一部分在AIPU上做(这有个装逼的名字叫"切图"),这要怎么搞得呀?“

我相信上述的这些问题,对大部分非经验丰富的开发者来说,是有一点点困扰的吧!?

因此本着传播知识的初衷,我将从底层细节一点点带大家开荒R329,尽量讲清楚为什么这么做的原因,而不是机械的教大家去操作。

同时,为了尽可能照顾到不同开发环境的童鞋,我准备了两个版本的开发环境,一个是WIN10自带的WSL(ubuntu18),一个是虚拟机下的ubuntu14.04, 目的是为了尽可能的把坑给踩完先。

    • -




第一节  我要怎样才能在R329上跑起来AIPU的呀?

可能有小伙伴不想看啰嗦的分析,只想一步到位跑起来呀,所以,请跟着我左手右手一个慢动作。。。。

  1. 先跟着教程第一章把板子准备好,注意此时的板子的linux kernel驱动是有的,但是没有把AIPU的runtime库编译进去的。
  2. 从【点击这里】获取下载链接,得到lib库文件;从【这里这里】下载resnet50 demo的执行文件。
  3. 将libaipudrv.so*文件放到开发版的/lib目录下(用usb的话直接adb push就好啦, 用wifi传的话就直接scp下载了);
  4. 将resnet50文件夹放到开发版的/etc/zhouyi/目录下即可;
  5. 运行sh脚本, 加上参数-C resnet\_50即可;加上time指令就大致知道花了多少的时间啦~

下面就是详细的分析啦,不想看我叭叭的就直接跳过哈~

要能跑起来AIPU首先驱动层得支持,其次runtime库也得支持,接着我们才可以在应用层调用AIPU;

这里得好消息是驱动默认就支持了,坏消息是runtime库一时半会还不会完全开源,因此我们需要先解决runtime库得问题。

上期我们说过了,由于AIPU的知识产权是属于ARM china的,所以全志暂时是不会开放出来给我们使用的(对应的就是在开发的源码中把这一部分库代码给删除了),

但值得注意的是内核驱动时有支持的,既然有内核驱动,因此我们只需要在库层面添加用户runtime包就可支持AIPU啦~


啥?你问这个runtime包是啥?

这个runtime包的定义在不同语境里都不一样的,在这里代表的就是对AIPU驱动的一层封装;常规的嵌入式开发流程里面,有个外设,你要在应用层调用它,你就得

直接用read/write/ioctl等一系列操作来控制这个设备,很原始很低效,于是我们就在此基础上封装出一个库,来屏蔽底层的繁琐细节,这就是runtime库存在的意义。

理论可行,但实际上这个包一般也是拿不到的,因为官方没开放也没开源呀\~

头疼,俗话说的好,万事开头难在,中间难,后面也难,既然都这么难了,那我选择躺好,选择求助官方。

于是经过三分钟的对话沟通,我从ARM china官方接口那获取到了runtime库的lib包,因此我会把该文件分享给有需要的人,以及教会大家如何使用它。

0.jpg

把这些文件给adb push *** /etc/zhouyi/即可,记得把libaipudrv.so/libaipudrv.so.3/libaipudrv.so.3.5.3全部复制到开发板的/lib/目录下;

跑出来的效果如下:

1.jpg

可以看到用户空间的耗时大致是10ms的样子,于是我们就可以根据数据分析一波了:

Resnet50 OPsINT8
MAC 3857973248  3.8G
OUT  35778536 35MB
PARA25557096        25MB

我们知道resnet50的理论计算量大概是4Gops,而我们AIPU的理论算力是128MAC *0.8Gpbs=102.4GOPS

因此理论峰值算力下的FPS为:FPS=102.4/4=25FPS

而真实的算力约为10FPS,算力仅利用完一半不到,也就是可以大致推断出访存部分是瓶颈,嗯,访存带宽的问题没有做好呀\~

做优化的都知道,访存其实这个也好分析,resnet中存在大量的3X3跟1X1卷积层,这些卷积层在硬件底层都是由脉动阵列进行计算的,而3x3的卷积属于计算密集型,实际上MAC利用率能达到百分之八九十,

而1x1卷积属于访存密集型,也就是说巨大的算力往往都在等数据ready,实际中MAC利用率也就50%左右的样子,因此这个帧率跟最终的结果就对上啦\~

嗯,其他的硬件如CPU,GPU、NPU都可以这么分析,基本原理都是一样滴哇\~


第二节  我要怎样才能用R329的双核A53上跑起来NCNN呀?

这部分看下如何使用交叉编译链!

上一篇中我们简单使用了gcc编译了个helloworld,可以正常运行,但是用g++则不行,后来百般调试无果之后,直接选择放弃官方提供的prebuild工具链。

我们自己动手丰衣足食。

思路是这样子的:
1. 先去板子上确认交叉编译器的版本,然后选择比这个版本低的来编译NCNN,因为发布的版本向后兼容,不会向前兼容的呀。
2. 在ubuntu上下载对应版本,解决其中出现的一些问题,链接到合适的版本,balbala\~很碎所的,这就是为什么叫开荒的由来了!
3. 下载NCNN源码,配置好CMAKE,然后修改并编译好benchmark文件,解决其中出现的一大堆问题;
4. 跑benchmark,看数据\~

我特意在几个版本的环境下编NCNN,期间出现了很多基本问题,虽然不是很难,但我知道总有一部分读者是需要的,需要这些“显而易见”的指引的,往往前行路上的一颗绊脚小石头就可能会阻碍你的脚步、消融你的激情,因此我希望我写的内容不单单是冰冷的技术分享,而应当是有温度的包容,这是我敲下这行字时想到的。

所以,我这里不计其繁地一一记录如下,大家权当debug手册来用吧\~

要知道编译器版本,我们先去板子上看下交叉编译的版本,怎么看呢?

我们去lib目录下,系统库文件都在下面呢!

2.jpg

用ldd指令可以看到libc.so使用的GCC 版本是6.4.1,因此我们在ubuntu下直接安装小于等于这个版本的GCC就行啦\~

通用的方法是去官网下载:

https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-a/downloads

但是我们可以简化处理,直接再ubuntu下在线下载安装。

由于我们得宿主机是ubuntu14.4,因此先看下有哪些版本得交叉编译工具可以下载:

3.jpg

可以看到,针对arm64的,只有4.8的版本,4.8的版本比R329的6.4.1版本低,因此编译出的文件送到板子上理论上可以运行。
4.jpg
5.jpg
然后再下载一个不带版本的GCC、G++:
6.jpg
7.jpg
8.jpg

此时可以发现安装的版本对应上啦,gcc version 4.8.4 ubuntu/linaro;

这个时候我们再验证下helloworld看看,这个时候gcc g++都得验证下:

9.jpg

然后我们看下ELF头信息:
10.jpg
可以看到对应的是aarch64,对应上了\~可以整活了\~

然后,直接把编译好的gcc/g++ 版本helloword, 通过adb push推送到开发板上执行,此时发现
11.jpg
ok啦\~
一切都好起来啦\~

然后我们开始编译复杂点的NCNN。

git clone https://github.com/Tencent/ncnn.git
cd ncnn
mkdir -p build-aarch64-linux
cd build-aarch64-linux
cmake -DCMAKE_TOOLCHAIN_FILE=../toolchains/aarch64-linux-gnu.toolchain.cmake ..
make -j8
make install

按照官网流程,一套组合拳直接打下来,看现象:

12.jpg
13.jpg
编译到36%直接报错,说是找不到某个layer的某个方法,一顿查,发现是交叉环境没装全:

sudo apt install g++-arm-linux-gnueabi g++-arm-linux-gnueabihf g++-aarch64-linux-gnu

补全后,再编译,发现最后链接时出问题啦又:

14.jpg

这里卡了好久,为啥不行,百般查询之后发现,是源的选择不对,我选的是阿里源,在这个源下只有4.8版本的可以安装,因此我们换一下,换成清华源:

# 清华大学源
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-security main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse
##測試版源
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-proposed main restricted universe multiverse
# 源碼
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic main restricted universe multiverse
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-security main restricted universe multiverse
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse
##測試版源
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-proposed main restricted universe multiverse

具体操作如下:

修改源文件 sources.list:
Ubuntu 的源存放在在 /etc/apt/ 目录下的 sources.list 文件中,
修改前我们先做个备份,在终端中执行以下命令:
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bcakup
然后执行下面的命令打开 sources.list 文件,清空里面的内容,把上面我们编辑好的清华的源复制进去,保存后退出。
sudo gedit /etc/apt/sources.list
更新软件列表和升级,在终端上执行以下命令更新软件列表,检测出可以更新的软件:
sudo apt-get update
在终端上执行以下命令进行软件更新:
sudo apt-get upgrade

此时再检查下有哪些交叉编译器版本:
15.jpg

可以看到5,6,7,8版本都有的,此时我们只需要选择一个版本安装就行啦!

选择哪个版本?当然是小于等于开发板的版本呀!前面查过开发板的是6.4.1版本,这里的6版本安装后发现是6.5.0版本的,因此不行的,我们只能用5开头的版本(当然7版本的我也试过,NCNN没出什么问题,但是不排除今后移植其他三方软件的时候出问题,因此稳妥起见这里用5开头的版本):



先把之前的6.4.1版本的aarch64-arm-linux-gnu gcc/g++都删掉:

sudo apt-get remove *(注意:把依赖也要删除干净,用autoremove指令。)

然后安装之后发现aarch64-linux-gcc/g++ 链接到的不是5开头的编译器,因此修改软连接ln -snf /usr/bin/aarch64-linux-gnu-gcc-5 /usr/bin/aarch64-linux-gnu-gcc;

好啦终于把NCNN编译出来啦,由于我们只跑benchmark,因此直接在build目录下找benchnn,推到板子上运行,注意对应的param文件也要推进去哟:
16.jpg
17.jpg

好家伙发现,直接内存爆掉了,一想也对,毕竟就256M内存,看一下剩余多少?
18.jpg

开机就只剩余71M内存,内存不够的呀,怎么办?

1. 修改镜像让CAM分配少一点内存给AIPU;
2. 修改ncnn的benchmark.cpp 使用inplace轻量内存模式,不开数据重排(数据重排可以使CPU加速的哟,这块我贼6,有空给大家叨叨);

方案一得该内核,太麻烦了,于是我直接改NCNN benchmark.cpp了,只跑mobilenet系列:
19.jpg

嗯结果出来啦,我跑得是A53单线程,理论峰值算力一二十个GFlops,加上访存得固有缺陷,因此跑出这个数据也是阔以理解的。

我们能得到的结论就是: AIPU大法好呀\~

第三节  我要怎样才能跑自己的模型呀??

好的,至此,从下往上看,我们的硬件能跑起来了,AIPU驱动是ok的了, runtime环境也ok了(见附录:如何编译第三方package),

接下来就可以到AIPUBuilder部分了,这部分是整个AI软件部分的核心部件,也就是能让你自己的模型在R329上跑起来的关键了。

jio逗嘛嘚\~\~等等。。。

肯定有人要问的啦,“啥啥啥?咋又出来个AIPUBuilder?这是啥?干啥用的?怎么用?

你之前整了这么多出,啰嗦了这么多,我都整晕了,你就从我使用者的角度出发,告诉我这个是啥?能解决啥问题?”

 ······
·······

好的,好的,不要再骂了,我错了,我知道错了,是我写博客流程有瑕疵了,我已经深刻地认识到了我的错误了\~
20.jpg

但是我还敢.......还敢不改(国家一级精分表演大师show场。。。)
21.jpg
22.jpg

各位大佬们此时此刻恰如彼时彼刻:
23.jpg
24.jpg
好liao好liao,不自娱自乐了,正式开整(真好,想怎么写怎么写,想怎么乐怎么乐\~周末还能宅一天):
25.jpg



我们看问题采用黑盒模型,输入是啥,目标(输出)是啥:

输入:是一个模型,可能来自pytorch/tensoflow/tflite/onnx/caffe….等等格式;

目标:就是把这个模型部署到R329硬件板子上,能跑起来看到效果,语音就能答应你,人脸识别就能认出你。

可以看到我们有头(输入),有尾(目标)了,因此只要在两者之间填充亿点点细节
26.jpg

我们的AI模型就在R329上跑起来啦\~ 怎么样是不是很简单呀\~

这个部分分三个阶段:

  1. 大致讲下AIPUBuilder是啥,能干啥;
  2. 准备AIPUBuilder的环境;
  3. 使用AIPUbuilder生成目标bin文件;(划掉,这次不想细写)
  4. AIPUBuilder能干啥?

你看我们的模型来自五湖四海吧(pytorch/tensoflow/tflite/onnx/caffe….),是不是格式都千奇百怪,算子也是奇形怪状,

这么多的差异要跑到特定的(固定的、单一的)硬件上来,那就可以简单认为是一个多对一的接口呀!

说个时髦一点的词,就是AI编译器啊!

说到AI编译器,我们可以看一篇2020年8月份的综述:《The Deep Learning Compiler: A Comprehensive Survey》
27.jpg

上面的图我大致解释下,所谓的AI编译器,分为前端跟后端,前端对接各种训练框架,把模型解析了,然后做各种与硬件无关的图优化,量化啊啥的,分的细一点的话这部分也会产生上层IR表示。

后端就是用来对接不同的硬件的,标准的AI编译器是要能对接任意硬件后端的,跟硬件相关的话简单来说就是各种算子实现了,基于各种不同硬件的算子实现,在这个层面就有各种优化策略,与上层的IR表示有所差异,因此一般会产生底层IR表示;

而R329的AIPU由于不是做通用的编译器,不需要支持很多的硬件后端,因此结构可以优化一下,比如只用一套IR表示、上层图优化与底层硬件绑定等等,当然其他的通用优化这里也都能做。

因此按照行业标准流程我们可以大致分为parser、quantization、graph optimizer、compiler,builder等等部分,但是为了简化用户操作,只留一个aipubuild的接口,具体怎么使用下一节再说。

我们简单解释下IR作用是啥?

上述的几个大的部分,就好比人的脑袋、躯体、手脚,这些部分之间需要一个东西来互相通信,在人体里面是神经系统,而在AIPUbuilder里面就是IR了,即中间表示。

IR是一套内部定义的标准让各个模块都按照这个标准来处理模型信息,厂商不同标准也不同的,比如intel的openvino里面用的肯定跟我们不一样了。

好了,到这基本解释清楚,AIPUBuilder是干啥的了!一言以蔽之:“AI 编译器”,负责将AI模型编译成能在AI芯片上跑的目标bin文件。

  1. AIPUBuilder环境配置

咱们接着在说下如何配置AIPU的环境,使之能跑AI模型;

咱们首先第一步先看全志的手册《AW\_R329\_Tina\_Linux\_开发入门\_v0.5》,在page18 的附录1:在ubuntu14中使用Conda搭建python3.6环境;注意哈,这里是指定了版本以及环境的:

28.jpg

我们跟着一顿操作,然后激活conda虚拟环境,总之跟着文档来就行啦\~

然后我们会拿到一个安装包AIPUBuilder-3.0.120-cp36-cp36m-linux_x86_64.whl(没有的找我要~),使用pip安装就好了。
29.jpg
然后就开始安装了哇:pip install  AIPUBuilder-3.0.120-cp36-cp36m-linux_x86_64.whl
30.jpg
由于网络不行,这里下载了好久,看到这里就说明,安装成功啦\~

注意观察log可以看到tensorflow/pytorch的版本,这个你们在转模型的时候需要特别注意的点。
我们看下帮助指令:
31.jpg
32.jpg
原来是安装的版本与要求的版本不一致:
33.jpg
我们先卸载当前版本:
34.jpg
然后随便安装一个满足要求的版本:
35.jpg
又发现AQT里面用了PIL:
36.jpg
于是我们接着装pillow:
37.jpg
接着装networkx:
38.jpg
通过这亿点点操作终于环境ok啦\~
39.jpg


  1. 如何使用GBuilder

我们去这里随便下载一个kws模型
https://github.com/ARM-software/ML-KWS-for-MCU



40.jpg

然后直接把pb文件喂给AIPUBuilder,记得加上kws.cfg文件,这个文件的参数分析留到下一次博客吧,原因是,这节内容太多啦~
最后就会生成如下的三个文件:
41.jpg

然后adb push推送到开发板上,运行效果如下:
42.jpg

正常运行啦\~

具体操作细节由于篇幅过大就留到下一章啦\~

第四节  我要怎样才能切图(异构计算)呀?

这部分打算配合tengine框架来用,他们的切图贼溜,但是我完全没时间,下次再写。

推荐阅读
关注数
2114
内容数
20
全志科技芯片方案学习专栏,欢迎关注。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息