MXNet版本更新很快,网上别人编译好的动态链接库很快就过时了,经常会出现某些方法不支持或者某个符号无法读取,这时就需要自己动手编译动态连接库so文件。这个地方无论是中文资料还是英文资料都很少,我折腾了两天才搞定,现在把我的方法记录一下。
工具
android-ndk-r13b
Ubutnu 14.04
过程
准备工作
在GitHub下载好OpenBLAS和MXNet的源代码,将OpenBLAS和MXNet放在同一个目录下面。我使用的MXNet版本号是0.7。
将NDK中工具链独立出来
cd /zhao/android-ndk-r13b/build/tools
python make_standalone_toolchain.py --arch arm --api 21 --install-dir /zhao/android-toolchain
这样会把NDK中api21的工具链独立出来,api21对应的是Android5.0。其中cd
命令和--install-dir
根据自己实际情况进行更改。这一步的效果是,我们可以在/zhao
目录下面生成一个android-toolchain
的文件夹,里面就是api21对应的ndk工具。
编译OpenBLAS
cd OpenBLAS
make clean
export PATH=$PATH:/zhao/android-toolchain/bin
make TARGET=ARMV7 HOSTCC=gcc CC=arm-linux-androideabi-gcc NOFORTRAN=1
其中环境变量的路径名根据自己实际情况修改。成功的标志是提示已经生成了libopenblas_armv7p-r0.2.20.dev.a
文件。
编译MXNet
这一步是最容易出错的一步。
我们先进入到/mxnet/amalgamation
路径下。修改一下Makefile
文件,在文件前面加上以下语句:
export SYS_ROOT=/zhao/android-toolchain/sysroot
export INCLUDE=/zhao/android-toolchain/incde/c++/4.9.x
export CXX=/zhao/android-toolchain/bin/arm-linux-androideabi-g++
export CC=/zhao/android-toolchain/bin/arm-linux-androideabi-gcc
具体的路径可以根据自己实际情况修改。
随后在终端中使用make进行编译:
make clean
make ANDROID=1
这时会出现很多warning
信息,这些都可以忽略,只需要注意error
信息即可。如果错误提示缺少jni.h
这样的头文件,那么就是环境变量设置有问题,需要仔细检查环境变量的设置。
这一步正常进行的标志是生成了mxnet_predict-all.cc
文件,但是仍会报错,提示找不到mkl
相关文件,比如:
jni/../mxnet_predict-all.cc:42:30: fatal error: mxnet/mkl_memory.h: No such file or directory
这是因为mxnet_predict-all.cc
找不到头文件,这些头文件在android上并不需要。打开mxnet_predict-all.cc
文件,注释掉所有和mkl
相关的头文件,如果还出错,继续注释出错头文件。还有omp.h
也需要注释掉,这个头文件错误比较隐蔽。
最终生成的jni_libmxnet_predict.so
文件大小约为7M,重命名为libmxnet_predict.so
就可以放到android工程里了。
Hack
由于amalgamation很久没有更新了,遇到一些问题也不足为怪。我遇到的一个问题是softmax_activation操作符找不到,这时就需要自己Hack了。
定位错误
打开mxnet_predict-all.cc
文件,发现里面没有softmax_activation
相关代码,由此可以判断错误的原因是没有加载softmax_activation.cc
源代码。
这样就需要简单阅读makefile文件,寻找生成mxnet_predict-all.cc的那一步:
mxnet_predict-all.cc: mxnet_predict0.d mxnet_predict0.cc
@echo "Generating amalgamation to " $@
python ./amalgamation.py $+ $@ $(MIN) $(ANDROID)
这段代码的大概意思是说:要生成mxnet_predict-all.cc
需要两个文件mxnet_predict0.d
和mxnet_predict0.cc
,生成的方法是使用amalgamation.py
。我先怀疑是不是amalgamation.py
中间加载过程出错,我把加载的文件名称保存下来就可以判断了。在amalgamation.py
中加入代码:
sources = get_sources(sys.argv[1])
with open("name.txt",'w') as f:
f.write(str(sources))
所有加载的文件名称都保存在name.txt
中,打开发现根本没有softmax_activation
文件的影子。为什么别的文件都能加载,但softmax_activation
加载不进来呢,于是我把传入amalgamation.py
的输入参数打印下来:
['./amalgamation.py', 'mxnet_predict0.d', 'mxnet_predict0.cc', 'mxnet_predict-all.cc', '0', '1']
四个参数就打印出来,结合对源代码的分析,所有加载文件的目录应该是由mxnet_predict0.d
文件决定的。打开mxnet_predict0.d'
文件,发现下面全是要加载的源代码路径,由此可以判断mxnet_predict-all.cc
中的所有加载代码路径是由mxnet_predict0.d
决定的。可是初始情况下并没有mxnet_predict0.d
这个文件,再次打开makefile
:
mxnet_predict0.d: mxnet_predict0.cc
${CXX} ${CFLAGS} -MD -MF $@ \
-I ${MXNET_ROOT}/ -I ${MXNET_ROOT}/mshadow/ -I ${MXNET_ROOT}/dmlc-core/include \
-I ${MXNET_ROOT}/include \
-D__MIN__=$(MIN) -c $+
rm mxnet_predict0.o
mxnet_predict0.d
文件是由mxnet_predict0.cc
生成的,那么这个mxnet_predict0.cc
里面是什么呢?里面全是一些头文件:
#include "src/ndarray/ndarray_function.cc"
#include "src/ndarray/ndarray.cc"
#include "src/engine/engine.cc"
#include "src/engine/naive_engine.cc"
#include "src/symbol/graph_executor.cc"
#include "src/symbol/graph_memory_allocator.cc"
#include "src/symbol/static_graph.cc"
由此可以判断,mxnet_predict-all.cc
中内容是由mxnet_predict0.cc
决定的,如果需要加载softmax_activation
,就需要加入相关的头文件,只需要加上一行:
#include "src/operator/softmax_activation.cc"
再次编译一下,发现softmax_activation.cc
已经加载进去了,最终在Android上测试成功。