由于工作需要,初次接触TVM,工作用的电脑是windows系统的,要从头搭建整个编译环境,嗯,真的硬核从头,从安装ubuntu子系统开始,中间遇坑无数,死磕各种报错很久,终于经过苦难的半个月折磨顺利按官方文档完成了tvm库到android_rpc客户端的编译工作!撒花完结!
目前找到的比较友好的大佬文档:
用TVM在Android上部署模型
tvm系列-Android TVM RPC
TVM系列 - 终于在手机成功部署Auto-TVM
源码是在2022年4月初下载的最新代码,高于V0.8版本,懒人包:
tvm/build目录覆盖:TVM share-lib build 目录完全内容下载
tvm/jvm目录覆盖:TVM jvmpkg 编译完成包下载
tvm/apps/android_rpc/app/src/main/libs目录覆盖:tvm android_rpc so完整库
tvm/apps/android_rpc/app/src/libs目录覆盖:tvm4j-core-0.0.1-SNAPSHOT.jar
宝宝心里苦啊,不知道是不是大佬们玩linux环境比较溜,基本上编译都是一笔带过环境问题,主角光环加身既视感,嗯,就像看小说里的那句“很多年过去了,男主神功大成”一毛一样!
废话不多说,我们开始爬坑之路:
安装windows10自带unbuntu子系统:
公司破电脑权限问题只能绕过微软应用商店,用shell装ubuntu16.4的系统,正常用应用商店应该是装18.4(推荐)或者20.4系统,最好18.4的,因为网上很多环境下载这个版本支持比较全,20.4实在没心情去折腾了,有兴趣的自己去搞一下试试,应该也差不多……吧?
新系统先跑一下下面这个命令,官方给的环境,16.4默认好些个版本都太低,cmake,gcc python3都低于官方要求版本,需要单独升级一下
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
step 1:TVM4J Installation Guide
官方文档(下载,环境配置,编译)
基本上先按文档走一遍,那么大部分环境就算OK了:
1.tvm源码下载地址
git clone --recursive https://github.com/apache/tvm tvm
For windows users who use github tools, you can open the git shell, and type the following command.
git submodule init
git submodule update
上面是官方原文建议,但是个人建议还是老老实实用ubuntu下敲命令下载吧,因为这里也会有点点坑,看人品,别问,问就是遇到过,对于像我这种触及到知识盲区的新入坑人员来说还是太深了:源码内 tvm/3rdparty/目录下会有子工程,直接git clone tvm这部分代码是不会下载的,要么按上面submodule操作自动下载,要么就手动点进github子模块链接下载,推荐手动点进去下载,因为这里也有坑
各个子系统git地址:
tvm/3rdparty/cutlass: https://github.com/NVIDIA/cutlass.git
tvm/3rdparty/dlpack: https://github.com/dmlc/dlpack.git
tvm/3rdparty/dmlc-core: https://github.com/dmlc/dmlc-core.git
tvm/3rdparty/libbacktrace: https://github.com/tlc-pack/libbacktrace.git
tvm/3rdparty/rang: https://github.com/agauniyal/rang.git
tvm/3rdparty/vta-hw:https://github.com/apache/tvm-vta.git
好了,源码下载完了,按官方文档更新下编译环境:
Requirements
- JDK 1.6+. Oracle JDK and OpenJDK are well tested.
我的版本是openJDK8 - Maven 3 for build.
我的版本是maven3.3.9 - LLVM (TVM4J need LLVM support. Please refer to build-the-shared-library for how to enable LLVM support.) LLVM一定要安装,推荐LLVM6.0版本(ubuntu16.4 apt支持的最高版本,手动安装太麻烦了),当然LLVM7应该也可以(这个是ubuntu18.4apt支持的最低版本,LLVM10也OK,官方推荐),如果config.mk不把LLVM打开容易遇到报错不通过编译,嗯,有一定概率,玄学玄学
android_rpc工程依赖tvm4j的jar包,因此首先需要编译生成tvm4j包,编译的环境要求为:
- A recent c++ compiler supporting C++ 14 (g++-5 or higher) :
我使用的是gcc g++ 7.5的版本(5.4的版本也能过),unbuntu16.4自带版本太低,需要手动升级一下,ubuntu18.4默认就是7的版本起步省心啊! - CMake 3.10 or higher:
我使用的是3.22.3以及3.23.xx版本都能过,ubuntu16.4 apt库的版本过低要手动升级 - We highly recommend to build with LLVM to enable all the features.
Python is also required. Avoid using Python 3.9.X+ which is not supported. 3.7.X+ and 3.8.X+ should be well supported however:
这个后续部署服务联调会用,但是前面阶段编译TVM用不到还,尽量按要求来吧
编译开始:
- First, check the cmake in your system. If you do not have cmake, you can obtain the latest version from official website
我的cmake版本3.22.3(3.23.xx也可以,家里电脑用的最新的没问题) - First create a build directory, copy the
cmake/config.cmake
to the directory.
在TVM目录创建一个build文件夹,复制cmake/config.cmake到build目录
mkdir build
cp cmake/config.cmake build
打开config.cmake找到下面这些参数,修改为打开
set(USE_GRAPH_EXECUTOR ON)
set(USE_PROFILER ON)
有需要的话可以开启debug:
set(USE_RELAY_DEBUG ON)
//命令行需要执行一下这一句命令
export TVM_LOG_DEBUG="ir/transform.cc=1;relay/ir/transform.cc=1"
打开LLVM功能
set(USE_LLVM /path/to/your/llvm/bin/llvm-config)
#我的实际路径是:set(USE_LLVM /usr/lib/llvm-6.0/bin/llvm-config)
然后切换到build目录进行编译:
cd build
cmake ..
make -j4
顺利的话,我们应该能喝一喝茶或者咖啡等待编译完成拿到我们需要的so库:
TVM share-lib build 目录完全内容下载
实在搞不出来又急着用,可以直接下这个包,整个build完整编译文件都在里面,包括要用到的libtvm.so和libtvm_runtime.so文件,解压覆盖就可以用
但是!有问题很正常,容易采坑的地方:
如果遇到问题,首先先对照一下编译环境是不是OK,回过头去耐心对照一下我上面加粗标注的那些环境版本,起码上面那些版本我趟过雷了,编译是没问题的,这个编译很磨人的地方就在这里,如果你环境对不上可能就会报各种莫名其妙的错误:
1.一定要ubuntu子系统内下载,不要windows下面下载,否则会莫名其妙遇到编码问题,比如里面加一个^M啊这种啥的,编译的时候会报错:
unrecognized command line xxx
...3rdparty/libbacktrace/configure: Syntax error: newline unexpected (expecting ")")
等类似错误,因为编译的时候会先运行各个子模块的configure进行验证,大概率这个地方会报语法错误,但是如果普通文本工具打开看是看不到这些编码的,如果你遇到查看configure内容明明语法没问题,但是死活告诉你语法有错,编译不过
解决方法:如果是这个错误,大概率就是下载的源码被污染了,可以通过命令:vim -b xxxx 来查看是否有奇怪编码,如果有乱码,那就重新在unbuntu里下一遍吧,如果只是单个配置文件出问题了,可以用这个命令清除整理一下:sed -i 's/\r//g' xxxx.sh 然后再用vim -b查看,非法字符就消失了,上面的configure可以通过运行 sh configure --help 验证是否正常,如果执行了清除非法字符的命令,应该是会正常运行,并提示configure文件内可用参数的相关help信息的2.就是前面下载说明的地方说的,要手动下载tvm/3rdparty目录下各个子模块编码放到对应的文件夹里去,否则编译的时候会报错:各种找不到文件之类的,目录指向tvm/3rdparty下面,那么就是这个问题了
3.编译遇到tvm/3rdparty下某些重复定义错误,对就是kDLHexagon,kDLWebGPU这些重复定义,指向的是c_runtime_api.h 以及dlpack.h
编译build so库的时候,给你的错误提醒是这样的:
In file included from /mnt/d/ubuntu/tvm/include/tvm/runtime/object.h:26:0,
from /mnt/d/ubuntu/tvm/src/support/libinfo.cc:19:
/mnt/d/ubuntu/tvm/include/tvm/runtime/c_runtime_api.h:89:16: error: redeclaration of ‘kDLHexagon’
kDLHexagon = 14,
^~
In file included from /mnt/d/ubuntu/tvm/include/tvm/runtime/c_runtime_api.h:72:0,
from /mnt/d/ubuntu/tvm/include/tvm/runtime/object.h:26,
from /mnt/d/ubuntu/tvm/src/support/libinfo.cc:19:
/mnt/d/ubuntu/tvm/3rdparty/dlpack/include/dlpack/dlpack.h:64:3: note: previous declaration ‘DLDeviceType kDLHexagon’
kDLHexagon = 15,
^~~~~~~~~~
In file included from /mnt/d/ubuntu/tvm/include/tvm/runtime/object.h:26:0,
from /mnt/d/ubuntu/tvm/src/support/libinfo.cc:19:
/mnt/d/ubuntu/tvm/include/tvm/runtime/c_runtime_api.h:90:15: error: redeclaration of ‘kDLWebGPU’
kDLWebGPU = 15
^~
In file included from /mnt/d/ubuntu/tvm/include/tvm/runtime/c_runtime_api.h:72:0,
from /mnt/d/ubuntu/tvm/include/tvm/runtime/object.h:26,
from /mnt/d/ubuntu/tvm/src/support/libinfo.cc:19:
/mnt/d/ubuntu/tvm/3rdparty/dlpack/include/dlpack/dlpack.h:65:3: note: previous declaration ‘DLDeviceType kDLWebGPU’
kDLWebGPU = 16
^~~~~~~~~
^CCMakeFiles/tvm_runtime_objs.dir/build.make:89: recipe for target 'CMakeFiles/tvm_runtime_objs.dir/src/runtime/c_runtime_api.cc.o' failed
同时这个问题还会导致android so 的jni编译脚本无法通过,报错与kDLWebGPU,kDLHexagon重复定义有关,这个就是子模块自动下载可能带来的问题:git submodule update拉取的代码是⽗项⽬中记录的那个submodule版本,但不⼀定是submoudle远程仓库⾥最新的版本,实际上最新的版本dlpack.h相关重复定义字段已经删除了,泪流满面,因为大佬的文章都是一笔带过,所以坚信源码没问题没问题没问题!浪费了大量时间有木有有木有!!
踩过坑以后,我们对于TVM相关share-lib so库的编译工作就算完成了,我们可以继续按流程往下走,编译jar库,在上面编译通过的基础上,环境问题的坑基本我们都补好了,就不太可能出啥问题了,直接就能顺利编译过了:
First please refer to Installation Guide and build runtime shared library from the C++ codes (libtvm_runtime.so for Linux and libtvm_runtime.dylib for OSX).
Then you can compile tvm4j by
#source-shell tvm目录运行shell脚本
make jvmpkg
(Optional) run unit test by 这个其实不跑也没关系……吧?
#source-shell tvm目录运行shell脚本
sh tests/scripts/task_java_unittest.sh
After it is compiled and packaged, you can install tvm4j in your local maven repository,
#source-shell tvm目录运行shell脚本
make jvminstall
顺利完成tvm4j jar库的编译,我们可以在tvm/jvm/core/target里找到编译成功的jar库
tvm4j-core-0.0.1-SNAPSHOT.jar 这个jar包我们在后面的android 编译的时候需要用到,附上编译好的jvm目录,懒人可以直接覆盖tvm/jvm文件夹跳过编译过程:
TVM jvmpkg 编译完成包下载
文件包括所有编译出的文件,包括tvm4j-core-0.0.1-SNAPSHOT.jar,以及后续要用的编译文件
Step2. Android TVM RPC
You will need JDK(我的版本是openJDK8,尽量安装这个版本,因为我在论坛查找各种问题的issue的时候看到有人因为jdk版本问题无法正确编译的), Android NDK(务必使用NDK 16 rb版本我测试过10e, 12b, 23b统统卡死在ndk-build编译JNI那一步,no zuo no die!!!) and an Android device to use this.
Build APK
We use Gradle (请使用相对高一些的版本,推荐gradle 6 或者 7 这两个都试过,没问题,有人在官方论坛说的,gradle版本低了无法正常编译,不信可以试试嗷)to build. Please follow the installation instruction for your operating system.
Before you build the Android application, please refer to TVM4J Installation Guide and install tvm4j-core to your local maven repository. You can find tvm4j dependency declare in app/build.gradle
. Modify it if it is necessary.
我们前面编辑出来jar包了,所以用简单点的方法,直接换成本地libs的jar引用方式吧,maven库的方式是在有点麻烦,附上懒人包(其中jar包也可以在jvm那个包里的jvm/core/target下面找到),使用so懒人包的时候,记得把buildJni注释掉:
tasks.withType(JavaCompile) {
//像这样,注释掉,很重要!!!
//compileTask -> compileTask.dependsOn buildJni
}
这样就不用走buildJni去重复编译so库了,否则会直接就删掉我们懒人包的so重新编译了:
tvm/apps/android_rpc/app/src/main/libs目录覆盖:tvm android_rpc so完整库
tvm/apps/android_rpc/app/src/libs目录覆盖:tvm4j-core-0.0.1-SNAPSHOT.jar
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
implementation 'com.android.support:appcompat-v7:26.0.1'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.android.support:design:26.0.1'
//划重点,这里替换成我们编译出来的jar包放到工程目录src/libs里
implementation files('src/libs/tvm4j-core-0.0.1-SNAPSHOT.jar')
//implementation 'org.apache.tvm:tvm4j-core:0.0.1-SNAPSHOT'
testImplementation 'junit:junit:4.12'
}
Now use Gradle to compile JNI, resolve Java dependencies and build the Android application together with tvm4j. Run following script to generate the apk file.
如果一切正常,我们就能得到编译出来的的apk用于连接服务器测试了,如果要加入opencl库的APK请参考官方文档进行设置打包,好了,暂时更新到这里,后面继续测试连接服务器运算以及opencl库启用踩坑!
坑来了
NDK在这里,版本很重要我测试过(android-ndk-r10e,android-ndk-r12b,android-ndk-r16b,android-ndk-r23b),太高的版本(android-ndk-r23b)会报错,各种错,一个是找不到tools-chain里面的编译器工具错误,因为新版已经把这些工具包去掉了,只有llvm,renderscript文件夹,后续用到的Cross Compile也会需要,高版本是没法正常使用的:
cd /opt/android-ndk/build/tools/
//高版本NDK这条命令没法正常执行
./make-standalone-toolchain.sh --platform=android-24 --use-llvm --arch=arm64 --install-dir=/opt/android-toolchain-arm64
更新了编译机制,就算你从低版本复制对应的chain包进去,依然会有其他错误,错误是没有具体提醒的,具体姿势是这样的:
make: Entering directory '/mnt/d/ubuntu/tvm/apps/android_rpc/app/src/main/jni'
[armeabi-v7a] Compile++ arm : tvm4j_runtime_packed <= org_apache_tvm_native_c_api.cc
clang: error: no input files
//gradle执行脚本的话会报这个错误
Process 'command 'sh'' finished with non-zero exit value 2
NDK版本太低的版本(10e,12b)会报错误,而且ubuntu16.4默认的clang是3.8的版本,最好手动更新下默认clang版本,同理还有gcc 和 g++版本一定要检查(我用的是gcc g++ 7.5),否者会遇到下面错误,这两个错误主要能在jni 对应的Application.mk配置里找到对应的命令行,但本质上就是编译版本对不上的问题:
//错误1( gcc最好升级到7)
arm-linux-androideabi-g++: error: unrecognized command line option '-std=c++14'
//错误2
cc1plus: error: argument to '-O' should be a non-negative integer, 'g', 's' or 'fast'
然后正确的NDK版本,如果遇到之前编译so的时候没有处理tvm/3rdparty下面dlpack库同步问题,会有这样的的错误:
In file included from /mnt/d/ubuntu/tvm/apps/android_rpc/app/src/main/jni/org_apache_tvm_native_c_api.cc:25:
In file included from /mnt/d/ubuntu/tvm/apps/android_rpc/app/src/main/jni/tvm_runtime.h:36:
In file included from /mnt/d/ubuntu/tvm/apps/android_rpc/app/src/main/jni/../../../../../../include/../src/runtime/c_runtime_api.cc:25:
In file included from /mnt/d/ubuntu/tvm/apps/android_rpc/app/src/main/jni/../../../../../../include/tvm/runtime/c_backend_api.h:31:
/mnt/d/ubuntu/tvm/apps/android_rpc/app/src/main/jni/../../../../../../include/tvm/runtime/c_runtime_api.h:90:3: error:
redefinition of enumerator 'kDLWebGPU'
kDLWebGPU = 15
^
/mnt/d/ubuntu/tvm/apps/android_rpc/app/src/main/jni/../../../../../../3rdparty/dlpack/include/dlpack/dlpack.h:65:3: note:
previous definition is here
kDLWebGPU = 16
另外,我在参考有大佬文档的时候,说自己运行gradle clean build 进行打包的时候会报错,但是我后面把各种坑踩完以后,倒是没遇到这个问题,这里提供一种担心gradle版本过高(反正我用最新的gradle 7没遇到),可能引起错误的规避方法,使用gradlew脚本进行指定版本编译,操作如下:
//先切换到android_rpc根目录,执行下面命令,会自动生成gradlew脚本
//且会自动下载对应的gradle插件包,避免版本不匹配的问题
//我这里设置的4.4版本,工程里3.1版本下不下来
gradle wrapper --gradle-version 4.4
//执行完毕以后就生成好gradlew脚本文件了,执行工程编译
./gradlew clean build