不编译apk直接在android上部署tvm编译结果

技术路线

理论上android就是一个linux内核加用户态库。因此tvm部署模型到android并不一定需要做一个apk,只需构建一个elf程序提供rpc的功能就可以了。
查看到https://github.com/apache/incubator-tvm/pull/4281,tvm主线已经添加了c++版本的rpc实现。
因此,可以使用android ndk中的工具链编译tvm rpc c++实现,绕开复杂且不必要的android apk构建(当然有一个可能的问题是,由于android的权限管控,编译好的程序在非root情况下可能无法启动。本次是在android7.1上/data/local/tmp目录可以用于执行)。

编译构建

根据手机型号(坚果pro)和android版本7.1.1,下载android-ndk-r21并选择aarch64-linux-android24-clang++作为交叉编译器。

tvm创建build目录并编辑config.make

1
2
3
4
在tvm目录下
mkdir build_arm64
cd build_arm64
cp ../config.make ./

为了支持rpc和gpu运算,编辑config.make确保下面两项正确。其中vulkan的设置目录dep_dirs会在后面的步骤中配置好。

1
2
3
# Whether enable RPC runtime
set(USE_RPC ON)
set(USE_VULKAN /home/majiang/hd/opensource/tvm/build_arm64/dep_dirs)

构建交叉版本的spirv-tools

