Skip to content

paddle mobile编译

Houjiang Chen edited this page Dec 8, 2017 · 8 revisions

下面的编译过程要求在linux环境上进行,由于编译过程中会自动下载依赖包,所以机器要求能联网,OSX环境会出现各种奇怪的问题,暂时还没有解决。编译过程主要参考官方paddle mobile中相关的文档,但这些文档非常分散,而且很容易给读者造成困扰。本文结合实践将编译和部署的整个过程记录一下。

构建独立工具链

一般情况下,将代码编译成android平台的可执行程序可以使用Android NDK提供的ndk-build工具,ndk-build依赖jni文件夹,所以需要将代码放至jni目录下,另外还必须得有Android.mk和Application.mk这两个文件。Android.mk文件中定义代码目录、源文件、依赖包和编译链接的一些信息,Application.mk中可以指定编译的目标平台等信息。更改代码或目标平台都可能需要对Application.mk和Android.mk进行改动,甚至需要改jni文件和java工程。

我们可以通过构建独立的工具链来为了避免多处改动,不同的目标平台只需要指定不同平台的独立工具链即可,并且独立工具链可以结合cmake进行交叉编译。

  • 首先下载Android NDK
wget https://dl.google.com/android/repository/android-ndk-r14b-linux-x86_64.zip
unzip android-ndk-r14b-linux-x86_64.zip
  • 构建工具链
./android-ndk-r14b/build/tools/make-standalone-toolchain.sh --arch=arm --platform=android-9 --install-dir=/home/nlp/chenhoujiang/android-toolchain --toolchain=arm-linux-androideabi-4.9

参数解析:

--arch: 指定目标平台的CPU架构,比如arm/arm64/x86/mips。有些安卓机器使用的是64位的CPU可以使用arm64,一般arm v7(32位)架构的都用arm,64位也可以使用arm。

--platform:指定目标机器的最低API level,android-9代表的安卓版本为Android 2.3([参考官网](src https://developer.android.com/guide/topics/manifest/uses-sdk-element.html))。

--toolchain:指定工具链名称,会将指定的工具链从ndk目录下拷贝到构建的独立工具链中。支持的工具链可以到ndk目录的toolchains下查看。

##交叉编译paddle

下载paddle

git clone https://github.com/PaddlePaddle/Paddle.git Paddle
cd Paddle

在Paddle目录下新建文件build-android.sh,文件内容如下:

rm -rf build_android && mkdir build_android

cd build_android

cmake -DCMAKE_SYSTEM_NAME=Android \
    -DANDROID_STANDALONE_TOOLCHAIN=/home/nlp/chenhoujiang/android-toolchain \
    -DCMAKE_BUILD_TYPE=MinSizeRel \
    -DANDROID_ABI="armeabi-v7a" \
    -DANDROID_NATIVE_API_LEVEL=android-16 \
    -DANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-4.9 \
    -DANDROID_ARM_NEON=ON \
    -DANDROID_ARM_MODE=ON \
    -DUSE_EIGEN_FOR_BLAS=ON \
    -DCMAKE_INSTALL_PREFIX=./install \
    -DWITH_C_API=ON \
    -DWITH_SWIG_PY=OFF \
    -DWITH_GPU=OFF \
    -DWITH_TESTING=OFF \
    -DWITH_PYTHON=OFF \
    -DWITH_GOLANG=OFF \
    -DGLIDE_INSTALL=OFF \
    ..

make -j10
make install

注意:

可能在cmake的过程中会报错"No CMAKE_Go_COMPILER could be found",这时需要手动设置一下go的路径,比如设置-DCMAKE_Go_COMPILER=/home/xxx/xxx/go/bin/go。

paddle mobile文档中说设置CMAKE_SYSTEM_NAME=Android会自动将WITH_PYTHON、WITH_AVX等设为OFF,实际上并没有,所以一开始我们编出来的静态库有100多M,动态库也有70多M。手动OFF一些选项后,最终在一台ubuntu的虚拟机上编译的paddle_capi_whole静态库在10M左右,另一台centos机器上编出来是13Mz左右。完整的编译命令如上所示。

demo开发和编译

安卓demo部分参考了dangbo的ncnn-mobile项目,使用paddle capi重写了jni文件,具体可以参考code中jni目录下的mobilenet_jni.cpp文件。

  • 初始化paddle
    // init paddle
    if (paddle_init(0 , NULL) != kPD_NO_ERROR) {
        __android_log_print(ANDROID_LOG_ERROR, "MobileNet", "init paddle failed");
    }
  • 加载模型参数
paddle_error ret = paddle_gradient_machine_create_for_inference_with_parameters(
            &machine, mobilenet_bin.data(), len);
  • 数据预处理
    由于paddle卷积要求图片是BGR格式的,因此对于RGBA的输入需要做一次转换,并且对数据进行预处理。
        // RGBA --> RGB
        const float mean_vals[3] = {103.94, 116.78, 123.68};
        const float norm_vals[3] = {0.017, 0.017, 0.017};
        for (int i = 0; i < width * height; ++i) {
            array_r[i] = (indata_t[2] - mean_vals[0]) * norm_vals[0];
            array_g[i] = (indata_t[1] - mean_vals[1]) * norm_vals[1];
            array_b[i] = (indata_t[0] - mean_vals[2]) * norm_vals[2];
            indata_t += 4;
        }
  • 编译
    可以使用ndk-build,也可以使用上面生成的独立工具链进行编译。使用ndk-build编译时需要将mobilenet_jni.cpp文件放在jni目录下,并在jni目录下写好对应的Android.mk和Application.mk文件,需要注意的是编译时需要全链接paddle的libpaddle_capi_whole.a库,最后在jni同级目录下执行ndk-build命令,没有出错的话编译好的文件应该已经在libs目录下了。如果使用独立工具链编译,可以写一个cmake文件(在code的jni目录下已经存在),编译时执行如下命令:
rm -rf build && mkdir build
cd build

cmake .. \
-DANDROID_ABI=armeabi-v7a \
-DANDROID_STANDALONE_TOOLCHAIN=/home/xxx/xxx/android-toolchain \
-DPADDLE_ROOT=/home/xxx/xxx/Paddle/build_android/install \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DBUILD_SHARED_LIBS=ON

make

再将编好的文件拷贝到libs目录下对应的ANDROID_ABI目录即可。

准备模型

  • caffe2paddle
    将caffe预训练的mobilenet模型转换成paddle模型,转换时要求已经安装好了python paddlepaddle和python caffe。 转换过程可以参考paddlepaddle关于caffe2paddle的说明

  • 合并模型
    准备paddle mobilenet的模型结构文件,并将最后一层全连接层用1*1的卷积层替换,使用merge脚本将模型结构文件和模型参数合并成一个模型文件。详细过程可以参考paddlepaddle关于合并模型的说明

Clone this wiki locally