技术路线 理论上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)
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 buildcmake .. -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 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) { } } else if (ring_.size () > n * 8 && ring_.size () > kInitCapacity && bytes_available_ > 0 ) { size_t old_bytes = bytes_available_; std ::vector <char > tmp (old_bytes) ; Read(&tmp[0 ], old_bytes); 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版本有关系,缺失了库的依赖,暂不进一步分析。