PaddleLite使用OpenCL预测部署

Lite支持在Android系统上运行基于OpenCL的程序,目前支持Ubuntu环境下armv8、armv7的交叉编译。

1. 编译

1.1 编译环境

  1. Docker 容器环境;

  2. Linux(推荐 Ubuntu 16.04)环境。

详见 源码编译指南-环境准备 章节。

1.2 编译Paddle-Lite OpenCL库范例

注:以android/armv7/opencl的目标、Docker容器的编译开发环境为例,CMake3.10,android-ndk-r17c位于/opt/目录下。

针对 Lite 用户的编译命令(无单元测试,有编译产物,适用于benchmark)

  • with_opencl: [ON | OFF],编译OpenCL必选;

  • arm_abi: [armv7 | armv8]

  • toolchain: [gcc | clang]

  • build_extra: [OFF | ON],编译全量op和kernel,包含控制流NLP相关的op和kernel体积会大,编译时间长;

  • build_cv: [OFF | ON],编译arm cpu neon实现的的cv预处理模块;

  • android_stl: [c++_shared | c++_static | gnu_static | gnu_shared],paddlelite的库以何种方式链接android_stl,选择c++_shared得到的动态库体积更小,但使用时候记得上传paddlelite所编译版本(armv7或armv8)一致的libc++_shared.so。默认使用c++_static

  1. ######################################
  2. # 假设当前位于处于Lite源码根目录下 #
  3. ######################################
  4. # 导入NDK_ROOT变量,注意检查NDK安装目录若与本示例是否不同
  5. export NDK_ROOT=/opt/android-ndk-r17c
  6. # 删除上一次CMake自动生成的.h文件
  7. rm ./lite/api/paddle_use_kernels.h
  8. rm ./lite/api/paddle_use_ops.h
  9. # 设置编译参数并开始编译
  10. # android-armv7:cpu+gpu+cv+extra
  11. ./lite/tools/build_android.sh \
  12. --arch=armv7 \
  13. --toolchain=clang \
  14. --with_log=OFF \
  15. --with_extra=ON \
  16. --with_cv=ON \
  17. --with_opencl=ON
  18. # android-armv8:cpu+gpu+cv+extra
  19. ./lite/tools/build_android.sh \
  20. --arch=armv8 \
  21. --toolchain=clang \
  22. --with_log=OFF \
  23. --with_extra=ON \
  24. --with_cv=ON \
  25. --with_opencl=ON
  26. # 注:编译帮助请执行: ./lite/tools/build_android.sh help

注:该方式的编译产物中的demo/cxx/mobile_light适用于做benchmark,该过程不会打印开发中加入的log,注意需要提前转好模型。关于使用,详见下文运行示例1: 编译产物demo示例

针对 Lite 开发者的编译命令(有单元测试,编译产物)

注:调用./lite/tools/ci_build.sh执行编译,该命令会编译armv7和armv8的opencl库。虽然有编译产物,但因编译单元测试,编译产物包体积可能较大,生产环境不推荐使用。

  1. # 假设当前位于处于Lite源码根目录下
  2. # 导入NDK_ROOT变量,注意检查您的安装目录若与本示例不同
  3. export NDK_ROOT=/opt/android-ndk-r17c
  4. # 删除上一次CMake自动生成的.h文件
  5. rm ./lite/api/paddle_use_kernels.h
  6. rm ./lite/api/paddle_use_ops.h
  7. # 根据指定编译参数编译
  8. ./lite/tools/ci_build.sh \
  9. --arm_os=android \
  10. --arm_abi=armv8 \
  11. --arm_lang=gcc \
  12. build_opencl

注:如果要调试cl kernel,假设已经完成上述脚本编译(已生成cmake文件)。调试只需要修改./lite/backends/opencl/cl_kernel/下对应的kernel文件,保存后在项目根目录执行python ./lite/tools/cmake_tools/gen_opencl_code.py ./lite/backends/opencl/cl_kernel ./lite/backends/opencl/opencl_kernels_source.cc,该命令会自动将修改后,再切到build目录下执行make publish_inference或者你要编译的单测的可执行文件名,cl kernel文件的内容会随着编译自动打包到产物包如 .so 中或者对应单测可执行文件中。