1
2
3
4
5
6
7
8
9
10
11
git clone https://github.com/KhronosGroup/SPIRV-Tools.git
cd SPIRV-Tools/
git clone https://github.com/KhronosGroup/SPIRV-Headers.git external/spirv-headers
mkdir build
cd build
cmake .. -DCMAKE_CXX_COMPILER="/home/majiang/hd/opensource/android_sdk/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang++" -DCMAKE_C_COMPILER="/home/majiang/hd/opensource/android_sdk/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang"
make -j 8
mkdir inst
make install DESTDIR=`pwd`/inst
#本来使用make install-headers 应该更为标准,但是spirv-headers的makefile没有写好,其忽略了DESTDIR变量,直接把头文件拷贝到了/usr/local下。规避方案直接copy
cp ../external/spirv-headers/include/* inst/usr/local/include/ -r

构建交叉版本的runtime

进入build_arm64目录
mkdir dep_dirs
cd dep_dirs/
mkdir include
cp ~/hd/opensource/android_sdk/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/vulkan ./include -r
cp ~/hd/opensource/android_sdk/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/24 ./lib -r
cp ~/hd/opensource/android_sdk/spirv-tools/SPIRV-Tools/build/inst/usr/local/include/spirv-tools/ ./include/ -r
cp ~/hd/opensource/android_sdk/spirv-tools/SPIRV-Tools/build/inst/usr/local/lib/* ./lib/
#(可选的strip -g)
cp ~/hd/opensource/android_sdk/spirv-tools/SPIRV-Tools/external/spirv-headers/include/spirv/ ./include/ -r
cd ../
cmake .. -DCMAKE_CXX_COMPILER=”/home/majiang/hd/opensource/android_sdk/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang++” -DCMAKE_C_COMPILER=”/home/majiang/hd/opensource/android_sdk/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang”
(修改config.cmake 将修改 USE_VULKAN指向 dep_dirs set(USE_VULKAN /.xxx…/tvm/build_arm64/dep_dirs))
make runtime -j8

构建cpp版本的rpc服务程序

在build_arm64目录下执行如下命令。

1
make -C ../apps/cpp_rpc CXX=/home/majiang/hd/opensource/android_sdk/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang++  TVM_RUNTIME_DIR=/home/majiang/hd/opensource/tvm/build_arm64/

tvm当前的makefile会把所有的cc都加进去编译(cmake文件不会),其中包括windows的win32_process.cc。为了阻止编译错误,手动将其改名为win32_process.cc-nouse。
有可能因为搜索路径的问题,找不到vulkan库,可以使用如下命令手动链接(添加-Wl,-rpath-link到对应api的lib目录;-Wl,-rpath-link=/home/majiang/hd/opensource/android_sdk/android-ndk-r21/platforms/android-24/arch-arm64/usr/lib/)。

1
/home/majiang/hd/opensource/android_sdk/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang++ -std=c++14 -O2 -fPIC -Wall -I/media/majiang/c6b38ac3-8b8a-4613-8259-dddbffe2f4cb/majiang/opensource/tvm/include -I/media/majiang/c6b38ac3-8b8a-4613-8259-dddbffe2f4cb/majiang/opensource/tvm/3rdparty/dmlc-core/include -I/media/majiang/c6b38ac3-8b8a-4613-8259-dddbffe2f4cb/majiang/opensource/tvm/3rdparty/dlpack/include -o tvm_rpc main.cc rpc_env.cc rpc_server.cc -L/home/majiang/hd/opensource/tvm/build_arm64_new/  -ltvm_runtime -ldl -Wl,-R/home/majiang/hd/opensource/tvm/build_arm64_new/ -Wl,-rpath-link=/home/majiang/hd/opensource/android_sdk/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/bin/../sysroot/usr/lib/aarch64-linux-android/24/

更新本地的tvm使其支持opencl和vulkan代码生成

修改本地tvm build目录下的config.cmake,确保USE_OPENCL/VULKAN是ON状态。
并且确保装好了opencl和vulkan 的sdk(可参考https://www.codenong.com/cs105410317/,直接去https://vulkan.lunarg.com/sdk/home 下载vulkan sdk)。

配置手机

打开USB调试

在手机设置的“关于本机”页面中连续点击 “软件版本” 条目,可以打开开发者模式。然后在全局高级设置中会出现 “开发者选项”,进入其条目打开“USB调试”即可。

安装adb

apt install adb -y

上传文件并设置权限

android高版本在没有root的情况下,不能直接给sd卡中的程序加上可执行权限,参考https://my.oschina.net/jerikc/blog/497090 ,可以拷贝到/data/local/tmp 的特殊路径下,并添加执行权限。
在tvm的目录下将两个必须的文件上传。

1
2
adb push apps/cpp_rpc/tvm_rpc  /data/local/tmp
adb push build_arm64_new/libtvm_runtime.so /data/local/tmp

另外,由于使用了c++,还需上传 libc++_shared.so(参考https://developer.android.com/ndk/guides/cpp-support#libc)。

1
adb push /home/majiang/hd/opensource/android_sdk/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/bin/../sysroot/usr/lib/aarch64-linux-android/libc++_shared.so  /data/local/tmp

然后进入手机,为程序设置可执行权限。

1
2
3
adb shell
cd /data/local/tmp
chmod 777 *

测试rpc程序

使用下面的命令,初步测试程序能否正常启动。

1
2
3
4
adb shell
cd /data/local/tmp
export LD_LIBRARY_PATH=`pwd`
./tvm_rpc

如果正常,应该能看到help信息,示例如下。

1
2
3
4
5
6
7
8
9
[10:17:53] main.cc:289: Command line usage
server - Start the server
--host - The hostname of the server, Default=0.0.0.0
--port - The port of the RPC, Default=9090
--port-end - The end search port of the RPC, Default=9199
--tracker - The RPC tracker address in host:port format e.g. 10.1.1.2:9190 Default=""
--key - The key used to identify the device type in tracker. Default=""
--custom-addr - Custom IP Address to Report to RPC Tracker. Default=""
--silent - Whether to run in silent mode. Default=False

启动rpc服务,进行测试

启动cpp版本的rpc后,测试其功能是否正常。
首先在host主机上启动rpc tracker。使用如下命令。
应该会看到”INFO:RPCTracker:bind to 0.0.0.0:9190”这样的提示。

1
2
3
export TVM_HOME=/home/majiang/hd/opensource/tvm
export PYTHONPATH=$TVM_HOME/python:$TVM_HOME/topi/python:${PYTHONPATH}
python3 -m tvm.exec.rpc_tracker

然后在手机上启动cpp 版本的rpc server。注意tracker选项中的ip地址是电脑主机的ip,不是手机的ip,9190是前面启动tracker给出的port。–key一定写成android,否则后面的android_rpc_test.py会找不到设备(它写死了设备的key为android)。

1
2
3
4
adb shell
cd /data/local/tmp
export LD_LIBRARY_PATH=`pwd`
./tvm_rpc server --tracker=192.168.3.4:9190 --key=android

此时,在电脑主机上可以查询到手机了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 python3 -m tvm.exec.query_rpc_tracker
Tracker address 192.168.3.4:9190

Server List
----------------------------
server-address key
----------------------------
192.168.3.33:38151 server:android
----------------------------

Queue Status
-------------------------------
key total free pending
-------------------------------
android 1 1 0
-------------------------------

最后,进入tvm/apps/android_rpc目录,启动android_rpc的测试。

1
2
3
4
5
6
export TVM_HOME=/home/majiang/hd/opensource/tvm
export PYTHONPATH=$TVM_HOME/python:$TVM_HOME/topi/python:${PYTHONPATH}
export TVM_TRACKER_HOST=192.168.3.4
export TVM_TRACKER_PORT=9190
export TVM_NDK_CC=/home/majiang//hd/opensource/android_sdk/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang++
python3 tests/android_rpc_test.py

正常时可以看到如下的提示(打开GPU测试需要修改android_rpc_test.py,设置test_vulkan = True)。

1
2
3
4
5
Run CPU test ...
0.000340646 secs/op

Run GPU(Vulkan Flavor) test ...
4.40886e-05 secs/op

android端json解析报错问题分析

使用简单的rpc测试正常,但是使用apps/benchmark/mobile_gpu_imagenet_bench.py ( python3 ./mobile_gpu_imagenet_bench.py –model rk3399 –network mobilenet –rpc-key android)等复杂测试,会出现手机端报json格式错误。具体的表现是运行到runtime.create时,手机端的runtime解析json格式assert报错,形式不固定(JSONReader::BeginObject等期望的字符没有读到)。

使用gdb进行初步调试定位

在没有任何背景信息的情况下,可以先行利用gdb继续初步查看。
android-ndk提供了arm64版本的gdb-server和x86版本的gdb。

1
2
在ndk目录下
adb push ./prebuilt/android-arm64/gdbserver/gdbserver /data/local/tmp

然后。
android端
cd /data/local/tmp
./gdbserver 192.168.3.33:8888(手机ip和希望使用的端口) –attach 2126 (使用ps |grep tvm_rpc看到的rpc进程)

host端
./android-ndk-r21/prebuilt/linux-x86_64/bin/gdb ../tvm/apps/cpp_rpc/tvm_rpc

使用asan排查可疑内存问题

编译

需要将libtvm_runtime.so和tvm_rpc都加上asan重新编译(注意如果不重新编译tvm_rpc,asan的检查可能无法准确输出信息)。
对于前者,需要修改config.make,在其尾部加上如下语句,然后重新cmake一次(如果是干净的环境下cmake,可能会出现找不到pthread.h的错误。去掉下面语句成功cmake一次,再重新加上后cmake一次,就可以了,原因暂未调查。)。

1
2
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")

对于后者,可以在make的时候添加CXXFLAGS,也可以直接手动加编译参数(因为编译tvm_rpc只需要单条命令)。

1
/home/majiang/hd/opensource/android_sdk/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang++ -std=c++14 -O2 -fPIC -Wall -I/media/majiang/c6b38ac3-8b8a-4613-8259-dddbffe2f4cb/majiang/opensource/tvm/include -I/media/majiang/c6b38ac3-8b8a-4613-8259-dddbffe2f4cb/majiang/opensource/tvm/3rdparty/dmlc-core/include -I/media/majiang/c6b38ac3-8b8a-4613-8259-dddbffe2f4cb/majiang/opensource/tvm/3rdparty/dlpack/include -o tvm_rpc main.cc rpc_env.cc rpc_server.cc -L/home/majiang/hd/opensource/tvm/build_arm64/  -ltvm_runtime -ldl -Wl,-R/home/majiang/hd/opensource/tvm/build_arm64/ -Wl,-rpath-link=/home/majiang/hd/opensource/android_sdk/android-ndk-r21/platforms/android-24/arch-arm64/usr/lib/ -fsanitize=address

运行

使用adb push将新的rumtime和tvm_rpc上传。
然后使用下面命令上传asan需要的动态库。

1
adb push /home/majiang/hd/opensource/android_sdk/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/9.0.8/lib/linux/libclang_rt.asan-aarch64-android.so /data/local/tmp

然后使用正常方式启动tvm_rpc即可。

再次运行触发json解析错误的测试用例,这次asan给出了准确的输出,确实有堆内存越界。在启动tvm_rpc的终端上可以看到如下输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
==5713==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x0076a6ec7100 at pc 0x007fa7f66f50 bp 0x005fa41fc130 sp 0x005fa41fb8d8
WRITE of size 20942 at 0x0076a6ec7100 thread T1
#0 0x7fa7f66f4c (/data/local/tmp/libclang_rt.asan-aarch64-android.so+0x85f4c)
#1 0x7fa7f66c64 (/data/local/tmp/libclang_rt.asan-aarch64-android.so+0x85c64)
#2 0x7fa7757a68 (/data/local/tmp/libtvm_runtime.so+0x55ba68)
#3 0x7fa7757458 (/data/local/tmp/libtvm_runtime.so+0x55b458)
#4 0x7fa770a0cc (/data/local/tmp/libtvm_runtime.so+0x50e0cc)
#5 0x7fa76faf00 (/data/local/tmp/libtvm_runtime.so+0x4fef00)
#6 0x7fa76f9ce0 (/data/local/tmp/libtvm_runtime.so+0x4fdce0)
#7 0x7fa76fcb3c (/data/local/tmp/libtvm_runtime.so+0x500b3c)
#8 0x7fa774d3c8 (/data/local/tmp/libtvm_runtime.so+0x5513c8)
#9 0x555558b444 (/data/local/tmp/tvm_rpc+0x36444)
#10 0x5555583db0 (/data/local/tmp/tvm_rpc+0x2edb0)
#11 0x5555585180 (/data/local/tmp/tvm_rpc+0x30180)
#12 0x5555585354 (/data/local/tmp/tvm_rpc+0x30354)
#13 0x7fa7e6a41c (/system/lib64/libc.so+0x6841c)
#14 0x7fa7e1fe00 (/system/lib64/libc.so+0x1de00)

0x0076a6ec7100 is located 0 bytes to the right of 4096-byte region [0x0076a6ec6100,0x0076a6ec7100)
allocated by thread T1 here:
#0 0x7fa7f8b374 (/data/local/tmp/libclang_rt.asan-aarch64-android.so+0xaa374)
#1 0x7fa73a3460 (/data/local/tmp/libtvm_runtime.so+0x1a7460)
#2 0x7fa73a3438 (/data/local/tmp/libtvm_runtime.so+0x1a7438)
#3 0x7fa73a2bf0 (/data/local/tmp/libtvm_runtime.so+0x1a6bf0)
#4 0x7fa7683008 (/data/local/tmp/libtvm_runtime.so+0x487008)
#5 0x7fa7682240 (/data/local/tmp/libtvm_runtime.so+0x486240)
#6 0x7fa7681448 (/data/local/tmp/libtvm_runtime.so+0x485448)
#7 0x7fa76fab4c (/data/local/tmp/libtvm_runtime.so+0x4feb4c)
#8 0x7fa76f9ce0 (/data/local/tmp/libtvm_runtime.so+0x4fdce0)
#9 0x7fa76fcb3c (/data/local/tmp/libtvm_runtime.so+0x500b3c)
#10 0x7fa774d3c8 (/data/local/tmp/libtvm_runtime.so+0x5513c8)
#11 0x555558b444 (/data/local/tmp/tvm_rpc+0x36444)
#12 0x5555583db0 (/data/local/tmp/tvm_rpc+0x2edb0)
#13 0x5555585180 (/data/local/tmp/tvm_rpc+0x30180)
#14 0x5555585354 (/data/local/tmp/tvm_rpc+0x30354)
#15 0x7fa7e6a41c (/system/lib64/libc.so+0x6841c)
#16 0x7fa7e1fe00 (/system/lib64/libc.so+0x1de00)

Thread T1 created by T0 here:
#0 0x7fa7f725a0 (/data/local/tmp/libclang_rt.asan-aarch64-android.so+0x915a0)
#1 0x5555584ebc (/data/local/tmp/tvm_rpc+0x2febc)
#2 0x5555584c20 (/data/local/tmp/tvm_rpc+0x2fc20)
#3 0x5555582608 (/data/local/tmp/tvm_rpc+0x2d608)
#4 0x5555581224 (/data/local/tmp/tvm_rpc+0x2c224)
#5 0x555556cdd4 (/data/local/tmp/tvm_rpc+0x17dd4)
#6 0x555556d8ac (/data/local/tmp/tvm_rpc+0x188ac)
#7 0x7fa7e1c7d8 (/system/lib64/libc.so+0x1a7d8)
#8 0x5555566b10 (/data/local/tmp/tvm_rpc+0x11b10)
#9 0x7fa8460d54 (/system/bin/linker64+0x6d54)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/data/local/tmp/libclang_rt.asan-aarch64-android.so+0x85f4c)
Shadow bytes around the buggy address:
0x001ed4dd8dd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x001ed4dd8de0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x001ed4dd8df0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x001ed4dd8e00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x001ed4dd8e10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x001ed4dd8e20:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x001ed4dd8e30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x001ed4dd8e40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x001ed4dd8e50: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x001ed4dd8e60: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x001ed4dd8e70: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==5713==ABORTING

由于是交叉编译运行,asan无法直接给出了文件和行号信息。
为了便于理解日志,可以将上面的日志信息复制并保存到host机器的文件中,再使用asan提供的专用symbolize工具获得文件和行号。

日志解析

为了解析交叉编译的日志,asan提供了专门的symbolizer工具。
该工具的核心任务就是将 “binary文件+offset” 翻译为 “文件:函数:行号”。
llvm的asan提供的工具在, https://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/asan/scripts/asan_symbolize.py。它对翻译工作做了封装,可以使用llvm-symbolizer/address2line等多种底层工具完成翻译。

其使用也非常简单,如下一条命令即可完成翻译。
error.log是含有错误信息的文件。
-c 是交叉编译的prefix
-s 是sysroot,该路径下需要含有带调试信息的binary文件

1
./asan_symbolize.py  -d -c "/home/majiang/hd/opensource/android_sdk/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-"  -s "/home/majiang/hd/opensource/tvm/debug_arm64/" < error.log

这个脚本错误信息不是非常友好,需要先行确认底层的symbolizer能正常工作,sysroot中含有正确的binary文件。
例如,如果系统PATH中没有llvm-symbolizer,只有llvm-symbolizer-9(没有安装默认的llvm版本,而是安装了新的9版本),需要先export ASAN_SYMBOLIZER_PATH=llvm-symbolizer-9,再运行脚本。
sysroot中的路径布置必须与log中的日志完全一致,否则脚本报错信息也比较难以理解。

故障修复

使用asan,可以获得如下的故障日志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
==5713==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x0076a6ec7100 at pc 0x007fa7f66f50 bp 0x005fa41fc130 sp 0x005fa41fb8d8
WRITE of size 20942 at 0x0076a6ec7100 thread T1
#0 0x7fa7f66f4c in recvfrom /toolchain/llvm-project/compiler-rt/lib/asan/../sanitizer_common/sanitizer_common_interceptors.inc:6404:5
#1 0x7fa7f66c64 in recv /toolchain/llvm-project/compiler-rt/lib/asan/../sanitizer_common/sanitizer_common_interceptors.inc:6385:17
#2 0x7fa7757a68 in tvm::support::TCPSocket::Recv(void*, unsigned long, int) /home/majiang/hd/opensource/tvm/src/runtime/rpc/../../support/socket.h:483:12
#3 0x7fa7757458 in tvm::runtime::SockChannel::Recv(void*, unsigned long) /home/majiang/hd/opensource/tvm/src/runtime/rpc/rpc_socket_impl.cc:53:23
#4 0x7fa770a0cc in tvm::runtime::RPCSession::HandleUntilReturnEvent(tvm::runtime::TVMRetValue*, bool, tvm::runtime::PackedFunc const*)::$_1::operator()(void*, unsigned long) const /home/majiang/hd/opensource/tvm/src/runtime/rpc/rpc_session.cc:880:28
#5 0x7fa76faf00 in unsigned long tvm::support::RingBuffer::WriteWithCallback<tvm::runtime::RPCSession::HandleUntilReturnEvent(tvm::runtime::TVMRetValue*, bool, tvm::runtime::PackedFunc const*)::$_1>(tvm::runtime::RPCSession::HandleUntilReturnEvent(tvm::runtime::TVMRetValue*, bool, tvm::runtime::PackedFunc const*)::$_1, unsigned long) /home/majiang/hd/opensource/tvm/src/runtime/rpc/../../support/ring_buffer.h:160:25
#6 0x7fa76f9ce0 in tvm::runtime::RPCSession::HandleUntilReturnEvent(tvm::runtime::TVMRetValue*, bool, tvm::runtime::PackedFunc const*) /home/majiang/hd/opensource/tvm/src/runtime/rpc/rpc_session.cc:879:26
#7 0x7fa76fcb3c in tvm::runtime::RPCSession::ServerLoop() /home/majiang/hd/opensource/tvm/src/runtime/rpc/rpc_session.cc:952:3
#8 0x7fa774d3c8 in tvm::runtime::RPCServerLoop(int) /home/majiang/hd/opensource/tvm/src/runtime/rpc/rpc_socket_impl.cc:113:30
#9 0x555558b444 in tvm::runtime::RPCServer::ServerLoopProc(tvm::support::TCPSocket, tvm::support::SockAddr) ??:0:0
#10 0x5555583db0 in tvm::runtime::RPCServer::ListenLoopProc() ??:0:0
#11 0x5555585180 in std::__ndk1::__async_assoc_state<void, std::__ndk1::__async_func<void (tvm::runtime::RPCServer::*)(), tvm::runtime::RPCServer*> >::__execute() ??:0:0
#12 0x5555585354 in void* std::__ndk1::__thread_proxy<std::__ndk1::tuple<std::__ndk1::unique_ptr<std::__ndk1::__thread_struct, std::__ndk1::default_delete<std::__ndk1::__thread_struct> >, void (std::__ndk1::__async_assoc_state<void, std::__ndk1::__async_func<void (tvm::runtime::RPCServer::*)(), tvm::runtime::RPCServer*> >::*)(), std::__ndk1::__async_assoc_state<void, std::__ndk1::__async_func<void (tvm::runtime::RPCServer::*)(), tvm::runtime::RPCServer*> >*> >(void*) ??:0:0
#13 0x7fa7e6a41c in __pthread_start(void*) ??:0:0
#14 0x7fa7e1fe00 in __start_thread ??:0:0

0x0076a6ec7100 is located 0 bytes to the right of 4096-byte region [0x0076a6ec6100,0x0076a6ec7100)
allocated by thread T1 here:
#0 0x7fa7f8b374 in operator new(unsigned long) _asan_rtl_:3
#1 0x7fa73a3460 in std::__ndk1::__libcpp_allocate(unsigned long, unsigned long) /home/majiang/hd/opensource/android_sdk/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/bin/../sysroot/usr/include/c++/v1/new:253:10
#2 0x7fa73a3438 in std::__ndk1::allocator<char>::allocate(unsigned long, void const*) /home/majiang/hd/opensource/android_sdk/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/bin/../sysroot/usr/include/c++/v1/memory:1813:37
#3 0x7fa73a2bf0 in std::__ndk1::allocator_traits<std::__ndk1::allocator<char> >::allocate(std::__ndk1::allocator<char>&, unsigned long) /home/majiang/hd/opensource/android_sdk/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/bin/../sysroot/usr/include/c++/v1/memory:1546:21
#4 0x7fa7683008 in std::__ndk1::__split_buffer<char, std::__ndk1::allocator<char>&>::__split_buffer(unsigned long, unsigned long, std::__ndk1::allocator<char>&) /home/majiang/hd/opensource/android_sdk/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/bin/../sysroot/usr/include/c++/v1/__split_buffer:318:29
#5 0x7fa7682240 in std::__ndk1::vector<char, std::__ndk1::allocator<char> >::shrink_to_fit() /home/majiang/hd/opensource/android_sdk/android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/bin/../sysroot/usr/include/c++/v1/vector:1598:57
#6 0x7fa7681448 in tvm::support::RingBuffer::Reserve(unsigned long) /home/majiang/hd/opensource/tvm/src/runtime/rpc/../../support/ring_buffer.h:74:15
#7 0x7fa76fab4c in unsigned long tvm::support::RingBuffer::WriteWithCallback<tvm::runtime::RPCSession::HandleUntilReturnEvent(tvm::runtime::TVMRetValue*, bool, tvm::runtime::PackedFunc const*)::$_1>(tvm::runtime::RPCSession::HandleUntilReturnEvent(tvm::runtime::TVMRetValue*, bool, tvm::runtime::PackedFunc const*)::$_1, unsigned long) /home/majiang/hd/opensource/tvm/src/runtime/rpc/../../support/ring_buffer.h:148:11
#8 0x7fa76f9ce0 in tvm::runtime::RPCSession::HandleUntilReturnEvent(tvm::runtime::TVMRetValue*, bool, tvm::runtime::PackedFunc const*) /home/majiang/hd/opensource/tvm/src/runtime/rpc/rpc_session.cc:879:26
#9 0x7fa76fcb3c in tvm::runtime::RPCSession::ServerLoop() /home/majiang/hd/opensource/tvm/src/runtime/rpc/rpc_session.cc:952:3
#10 0x7fa774d3c8 in tvm::runtime::RPCServerLoop(int) /home/majiang/hd/opensource/tvm/src/runtime/rpc/rpc_socket_impl.cc:113:30
#11 0x555558b444 in tvm::runtime::RPCServer::ServerLoopProc(tvm::support::TCPSocket, tvm::support::SockAddr) ??:0:0
#12 0x5555583db0 in tvm::runtime::RPCServer::ListenLoopProc() ??:0:0
#13 0x5555585180 in std::__ndk1::__async_assoc_state<void, std::__ndk1::__async_func<void (tvm::runtime::RPCServer::*)(), tvm::runtime::RPCServer*> >::__execute() ??:0:0
#14 0x5555585354 in void* std::__ndk1::__thread_proxy<std::__ndk1::tuple<std::__ndk1::unique_ptr<std::__ndk1::__thread_struct, std::__ndk1::default_delete<std::__ndk1::__thread_struct> >, void (std::__ndk1::__async_assoc_state<void, std::__ndk1::__async_func<void (tvm::runtime::RPCServer::*)(), tvm::runtime::RPCServer*> >::*)(), std::__ndk1::__async_assoc_state<void, std::__ndk1::__async_func<void (tvm::runtime::RPCServer::*)(), tvm::runtime::RPCServer*> >*> >(void*) ??:0:0
#15 0x7fa7e6a41c in __pthread_start(void*) ??:0:0
#16 0x7fa7e1fe00 in __start_thread ??:0:0

Thread T1 created by T0 here:
#0 0x7fa7f725a0 in pthread_create _asan_rtl_:3
#1 0x5555584ebc in std::__ndk1::thread::thread<void (std::__ndk1::__async_assoc_state<void, std::__ndk1::__async_func<void (tvm::runtime::RPCServer::*)(), tvm::runtime::RPCServer*> >::*)(), std::__ndk1::__async_assoc_state<void, std::__ndk1::__async_func<void (tvm::runtime::RPCServer::*)(), tvm::runtime::RPCServer*> >*, void>(void (std::__ndk1::__async_assoc_state<void, std::__ndk1::__async_func<void (tvm::runtime::RPCServer::*)(), tvm::runtime::RPCServer*> >::*&&)(), std::__ndk1::__async_assoc_state<void, std::__ndk1::__async_func<void (tvm::runtime::RPCServer::*)(), tvm::runtime::RPCServer*> >*&&) ??:0:0
#2 0x5555584c20 in std::__ndk1::future<void> std::__ndk1::__make_async_assoc_state<void, std::__ndk1::__async_func<void (tvm::runtime::RPCServer::*)(), tvm::runtime::RPCServer*> >(std::__ndk1::__async_func<void (tvm::runtime::RPCServer::*)(), tvm::runtime::RPCServer*>&&) ??:0:0
#3 0x5555582608 in tvm::runtime::RPCServer::Start() ??:0:0
#4 0x5555581224 in tvm::runtime::RPCServerCreate(std::__ndk1::basic_string<char, std::__ndk1::char_traits<char>, std::__ndk1::allocator<char> >, int, int, std::__ndk1::basic_string<char, std::__ndk1::char_traits<char>, std::__ndk1::allocator<char> >, std::__ndk1::basic_string<char, std::__ndk1::char_traits<char>, std::__ndk1::allocator<char> >, std::__ndk1::basic_string<char, std::__ndk1::char_traits<char>, std::__ndk1::allocator<char> >, bool) ??:0:0
#5 0x555556cdd4 in RpcServer(int, char**) ??:0:0
#6 0x555556d8ac in main ??:0:0
#7 0x7fa7e1c7d8 in __libc_init ??:0:0
#8 0x5555566b10 in _start_main ??:0:0

从这个日志可以很快定位到核心的错误逻辑。
ring_buffer.h 中的Reserve函数实现逻辑有问题,没有为数据预留足够的空间,后续直接recv到buffer中的数据会导致overflow。
如下逻辑所示,当请求reserver的size n小于当前buffer的size
时,reserve函数会减小buffer保有的内存,节约资源。
但是其减小内存后,只保留kInitCapacity个byte,忽略了输入请求n可能大于kInitCapacity的情况。
发生错误时,输入的n为25038 (graph json的string长度)kInitCapacity 只有 4096。recv到的json字符串破坏了buffer,导致后续逻辑混乱。
修复的逻辑也简单,只需要保证收缩后的尺寸不小于n就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void Reserve(size_t n) {
if (ring_.size() < n) {
//扩大ring buffer的size
}
} else if (ring_.size() > n * 8 && ring_.size() > kInitCapacity && bytes_available_ > 0) {
// shrink too large temporary buffer to avoid out of memory on some embedded devices
size_t old_bytes = bytes_available_;

std::vector<char> tmp(old_bytes);

Read(&tmp[0], old_bytes);
//ring_.resize(kInitCapacity); this may cause overflow when n>kInitCapacity
ring_.resize(kInitCapacity > n? kInitCapacity : n);
ring_.shrink_to_fit();

memcpy(&ring_[0], &tmp[0], old_bytes);
head_ptr_ = 0;
bytes_available_ = old_bytes;
}
}

查看错误输出

rpc时看不到错误信息。查看apps/android_camera/app/src/main/jni/tvm_runtime.h 可以发现,原因是android上需要特殊的打印指令,但是编译时我们没有打开对应的宏,也没有添加对应的打印函数,如下代码所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* Enable custom logging - this will cause TVM to pass every log message
* through CustomLogMessage instead of LogMessage. By enabling this, we must
* implement dmlc::CustomLogMessage::Log. We use this to pass TVM log
* messages to Android logcat.
*/
#define DMLC_LOG_CUSTOMIZE 1

