0. 前言
- 之前在 V100服务器上稍微尝试了一下TVM。
- 目前好像没有什么 TVM + Jetson Nano 的资料,花了一周差不多把坑填平了,希望能给大家一点帮助吧。贴个代码,供大家参考吧。
- server_rpc_tune.py:远程Auto-tuning
- server_cross_compile.py:交叉编译,并导出 lib/graph/params(传到Jetbot后直接调用模型)
- jetson_detect_video.py:Jetbot本地测试代码。
- 想强调三点:
- 本人C++与TVM的水平非常有限,很可能有错误。
- 本文只是跑通了整个流程,对于TVM本身如何更好的优化模型并没有研究。
- 得到比较好的性能,主要是大力出奇迹,猜测了解更多TVM内容后性能还有上升空间。
- 进行了两次Auto-tuning
- v1(耗时2-3小时):
n_trial=200, early_stopping=100
- v2(耗时65小时):
n_trial=8000, early_stopping=4000
- v1(耗时2-3小时):
- 放个试验结果吧,可以看到,TVM Auto-tuning的结果比DarkNet的检测时间快了将近30%。
- 本文结构
- Jetbot上TVM初体验:包括在Jetbot上编译TVM,尝试进行远程Auto-tuning,记录遇到的一些坑。
- Jetbot上使用TVM运行Yolov3-tiny:通过交叉编译成功在Jetbot上运行Yolov3-tiny,并在Jetbot运行Tuned Model,记录一些坑。
- 试验结果与自己的感想:试验结果,其他的一些坑,下一步想做的事
1. Jetbot上TVM初体验
1.1. Jetbot中TVM的编译
- 参考资料:
- 官方文档总是最有用的。
- 网上资料只找到了这篇,帮助很大。
- 编译流程:
- 第一步:在Jetbot上下载TVM源码,安装各种依赖。具体参考官方文档。
- 第二步:下载LLVM源码并编译。
- 在普通服务器上,可以直接下载LLVM的预编译包。
- 但官网能下的预编译包放到Jetbot上没用,所以要自己下载源码编译。
- 编译流程参考这篇,编译过程花了5-6小时。
- 第三步:按照官方文档中写的,修改
config.cmake
,设置USE_CUDA
和USE_LLVM
。 - 第四步:执行编译,即执行
cmake
和make
命令。 - 第五步:添加
PYTHONPATH
,以及安装一些Python库。
- 采坑一: 最开始尝试只安装
runtime
(这种方法不需要编译llvm)而不是整体编译。- 其实我不太懂
runtime
和 完整版的区别,猜测runtime
版应该是用于部署的。 - 仅使用
runtime
版在交叉编译的时候碰到一些问题,搞不清楚原因,所以干脆就完整编译了,之后当时碰到的问题就不见了。
- 其实我不太懂
- 采坑二:在运行
make
命令时报错/usr/bin/ld: cannot find -l/usr/lib/aarch64-linux-gnu/libxml2.so
- 这问题我都傻了,字面意思就是找不到
/usr/lib/aarch64-linux-gnu/libxml2.so
这个文件。 - 这问题搞了仨小时:确定文件存在,添加了
LD_LIBRARY_PATH
,仔细研究了libxml2.so
的版本,搞了很久,问题还在。 - 解决思路:
- 运行
make VERBOSE=1
看看是哪条命令除了错。出错的命令太长了,就不复制了。 - 查看这条命令,发现
-l
就出现了libxml2.so
这一次,而且我也确定把这文件添加到LD_LIBRARY_PATH
中,所以可以单独运行这条命令,且把-l/usr/lib/aarch64-linux-gnu/libxml2.so
这个删除就行了。 - 还好
make
命令执行失败后,再次执行make
时会从上次失败的地方开始。 - 所以手动执行出错的这条命令(去掉
-l
相关内容),然后再执行make
,最终终于编译成功了。
- 运行
- 这问题我都傻了,字面意思就是找不到
- 采坑三:利用
pip3 install
时失败。- 安装scipy失败,参考这里。
- 使用
sudo apt-get install python3-scipy
这样成功了。 - Jetbot上安装一些Python库很奇怪,经常要用到
apt-get
这样来安装。
- 复制粘贴一下用到的命令
# 下载源码
git clone --recursive https://github.com/apache/incubator-tvm tvm
# 安装依赖
sudo apt-get update
sudo apt-get install -y python3 python3-dev python3-setuptools gcc libtinfo-dev zlib1g-dev build-essential cmake libedit-dev libxml2-dev
# LLVM编译命令
git clone https://github.com/llvm/llvm-project llvm-project
cd llvm-project
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=lld -DCMAKE_INSTALL_PREFIX=/usr/local ../llvm
make -j3 && make install
1.2. TVM 远程 Auto-tune
- 目标:利用官方样例,测试下Jetbot上的TVM是否安装成功了。
- 远程 Auto-tuned 结构在 这篇博客 中已经有说明,推荐大家看看。
- 简单说下自己对Auto-tune的理解:
- 存在一个中心点(即
rpc_tracker
)专门用于记录所有需要Tune的设备。 - 需要Tune的设备(即
rpc_server
)都需要主动连接到中心点上。- 注意,如果中心点本身就有设备,则中心点也需要在执行一条命令连接自己。
- 每个设备都有一个自己独立的名称。
- Auto-tuning 程序连接中心点,通过设备名称来选择对应的设备。
- Auto-tuning需要大量CPU资源,性能瓶颈可能在CPU上。(我的试验,性能瓶颈在公司网络是否稳定上……)
- 存在一个中心点(即
- 思路:
- 现有设备:一台服务器和一台Jetbot。
- 在服务器上运行中心点,Jetbot主动连接服务器,运行Server。
- 利用服务器CPU资源执行Auto-tuning。
- 实现过程:
- 在服务器上建立中心点,即
python -m tvm.exec.rpc_tracker --host=0.0.0.0 --port=9190
- 将Jetbot连接到服务器,即
python3 -m tvm.exec.rpc_server --tracker=10.0.10.56:9190 --key=jetbot
- 查看中心点连接的设备:
python -m tvm.exec.query_rpc_tracker --host=0.0.0.0 --port=9190
- 在服务器上运行 Auto-tuning 程序,即这个官方样例。
- 在服务器上建立中心点,即
- 采坑一:重要!重要!重要!
- 在Jetbot上运行,需要设置 CUDA ARCH,否则会报错。
- 如何知道是
sm_53
:在 Nvidia 算力查看页面,选择对应的算力即可。- 这个是我猜的,最后发现猜对了。
- 查资料的时候看到有人在Jetson TX2中设置 CUDA ARCH 为
sm_62
,对照上面的链接,那Nano应该就是sm_53
。
- 在官方样例的开头添加下面的内容
from tvm.autotvm.measure.measure_methods import set_cuda_target_arch
set_cuda_target_arch('sm_53')
- 采坑二:
- 官方样例中的Auto-tuning的参数如下。
autotvm.LocalBuilder
指定需要使用谁的CPU资源进行 Auto-tuning。autotvm.RPCRunner
指定需要对谁的算力进行 Auto-tuning。- 由于网络连接不稳定,所以需要加大
timeout
的数值,否则会报错。 - 公司迷之网络,难受……
tuning_option = {
'log_filename': log_file,
'tuner': 'xgb',
'n_trial': 2000,
'early_stopping': 600,
'measure_option': autotvm.measure_option(
builder=autotvm.LocalBuilder(timeout=1000),
runner=autotvm.RPCRunner(
'1080ti', # change the device key to your key
'0.0.0.0', 9190,
number=20, repeat=3, timeout=1000, min_repeat_ms=150),
),
}
- 采坑三:
- 中间一直Auto-tuning失败,可以看到,连接是成功的,但是会有各种看不懂的
error_no=1
或error_no=4
的错误。 - 社区某帖子提到,如果服务器的CUDA版本和Jetbot的CUDA版本不一致,可能会报错。
- 还有要注意
采坑一
中提到的CUDA ARCH。 - 最终处理流程是,先在Jetbot上执行本地Auto-tune,解决运行过程中的一些小问题,然后再次远程Auto-tune就成功了……
- 然后 在Jetson上执行本地Auto-tune是个大坑,不推荐这么做,建议进行交叉编译,后面会提到。
- 中间一直Auto-tuning失败,可以看到,连接是成功的,但是会有各种看不懂的
2. Jetbot上使用TVM运行Yolov3-tiny
2.1. 坑:在Jetbot上直接建Relay计算图
- 首先给出结论:
- 在Jetbot上直接建Relay计算图是个大坑,非常不推荐这么做!
- 推荐使用交叉编译!
- 前面提到过,为了顺利执行远程Auto-tune,我先在Jetson上执行本地Auto-tune,所以为了执行Yolov3-tiny,我也这么做了……太惨了……
- Jetbot执行官方darknet样例失败的经历
- 在前一篇文章中,我直接运行官方样例在V100服务器上成功运行了DarkNet,所以在Jetbot上也是信心满满……
- 该样例通过一个
libdarknet2.0.so
构建计算图,然而该文件在 Jetbot 上并不能用,Jetbot毕竟是arm架构。 - 尝试通过
darknet
源码构建的libdarknet.so
文件替代,但失败了。 - 想尝试解决一下,发现是各种C语言问题,看不懂,不想深入。
- Jetbot直接转换TensorFlow模型失败的经历
- 目标:将TensorFlow的PB模型转换为TVM计算图。在V100上其实我已经成功实现了,所以也拿到Jetbot上试试。
- 结论:在Jetbot上转换TensorFlow模型完全行不通,原因是:
- TVM支持的最高版本Tensorflow是 1.12.0,而Jetson Nano支持的最低版本是 tensorflow 1.13.1
- 尝试之后会报
TypeError: resize() got an unexpected keyword argument 'half_pixel_centers'
的错误。 - 在社区各种帖子中了解到,这个问题就是因为tensorflow版本太高。
- 在使用tensorflow pb转TVM之前,需要先安装 antlr4,否则会报错。
- 社区某帖子中提到,需要在运行前安装
sudo pip3 install antlr4-python3-runtime
。
- 社区某帖子中提到,需要在运行前安装
2.2. TVM-DarkNet 交叉编译
- 资料:
- 我的源码
- 交叉编译官方Tutorials:这个sample我运行的时候存在一点问题,CPU环境下没问题,GPU环境(V100和Jetson Nano)下不行。
- 这个样例也说清楚了TVM如何进行交叉编译,想了解相关内容的话建议大家仔细看。
- 下面内容最多是对官方样例的一点注释。
- 实现的功能:
- 在服务器上编译模型,并直接部署到Jetbot上。
- 各种构件Relay的过程都是在服务器中进行,与Jetbot无关(
2.1.
中的所有问题都不存在),美滋滋。
- 交叉编译代码流程(注意,在服务器上/在Jetbot上)
- 准备:在Jetbot上运行本地Server,即
python3 -m tvm.exec.rpc_server --port 9090 --host 0.0.0.0
- 第一步:服务器任务,构建Relay计算图,即获取
mod, params
参数。 - 第二步:服务器任务,编译模型,即执行
relay.build
命令,特别要注意target_host
参数,后面会提到。 - 第三步:服务器任务,通过
lib.export_library
导出lib文件。- 编译好的模型,主要包括三个参数
lib/graph/params
。 - 其中,与运行平台相关的只有
lib
,另外两个在哪里都一样。
- 编译好的模型,主要包括三个参数
- 第四步:服务器任务,将导出的lib文件上传到Jetbot中。
- 第五步:Jetbot任务,通过
remote.load_module(lib_name)
命令导入刚刚传入的lib文件。 - 第六步:Jetbot任务,通过
graph/lib
以及Jetbot中的context信息,创建 runtime module,并导入params
。 - 第七步:Jetbot任务,通过创建好的runtime module运行程序。
- 准备:在Jetbot上运行本地Server,即
- 采坑一:需要重点关注
target_host
参数- 如果该参数设置有误,会报错
error adding symbols: File in wrong format
- 对于Jetbot来说,该参数的正确取值是
llvm -target=aarch64-linux-gnu
- 如何查询该数值:在Jetbot上运行
gcc -v
,查找target
开头的那行内容。 - 交叉编译时,
target_host
指的是Jetbot的环境。
- 如果该参数设置有误,会报错
- 采坑二,别忘记设置 CUDA ARCH
- 前文中提到的
set_cuda_target_arch('sm_53')
不能忘,这里就不细说了。
- 前文中提到的
2.3. Jetbot本地运行DarkNet
- 目标:将Auto-tuned完成的模型部署到Jetbot上,并测试性能。
- 资料:官方文档-模型导入导出和我的完整代码
- 基本流程流程:
- 第一步:在服务器上导出TVM-DarkNet的
lib/graph/params
- 有一点不同,就是要导入Auto-tuned结束的模型。
- 做法就是用
with autotvm.apply_history_best(log_file)
修饰relay.build
,然后再保存lib/graph/params
。
- 第二步:在Jetbot上导入第一步生成的
lib/graph/params
。 - 第三步:之后就是普通TVM程序运行了,就不说了。
- 第一步:在服务器上导出TVM-DarkNet的
- 采坑(技术无关,没兴趣的掉过):
- 聪明如我,当然能从官方文档中找到怎么保存与导入模型啦。
- 聪明如我,当然还能从官方文档中找到怎么使用Auto-tuned完的模型啦。
- 那要怎么在Jetbot上导入已经Auto-tuned完的模型呢?太简单了,把上面两个例子结合一下不就行了。
- 结局:检测一张图片耗时20s,我的测试代码完美无缺,肯定是TVM Auto-tuning出了问题
- 当时写的代码
with autotvm.apply_history_best(log_file):
loaded_json = open(graph_path).read()
loaded_lib = tvm.module.load(path_lib)
loaded_params = bytearray(open(params_path, "rb").read())
3. 试验结果与感想
3.1. 试验结果
- 与我前一篇文章《TVM 初体验 - DarkNet 性能测试》类似,测试内容主要是:
- 测试的是Yolov3-tiny,全部通过Python实现。
- 通过
cv2
读取视频同一段视频,并利用cv2, np
进行数据预处理。 - 通过 DarkNet 的Python接口以及 TVM-DarkNet 的Python接口进行检测。
- DarkNet性能比较结果
- DarkNet中的nms已经集成在模型内部,而TVM-DarkNet的nms需要通过
np
实现。 - DarkNet(Jetbot):在Jetbot编译DarkNet源码,执行这个Python脚本进行测试。
- Un-tuned TVM-DarkNet:尚未进行Auto-tuning的TVM-DarkNet模型。
- Tuned(v1) TVM-DarkNet(cross-compilation):经过Auto-tuning的TVM-DarkNet模型,在服务器通过交叉编译测试。
- Tuned(v1) TVM-DarkNet(jetbot):经过Auto-tuning的TVM-DarkNet模型,在Jetbot本地测试。
- Tuned(v2) TVM-DarkNet(jetbot):经过Auto-tuning的TVM-DarkNet模型,在Jetbot本地测试。
- DarkNet中的nms已经集成在模型内部,而TVM-DarkNet的nms需要通过
- 比较结果的一些自己的理解:
- 大力出奇迹
- 交叉编译时,视频读取以及数据预处理都是服务器CPU实现,所以明显快很多。
- 在Jetbot上运行DarkNet和TVM-DarkNet时:
- 图片预处理的工作其实不完全一样,所以有一定差距稍微可以理解。
- 视频读取时间差距有点大,不知道为啥。
- 检测的性能基本上在同一数量级了,如果有更长时间进行Auto-tune,很可能能获取更好的结果。
- 交叉编译的检测时间这么大,应该是网络传输的关系吧。
3.2. 感想
- 关于相关资料获取:TVM资料只能在社区里找,TVM+Nano的就更少了,可以看看TX2的(与Nano类似)。
- 现在全部做完了回过头看,感觉自己主要就是采坑了,也做啥实事。
- 其他的一些坑
- 公司网络问题,经常连接失败。
- Jetbot要注意功率,否则很容易电流过大自动关机,可以通过这里,执行
sudo nvpmodel -m1
选择最高功率5W(我猜是这个意思)。 - 就算设置了5W,电源线不给力,也经常自动关机……换了两根线,终于OK了。
- 后续想要尝试的工作:
- 利用C++运行DarkNet
- 利用C++部署TVM-Yolov3-tiny
- 将NMS添加到TVM计算图中。
- tensorflow提供了NMS op,但TVM中并不支持tensorflow的这个OP。
- TVM官方DarkNet实例中,TVM计算图没有包含NMS,后续是通过numpy实现的。
- 发现其实存在API
tvm.relay.op.vision.nms.non_max_suppression
。 - 后续想把样例利用C++跑一次,很显然,利用C++实现NMS对于我来说可能有点费劲。