1.3 编译产物说明

编译产物位于build.lite.android.armv8.gcc.opencl下的inference_lite_lib.android.armv8.opencl文件夹内,根据编译参数不同,文件夹名字会略有不同。这里仅罗列关键产物:

  • cxx:该目录是编译目标的C++的头文件和库文件;

  • demo:该目录包含了两个demo,用来调用使用libpaddle_api_full_bundled.alibpaddle_api_light_bundled.a,分别对应mobile_fullmobile_light文件夹。编译对应的demo仅需在mobile_fullmobile_light文件夹下执行make

    • mobile_full:使用cxx config,可直接加载fluid模型,若使用OpenCL需要在mobilenetv1_full_api.cc代码里开启DEMO_USE_OPENCL的宏,详细见该文件的代码注释;

    • mobile_light:使用mobile config,只能加载model_optimize_tool优化过的模型。 注:opencl实现的相关kernel已经打包到动态库中。

  1. .
  2. |-- cxx
  3. | |-- include
  4. | | |-- paddle_api.h
  5. | | |-- paddle_image_preprocess.h
  6. | | |-- paddle_lite_factory_helper.h
  7. | | |-- paddle_place.h
  8. | | |-- paddle_use_kernels.h
  9. | | |-- paddle_use_ops.h
  10. | | `-- paddle_use_passes.h
  11. | `-- lib
  12. | |-- libpaddle_api_full_bundled.a
  13. | |-- libpaddle_api_light_bundled.a
  14. | |-- libpaddle_full_api_shared.so
  15. | `-- libpaddle_light_api_shared.so
  16. `-- demo
  17. `-- cxx
  18. |-- Makefile.def
  19. |-- README.md
  20. |-- include
  21. | |-- paddle_api.h
  22. | |-- paddle_lite_factory_helper.h
  23. | |-- paddle_place.h
  24. | |-- paddle_use_kernels.h
  25. | |-- paddle_use_ops.h
  26. | `-- paddle_use_passes.h
  27. |-- mobile_full
  28. | |-- Makefile
  29. | `-- mobilenetv1_full_api.cc
  30. `-- mobile_light
  31. |-- Makefile
  32. `-- mobilenetv1_light_api.cc

调用libpaddle_api_full_bundled.alibpaddle_api_light_bundled.a见下一部分运行示例。

2. 运行示例

下面以android的环境为例,介绍3个示例,分别如何在手机上执行基于OpenCL的ARM GPU推理过程。

2.1 运行示例1: 编译产物demo示例和benchmark

需要提前用模型优化工具opt转好模型(下面假设已经转换好模型,且模型名为mobilenetv1_opencl_fp32_opt_releasev2.6_b8234efb_20200423.nb)。编译脚本为前文针对 Lite 用户的编译命令(无单元测试,有编译产物,适用于benchmark)

  1. #################################
  2. # 假设当前位于build.xxx目录下 #
  3. #################################
  4. # prepare enviroment on phone
  5. adb shell mkdir -p /data/local/tmp/opencl/
  6. # build demo
  7. cd inference_lite_lib.android.armv7.opencl/demo/cxx/mobile_light/
  8. make
  9. cd -
  10. # push executable binary, library to device
  11. adb push inference_lite_lib.android.armv7.opencl/demo/cxx/mobile_light/mobilenetv1_light_api /data/local/tmp/opencl/
  12. adb shell chmod +x /data/local/tmp/opencl/mobilenetv1_light_api
  13. adb push inference_lite_lib.android.armv7.opencl/cxx/lib/libpaddle_light_api_shared.so /data/local/tmp/opencl/
  14. # push model with optimized(opt) to device
  15. adb push ./mobilenetv1_opencl_fp32_opt_releasev2.6_b8234efb_20200423.nb /data/local/tmp/opencl/
  16. # run demo on device
  17. adb shell "export LD_LIBRARY_PATH=/data/local/tmp/opencl/; \
  18. /data/local/tmp/opencl/mobilenetv1_light_api \
  19. /data/local/tmp/opencl/mobilenetv1_opencl_fp32_opt_releasev2.6_b8234efb_20200423.nb \
  20. 1,3,224,224 \
  21. 100 10 0" # round=100, warmup=10, print_output_tensor=0

注: 权重参数会在第一次运行时加载,且.cl文件也会在第一次运行时在线编译,所以第一次执行时间略长。一般将warmup的值设为10,repeats值设为多次。

2.2 运行示例2: test_mobilenetv1单元测试

编译脚本为前文针对 Lite 开发者的编译命令(有单元测试,编译产物)

  • 运行文件准备
  1. # 在/data/local/tmp目录下创建OpenCL文件目录
  2. adb shell mkdir -p /data/local/tmp/opencl
  3. # 将mobilenet_v1的fluid格式模型文件推送到/data/local/tmp/opencl/mobilenet_v1目录下
  4. adb push build.lite.android.armv8.gcc.opencl/third_party/install/mobilenet_v1/ /data/local/tmp/opencl/mobilenet_v1
  5. # 将OpenCL单元测试程序test_mobilenetv1,推送到/data/local/tmp/opencl目录下
  6. adb push build.lite.android.armv8.gcc.opencl/lite/api/test_mobilenetv1 /data/local/tmp/opencl
  • 执行OpenCL推理过程
  1. adb shell chmod +x /data/local/tmp/opencl/test_mobilenetv1
  2. adb shell "export GLOG_v=1; \
  3. /data/local/tmp/opencl/test_mobilenetv1 \
  4. --model_dir=/data/local/tmp/opencl/mobilenet_v1/ \
  5. --warmup=10 \
  6. --repeats=100"

2.3 运行示例3: test_layout_opencl单元测试

编译脚本为前文针对 Lite 开发者的编译命令(有单元测试,编译产物)

  1. adb shell mkdir -p /data/local/tmp/opencl
  2. adb push build.lite.android.armv8.gcc.opencl/lite/kernels/opencl/test_layout_opencl /data/local/tmp/opencl/
  3. adb shell chmod +x /data/local/tmp/opencl/test_layout_opencl
  4. adb shell "export GLOG_v=4; \
  5. /data/local/tmp/opencl/test_layout_opencl"

3. 如何在Code中使用

即编译产物demo/cxx/mobile_light目录下的代码,在线版参考GitHub仓库./lite/demo/cxx/mobile_light/mobilenetv1_light_api.cc,其中也包括判断当前设备是否支持OpenCL的方法;

注:这里给出的链接会跳转到线上最新develop分支的代码,很可能与您本地的代码存在差异,建议参考自己本地位于lite/demo/cxx/目录的代码,查看如何使用。

NOTE: 对OpenCL的支持还在持续开发中。

4. 常见问题

  1. opencl计算过程中大多以cl::Image2D的数据排布进行计算,不同gpu支持的最大cl::Image2D的宽度和高度有限制,模型输入的数据格式是buffer形式的NCHW数据排布方式。要计算你的模型是否超出最大支持(大部分手机支持的cl::Image2D最大宽度和高度均为16384),可以通过公式image_h = tensor_n * tensor_h, image_w=tensor_w * (tensor_c + 3) / 4计算当前层NCHW排布的Tensor所需的cl::Image2D的宽度和高度;

  2. 部署时需考虑不支持opencl的情况,可预先使用APIbool ::IsOpenCLBackendValid()判断,对于不支持的情况加载CPU模型,详见./lite/demo/cxx/mobile_light/mobilenetv1_light_api.cc

  3. 对性能不满足需求的场景,可以考虑使用调优APIconfig.set_opencl_tune(CL_TUNE_NORMAL),首次会有一定的初始化耗时,详见./lite/demo/cxx/mobile_light/mobilenetv1_light_api.cc

  4. 对精度要求较高的场景,可以考虑通过APIconfig.set_opencl_precision(CL_PRECISION_FP32)强制使用FP32精度,详见./lite/demo/cxx/mobile_light/mobilenetv1_light_api.cc

  5. 对首次加载耗时慢的问题,可以考虑使用APIconfig.set_opencl_binary_path_name(bin_path, bin_name),在二次加载Predictor时,省去编译耗时极大提高二次及以后第一次推理耗时,详见./lite/demo/cxx/mobile_light/mobilenetv1_light_api.cc