/* Ensure that fatal errors are passed to the logger before throwing
* in LogMessageFatal
*/
#define DMLC_LOG_BEFORE_THROW 1



#include <android/log.h>

void dmlc::CustomLogMessage::Log(const std::string& msg) {
// This is called for every message logged by TVM.
// We pass the message to logcat.
__android_log_write(ANDROID_LOG_DEBUG, "TVM_RUNTIME", msg.c_str());
}

添加adreon opencl支持

从手机中pull出libOpenCL_system.so libion.so放入dep_libs_from_phone
从高通网站下载opencl sdk
同样在build_arm64中
cmake ./. -DCMAKE_CXX_COMPILER=”/mnt/d/opensource/android_ndk/android-ndk-r21b/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android28-clang++” -DCMAKE_C_COMPILER=”/mnt/d/opensource/android_ndk/android-ndk-r21b/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android28-clang” -DOpenCL_INCLUDE_DIR=/mnt/d/opensource/opencl-sdk-1.2.2/inc -DOpenCL_LIBRARY=/mnt/d/opensource/opencl-sdk-1.2.2/dep_libs_from_phone

make -j 32 runtime

make -C ../apps/cpp_rpc CXX=/mnt/d/opensource/android_ndk/android-ndk-r21b/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android28-clang++ TVM_RUNTIME_DIR=/mnt/d/opensource/opensrc_tvm/tvm/build_arm64

