# Lite基于OpenCL的模型预测

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

## 编译

### 编译环境

1. Docker 容器环境；
2. Linux（推荐 Ubuntu 16.04）环境。

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

### 编译选项

|参数|介绍|值|
|--------|--------|--------|
|--arm_os|代表目标操作系统|目前仅支持且默认为`android`|
|--arm_abi|代表体系结构类型，支持armv8和armv7|默认为`armv8`即arm64-v8a；`armv7`即armeabi-v7a|
|--arm_lang|代表编译目标文件所使用的编译器|默认为gcc，支持 gcc和clang两种|

### 编译Paddle-Lite OpenCL库范例

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

```bash
# 假设当前位于处于Lite源码根目录下

# 导入NDK_ROOT变量，注意检查您的安装目录若与本示例不同
export NDK_ROOT=/opt/android-ndk-r17c

# 删除上一次CMake自动生成的.h文件
rm ./lite/api/paddle_use_kernels.h
rm ./lite/api/paddle_use_ops.h

# 根据指定编译参数编译
./lite/tools/ci_build.sh \
  --arm_os=android \
  --arm_abi=armv8 \
  --arm_lang=gcc \
  build_test_arm_opencl
```

编译产物位于`build.lite.android.armv8.gcc.opencl`下的`inference_lite_lib.android.armv8.opencl`文件夹内，这里仅罗列关键产物：

- `cxx`:该目录是编译目标的C++的头文件和库文件;
- `demo`:该目录包含了两个demo，用来调用使用`libpaddle_api_full_bundled.a`和`libpaddle_api_light_bundled.a`，分别对应`mobile_full`和`mobile_light`文件夹。编译对应的demo仅需在`mobile_full`或`mobile_light`文
  - `mobile_full`:使用cxx config，可直接加载fluid模型，若使用OpenCL需要在`mobilenetv1_full_api.cc`代码里开启`DEMO_USE_OPENCL`的宏，详细见代码注释;
  - `mobile_light`:使用mobile config，只能加载`model_optimize_tool`优化过的模型;
- `opencl`:该目录存放opencl实现的相关kernel。

```bash
.
|-- cxx
|   |-- include
|   |   |-- paddle_api.h
|   |   |-- paddle_image_preprocess.h
|   |   |-- paddle_lite_factory_helper.h
|   |   |-- paddle_place.h
|   |   |-- paddle_use_kernels.h
|   |   |-- paddle_use_ops.h
|   |   `-- paddle_use_passes.h
|   `-- lib
|       |-- libpaddle_api_full_bundled.a
|       |-- libpaddle_api_light_bundled.a
|       |-- libpaddle_full_api_shared.so
|       `-- libpaddle_light_api_shared.so
|-- demo
|   `-- cxx
|       |-- Makefile.def
|       |-- README.md
|       |-- include
|       |   |-- paddle_api.h
|       |   |-- paddle_lite_factory_helper.h
|       |   |-- paddle_place.h
|       |   |-- paddle_use_kernels.h
|       |   |-- paddle_use_ops.h
|       |   `-- paddle_use_passes.h
|       |-- mobile_full
|       |   |-- Makefile
|       |   `-- mobilenetv1_full_api.cc
|       `-- mobile_light
|           |-- Makefile
|           `-- mobilenetv1_light_api.cc
`-- opencl
    `-- cl_kernel
        |-- buffer
        |   |-- depthwise_conv2d_kernel.cl
        |   |-- elementwise_add_kernel.cl
        |   |-- fc_kernel.cl
        |   |-- im2col_kernel.cl
        |   |-- layout_kernel.cl
        |   |-- mat_mul_kernel.cl
        |   |-- pool_kernel.cl
        |   `-- relu_kernel.cl
        |-- cl_common.h
        `-- image
            |-- channel_add_kernel.cl
            |-- elementwise_add_kernel.cl
            |-- pool_kernel.cl
            `-- relu_kernel.cl
```

调用`libpaddle_api_full_bundled.a`和`libpaddle_api_light_bundled.a`见下一部分运行示例。



## 运行示例

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


**注意：** 以下命令均在Lite源码根目录下运行。在3个示例前，下面这段命令都先要执行用来准备环境:

```bash
# 在/data/local/tmp目录下创建OpenCL文件目录
adb shell mkdir -p /data/local/tmp/opencl
adb shell mkdir -p /data/local/tmp/opencl/cl_kernel/buffer
adb shell mkdir -p /data/local/tmp/opencl/cl_kernel/image