链接cpp_rpc仍然有错,手动添加 -lOpenCL_system -Wl,-rpath-link=xx 通过(需要把android 的system/lib64下的库全拉过来)
/mnt/d/opensource/android_ndk/android-ndk-r21b/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android28-clang++ -std=c++14 -O2 -fPIC -Wall -I/mnt/d/opensource/opensrc_tvm/tvm/include -I/mnt/d/opensource/opensrc_tvm/tvm/3rdparty/dmlc-core/include -I/mnt/d/opensource/opensrc_tvm/tvm/3rdparty/dlpack/include -o tvm_rpc main.cc rpc_env.cc rpc_server.cc -L/mnt/d/opensource/opensrc_tvm/tvm/build_arm64 -ltvm_runtime -ldl -Wl,-R/mnt/d/opensource/opensrc_tvm/tvm/build_arm64 -L /mnt/d/opensource/opencl-sdk-1.2.2/dep_libs_from_phone -lOpenCL_system -Wl,-rpath-link=/mnt/d/opensource/opencl-sdk-1.2.2/dep_libs_from_phone/lib64 -Wl,-v -v -Wl,-t

启动 apps/android_rpc//tests/android_rpc_test.py 段错误

同时支持opencl和vulkan后,启动android_rpc_test.py 出现段错误。
使用pdb启动,可以看到打印出的错误信息。

1
2
3
4
5
  File "/usr/lib/python3.6/ctypes/__init__.py", line 348, in __init__
self._handle = _dlopen(self._name, mode)
OSError: /home/majiang/opensrc/tvm/build/libtvm.so: undefined symbol: spvContextDestroy
Uncaught exception. Entering post mortem debugging
Running 'cont' or 'step' will restart the program

google了这个错误,找到了https://github.com/google/shaderc/issues/470,原因应该是少链接了一个库。
删除libtvm.so后,make VERBOSE=1拷贝出链接命令,在尾部添加-lSPIRV-Tools后重新链接。
链接完成后,故障消失。
怀疑与cmake版本有关系,缺失了库的依赖,暂不进一步分析。