# 将OpenCL的kernels文件推送到/data/local/tmp/opencl目录下
adb push lite/backends/opencl/cl_kernel/cl_common.h /data/local/tmp/opencl/cl_kernel/
adb push lite/backends/opencl/cl_kernel/buffer/* /data/local/tmp/opencl/cl_kernel/buffer/
adb push lite/backends/opencl/cl_kernel/image/* /data/local/tmp/opencl/cl_kernel/image/
```

### 运行示例1: 编译产物demo示例

```bash
######################################################################
# 编译mobile_full的demo                                              #
######################################################################
# 步骤:                                                              #
#   0.确保编译Paddle-Lite时编译了OpenCL;                             #
#   1.编辑`mobilenetv1_full_api.cc`代码, 开启`DEMO_USE_OPENCL`的宏;  #
#   2.在产物目录`demo/cxx/mobile_full`下编译`mobile_full`的demo;     #
#   3.上传demo, 模型, opencl kernel文件到手机;                       #
#   4.运行demo得到预期结果.                                          #
######################################################################
adb shell mkdir /data/local/tmp/opencl/mobilenet_v1
chmod +x ./build.lite.android.armv8.gcc.opencl/inference_lite_lib.android.armv8.opencl/demo/cxx/mobile_full/mobilenetv1_full_api
adb push ./build.lite.android.armv8.gcc.opencl/inference_lite_lib.android.armv8.opencl/demo/cxx/mobile_full/mobilenetv1_full_api /data/local/tmp/opencl/
adb push ./build.lite.android.armv8.gcc.opencl/install/mobilenet_v1/* /data/local/tmp/opencl/mobilenet_v1

# use mobile_full run mobilenet_v1
# `GLOG_v` is log level
adb shell "export GLOG_v=0; \
    /data/local/tmp/opencl/mobilenetv1_full_api \
    --model_dir=/data/local/tmp/opencl/mobilenet_v1 \
    --optimized_model_dir=/data/local/tmp/opencl/full_api_opt_model"



######################################################################
# 编译mobile_light的demo                                             #
######################################################################
# 步骤:                                                              #
#   0.确保编译Paddle-Lite时编译了OpenCL;                             #
#   1.编译model_optimize_tool并对模型优化, `targets`参数为`opencl`;  #
#   2.在产物目录`demo/cxx/mobile_light`下编译`mobile_light`的demo;   #
#   3.上传demo, 模型, opencl kernel文件到手机;                       #
#   4.运行demo得到预期结果.                                          #
######################################################################

# use model_optimize_tool to optimize model
./build.model_optimize_tool/lite/api/model_optimize_tool \
  --model_dir=./build.lite.android.armv8.gcc.opencl/install/mobilenet_v1/ \
  --optimize_out_type=naive_buffer \
  --optimize_out=./build.lite.android.armv8.gcc.opencl/install/mobilenet_v1/ \
  --valid_targets=opencl

adb shell mkdir /data/local/tmp/opencl/mobilenet_v1
chmod +x ./build.lite.android.armv8.gcc.opencl/inference_lite_lib.android.armv8.opencl/demo/cxx/mobile_light/mobilenetv1_light_api
adb push ./build.lite.android.armv8.gcc.opencl/inference_lite_lib.android.armv8.opencl/demo/cxx/mobile_light/mobilenetv1_light_api /data/local/tmp/opencl/
adb push ./build.lite.android.armv8.gcc.opencl/install/mobilenet_v1/* /data/local/tmp/opencl/mobilenet_v1

# use mobile_light run mobilenet_v1
adb shell "export GLOG_v=5; \
  /data/local/tmp/opencl/mobilenetv1_light_api \
  --model_dir=/data/local/tmp/opencl/"
```

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

- **运行文件准备**

```bash
# 将mobilenet_v1的模型文件推送到/data/local/tmp/opencl目录下
adb shell mkdir -p /data/local/tmp/opencl/mobilenet_v1
adb push build.lite.android.armv8.gcc.opencl/third_party/install/mobilenet_v1/* /data/local/tmp/opencl/mobilenet_v1/

# 将OpenCL单元测试程序test_mobilenetv1，推送到/data/local/tmp/opencl目录下
adb push build.lite.android.armv8.gcc.opencl/lite/api/test_mobilenetv1 /data/local/tmp/opencl
```

- **执行OpenCL推理过程**

使用如下命令运行OpenCL程序。其中：

- `--cl_path`指定了OpenCL的kernels文件即cl\_kernel所在目录；
- `--modle_dir`指定了模型文件所在目录。

```bash
adb shell chmod +x /data/local/tmp/opencl/test_mobilenetv1

adb shell /data/local/tmp/opencl/test_mobilenetv1 \
  --cl_path=/data/local/tmp/opencl \
  --model_dir=/data/local/tmp/opencl/mobilenet_v1 \
  --warmup=1 \
  --repeats=1
```

**注意：** 因为权重参数均会在Op Kernel第一次运行时进行加载，所以第一次的执行时间会略长。一般将warmup的值设为1，repeats值设为多次。

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

- **运行文件准备**

```bash
# 将OpenCL单元测试程序test_layout_opencl，推送到/data/local/tmp/opencl目录下
adb push build.lite.android.armv8.gcc.opencl/lite/kernels/opencl/test_layout_opencl /data/local/tmp/opencl/
```


OpenCL推理过程**

```bash
adb shell chmod +x /data/local/tmp/opencl/test_layout_opencl
adb shell /data/local/tmp/opencl/test_layout_opencl
```


### 如何在Code中使用

见运行示例1的demo代码:

1. [./lite/demo/cxx/mobile_light/mobilenetv1_light_api.cc](https://github.com/PaddlePaddle/Paddle-Lite/blob/develop/lite/demo/cxx/mobile_light/mobilenetv1_light_api.cc);
2. [./lite/demo/cxx/mobile_full/mobilenetv1_full_api.cc](https://github.com/PaddlePaddle/Paddle-Lite/blob/develop/lite/demo/cxx/mobile_full/mobilenetv1_full_api.cc).

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

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