Welcome to Paddle-Lite's documentation!¶
技术特点¶
不同于普通的移动端预测基于类 Caffe 的架构,Lite 架构最早的设计目标来源于 Paddle Server 和 Mobile 两种场景的要求,其中 Server 端需要有完善的图分析和优化能力,而 Mobile 端要求有轻量级部署的能力,两种场景共同的要求是高性能,多硬件支持等。
基于上述要求,Lite 架构完整实现了相应的能力,重点描述如下。
多硬件支持¶
Lite 架构已经验证和完整支持从 Mobile 到 Server 多种硬件的支持需求,包括 ARM CPU, ARM GPU, Huawei NPU, Intel X86 CPU, NV GPU 等。 得益于对不同硬件适度的抽象,在Lite 框架本身清晰的同时支持不同硬件的特殊调度需求,使得Lite架构在框架的清晰程度和硬件的特定调度优化上达到很好的平衡,比如 Nvidia GPU 上复杂的 stream, event 分配,在 Lite 中可以清晰表示。
多种硬件的 Kernel 在代码层和执行层均互不干扰,用户可以自由插拔任何硬件的支持。
高性能¶
高性能来源于两方面,一是 Kernel 优化;二是框架执行。
Kernel 方面,我们对相应硬件上的 Kernel 通过指令集、操作熔合、算法改写等方式进行了深入优化。
框架执行方面,通过简化 Op 和 Kernel 的功能,使得执行期的框架开销极低;此外,框架极大的灵活性可以支持各种硬件的特定调度优化以提升整体效率。
量化支持¶
Lite 支持Paddle Slim 强大的量化训练完毕的模型,因此完整保留了量化计算的高性能以及量化训练的高精度。
强大的图分析和优化能力¶
在图分析优化上,不同于常规的移动端预测引擎基于 Python 脚本工具转化模型, Lite 架构上有完整基于 C++ 开发的 IR 及相应 Pass 集合,以支持操作熔合 (Operator fusion),计算剪枝 (Computation pruning),存储优化 (Memory optimization),量化计算 (Quantitative computation) 等多类计算图优化。
更多的优化策略可以简单通过添加 Pass 的方式模块化支持。
轻量级部署¶
尽管图优化上有复杂的策略,但并不影响移动端的轻量级部署,图分析模块和最终的执行引擎可以拆开使用,最终部署只有一层薄薄的 Kernel 。
可支持任意硬件的混合调度¶
Lite 支持系统可见任意硬件的混合调度,目前已经支持 ARM CPU 和 ARM GPU 的 Kernel 自动混合调度,并验证了 X86 CPU 和 Nvidia GPU 间的混合调度。
支持混合调度的考量有两点:
- 当系统内同时存在多种硬件可用时,混合调度可以充分利用各类硬件资源
- 随着支持模型的增多,各硬件对kernel的支持丰富度不一,难免需要混合调度才能跑通
Lite架构通过从底层支持 Type system
的方式通用建模各类混合执行的行为,从而能够相对完备地支持混调。
架构设计¶
Mobile 在这次升级为 Lite 架构, 侧重多硬件、高性能的支持,其主要设计思想如下
- 引入 Type system,强化多硬件、量化方法、data layout 的混合调度能力
- 硬件细节隔离,通过不同编译开关,对支持的任何硬件可以自由插拔
- 引入 MIR(Machine IR) 的概念,强化带执行环境下的优化支持
- 优化期和执行期严格隔离,保证预测时轻量和高效率
架构图如下
Paddle Inference Refactor1.0
编译期和执行期严格隔离设计¶
- compile time 优化完毕可以将优化信息存储到模型中;execution time 载入并执行
- 两套 API 及对应的预测lib,满足不同场景
CxxPredictor
打包了Compile Time
和Execution Time
,可以 runtime 在具体硬件上做分析和优化,得到最优效果MobilePredictor
只打包Execution Time
,保持部署和执行的轻量
Execution Time
轻量级设计和实现¶
- 每个 batch 实际执行只包含两个步骤执行
Op.InferShape
Kernel.Run
,Kernel 相关参数均使用指针提前确定,后续无查找或传参消耗- 设计目标,执行时,只有 kernel 计算本身消耗
- 轻量级
Op
及Kernel
设计,避免框架额外消耗Op
只有CreateKernels
和InferShape
两个重要职能Kernel
只有Run
职能
多硬件后端支持¶
- 硬件通用行为,使用
TargetWrapper
模块做适配器适配,对上层框架提供一致界面 - 框架上层策略保持硬件无关,如存储优化 (Memory optimize),计算剪枝 (Computation prune) 等,任何硬件接入均可直接复用
- 框架支持了硬件通用行为,特定硬件细节不做过多约束,各硬件可以自行实现并接入框架
- 计算模式上目前支持两种主流模型,一种是类似 X86, ARM CPU 等非异构设备;一种是 GPU,或 FPGA 等异构设备(支持 stream, event异步执行模式以及跨设备拷贝)
多硬件及算法混合调度支持¶
TensorTy
用来表示 Tensor 类型
struct TensorTy {
TargetType target;
PrecisionType precision;
DataLayout layout;
int deviceid;
};
enum class TargetType { kARM, kX86, kCUDA, kOpenCL };
enum class PrecisionType { kFP32, kFP16, kInt8, kInt16 };
enum class DataLayout { kNCHW, kNHWC };
注册 Kernel,确定特定 Kernel 的输入输出特征
REGISTER_LITE_KERNEL(
mul, kARM, kFloat, kNCHW, arm::MulCompute, def)
.BindInput("X", {LiteType::GetTensorTy(kARM, kFloat, kNCHW)})
.BindInput("Y", {LiteType::GetTensorTy(kARM, kFloat, kNCHW))})
.BindOutput("Out", {LiteType::GetTensorTy(kARM, kFloat, kNCHW)})
.Finalize();
同一个 Op 的不同 Kernel 类似函数重载
用于支持任意的混合调度:
- 标记模型中所有 tensor 的 Type
- 标记 Kernel 的 硬件、执行精度、data layout 等信息
全局做类型推断,当发现 tensor 传递中有类型冲突,采用 type cast 操作,通过插入特定功能 Op 来实现正确的传导
lite-7
MIR 用于图分析优化¶
基于 Type System 的 SSA,通过 IR Pass 对计算图进行分析和优化:
- 支持对整个 graph 进行类型推断,发现类型冲突并加入 type cast op,来支持通用混合调度
- 计算剪枝 (Compute prune),比如去掉 scale(1), assign op 等
- 存储优化 (Memory optimize)
- 操作熔合 (Operator fuse)(已经支持 fc, conv_bn, ele_add+act 等6种 fuse 策略)
- 支持量化处理(已支持 Int8预测)
支持硬件¶
ARM CPU¶
Paddle Lite支持ARM Cortex-A系列处理器,支持列表如下:
32bit(ARMv7a)¶
- Cortex-A5
- Cortex-A7
- Cortex-A8
- Cortex-A9
- Cortex-A12
- Cortex-A15
- Cortex-A17(RK3288)
- Cortex-A32
64bit(ARMv7a, ARMv8a)¶
- Cortex-A35
- Cortex-A53(树莓派3)
- Cortex-A55
- Cortex-A57(Nvidia tx1,Nvidia tx2, 高通810等)
- Cortex-A72(麒麟95X,高通820, RK3399,树莓派4等)
- Cortex-A73(麒麟960,麒麟970,高通835, 联发科X30等)
- Cortex-A75(高通845等)
- Cortex-A76(麒麟980,麒麟990,高通855,高通730,联发科G90等)
- Cortex-A77
- ARMv8-A compatible(Apple A系列处理器, Nvidia tegra, Qualcomm Kryo, Falkor, Samsung Mongoose)
移动端GPU¶
Paddle Lite支持移动端GPU和Nvidia端上GPU设备,支持列表如下:
- ARM Mali G 系列
- Qualcomm Adreno 系列
- Nvida tegra系列: tx1, tx2, nano, xavier
支持OP¶
Ops (共计158个算子)¶
Basic Operators (默认编译的算子)¶
- affine_channel
- arg_max
- batch_norm
- bilinear_interp
- box_coder
- calib
- cast
- concat
- conv2d
- conv2d_transpose
- density_prior_box
- depthwise_conv2d
- dropout
- elementwise_add
- elementwise_div
- elementwise_max
- elementwise_mul
- elementwise_sub
- exp
- expand
- fake_channel_wise_dequantize_max_abs
- fake_dequantize_max_abs
- fake_quantize_abs_max
- fake_quantize_dequantize_moving_average_abs_max
- fake_quantize_moving_average_abs_max
- fake_quantize_range_abs_max
- fc
- feed
- fetch
- fill_constant
- fill_constant_batch_size_like
- flatten
- flatten2
- floor
- fusion_elementwise_add_activation
- fusion_elementwise_div_activation
- fusion_elementwise_max_activation
- fusion_elementwise_mul_activation
- fusion_elementwise_sub_activation
- gelu
- grid_sampler
- hard_sigmoid
- instance_norm
- io_copy
- io_copy_once
- layout
- leaky_relu
- log
- matmul
- mean
- mul
- multiclass_nms
- nearest_interp
- pad2d
- pool2d
- prelu
- prior_box
- range
- reduce_mean
- relu
- relu6
- relu_clipped
- reshape
- reshape2
- rsqrt
- scale
- search_fc
- sequence_topk_avg_pooling
- shuffle_channel
- sigmoid
- slice
- softmax
- softsign
- split
- sqrt
- square
- squeeze
- squeeze2
- stack
- subgraph
- swish
- tanh
- transpose
- transpose2
- unsqueeze
- unsqueeze2
- yolo_box
Extra Operators (打开 --build_extra=ON
开关才会编译)¶
- anchor_generator
- assign
- assign_value
- attention_padding_mask
- axpy
- beam_search
- beam_search_decode
- box_clip
- calib_once
- collect_fpn_proposals
- conditional_block
- crop
- decode_bboxes
- distribute_fpn_proposals
- equal
- gather
- generate_proposals
- graph_op
- greater_equal
- greater_than
- gru
- gru_unit
- im2sequence
- increment
- is_empty
- layer_norm
- layout_once
- less_equal
- less_than
- lod_reset
- logical_and
- logical_not
- logical_or
- logical_xor
- lookup_table
- lookup_table_v2
- lrn
- match_matrix_tensor
- merge_lod_tensor
- negative
- norm
- not_equal
- power
- read_from_array
- reduce_max
- reduce_prod
- reduce_sum
- roi_align
- search_aligned_mat_mul
- search_attention_padding_mask
- search_grnn
- search_group_padding
- search_seq_arithmetic
- search_seq_depadding
- search_seq_fc
- search_seq_softmax
- sequence_arithmetic
- sequence_concat
- sequence_expand
- sequence_expand_as
- sequence_pool
- sequence_reshape
- sequence_reverse
- sequence_softmax
- shape
- split_lod_tensor
- top_k
- uniform_random
- var_conv_2d
- while
- write_to_array
Kernels¶
Host kernels¶
- feed
- fetch
- flatten
- flatten2
- multiclass_nms
- reshape
- reshape2
ARM kernels¶
- affine_channel
- anchor_generator
- arg_max
- assign
- assign_value
- axpy
- batch_norm
- beam_search
- beam_search_decode
- bilinear_interp
- box_clip
- box_coder
- cast
- collect_fpn_proposals
- concat
- conditional_block
- conv2d
- conv2d_transpose
- crop
- decode_bboxes
- density_prior_box
- depthwise_conv2d
- distribute_fpn_proposals
- dropout
- elementwise_add
- elementwise_div
- elementwise_max
- elementwise_mul
- elementwise_sub
- equal
- exp
- expand
- fc
- fill_constant
- fill_constant_batch_size_like
- floor
- fusion_elementwise_add_activation
- fusion_elementwise_div_activation
- fusion_elementwise_max_activation
- fusion_elementwise_mul_activation
- fusion_elementwise_sub_activation
- gather
- generate_proposals
- greater_equal
- greater_than
- gru
- gru_unit
- hard_sigmoid
- im2sequence
- increment
- instance_norm
- is_empty
- layer_norm
- layout
- layout_once
- leaky_relu
- less_equal
- less_than
- lod_reset
- log
- logical_and
- logical_not
- logical_or
- logical_xor
- lookup_table
- lookup_table_v2
- lrn
- matmul
- merge_lod_tensor
- mul
- nearest_interp
- negative
- norm
- not_equal
- pad2d
- pool2d
- power
- prelu
- prior_box
- range
- read_from_array
- reduce_max
- reduce_mean
- reduce_prod
- relu
- relu6
- relu_clipped
- roi_align
- rsqrt
- scale
- sequence_expand
- sequence_pool
- sequence_softmax
- shape
- shuffle_channel
- sigmoid
- slice
- softmax
- split
- split_lod_tensor
- squeeze
- squeeze2
- stack
- swish
- tanh
- top_k
- transpose
- transpose2
- unsqueeze
- unsqueeze2
- while
- write_to_array
- yolo_box
X86 kernels¶
- batch_norm
- cast
- concat
- conv2d
- depthwise_conv2d
- dropout
- elementwise_add
- elementwise_sub
- fc
- fill_constant_batch_size_like
- gather
- gelu
- gru
- layer_norm
- match_matrix_tensor
- matmul
- mul
- pool2d
- reduce_sum
- relu
- reshape
- reshape2
- scale
- search_aligned_mat_mul
- search_attention_padding_mask
- search_fc
- search_grnn
- search_group_padding
- search_seq_arithmetic
- search_seq_depadding
- search_seq_fc
- search_seq_softmax
- sequence_arithmetic
- sequence_concat
- sequence_expand_as
- sequence_pool
- sequence_reverse
- sequence_topk_avg_pooling
- shape
- slice
- softmax
- softsign
- square
- squeeze
- squeeze2
- stack
- tanh
- transpose
- transpose2
- var_conv_2d
CUDA kernels¶
- attention_padding_mask
- bilinear_interp
- calib
- concat
- conv
- dropout
- elementwise_add
- fusion_elementwise_add_activation
- fusion_elementwise_mul_activation
- elementwise_mul
- feed
- io_copy
- layout
- layout_once
- leaky_relu
- lookup_table
- match_matrix_tensor
- mul
- nearest_interp
- pool2d
- relu
- scale
- search_aligned_mat_mul
- search_fc
- search_grnn
- search_group_padding
- search_seq_depadding
- search_seq_fc
- sequence_arithmetic
- sequence_concat
- sequence_pool
- sequence_reverse
- sequence_topk_avg_pooling
- softmax
- transpose
- var_conv_2d
- yolo_box
OpenCL kernels¶
- conv2d
- depthwise_conv2d
- elementwise_add
- fc
- fusion_elementwise_add_activation
- layout
- layout_once
- io_copy
- io_copy_once
- mul
- pool2d
- relu
性能数据¶
可以参考benchmark_tools,推荐一键benchmark。
测试环境¶
- 测试模型
- fp32模型
- mobilenet_v1
- mobilenet_v2
- squeezenet_v1.1
- mnasnet
- shufflenet_v2
- int8模型
- mobilenet_v1
- mobilenet_v2
- fp32模型
- 测试机器(android ndk ndk-r17c)
- 骁龙855
- xiaomi mi9, snapdragon 855
- 4xA76(1@2.84GHz + 3@2.4GHz) + 4xA55@1.78GHz
- 骁龙845
- xiaomi mi8, 845
- 2.8GHz(大四核),1.7GHz(小四核)
- 骁龙835
- xiaomi mix2, snapdragon 835
- 2.45GHz(大四核),1.9GHz(小四核)
- 麒麟970
- HUAWEI Mate10
- 骁龙855
- 测试说明
- branch: release/v2.3.0
- warmup=10, repeats=30,统计平均时间,单位是ms
- 当线程数为1时,
DeviceInfo::Global().SetRunMode
设置LITE_POWER_HIGH,否者设置LITE_POWER_NO_BIND - 模型的输入图像的维度是{1, 3, 224, 224},输入图像的每一位数值是1
测试数据¶
fp32模型测试数据¶
paddlepaddle model¶
骁龙855 | armv7 | armv7 | armv7 | armv8 | armv8 | armv8 |
---|---|---|---|---|---|---|
threads num | 1 | 2 | 4 | 1 | 2 | 4 |
mobilenet_v1 | 33.27 | 19.52 | 11.14 | 31.72 | 18.76 | 10.24 |
mobilenet_v2 | 29.08 | 15.79 | 9.25 | 25.89 | 14.17 | 8.38 |
shufflenet_v2 | 4.40 | 3.09 | 2.30 | 4.28 | 3.02 | 2.35 |
squeezenet_v1.1 | 19.96 | 12.61 | 8.76 | 18.25 | 11.46 | 7.97 |
mnasnet | 21.00 | 12.54 | 7.28 | 19.65 | 11.65 | 6.96 |
骁龙845 | armv7 | armv7 | armv7 | armv8 | armv8 | armv8 |
---|---|---|---|---|---|---|
threads num | 1 | 2 | 4 | 1 | 2 | 4 |
mobilenet_v1 | 66.36 | 35.97 | 19.45 | 62.66 | 33.87 | 17.85 |
mobilenet_v2 | 45.86 | 25.53 | 14.6 | 41.58 | 23.24 | 13.39 |
shufflenet_v2 | 7.58 | 4.89 | 3.41 | 7.44 | 4.91 | 3.58 |
squeezenet_v1.1 | 37.15 | 22.74 | 13.51 | 34.69 | 21.27 | 12.74 |
mnasnet | 40.09 | 21.73 | 11.91 | 38.19 | 21.02 | 12.11 |
骁龙835 | armv7 | armv7 | armv7 | armv8 | armv8 | armv8 |
---|---|---|---|---|---|---|
threads num | 1 | 2 | 4 | 1 | 2 | 4 |
mobilenet_v1 | 96.98 | 53.92 | 32.24 | 89.31 | 48.02 | 27.58 |
mobilenet_v2 | 67.72 | 37.66 | 23.82 | 60.10 | 34.36 | 21.05 |
shufflenet_v2 | 10.72 | 6.62 | 4.63 | 10.10 | 6.44 | 4.63 |
squeezenet_v1.1 | 53.89 | 33.28 | 20.73 | 50.83 | 32.31 | 19.51 |
mnasnet | 59.55 | 33.53 | 20.32 | 56.21 | 31.58 | 19.06 |
caffe model¶
骁龙855 | armv7 | armv7 | armv7 | armv8 | armv8 | armv8 |
---|---|---|---|---|---|---|
threads num | 1 | 2 | 4 | 1 | 2 | 4 |
mobilenet_v1 | 33.36 | 19.45 | 11.26 | 31.63 | 18.74 | 10.31 |
mobilenet_v2 | 31.63 | 19.21 | 11.61 | 28.34 | 17.14 | 10.16 |
shufflenet_v2 | 4.46 | 3.08 | 2.32 | 4.26 | 2.98 | 2.35 |
骁龙845 | armv7 | armv7 | armv7 | armv8 | armv8 | armv8 |
---|---|---|---|---|---|---|
threads num | 1 | 2 | 4 | 1 | 2 | 4 |
mobilenet_v1 | 66.32 | 35.83 | 19.56 | 62.52 | 33.79 | 17.91 |
mobilenet_v2 | 58.46 | 32.69 | 18.56 | 53.72 | 29.86 | 16.80 |
shufflenet_v2 | 7.65 | 4.82 | 3.46 | 7.55 | 4.97 | 3.62 |
骁龙835 | armv7 | armv7 | armv7 | armv8 | armv8 | armv8 |
---|---|---|---|---|---|---|
threads num | 1 | 2 | 4 | 1 | 2 | 4 |
mobilenet_v1 | 95.38 | 54.09 | 32.03 | 95.05 | 48.33 | 27.54 |
mobilenet_v2 | 88.46 | 48.98 | 30.23 | 79.28 | 44.64 | 27.10 |
shufflenet_v2 | 10.07 | 6.51 | 4.61 | 10.31 | 6.50 | 4.66 |
int8量化模型测试数据¶
骁龙855 | armv7 | armv7 | armv7 | armv8 | armv8 | armv8 |
---|---|---|---|---|---|---|
threads num | 1 | 2 | 4 | 1 | 2 | 4 |
mobilenet_v1 | 36.80 | 21.58 | 11.12 | 14.01 | 8.13 | 4.32 |
mobilenet_v2 | 28.72 | 19.08 | 12.49 | 17.24 | 11.55 | 7.82 |
骁龙835 | armv7 | armv7 | armv7 | armv8 | armv8 | armv8 |
---|---|---|---|---|---|---|
threads num | 1 | 2 | 4 | 1 | 2 | 4 |
mobilenet_v1 | 60.76 | 32.25 | 16.66 | 56.57 | 29.84 | 15.24 |
mobilenet_v2 | 49.38 | 31.10 | 22.07 | 47.52 | 28.18 | 19.24 |
麒麟970 | armv7 | armv7 | armv7 | armv8 | armv8 | armv8 |
---|---|---|---|---|---|---|
threads num | 1 | 2 | 4 | 1 | 2 | 4 |
mobilenet_v1 | 65.95 | 34.39 | 18.68 | 60.86 | 30.98 | 16.31 |
mobilenet_v2 | 68.87 | 39.39 | 24.43 | 65.57 | 37.31 | 20.87 |
测试方法¶
本文将会介绍,在Ubuntu:16.04交叉编译环境下,用安卓手机在终端测试Paddle-Lite的性能,并介绍两种Benchmark方法:
- 一键Benchmark:适用于想快速获得常见模型性能的用户,下载预编译好的benchmark可执行文件;
- 逐步Benchmark:将一键Benchmark流程拆解讲解。
环境准备¶
- 准备adb等必备软件:
sudo apt update
sudo apt install -y wget adb
- 检查手机与电脑连接。安卓手机USB连上电脑,打开设置 -> 开启开发者模式 -> 开启USB调试 -> 允许(授权)当前电脑调试手机;
- 在电脑终端输入
adb devices
命令,查看当前连接到的设备:
adb devices
命令成功执行,显示结果类似下面(序列码略有不同):
List of devices attached
712QSDSEMMS7C device
一. 一键Benchmark¶
执行以下命令,完成Benchmark:
wget -c https://paddle-inference-dist.bj.bcebos.com/PaddleLite/benchmark_0/run_benchmark.sh
sh run_benchmark.sh
该run_benchmark.sh
脚本会:
- 下载模型,并上传手机:包含mobilenetv1/v2、shufflenetv2、squeezenetv1.1、mnasnet;
- 下载pre-built android-armv7和android-armv8的可执行文件,并上传手机:
benchmark_bin_v7
和benchmark_bin_v8
; - 自动执行另一个脚本
benchmark.sh
(多台手机连接USB,请在benchmark.sh
脚本中对adb
命令后加上测试手机的serial number
); - 从手机下载benchmark结果
result_armv7.txt
和result_armv8.txt
,到当前目录,并显示Benchmark结果。
二. 逐步Benchmark¶
1. 获取benchmark可执行文件¶
benchmark_bin文件可以测试PaddleLite的性能,有下面两种方式获得。
方式一:下载benchmark_bin可执行文件¶
# Download benchmark_bin for android-armv7
wget -c https://paddle-inference-dist.bj.bcebos.com/PaddleLite/benchmark_0/benchmark_bin_v7
# Download benchmark_bin for android-armv8
wget -c https://paddle-inference-dist.bj.bcebos.com/PaddleLite/benchmark_0/benchmark_bin_v8
方式二:由源码编译benchmark_bin文件¶
根据源码编译准备编译环境,拉取PaddleLite最新release发布版代码,并在仓库根目录下,执行:
###########################################
# Build benchmark_bin for android-armv7 #
###########################################
./lite/tools/ci_build.sh \
--arm_os="android" \
--arm_abi="armv7" \
--arm_lang="gcc " \
build_arm
# `benchmark_bin` 在: <paddle-lite-repo>/build.lite.android.armv7.gcc/lite/api/benchmark_bin
###########################################
# Build benchmark_bin for android-armv8 #
###########################################
./lite/tools/ci_build.sh \
--arm_os="android" \
--arm_abi="armv8" \
--arm_lang="gcc " \
build_arm
# `benchmark_bin` 在: <paddle-lite-repo>/build.lite.android.armv8.gcc/lite/api/benchmark_bin
注意:为了避免在docker内部访问不到手机的问题,建议编译得到benchmark_bin后退出到docker外面,并且将benchmark_bin文件拷贝到一个临时目录。然后在该临时目录下,按照下面步骤下载模型、拷贝脚本、测试。
2. 准备模型¶
PaddleLite为Benchmark准备好了常见Benchmark模型。
执行以下命令,下载常见Benchmark模型并解压:
wget -c https://paddle-inference-dist.bj.bcebos.com/PaddleLite/benchmark_0/benchmark_models.tgz
tar zxvf benchmark_models.tgz
如果测试其他模型,请将模型文件放到 benchmark_models
文件夹中。
3. benchmark.sh脚本¶
benchmark测试的执行脚本benchmark.sh
位于源码中的/PaddleLite/lite/tools/benchmark.sh
位置,测试时需要将benchmark.sh
、 benchmark_bin
、 benchmark_models
文件复制到同一目录下。
4. 测试¶
从终端进入benchmark.sh、可执行文件(benchmark_bin_v7、benchmark_bin_v8)和模型文件(benchmark_models)所在文件夹。
如果 benchmark_models
中所有模型文件都已经使用 model_optimize_tool
进行转换,则使用 benchmark.sh 脚本执行如下命令进行测试:
# Benchmark for android-armv7
sh benchmark.sh ./benchmark_bin_v7 ./benchmark_models result_armv7.txt
# Benchmark for android-armv8
sh benchmark.sh ./benchmark_bin_v8 ./benchmark_models result_armv8.txt
如果 benchmark_models
中所有模型文件都没有使用 model_optimize_tool
进行转换,则执行下面的命令。benchmark_bin
会首先转换模型,然后加载模型进行测试。
# Benchmark for android-armv7
sh benchmark.sh ./benchmark_bin_v7 ./benchmark_models result_armv7.txt true
# Benchmark for android-armv8
sh benchmark.sh ./benchmark_bin_v8 ./benchmark_models result_armv8.txt true
测试结束后,armv7和armv8的结果,分别保存在当前目录下的result_armv7.txt
和result_armv8.txt
文件中。
查看测试结果
在当前目录的result_armv7.txt
和result_armv8.txt
文件,查看测试结果。
不同手机,不同版本,测试模型的性能数据不同。
run benchmark armv8
--------------------------------------
PaddleLite Benchmark
Threads=1 Warmup=10 Repeats=30
mnasnet min = 19.83500 max = 19.38500 average = 19.65503
mobilenetv1 min = 32.00600 max = 31.56900 average = 31.81983
mobilenetv2 min = 22.37900 max = 22.08700 average = 22.28623
shufflenetv2 min = 10.80400 max = 10.62900 average = 10.68890
squeezenet min = 17.67400 max = 17.47900 average = 17.57677
Threads=2 Warmup=10 Repeats=30
mnasnet min = 11.85600 max = 11.72000 average = 11.77127
mobilenetv1 min = 18.75000 max = 18.64300 average = 18.70593
mobilenetv2 min = 14.05100 max = 13.59900 average = 13.71450
shufflenetv2 min = 6.67200 max = 6.58300 average = 6.63400
squeezenet min = 12.07100 max = 11.33400 average = 11.41253
Threads=4 Warmup=10 Repeats=30
mnasnet min = 7.19300 max = 7.02600 average = 7.08480
mobilenetv1 min = 10.42000 max = 10.29100 average = 10.34267
mobilenetv2 min = 8.61900 max = 8.46900 average = 8.54707
shufflenetv2 min = 4.55200 max = 4.41900 average = 4.46477
squeezenet min = 8.60000 max = 7.85200 average = 7.98407
--------------------------------------
run benchmark armv7
--------------------------------------
PaddleLite Benchmark
Threads=1 Warmup=10 Repeats=30
mnasnet min = 20.98300 max = 20.81400 average = 20.92527
mobilenetv1 min = 33.19000 max = 32.81700 average = 33.08490
mobilenetv2 min = 25.91400 max = 25.61700 average = 25.73097
shufflenetv2 min = 11.14300 max = 10.97600 average = 11.06757
squeezenet min = 19.31800 max = 19.20000 average = 19.26530
Threads=2 Warmup=10 Repeats=30
mnasnet min = 12.59900 max = 12.46600 average = 12.52207
mobilenetv1 min = 19.05800 max = 18.94700 average = 18.97897
mobilenetv2 min = 15.28400 max = 15.11300 average = 15.19843
shufflenetv2 min = 6.97000 max = 6.81400 average = 6.90863
squeezenet min = 12.87900 max = 12.12900 average = 12.22530
Threads=4 Warmup=10 Repeats=30
mnasnet min = 7.31400 max = 7.12900 average = 7.20357
mobilenetv1 min = 11.44000 max = 10.86900 average = 10.94383
mobilenetv2 min = 9.14900 max = 9.03800 average = 9.09907
shufflenetv2 min = 4.60600 max = 4.49400 average = 4.53360
squeezenet min = 8.27000 max = 8.10600 average = 8.19000
--------------------------------------
使用流程¶
Lite是一种轻量级、灵活性强、易于扩展的高性能的深度学习预测框架,它可以支持诸如ARM、OpenCL、NPU等等多种终端,同时拥有强大的图优化及预测加速能力。如果您希望将Lite框架集成到自己的项目中,那么只需要如下几步简单操作即可。
一. 准备模型¶
Lite框架目前支持的模型结构为PaddlePaddle深度学习框架产出的模型格式。因此,在您开始使用 Lite 框架前您需要准备一个由PaddlePaddle框架保存的模型。 如果您手中的模型是由诸如Caffe2、Tensorflow等框架产出的,那么我们推荐您使用 X2Paddle 工具进行模型格式转换。
二. 模型优化¶
Lite框架拥有强大的加速、优化策略及实现,其中包含诸如量化、子图融合、Kernel优选等等优化手段,为了方便您使用这些优化策略,我们提供了opt帮助您轻松进行模型优化。优化后的模型更轻量级,耗费资源更少,并且执行速度也更快。
opt的详细介绍,请您参考 模型优化方法 。
使用opt,您只需编译后在开发机上执行以下代码:
$ cd <PaddleLite_base_path>
$ cd build.opt/lite/api/
$ ./opt \
--model_dir=<model_param_dir> \
--model_file=<model_path> \
--param_file=<param_path> \
--optimize_out_type=(protobuf|naive_buffer) \
--optimize_out=<output_optimize_model_dir> \
--valid_targets=(arm|opencl|x86)
其中,optimize_out为您希望的优化模型的输出路径。optimize_out_type则可以指定输出模型的序列化方式,其目前支持Protobuf与Naive Buffer两种方式,其中Naive Buffer是一种更轻量级的序列化/反序列化实现。如果你需要使用Lite在mobile端进行预测,那么您需要设置optimize_out_type=naive_buffer。
三. 使用Lite框架执行预测¶
在上一节中,我们已经通过opt
获取到了优化后的模型,使用优化模型进行预测也十分的简单。为了方便您的使用,Lite进行了良好的API设计,隐藏了大量您不需要投入时间研究的细节。您只需要简单的五步即可使用Lite在移动端完成预测(以C++ API进行说明):
- 声明MobileConfig。在config中可以设置从文件加载模型也可以设置从memory加载模型。从文件加载模型需要声明模型文件路径,如
config.set_model_from_file(FLAGS_model_file)
;从memory加载模型方法现只支持加载优化后模型的naive buffer,实现方法为:void set_model_from_buffer(model_buffer)
- 创建Predictor。Predictor即为Lite框架的预测引擎,为了方便您的使用我们提供了
CreatePaddlePredictor
接口,你只需要简单的执行一行代码即可完成预测引擎的初始化,std::shared_ptr<PaddlePredictor> predictor = CreatePaddlePredictor(config)
。 - 准备输入。执行predictor->GetInput(0)您将会获得输入的第0个field,同样的,如果您的模型有多个输入,那您可以执行
predictor->GetInput(i)
来获取相应的输入变量。得到输入变量后您可以使用Resize方法指定其具体大小,并填入输入值。 - 执行预测。您只需要执行
predictor->Run()
即可使用Lite框架完成预测。 - 获取输出。与输入类似,您可以使用
predictor->GetOutput(i)
来获得输出的第i个变量。您可以通过其shape()方法获取输出变量的维度,通过data<T>()
模板方法获取其输出值。
四. Lite API¶
为了方便您的使用,我们提供了C++、Java、Python三种API,并且提供了相应的api的完整使用示例:C++完整示例、Java完整示例、Python完整示例,您可以参考示例中的说明快速了解C++/Java/Python的API使用方法,并集成到您自己的项目中去。需要说明的是,为了减少第三方库的依赖、提高Lite预测框架的通用性,在移动端使用Lite API您需要准备Naive Buffer存储格式的模型,具体方法可参考第2节模型优化
。
预编译库¶
编译版本介绍¶
- ARM_Version=
armv7/armv8
arm版本,可选择armv7或者armv8 - arm_os=
android\ios\ios64\armlinux
安装平台,支持的arm端移动平台包括ios\ios64
、armlinux
和android
- arm_lang=
gcc/clang
源码编译时的编译器,默认为gcc
编译器 - arm_stl=
c++_static/c++_shared
Lite预测库链接STL库的方式,支持静态或动态链接 - build_extra=
ON/OFF
是否编译全量OP,OFF时只编译CV相关基础OP,参数详情 tiny_publish/full_publish
编译模式,tiny_publish
编译移动端部署库、full_publish
编译部署库的同时编译第三方依赖库
Android¶
ARM Version | build_extra | arm_stl | target | 下载 |
---|---|---|---|---|
armv7 | OFF | c++_static | tiny_publish | release/v2.3 |
armv7 | OFF | c++_static | full_publish | release/v2.3 |
armv7 | OFF | c++_shared | tiny_publish | release/v2.3 |
armv7 | OFF | c++_shared | full_publish | release/v2.3 |
armv7 | ON | c++_static | tiny_publish | release/v2.3 |
armv7 | ON | c++_static | full_publish | release/v2.3 |
armv7 | ON | c++_shared | tiny_publish | release/v2.3 |
armv7 | ON | c++_shared | full_publish | release/v2.3 |
armv8 | OFF | c++_static | tiny_publish | release/v2.3 |
armv8 | OFF | c++_static | full_publish | release/v2.3 |
armv8 | OFF | c++_shared | tiny_publish | release/v2.3 |
armv8 | OFF | c++_shared | full_publish | release/v2.3 |
armv8 | ON | c++_static | tiny_publish | release/v2.3 |
armv8 | ON | c++_static | full_publish | release/v2.3 |
armv8 | ON | c++_shared | tiny_publish | release/v2.3 |
armv8 | ON | c++_shared | full_publish | release/v2.3 |
iOS¶
ARM Version | arm_os | with_extra | 下载 |
---|---|---|---|
armv7 | ios | OFF | release/v2.3 |
armv7 | ios | ON | release/v2.3 |
armv8 | ios64 | OFF | release/v2.3 |
armv8 | ios64 | ON | release/v2.3 |
opt 工具¶
运行系统 | 下载 |
---|---|
Linux | release/v2.3 |
MacOs | release/v2.3 |
预测库编译¶
PaddleLite已经提供官方Release预测库下载,请参考文档。
PaddleLite 提供了移动端的一键源码编译脚本 lite/tools/build.sh
,编译流程如下:
- 环境准备(选择其一):Docker交叉编译环境、Linux交叉编译环境
- 编译:调用
build.sh
脚本一键编译
一、环境准备¶
目前支持三种编译的环境:
- Docker 容器环境,
- Linux(推荐 Ubuntu 16.04)环境,
- Mac OS 环境。
1、 Docker开发环境¶
Docker 是一个开源的应用容器引擎, 使用沙箱机制创建独立容器,方便运行不同程序。Docker初学者可以参考Docker使用方法正确安装Docker。
准备Docker镜像¶
有两种方式准备Docker镜像,推荐从Dockerhub直接拉取Docker镜像
# 方式一:从Dockerhub直接拉取Docker镜像
docker pull paddlepaddle/paddle-lite:2.0.0_beta
# 方式二:本地源码编译Docker镜像
git clone https://github.com/PaddlePaddle/Paddle-Lite.git
cd Paddle-Lite/lite/tools
mkdir mobile_image
cp Dockerfile.mobile mobile_image/Dockerfile
cd mobile_image
docker build -t paddlepaddle/paddle-lite .
# 镜像编译成功后,可用`docker images`命令,看到`paddlepaddle/paddle-lite`镜像。
进入Docker容器¶
在拉取Paddle-Lite仓库代码的上层目录,执行如下代码,进入Docker容器:
docker run -it \
--name paddlelite_docker \
-v $PWD/Paddle-Lite:/Paddle-Lite \
--net=host \
paddlepaddle/paddle-lite /bin/bash
该命令的含义:将容器命名为paddlelite_docker
即<container-name>
,将当前目录下的Paddle-Lite
文件夹挂载到容器中的/Paddle-Lite
这个根目录下,并进入容器中。至此,完成Docker环境的准备。
Docker常用命令¶
# 退出容器但不停止/关闭容器:键盘同时按住三个键:CTRL + q + p
# 启动停止的容器
docker start <container-name>
# 从shell进入已启动的容器
docker attach <container-name>
# 停止正在运行的Docker容器
docker stop <container-name>
# 重新启动正在运行的Docker容器
docker restart <container-name>
# 删除Docker容器
docker rm <container-name>
2、Linux 开发环境¶
Android¶
交叉编译环境要求¶
- gcc、g++、git、make、wget、python、adb
- Java environment
- cmake(建议使用3.10或以上版本)
- Android NDK (建议ndk-r17c)
具体步骤¶
安装软件部分以 Ubuntu 为例,其他 Linux 发行版类似。
# 1. Install basic software
apt update
apt-get install -y --no-install-recommends \
gcc g++ git make wget python unzip adb curl
# 2. Prepare Java env.
apt-get install -y default-jdk
# 3. Install cmake 3.10 or above
wget -c https://mms-res.cdn.bcebos.com/cmake-3.10.3-Linux-x86_64.tar.gz && \
tar xzf cmake-3.10.3-Linux-x86_64.tar.gz && \
mv cmake-3.10.3-Linux-x86_64 /opt/cmake-3.10 && \
ln -s /opt/cmake-3.10/bin/cmake /usr/bin/cmake && \
ln -s /opt/cmake-3.10/bin/ccmake /usr/bin/ccmake
# 4. Download Android NDK for linux-x86_64
# Note: Skip this step if NDK installed
# recommand android-ndk-r17c-darwin-x86_64
# ref: https://developer.android.com/ndk/downloads
cd /tmp && curl -O https://dl.google.com/android/repository/android-ndk-r17c-linux-x86_64.zip
cd /opt && unzip /tmp/android-ndk-r17c-linux-x86_64.zip
# 5. Add environment ${NDK_ROOT} to `~/.bashrc`
echo "export NDK_ROOT=/opt/android-ndk-r17c" >> ~/.bashrc
source ~/.bashrc
ARM Linux¶
适用于基于 ARMv8 和 ARMv7 架构 CPU 的各种开发板,例如 RK3399,树莓派等,目前支持交叉编译和本地编译两种方式,对于交叉编译方式,在完成目标程序编译后,可通过 scp 方式将程序拷贝到开发板运行。
交叉编译¶
- gcc、g++、git、make、wget、python、scp
- cmake(建议使用3.10或以上版本)
安装软件部分以 Ubuntu 为例,其他 Linux 发行版类似。
# 1. Install basic software
apt update
apt-get install -y --no-install-recommends \
gcc g++ git make wget python unzip
# 2. Install arm gcc toolchains
apt-get install -y --no-install-recommends \
g++-arm-linux-gnueabi gcc-arm-linux-gnueabi \
g++-arm-linux-gnueabihf gcc-arm-linux-gnueabihf \
gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
# 3. Install cmake 3.10 or above
wget -c https://mms-res.cdn.bcebos.com/cmake-3.10.3-Linux-x86_64.tar.gz && \
tar xzf cmake-3.10.3-Linux-x86_64.tar.gz && \
mv cmake-3.10.3-Linux-x86_64 /opt/cmake-3.10 && \
ln -s /opt/cmake-3.10/bin/cmake /usr/bin/cmake && \
ln -s /opt/cmake-3.10/bin/ccmake /usr/bin/ccmake
本地编译(直接在RK3399或树莓派上编译)¶
- gcc、g++、git、make、wget、python
- cmake(建议使用3.10或以上版本)
安装软件部分以 Ubuntu 为例,其他 Linux 发行版本类似。
# 1. Install basic software
apt update
apt-get install -y --no-install-recomends \
gcc g++ make wget python unzip
# 2. install cmake 3.10 or above
wget https://www.cmake.org/files/v3.10/cmake-3.10.3.tar.gz
tar -zxvf cmake-3.10.3.tar.gz
cd cmake-3.10.3
./configure
make
sudo make install
之后可通过cmake --version查看cmake是否安装成功。
至此,完成 Linux 交叉编译环境的准备。
3、Mac OS 开发环境¶
交叉编译环境要求¶
- gcc、git、make、curl、unzip、java
- cmake(Android编译请使用3.10版本,IOS编译请使用3.15版本)
- 编译Android: Android NDK (建议ndk-r17c)
- 编译IOS: XCode(Version 10.1)
具体步骤¶
# 1. Install basic software
brew install curl gcc git make unzip wget
# 2. Install cmake: mac上实现IOS编译和Android编译要求的cmake版本不一致,可以根据需求选择安装。
# (1)在mac环境编译 Paddle-Lite 的Android版本,需要安装cmake 3.10
# mkdir /usr/local/Cellar/cmake/ && cd /usr/local/Cellar/cmake/
# wget https://cmake.org/files/v3.10/cmake-3.10.2-Darwin-x86_64.tar.gz
# tar zxf ./cmake-3.10.2-Darwin-x86_64.tar.gz
# mv cmake-3.10.2-Darwin-x86_64/CMake.app/Contents/ ./3.10.2
# ln -s /usr/local/Cellar/cmake/3.10.2/bin/cmake /usr/local/bin/cmake
# (2)在mac环境编译 Paddle-Lite 的IOS版本,需要安装cmake 3.15
# mkdir /usr/local/Cellar/cmake/ && cd /usr/local/Cellar/cmake/
# cd /usr/local/Cellar/cmake/
# wget https://cmake.org/files/v3.15/cmake-3.15.2-Darwin-x86_64.tar.gz
# tar zxf ./cmake-3.15.2-Darwin-x86_64.tar.gz
# mv cmake-3.15.2-Darwin-x86_64/CMake.app/Contents/ ./3.15.2
# ln -s /usr/local/Cellar/cmake/3.15.2/bin/cmake /usr/local/bin/cmake
# 3. Download Android NDK for Mac
# recommand android-ndk-r17c-darwin-x86_64
# ref: https://developer.android.com/ndk/downloads
# Note: Skip this step if NDK installed
cd ~/Documents && curl -O https://dl.google.com/android/repository/android-ndk-r17c-darwin-x86_64.zip
cd ~/Library && unzip ~/Documents/android-ndk-r17c-darwin-x86_64.zip
# 4. Add environment ${NDK_ROOT} to `~/.bash_profile`
echo "export NDK_ROOT=~/Library/android-ndk-r17c" >> ~/.bash_profile
source ~/.bash_profile
# 5. Install Java Environment
brew cask install java
# 6. 编译IOS需要安装XCode(Version 10.1),可以在App Store里安装。安装后需要启动一次并执行下面语句。
# sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
至此,完成 Mac 交叉编译环境的准备。
注意: Mac上编译Paddle-Lite的full_publish版本时,Paddle-Lite所在路径中不可以含有中文字符
二、编译PaddleLite¶
注:编译OpenCL、华为NPU、FPGA、CUDA、X86预测库、CV模块,见进阶使用指南的对应章节。
下载代码¶
git clone https://github.com/PaddlePaddle/Paddle-Lite.git
cd Paddle-Lite
git checkout <release-version-tag>
编译模式与参数¶
编译脚本./lite/tools/build.sh
,支持三种编译模式:
编译模式 | 介绍 | 适用对象 |
---|---|---|
tiny_publish | 编译移动端部署库,无第三方库依赖 | 用户 |
full_publish | 编译移动端部署库,有第三方依赖如protobuf、glags等,含有可将模型转换为无需protobuf依赖的naive buffer格式的工具,供tiny_publish库使用 | 用户 |
test | 编译指定arm_os 、arm_abi 下的移动端单元测试 |
框架开发者 |
编译脚本./lite/tools/build.sh
,追加参数说明:
参数 | 介绍 | 值 |
---|---|---|
--arm_os | 必选,选择安装平台 | android 、ios 、ios64 、armlinux |
--arm_abi | 必选,选择编译的arm版本,其中armv7hf 为ARMLinux编译时选用 |
armv8 、armv7 、armv7hf (仅armlinux 支持) |
--arm_lang | arm_os=android时必选,选择编译器 | gcc 、clang (clang 当前暂不支持) |
--android_stl | arm_os=android时必选,选择静态链接STL或动态链接STL | c++_static 、c++_shared |
--build_java | 可选,是否编译java预测库(默认为ON) | ON 、OFF |
--build_extra | 可选,是否编译全量预测库(默认为OFF)。详情可参考预测库说明。 | ON 、OFF |
target | 必选,选择编译模式,tiny_publish 为编译移动端部署库、full_publish 为带依赖的移动端部署库、test 为移动端单元测试、ios 为编译ios端tiny_publish |
tiny_publish 、full_publish 、test 、 ios |
编译代码¶
注意:非开发者建议在编译前使用“加速第三方依赖库的下载”的方法,加速工程中第三方依赖库的下载与编译。
编译tiny publish
动态库¶
Android¶
./lite/tools/build.sh \
--arm_os=android \
--arm_abi=armv8 \
--build_extra=OFF \
--arm_lang=gcc \
--android_stl=c++_static \
tiny_publish
IOS¶
./lite/tools/build.sh \
--arm_os=ios64 \
--arm_abi=armv8 \
--build_extra=OFF \
ios
注意:mac环境编译IOS 时,cmake版本需要高于cmake 3.15;mac环境上编译Android时,cmake版本需要设置为cmake 3.10。
ios tiny publish支持的编译选项:
--arm_os
: 可选ios或者ios64--arm_abi
: 可选armv7和armv8(注意:当arm_os=ios
时只能选择arm_abi=armv7
,当arm_os=ios64
时只能选择arm_abi=armv8
)- 如果mac编译过程中报错:"Invalid CMAKE_DEVELOPER_ROOT: does not exist", 运行:
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
ARMLinux¶
./lite/tools/build.sh \
--build_extra=OFF \
--arm_os=armlinux \
--arm_abi=armv7hf \
--arm_lang=gcc \
tiny_publish
--arm_abi
: 树莓派3b使用armv7hf,RK3399使用armv8
编译full publish
动态库¶
Android¶
./lite/tools/build.sh \
--arm_os=android \
--arm_abi=armv8 \
--build_extra=OFF \
--arm_lang=gcc \
--android_stl=c++_static \
full_publish
ARMLinux¶
./lite/tools/build.sh \
--arm_os=armlinux \
--arm_abi=armv7hf \
--arm_lang=gcc \
--build_extra=OFF \
full_publish
--arm_abi
: 树莓派3b使用armv7hf,RK3399使用armv8
编译结果说明¶
编译最终产物位置在 build.lite.xxx.xxx.xxx
下的 inference_lite_lib.xxx.xxx
,如 Android 下 ARMv8 的产物位于inference_lite_lib.android.armv8
:
目录内容(可能)如下:
Full_publish编译结果:
Tiny_publish结果:
IOS编译结果:
具体内容说明:
1、 bin
文件夹:可执行工具文件 paddle_code_generator
、test_model_bin
2、 cxx
文件夹:包含c++的库文件与相应的头文件
include
: 头文件lib
: 库文件- 打包的静态库文件:
libpaddle_api_full_bundled.a
:包含 full_api 和 light_api 功能的静态库libpaddle_api_light_bundled.a
:只包含 light_api 功能的静态库
- 打包的动态态库文件:
libpaddle_full_api_shared.so
:包含 full_api 和 light_api 功能的动态库libpaddle_light_api_shared.so
:只包含 light_api 功能的动态库
- 打包的静态库文件:
3、 demo
文件夹:示例 demo ,包含 C++ demo 和 Java demo。
cxx
: C++示例 demomobile_full
: full_api 的使用示例mobile_light
: light_api的使用示例
java
:Java 示例 demoandroid
: Java的 Android 示例
4、 java
文件夹:包含 Jni 的动态库文件与相应的 Jar 包
jar
:PaddlePredictor.jar
so
: Jni动态链接库libpaddle_lite_jni.so
5、 third_party
文件夹:第三方库文件gflags
注意:
1、 只有当--arm_os=android
时才会编译出:
- Java库文件与示例:
Java
和demo/java
- 动态库文件:
libpaddle_full_api_shared.so
,libpaddle_light_api_shared.so
2、 tiny_publish
编译结果不包括 C++ demo和 C++ 静态库,但提供 C++ 的 light_api 动态库、 Jni 动态库和Java demo
加速第三方依赖库的下载¶
移动端相关编译所需的第三方库均位于 <PaddleLite>/third-party
目录下,默认编译过程中,会利用git submodule update --init --recursive
链上相关的第三方依赖的仓库。
为加速full_publish
、test
编译模式中对protobuf
等第三方依赖的下载,build.sh
和 ci_build.sh
支持了从国内 CDN 下载第三方依赖的压缩包。
使用方法:git clone
完Paddle-Lite
仓库代码后,手动删除本地仓库根目录下的third-party
目录:
git clone https://github.com/PaddlePaddle/Paddle-Lite.git
git checkout <release-version-tag>
cd Paddle-Lite
rm -rf third-party
之后再根据本文档,进行后续编译时,便会忽略第三方依赖对应的submodule
,改为下载第三方压缩包。
模型转换工具 X2Paddle¶
X2Paddle可以将caffe、tensorflow、onnx模型转换成Paddle支持的模型。
X2Paddle支持将Caffe/TensorFlow模型转换为PaddlePaddle模型。目前X2Paddle支持的模型参考x2paddle_model_zoo。
多框架支持¶
模型 | caffe | tensorflow | onnx |
---|---|---|---|
mobilenetv1 | Y | Y | |
mobilenetv2 | Y | Y | Y |
resnet18 | Y | Y | |
resnet50 | Y | Y | Y |
mnasnet | Y | Y | |
efficientnet | Y | Y | Y |
squeezenetv1.1 | Y | Y | Y |
shufflenet | Y | Y | |
mobilenet_ssd | Y | Y | |
mobilenet_yolov3 | Y | ||
inceptionv4 | |||
mtcnn | Y | Y | |
facedetection | Y | ||
unet | Y | Y | |
ocr_attention | |||
vgg16 |
安装¶
pip install x2paddle
安装最新版本,可使用如下安装方式
pip install git+https://github.com/PaddlePaddle/X2Paddle.git@develop
使用¶
Caffe¶
x2paddle --framework caffe \
--prototxt model.proto \
--weight model.caffemodel \
--save_dir paddle_model
TensorFlow¶
x2paddle --framework tensorflow \
--model model.pb \
--save_dir paddle_model
转换结果说明¶
在指定的save_dir
下生成两个目录
- inference_model : 模型结构和参数均序列化保存的模型格式
- model_with_code : 保存了模型参数文件和模型的python代码
问题反馈¶
X2Paddle使用时存在问题时,欢迎您将问题或Bug报告以Github Issues的形式提交给我们,我们会实时跟进。
模型优化工具 opt¶
Paddle-Lite 提供了多种策略来自动优化原始的训练模型,其中包括量化、子图融合、混合调度、Kernel优选等等方法。为了使优化过程更加方便易用,我们提供了opt 工具来自动完成优化步骤,输出一个轻量的、最优的可执行模型。
具体使用方法介绍如下:
注意:v2.2.0
之前的模型转化工具名称为model_optimize_tool
,从 v2.3
开始模型转化工具名称修改为 opt
准备opt¶
当前获得opt方法有三种:
- 推荐! 可以进入Paddle-Lite Github仓库的release界面,选择release版本下载对应的转化工具
opt
(release/v2.2.0之前的转化工具为model_optimize_tool、release/v2.3.0之后为opt) - 本文提供
release/v2.3
和release/v2.2.0
版本的优化工具下载
版本 | Linux | MacOS |
---|---|---|
release/v2.3 |
opt | opt_mac |
release/v2.2.0 |
model_optimize_tool | model_optimize_tool_mac |
- 如果 release 列表里的工具不符合您的环境,可以下载Paddle-Lite 源码,源码编译出opt工具
git clone https://github.com/PaddlePaddle/Paddle-Lite.git
cd Paddle-Lite
git checkout <release-version-tag>
./lite/tools/build.sh build_optimize_tool
编译结果位于Paddle-Lite/build.opt/lite/api/opt
注意:从源码编译opt前需要先安装Paddle-Lite的开发环境。
使用opt¶
opt是 x86 平台上的可执行文件,需要在PC端运行:支持Linux终端和Mac终端。
功能一:转化模型为Paddle-Lite格式¶
opt可以将PaddlePaddle的部署模型格式转化为Paddle-Lite 支持的模型格式,期间执行的操作包括:
- 将protobuf格式的模型文件转化为naive_buffer格式的模型文件,有效降低模型体积
- 执行“量化、子图融合、混合调度、Kernel优选”等图优化操作,提升其在Paddle-Lite上的运行速度、内存占用等效果
模型优化过程:
(1)准备待优化的PaddlePaddle模型
PaddlePaddle模型有两种保存格式:
Combined Param:所有参数信息保存在单个文件params
中,模型的拓扑信息保存在__model__
文件中。
opt_combined_model
Seperated Param:参数信息分开保存在多个参数文件中,模型的拓扑信息保存在__model__
文件中。
opt_seperated_model
(2) 终端中执行opt
优化模型
使用示例:转化mobilenet_v1
模型
./opt --model_dir=./mobilenet_v1 \
--valid_targets=arm \
--optimize_out_type=naive_buffer \
--optimize_out=mobilenet_v1_opt
以上命令可以将mobilenet_v1
模型转化为arm硬件平台、naive_buffer格式的Paddle_Lite支持模型,优化后的模型文件为mobilenet_v1_opt.nb
,转化结果如下图所示:
opt_resulted_model
(3) 更详尽的转化命令总结:
./opt \
--model_dir=<model_param_dir> \
--model_file=<model_path> \
--param_file=<param_path> \
--optimize_out_type=(protobuf|naive_buffer) \
--optimize_out=<output_optimize_model_dir> \
--valid_targets=(arm|opencl|x86|npu|xpu) \
--record_tailoring_info =(true|false)
选项 | 说明 |
---|---|
--model_dir | 待优化的PaddlePaddle模型(非combined形式)的路径 |
--model_file | 待优化的PaddlePaddle模型(combined形式)的网络结构文件路径。 |
--param_file | 待优化的PaddlePaddle模型(combined形式)的权重文件路径。 |
--optimize_out_type | 输出模型类型,目前支持两种类型:protobuf和naive_buffer,其中naive_buffer是一种更轻量级的序列化/反序列化实现。若您需要在mobile端执行模型预测,请将此选项设置为naive_buffer。默认为protobuf。 |
--optimize_out | 优化模型的输出路径。 |
--valid_targets | 指定模型可执行的backend,默认为arm。目前可支持x86、arm、opencl、npu、xpu,可以同时指定多个backend(以空格分隔),Model Optimize Tool将会自动选择最佳方式。如果需要支持华为NPU(Kirin 810/990 Soc搭载的达芬奇架构NPU),应当设置为npu, arm。 |
--record_tailoring_info | 当使用 根据模型裁剪库文件 功能时,则设置该选项为true,以记录优化后模型含有的kernel和OP信息,默认为false。 |
- 如果待优化的fluid模型是非combined形式,请设置
--model_dir
,忽略--model_file
和--param_file
。 - 如果待优化的fluid模型是combined形式,请设置
--model_file
和--param_file
,忽略--model_dir
。 - 优化后的模型为以
.nb
名称结尾的单个文件。 - 删除
prefer_int8_kernel
的输入参数,opt
自动判别是否是量化模型,进行相应的优化操作。
功能二:统计模型算子信息、判断是否支持¶
opt可以统计并打印出model中的算子信息、判断Paddle-Lite是否支持该模型。并可以打印出当前Paddle-Lite的算子支持情况。
(1)使用opt统计模型中算子信息
下面命令可以打印出mobilenet_v1模型中包含的所有算子,并判断在硬件平台valid_targets
下Paddle-Lite是否支持该模型
./opt --print_model_ops=true --model_dir=mobilenet_v1 --valid_targets=arm
opt_print_modelops
(2)使用opt打印当前Paddle-Lite支持的算子信息
./opt --print_all_ops=true
以上命令可以打印出当前Paddle-Lite支持的所有算子信息,包括OP的数量和每个OP支持哪些硬件平台:
opt_print_allops
./opt ----print_supported_ops=true --valid_targets=x86
以上命令可以打印出当valid_targets=x86
时Paddle-Lite支持的所有OP:
opt_print_supportedops
其他功能:合并x2paddle和opt的一键脚本¶
背景:如果想用Paddle-Lite运行第三方来源(tensorflow、caffe、onnx)模型,一般需要经过两次转化。即使用x2paddle工具将第三方模型转化为PaddlePaddle格式,再使用opt将PaddlePaddle模型转化为Padde-Lite可支持格式。 为了简化这一过程,我们提供一键脚本,将x2paddle转化和opt转化合并:
一键转化脚本:auto_transform.sh
环境要求:使用auto_transform.sh
脚本转化第三方模型时,需要先安装x2paddle环境,请参考x2paddle环境安装方法 安装x2paddle和x2paddle依赖项(tensorflow、caffe等)。
使用方法:
(1)打印帮助帮助信息:sh ./auto_transform.sh
(2)转化模型方法
USAGE:
auto_transform.sh combines the function of x2paddle and opt, it can
tranform model from tensorflow/caffe/onnx form into paddle-lite naive-buffer form.
----------------------------------------
example:
sh ./auto_transform.sh --framework=tensorflow --model=tf_model.pb --optimize_out=opt_model_result
----------------------------------------
Arguments about x2paddle:
--framework=(tensorflow|caffe|onnx);
--model='model file for tensorflow or onnx';
--prototxt='proto file for caffe' --weight='weight file for caffe'
For TensorFlow:
--framework=tensorflow --model=tf_model.pb
For Caffe:
--framework=caffe --prototxt=deploy.prototxt --weight=deploy.caffemodel
For ONNX
--framework=onnx --model=onnx_model.onnx
Arguments about opt:
--valid_targets=(arm|opencl|x86|npu|xpu); valid targets on Paddle-Lite.
--fluid_save_dir='path to outputed model after x2paddle'
--optimize_out='path to outputed Paddle-Lite model'
----------------------------------------
模型量化-有校准数据训练后量化¶
本文首先简单介绍有校准数据训练后量化,然后说明产出量化模型、量化模型预测,最后给出一个使用示例。 如果想快速上手,大家可以先参考使用示例,再查看详细使用方法。
1 简介¶
有校准数据训练后量化,使用少量校准数据计算量化因子,可以快速得到量化模型。使用该量化模型进行预测,可以减少计算量、降低计算内存、减小模型大小。
有校准数据训练后量化中,有两种计算量化因子的方法,非饱和量化方法和饱和量化方法。非饱和量化方法计算整个Tensor的绝对值最大值abs_max
,将其映射为127。饱和量化方法使用KL散度计算一个合适的阈值T
(0<T<mab_max
),将其映射为127。一般而言,待量化Op的权重采用非饱和量化方法,待量化Op的激活(输入和输出)采用饱和量化方法 。
使用条件:
- 有训练好的预测模型
- 有少量校准数据,比如100~500张图片
使用步骤:
- 产出量化模型:使用PaddlePaddle或者PaddleSlim调用有校准数据训练后量化接口,产出量化模型
- 量化模型预测:使用PaddleLite加载量化模型进行预测推理
优点:
- 减小计算量、降低计算内存、减小模型大小
- 不需要大量训练数据
- 快速产出量化模型,简单易用
缺点:
- 对少部分的模型,尤其是计算量小、精简的模型,量化后精度可能会受到影响
2 产出量化模型¶
大家可以使用PaddlePaddle或者PaddleSlim调用有校准数据训练后量化接口,得到量化模型。本文主要介绍使用PaddlePaddle产出量化模型,使用PaddleSlim可以参考文档。
2.2 准备模型和校准数据¶
准备已经训练好的FP32预测模型,即 save_inference_model()
保存的模型。
准备校准数据集,校准数据集应该是测试集/训练集中随机挑选的一部分,量化因子才会更加准确。对常见的视觉模型,建议校准数据的数量为100~500张图片。
2.3 配置校准数据生成器¶
有校准数据训练后量化内部使用异步数据读取的方式读取校准数据,大家只需要根据模型的输入,配置读取数据的sample_generator。sample_generator是Python生成器,必须每次返回单个样本数据,会用作DataLoader.set_sample_generator()
的数据源。
建议参考异步数据读取文档和本文示例,学习如何配置校准数据生成器。
2.4 调用有校准数据训练后量化¶
对于调用有校准数据训练后量化,首先给出一个例子,让大家有个直观了解。
import paddle.fluid as fluid
from paddle.fluid.contrib.slim.quantization import PostTrainingQuantization
exe = fluid.Executor(fluid.CPUPlace())
model_dir = path/to/fp32_model_params
# set model_filename as None when the filename is __model__,
# otherwise set it as the real filename
model_filename = None
# set params_filename as None when all parameters were saved in
# separate files, otherwise set it as the real filename
params_filename = None
save_model_path = path/to/save_model_path
# prepare the sample generator according to the model, and the
# sample generator must return a sample every time. The reference
# document: https://www.paddlepaddle.org.cn/documentation/docs/zh
# /user_guides/howto/prepare_data/use_py_reader.html
sample_generator = your_sample_generator
batch_size = 10
batch_nums = 10
algo = "KL"
quantizable_op_type = ["conv2d", "depthwise_conv2d", "mul"]
ptq = PostTrainingQuantization(
executor=exe,
sample_generator=sample_generator,
model_dir=model_dir,
model_filename=model_filename,
params_filename=params_filename,
batch_size=batch_size,
batch_nums=batch_nums,
algo=algo,
quantizable_op_type=quantizable_op_type)
ptq.quantize()
ptq.save_quantized_model(save_model_path)
对于调用有校准数据训练后量化,以下对接口进行详细介绍。
class PostTrainingQuantization(
executor=None,
scope=None,
model_dir=None,
model_filename=None,
params_filename=None,
sample_generator=None,
batch_size=10,
batch_nums=None,
algo="KL",
quantizable_op_type=["conv2d", "depthwise_conv2d", "mul"],
is_full_quantize=False,
weight_bits=8,
activation_bits=8,
is_use_cache_file=False,
cache_dir="./temp_post_training"):
调用上述api,传入必要的参数。参数说明如下:
- executor(fluid.Executor):执行模型的executor,可以指定在cpu或者gpu上执行。
- scope(fluid.Scope, optional):模型运行时使用的scope,默认为None,则会使用global_scope()。行首有optional,说明用户可以不设置该输入参数,直接使用默认值,下同。
- model_dir(str):待量化模型的路径,其中保存模型文件和权重文件。
- model_filename(str, optional):待量化模型的模型文件名,如果模型文件名不是
__model__
,则需要使用model_filename设置模型文件名。 - params_filename(str, optional):待量化模型的权重文件名,如果所有权重保存成一个文件,则需要使用params_filename设置权重文件名。
- sample_generator(Python Generator):配置的校准数据生成器。
- batch_size(int, optional):一次读取校准数据的数量。
- batch_nums(int, optional):读取校准数据的次数。如果设置为None,则从sample_generator中读取所有校准数据进行训练后量化;如果设置为非None,则从sample_generator中读取
batch_size*batch_nums
个校准数据。 - algo(str, optional):计算待量化激活Tensor的量化因子的方法。设置为
KL
,则使用饱和量化方法,设置为direct
,则使用非饱和量化方法。默认为KL
。 - quantizable_op_type(list[str], optional): 需要量化的op类型,默认是
["conv2d", "depthwise_conv2d", "mul"]
,列表中的值可以是任意支持量化的op类型。 - is_full_quantize(bool, optional):是否进行全量化。设置为True,则对模型中所有支持量化的op进行量化;设置为False,则只对
quantizable_op_type
中op类型进行量化。目前支持的量化类型如下:'conv2d', 'depthwise_conv2d', 'mul', "pool2d", "elementwise_add", "concat", "softmax", "argmax", "transpose", "equal", "gather", "greater_equal", "greater_than", "less_equal", "less_than", "mean", "not_equal", "reshape", "reshape2", "bilinear_interp", "nearest_interp", "trilinear_interp", "slice", "squeeze", "elementwise_sub"。 - weight_bits(int, optional):权重量化的比特数,可以设置为1~16。PaddleLite目前仅支持加载权重量化为8bit的量化模型。
- activation_bits(int, optional): 激活量化的比特数,可以设置为1~16。PaddleLite目前仅支持加载激活量化为8bit的量化模型。
- is_use_cache_file(bool, optional):是否使用缓存文件。如果设置为True,训练后量化过程中的采样数据会保存到磁盘文件中;如果设置为False,所有采样数据会保存到内存中。当待量化的模型很大或者校准数据数量很大,建议设置is_use_cache_file为True。默认为False。
- cache_dir(str, optional):当is_use_cache_file等于True,会将采样数据保存到该文件中。量化完成后,该文件中的临时文件会自动删除。
PostTrainingQuantization.quantize()
调用上述接口开始训练后量化。根据校准数据数量、模型的大小和量化op类型不同,训练后量化需要的时间也不一样。比如使用ImageNet2012数据集中100图片对MobileNetV1
进行训练后量化,花费大概1分钟。
PostTrainingQuantization.save_quantized_model(save_model_path)
调用上述接口保存训练后量化模型,其中save_model_path为保存的路径。
训练后量化支持部分量化功能:
- 方法1:设置quantizable_op_type,则只会对quantizable_op_type中的Op类型进行量化,模型中其他Op类型保持不量化。
- 方法2:构建网络的时候,将不需要量化的特定Op定义在
skip_quant
的name_scope中,则可以跳过特定Op的量化,示例如下。
with fluid.name_scope('skip_quant'):
pool = fluid.layers.pool2d(input=hidden, pool_size=2, pool_type='avg', pool_stride=2)
# 不对pool2d进行量化
3 量化模型预测¶
首先,使用PaddleLite提供的模型转换工具(model_optimize_tool)将量化模型转换成移动端预测的模型,然后加载转换后的模型进行预测部署。
3.1 模型转换¶
参考模型转换准备模型转换工具,建议从Release页面下载。
参考模型转换使用模型转换工具,参数按照实际情况设置。比如在安卓手机ARM端进行预测,模型转换的命令为:
./opt --model_dir=./mobilenet_v1_quant \
--optimize_out_type=naive_buffer \
--optimize_out=mobilenet_v1_quant_opt \
--valid_targets=arm
3.2 量化模型预测¶
和FP32模型一样,转换后的量化模型可以在Android/IOS APP中加载预测,建议参考C++ Demo、Java Demo、Android/IOS Demo。
4 使用示例¶
4.1 产出量化模型¶
参考本文 “2.1 安装PaddlePaddle” 安装PaddlePaddle。
下载打包文件,解压到本地。
wget https://paddle-inference-dist.cdn.bcebos.com/PaddleLite/quantization_demo/post_training_quantization_withdata.tgz
tar zxvf post_training_quantization_withdata.tgz
cd post_training_quantization_withdata
执行下面的命令,自动下载预测模型(mobilenetv1_fp32_model)和校准数据集,然后调用有校准数据训练后方法产出量化模型。
sh run_post_training_quanzation.sh
量化模型保存在mobilenetv1_int8_model文件夹中。
4.2 量化模型预测¶
下载测试文件(benchmark_bin)或者参考Benchmark测试方法编译测试文件。
将mobilenetv1_fp32_model、mobilenetv1_int8_model和benchmark_bin文件都保存到手机上。
adb push mobilenetv1_fp32_model /data/local/tmp
adb push mobilenetv1_int8_model /data/local/tmp
chmod 777 benchmark_bin
adb push benchmark_bin /data/local/tmp
测试量化模型和原始模型的性能,依次执行下面命令:
./benchmark_bin --is_quantized_model=true --run_model_optimize=true --result_filename=res.txt --warmup=10 --repeats=30 --model_dir=mobilenetv1_int8_model/
./benchmark_bin --is_quantized_model=true --run_model_optimize=true --result_filename=res.txt --warmup=10 --repeats=30 --model_dir=mobilenetv1_fp32_model/
cat res.txt
在res.txt文件中可以看到INT8量化模型和FP32原始模型的速度。 举例来说,在骁龙855手机、单线程的情况下测试mobilenetv1,INT8量化模型的计算时间是14.52ms,FP32原始模型的计算时间是31.7ms。
模型量化-无校准数据训练后量化¶
本文首先简单介绍无校准数据训练后量化,然后说明产出量化模型,最好阐述量化模型预测。
1 简介¶
无校准数据训练后量化,将模型中特定OP的权重从FP32类型量化成INT8/16类型,可以减小预测模型的大小。使用该量化模型预测,首先将INT8/16类型的权重反量化成FP32类型,然后再进行预测。
使用条件:
- 有训练好的预测模型
使用步骤:
- 产出量化模型:使用PaddlePaddle调用无校准数据训练后量化接口,产出量化模型
- 量化模型预测:使用PaddleLite加载量化模型进行预测推理
优点:
- 权重量化成INT16类型,模型精度不受影响,模型大小为原始的1/2
- 权重量化成INT8类型,模型精度会受到影响,模型大小为原始的1/4
缺点:
- 暂无
2 产出量化模型¶
大家可以使用PaddlePaddle调用无校准数据训练后量化接口,得到量化模型。
2.2 准备模型¶
准备已经训练好的FP32预测模型,即 save_inference_model()
保存的模型。
2.3 调用无校准数据训练后量化¶
对于调用无校准数据训练后量化,首先给出一个例子。
from paddle.fluid.contrib.slim.quantization import WeightQuantization
model_dir = path/to/fp32_model_params
save_model_dir = path/to/save_model_path
weight_quant = WeightQuantization(model_dir=model_dir)
weight_quant.quantize_weight_to_int(save_model_dir=save_model_dir,
weight_bits=16,
quantizable_op_type=['conv2d', 'depthwise_conv2d', 'mul'])
对于调用无校准数据训练后量化,以下对api接口进行详细介绍。
class WeightQuantization(model_dir, model_filename=None, params_filename=None)
参数说明如下:
- model_dir(str):待量化模型的路径,其中保存模型文件和权重文件。
- model_filename(str, optional):待量化模型的模型文件名,如果模型文件名不是
__model__
,则需要使用model_filename设置模型文件名。 - params_filename(str, optional):待量化模型的权重文件名,如果所有权重保存成一个文件,则需要使用params_filename设置权重文件名。
WeightQuantization.quantize_weight_to_int(save_model_dir,
save_model_filename=None,
save_params_filename=None,
quantizable_op_type=['conv2d', 'mul'],
weight_bits=8,
threshold_rate=0.0)
参数说明如下:
- save_model_dir(str):保存量化模型的路径。
- save_model_filename(str, optional):如果save_model_filename等于None,则模型的网络结构保存到__model__文件,如果save_model_filename不等于None,则模型的网络结构保存到特定的文件。默认为None。
- save_params_filename(str, optional):如果save_params_filename等于None,则模型的参数分别保存到一系列文件中,如果save_params_filename不等于None,则模型的参数会保存到一个文件中,文件名为设置的save_params_filename。默认为None。
- quantizable_op_type(list[str]): 需要量化的op类型,默认是
['conv2d', 'mul']
,列表中的值可以是任意支持量化的op类型['conv2d', 'depthwise_conv2d', 'mul']
。 - weight_bits(int, optional):权重量化保存的比特数,可以是8~16,一般设置为8/16。默认为8。
3 量化模型预测¶
目前,对于无校准数据训练后量化产出的量化模型,不支持PaddlePaddle加载执行,只能使用PaddleLite进行预测部署。
很简单,首先使用PaddleLite提供的模型转换工具(opt)将量化模型转换成移动端预测的模型,然后加载转换后的模型进行预测部署。
注意,PaddleLite 2.3版本才支持无校准数据训练后量化产出的量化,所以转换工具和预测库必须是2.3及之后的版本。
3.1 模型转换¶
参考模型转换准备模型转换工具,建议从Release页面下载。
参考模型转换使用模型转换工具。 比如在安卓手机ARM端进行预测,模型转换的命令为:
./opt --model_dir=./mobilenet_v1_quant \
--optimize_out_type=naive_buffer \
--optimize_out=mobilenet_v1_quant_opt \
--valid_targets=arm
3.2 量化模型预测¶
和FP32模型一样,转换后的量化模型可以在Android/IOS APP中加载预测,建议参考C++ Demo、Java Demo、Android/IOS Demo。
模型量化-量化训练¶
本文主要介绍使用Paddle-Lite加载PaddlePaddle产出的量化模型,并进行推理执行。我们以MobileNetV1模型为示例,首先说明产出量化模型,然后说明预测部署。
1 简介¶
量化训练是基于大量训练数据,对训练好的预测模型进行量化。该方法使用模拟量化的思想,在训练阶段更新权重,实现减小量化误差。
使用条件:
- 有预训练模型
- 有较多训练数据
使用步骤:
- 产出量化模型:使用PaddlePaddle调用量化训练接口,产出量化模型
- 量化模型预测:使用PaddleLite加载量化模型进行预测推理
优点:
- 减小计算量、降低计算内存、减小模型大小
- 模型精度受量化影响小
缺点:
- 使用条件较苛刻,使用门槛稍高
建议首先使用“有校准数据训练后量化”对模型进行量化,然后使用使用量化模型进行预测。如果该量化模型的精度达不到要求,再使用“量化训练”。
2 产出量化模型¶
目前,PaddlePaddle框架的量化训练主要针对卷积层(包括二维卷积和Depthwise卷积)、和全连接层,对应算子是conv2d、depthwise_conv2d和mul,更多量化训练的原理请参考文档。Paddle-Lite支持运行PaddlePaddle框架量化训练产出的模型,可以进一步加快模型在移动端的执行速度。
温馨提示:如果您是初次接触PaddlePaddle框架,建议首先学习新人入门和使用指南。
您可以选择下载训练好的量化模型,或者使用PaddleSlim模型压缩工具训练得到量化模型。
下载量化模型¶
官方发布了MobileNetV1量化模型,直接下载到本地。
wget https://paddle-inference-dist.bj.bcebos.com/int8%2Fpretrain%2Fmobilenet_v1_quant%2Ffloat.zip
使用PaddleSlim模型压缩工具训练量化模型¶
安装PaddlePaddle¶
根据操作系统、安装方式、Python版本和CUDA版本,按照官方说明安装PaddlePaddle。例如:
Ubuntu 16.04.4 LTS操作系统,CUDA9,cuDNN7,GPU版本安装:
pip install paddlepaddle-gpu==1.6.0.post97 -i https://mirrors.aliyun.com/pypi/simple/
Ubuntu 16.04.4 LTS操作系统,CPU版本安装:
pip install paddlepaddle==1.6.0 -i https://mirrors.aliyun.com/pypi/simple/
克隆量化训练所需的代码库¶
克隆PaddlePaddle/models到本地,并进入models/PaddleSlim路径。
git clone https://github.com/PaddlePaddle/models.git
cd models/PaddleSlim
准备数据和模型¶
训练数据准备¶
参考models/PaddleCV/image_classification中的数据准备教程,下载训练数据,并且保存到PaddleSlim/data路径下。
预训练模型准备¶
参考/models/PaddleSlim/run.sh脚本, 从models/PaddleCV/image_classification下载MobileNetV1的预训练模型,并保存到PaddleSlim/pretrain路径下。
经过以上三步,PaddleSlim目录下的文件结构如下所示:
.
├── compress.py # 模型压缩任务主脚本,定义了压缩任务需要的模型相关信息
├── configs # 压缩任务的配置文件,包括:蒸馏、int8量化量化、filter剪切和组合策略的配置文件
├── data # 存放训练数据(需要用户自己创建)
│ └── ILSVRC2012
├── pretrain # 存放预训练模型参数,执行run.sh自动生成
│ ├── MobileNetV1_pretrained
│ ├── MobileNetV1_pretrained.tar
│ ├── ResNet50_pretrained
│ └── ResNet50_pretrained.tar
├── docs # 文档目录
├── light_nas
├── models # 模型网络结构的定义,如MobileNetV1
├── quant_low_level_api # 量化训练的底层API, 用于灵活定制量化训练的过程,适用于高阶用户
├── reader.py # 定义数据处理逻辑
├── README.md
├── run.sh # 模型压缩任务启动脚本
└── utility.py # 定义了常用的工具方法
压缩脚本介绍¶
在compress.py
中定义了执行压缩任务需要的所有模型相关的信息,这里对几个关键的步骤进行简要介绍:
目标网络的定义 compress.py的以下代码片段定义了train program, 这里train program只有前向计算操作。
out = model.net(input=image, class_dim=args.class_dim)
cost = fluid.layers.cross_entropy(input=out, label=label)
avg_cost = fluid.layers.mean(x=cost)
acc_top1 = fluid.layers.accuracy(input=out, label=label, k=1)
acc_top5 = fluid.layers.accuracy(input=out, label=label, k=5)
然后,通过clone方法得到eval_program, 用来在压缩过程中评估模型精度,如下:
val_program = fluid.default_main_program().clone()
定义完目标网络结构,需要对其初始化,并根据需要加载预训练模型。
定义feed_list和fetch_list 对于train program, 定义train_feed_list用于指定从train data reader中取的数据feed给哪些variable。定义train_fetch_list用于指定在训练时,需要在log中展示的结果。如果需要在训练过程中在log中打印accuracy信心,则将('acc_top1', acc_top1.name)添加到train_fetch_list中即可。
train_feed_list = [('image', image.name), ('label', label.name)]
train_fetch_list = [('loss', avg_cost.name)]
注意: 在train_fetch_list里必须有loss这一项。
对于eval program. 同上定义eval_feed_list和train_fetch_list:
val_feed_list = [('image', image.name), ('label', label.name)]
val_fetch_list = [('acc_top1', acc_top1.name), ('acc_top5', acc_top5.name)]
Compressor和量化配置文件
compress.py
主要使用Compressor和yaml文件完成对模型的量化训练工作。Compressor类的定义如下:
class Compressor(object):
def __init__(self,
place,
scope,
train_program,
train_reader=None,
train_feed_list=None,
train_fetch_list=None,
eval_program=None,
eval_reader=None,
eval_feed_list=None,
eval_fetch_list=None,
teacher_programs=[],
checkpoint_path='./checkpoints',
train_optimizer=None,
distiller_optimizer=None):
在定义Compressor对象时,需要注意以下问题:
- train program如果带反向operators和优化更新相关的operators, 参数train_optimizer需要设置为None.
- eval_program中parameter的名称需要与train_program中的parameter的名称完全一致。
- 最终保存的量化模型是在eval_program网络基础上进行剪枝保存的。所以,如果用户希望最终保存的模型可以用于inference, 则eval program需要包含推理阶段需要的各种operators.
- checkpoint保存的是float数据类型的模型。
configs/quantization.yaml
量化配置文件示例如下:
version: 1.0
strategies:
quantization_strategy:
class: 'QuantizationStrategy'
start_epoch: 0
end_epoch: 9
float_model_save_path: './output/float'
mobile_model_save_path: './output/mobile'
int8_model_save_path: './output/int8'
weight_bits: 8
activation_bits: 8
weight_quantize_type: 'abs_max'
activation_quantize_type: 'moving_average_abs_max'
save_in_nodes: ['image']
save_out_nodes: ['fc_0.tmp_2']
compressor:
epoch: 10
checkpoint_path: './checkpoints_quan/'
strategies:
- quantization_strategy
其中,可配置参数包括:
- class: 量化策略的类名称,目前仅支持
QuantizationStrategy
。 - start_epoch: 在start_epoch开始之前,量化训练策略会往train_program和eval_program插入量化operators和反量化operators。 从start_epoch开始,进入量化训练阶段。
- end_epoch: 在end_epoch结束之后,会保存用户指定格式的模型。注意:end_epoch之后并不会停止量化训练,而是继续训练直到epoch数等于compressor.epoch值为止。举例来说,当start_epoch=0,end_epoch=0,compressor.epoch=2时,量化训练开始于epoch0,结束于epoch1,但保存的模型是epoch0结束时的参数状态。
- float_model_save_path: 保存float数据格式的模型路径,即该路径下的模型参数范围为int8范围但参数数据类型为float32。如果设置为None, 则不存储float格式的模型,默认为None。注意:Paddle-Lite即使用该目录下的模型进行量化模型推理优化,详见本文使用Paddle-Lite运行量化模型推理部分。
- int8_model_save_path: 保存int8数据格式的模型路径,即该路径下的模型参数范围为int8范围且参数数据类型为int8。如果设置为None, 则不存储int8格式的模型,默认为None.
- mobile_model_save_path: 保存兼容paddle-mobile框架的模型路径。如果设置为None, 则不存储paddle-mobile格式的模型,默认为None。目前paddle-mobile已升级为Paddle-Lite。
- weight_bits: 量化weight的bit数,注意偏置(bias)参数不会被量化。
- activation_bits: 量化activation的bit数。
- weight_quantize_type: weight量化方式,目前量化训练支持
abs_max
、channel_wise_abs_max
。 - activation_quantize_type: activation量化方式,目前量化训练支持
range_abs_max
、moving_average_abs_max
。PaddlePaddle中还支持abs_max
方法对激活进行量化,但是该方法动态计算输入的量化scale,这会增加计算量、减慢模型推理速度,所以lite不支持abs_max
激活量化方式。 - save_in_nodes: variable名称列表。在保存量化后模型的时候,需要根据save_in_nodes对eval programg 网络进行前向遍历剪枝。默认为eval_feed_list内指定的variable的名称列表。
- save_out_nodes: varibale名称列表。在保存量化后模型的时候,需要根据save_out_nodes对eval programg 网络进行回溯剪枝。默认为eval_fetch_list内指定的variable的名称列表。
备注:
1)
abs_max
意为在训练的每个step及inference阶段均动态计算量化scale值。channel_wise_abs_max
与abs_max
类似,不同点在于它会对卷积权重进行分channel求取量化scale。换言之,abs_max
属于tensor-wise量化,而channel_wise_abs_max
属于channel-wise量化,详细说明请猛戳此处。2)
moving_average_abs_max
和range_abs_max
意为在训练阶段计算出一个静态的量化scale值,并将其用于inference阶段。moving_average_abs_max
使用窗口滑动平均的方法计算量化scale,而range_abs_max
则使用窗口绝对值最大值的方式。3)目前,Paddle-Lite仅支持运行weight量化方式使用
abs_max
且activation量化方式使用moving_average_abs_max
或range_abs_max
产出的量化模型。
执行量化训练¶
修改run.sh,即注释掉# enable GC strategy
与# for sensitivity filter pruning
之间的内容并打开#for quantization
相关的脚本命令(所需打开注释的命令如下所示)。
# for quantization
#---------------------------
export CUDA_VISIBLE_DEVICES=0
python compress.py \
--batch_size 64 \
--model "MobileNet" \
--pretrained_model ./pretrain/MobileNetV1_pretrained \
--compress_config ./configs/quantization.yaml \
--quant_only True
最后,运行sh run.sh
命令开始int8量化训练。
上述量化训练过程完成后,若按照本文中所述configs/quantization.yaml
文件内容配置的模型输出路径,则可在models/PaddleSlim/output目录下看到float
、int8
和mobile
三个目录,其中:
- float目录: 参数范围为int8范围但参数数据类型为float32的量化模型。Paddle-Lite即使用该目录下的模型文件及参数进行量化模型的部署。
- int8目录: 参数范围为int8范围且参数数据类型为int8的量化模型。
- mobile目录:参数特点与int8目录相同且兼容paddle-mobile的量化模型(目前paddle-mobile已升级为Paddle-Lite)。
3 使用Paddle-Lite运行量化模型推理¶
使用模型优化工具对量化模型进行优化¶
接下来,使用原始的量化模型生成适合在移动端直接部署的模型。
参考源码编译配置编译环境,确保可以编译成功。参考模型转化方法,首先编译model_optimize_tool工具,然后执行下面命令对量化训练的模型进行优化(注意,需要自行修改model_file、param_file和optimize_out)。
./model_optimize_tool \
--model_file=mobilenet_v1_quant/float/model \
--param_file=mobilenet_v1_quant/float/weights \
--optimize_out_type=naive_buffer \
--optimize_out=mobilenet_v1_quant_opt \
--valid_targets=arm \
如前所述,量化训练后,float目录下的模型参数范围为int8,但参数数据类型仍为float32类型,这样确实没有起到模型参数压缩的效果。但是,经过model_optimize_tool工具优化后对应的量化参数均会以int8类型重新存储达到参数压缩的效果,且模型结构也被优化(如进行了各种operator fuse操作)。
在手机端准备量化模型文件¶
使用如下命令将mobilenet_v1_quant_opt目录下的量化模型文件导入到手机端:
adb push mobilenet_v1_quant_opt /data/local/tmp
使用mobilenetv1_light_api运行优化后的量化模型¶
参考源码编译配置编译环境后,在Paddle-Lite执行如下命令获取轻量级API的demo:
cd /Paddle-Lite/build.lite.android.armv8.gcc/inference_lite_lib.android.armv8/demo/cxx/mobile_light
make clean && make -j
执行完上述命令后,可在Paddle-Lite/build.lite.android.armv8.gcc/inference_lite_lib.android.armv8/demo/cxx/mobile_light/
路径下看到mobilenetv1_light_api
可执行文件。将mobilenetv1_light_api
导入到手机端并运行量化模型推理。执行命令如下:
adb push Paddle-Lite/build.lite.android.armv8.gcc/inference_lite_lib.android.armv8/demo/cxx/mobile_light/mobilenetv1_light_api /data/local/tmp
adb shell chmod +x /data/local/tmp/mobilenetv1_light_api
adb shell /data/local/tmp/mobilenetv1_light_api \
--model_dir=/data/local/tmp/mobilenet_v1_quant_opt
程序运行结果如下:
Output dim: 1000
Output[0]: 0.000228
Output[100]: 0.000260
Output[200]: 0.000250
Output[300]: 0.000560
Output[400]: 0.000950
Output[500]: 0.000275
Output[600]: 0.005143
Output[700]: 0.002509
Output[800]: 0.000538
Output[900]: 0.000969
在C++中使用Paddle-Lite API的方法请猛戳此处,用户也可参考mobilenetv1_light_api.cc的代码示例。
FAQ¶
问题:Compiled with WITH_GPU, but no GPU found in runtime
解答:检查本机是否支持GPU训练,如果不支持请使用CPU训练。如果在docker进行GPU训练,请使用nvidia_docker启动容器。
问题:Inufficient GPU memory to allocation. at [/paddle/paddle/fluid/platform/gpu_info.cc:262]
解答:正确设置run.sh脚本中CUDA_VISIBLE_DEVICES
,确保显卡剩余内存大于需要内存。
调试¶
Profiler工具¶
Basic profiler 用于 CPU 上kernel 耗时的统计。
使用示例:¶
在模型执行完毕后,会自动打印类似如下 profiler 的日志
kernel average min max count
feed/def/1/4/2 0 0 0 1
conv2d/def/4/1/1 1175 1175 1175 1
conv2d/def/4/1/1 1253 1253 1253 1
depthwise_conv2d/def/4/1/1 519 519 519 1
conv2d/def/4/1/1 721 721 721 1
elementwise_add/def/4/1/1 18 18 18 1
conv2d/def/4/1/1 2174 2174 2174 1
depthwise_conv2d/def/4/1/1 380 380 380 1
conv2d/def/4/1/1 773 773 773 1
elementwise_add/def/4/1/1 2 2 2 1
conv2d/def/4/1/1 1248 1248 1248 1
depthwise_conv2d/def/4/1/1 492 492 492 1
conv2d/def/4/1/1 1150 1150 1150 1
elementwise_add/def/4/1/1 33 33 33 1
elementwise_add/def/4/1/1 3 3 3 1
conv2d/def/4/1/1 1254 1254 1254 1
depthwise_conv2d/def/4/1/1 126 126 126 1
Debug工具¶
Lite Model Debug Tool 是用来检查Paddle-Lite框架与Paddle-Fluid框架运行时tensor(包括variable与weight)之间diff信息的基础工具。
编译方法:¶
- 参照 编译安装 中的full_publish部分进行环境配置和编译。
- 在生成的
build
目录下,执行make lite_model_debug_tool
,lite_model_debug_tool
产出在编译目录的lite/tools/debug
目录下。
工作流程:¶
- 运行
/bin/bash check_model.sh --model_dir=<your_model_path> --build_root_dir=<your_cmake_root_dir> debug_cpp_stage
获得模型在Paddle-Lite框架下的运行拓扑信息、varibles信息和weights信息。运行后拓扑信息将会存储在默认名为topo_file.txt
的文件中,variables和weights信息将会存储在默认名为tensor_cpp.txt
的文件中。 - 运行
/bin/bash check_model.sh --model_dir=<your_model_path> --build_root_dir=<your_cmake_root_dir> debug_py_stage
执行fluid框架预测以获取相同模型在fluid框架下的variable与weight信息(注意:我们使用fluid的python api运行fluid模型,因此您在运行此步之前应确保已正确安装fluid的python api)。然后debug tool将会自动比较Paddle-Lite框架输出的信息和Paddle-Fluid框架输出的信息来检查是否存在运行时diff。 执行Paddle-Fluid框架,输出的信息将会存储在默认名为tensor_py.txt
的文件中,相应的diff信息将会存储在默认名为diff.txt
的文件中(默认情况下,只会输出执行拓扑序中第一个有diff的variable相关的信息)。
注意事项:¶
- 输出的结果是在执行完一次预测后输出的相应变量/权重的最终值,因此如果您在预测过程进行过诸如变量复用/子图融合等优化方法,则相应的输出可能会出现偏差。
- 默认情况下debug tools将以全1作为输入进行比对。
- 默认情况下,为了保证与Paddle-Fluid框架的结果可比对,debug tool将会禁用掉所有的Paddle-Lite的优化策略。
- Paddle-Lite框架的执行环境由与您的编译选项有关,比如您开启了LITE_WITH_ARM编译选项,那debug tool的
debug_cpp_stage
也需要在ARM平台下运行。
Diff信息输出:¶
如果debug tool检测到diff信息,那么在diff.txt
中将会输出类似以下结构信息
>>>>>>>>>>>>>>>>>>DIFF VARIABLE: dropout_0.tmp_0<<<<<<<<<<<<<<<<<<<
dropout (X:pool2d_7.tmp_0) (Mask:dropout_0.tmp_1 Out:dropout_0.tmp_0)
--------------- Tensor File info ---------------
pool2d_7.tmp_0 {1,1536,1,1} 0.749892 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0150336 0.621641 0.147099 0.636727 0.0 0.0 0.00410917 0.784708 0.0 0.0704846 0.233599 0.840123 0.239201 0.112878 0.0 0.155352 0.306906 0.0 0.0 0.860938 0.221037 0.787316 0.256585 ...
dropout_0.tmp_0 {1,1536,1,1} 0.749892 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0150336 0.621641 0.147099 0.636727 0.0 0.0 0.00410917 0.784708 0.0 0.0704846 0.233599 0.840123 0.239201 0.112878 0.0 0.155352 0.306906 0.0 0.0 0.860938 0.221037 0.787316 0.256585 ...
--------------- Fluid Tensor info ---------------
pool2d_7.tmp_0 {1,1536,1,1} 0.7498912 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.015033395 0.6216395 0.14709876 0.63672537 0.0 0.0 0.0041093696 0.7847073 0.0 0.07048465 0.23359808 0.8401219 0.23919891 0.1128789 0.0 0.1553514 0.3069055 0.0 0.0 0.8609365 0.22103554 ...
dropout_0.tmp_0 {1,1536,1,1} 0.599913 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.012026716 0.4973116 0.117679015 0.5093803 0.0 0.0 0.0032874958 0.62776583 0.0 0.056387722 0.18687847 0.67209756 0.19135913 0.090303116 0.0 0.12428112 0.2455244 0.0 0.0 0.68874925 ...
其中第二行为op相关信息,标明了执行哪个op出现了diff及其对应的输入输出变量名。Tensor File info为Paddle-Lite框架的输出信息,而Fluid Tensor info为Paddle-Fluid框架的相应输出信息。
示例中的dropout_0.tmp_1
没有相应的tensor信息是因为工具检测到其在预测的后序流程中未被使用,因此不会对预测结果造成影响,从而将其自动屏蔽掉以保证输出尽量简洁。
其他选项:¶
Option | Description |
---|---|
--input_file | 输入文件名,不同field以逗号分隔,相同field内以空格分隔, 只有文件中的第一行输入信息会被使用. 如果您不指定input_file,那么所有输入将会被置为1。注意:debug_py_stage 目前不支持多field输入。 |
--cpp_topo_file | 存储运行时拓扑信息,由debug_cpp_stage 写入并且由debug_py_stage 读取使用。 默认为topo_file.txt 。 |
--cpp_tensor_file | 存储debug_cpp_stage 在运行拓扑序下的输出信息,默认为 tensor_cpp.txt 。 |
--tensor_names | 如果此选项不为空,那么只输出由此选项中指定名字的variable/weight信息,名字间用逗号分隔。 |
--tensor_output_length | 输出数据的长度,默认为全部输出。 |
--py_threshold | 判断diff发生的阈值,默认为 1e-5 。 |
--py_tensor_file | 存储debug_py_stage 在运行拓扑序下的输出信息,默认为tensor_py.txt . |
--py_output_file | diff信息的存储文件,默认为diff.txt 。 |
--py_only_output_first_diff | 是否只输出运行时拓扑序中第一个有diff的var/op信息,默认为true |
您可以参考 check_model.sh
脚本中的代码以获得更多细节.
裁剪预测库¶
Paddle-Lite支持根据模型裁剪预测库功能。Paddle-Lite的一般编译会将所有已注册的operator打包到预测库中,造成库文件体积膨胀;裁剪预测库能针对具体的模型,只打包优化后该模型需要的operator,有效降低预测库文件大小。
效果展示(Tiny_publish Android动态预测库体积)¶
测试模型 | 裁剪开关 | libpaddle_lite_jni.so | 转化后模型中的OP |
---|---|---|---|
mobilenetv1(armv8) | 裁剪前--build_tailor=OFF | 1.5M | feed,etch,conv2d,depthwise_conv2d,fc,fpool2d,softmax |
mobilenetv1(armv8) | 裁剪后--build_tailor=ON | 788K | feed,etch,conv2d,depthwise_conv2d,fc,fpool2d,softmax |
mobilenetv2(armv8) | 裁剪前--build_tailor=OFF | 1.5M | feed,fetch,conv2d,depthwise_conv2d,elementwise_add,fc,pool2d,relu6,softmax |
mobilenetv2(armv8) | 裁剪后--build_tailor=ON | 912K | feed,fetch,conv2d,depthwise_conv2d,elementwise_add,fc,pool2d,relu6,softmax |
mobilenetv1(armv7) | 裁剪前--build_tailor=OFF | 938K | feed,fetch,concat,conv2d,dropout,fc,pool2d,softmax |
mobilenetv1(armv7) | 裁剪后--build_tailor=ON | 607K | feed,fetch,concat,conv2d,dropout,fc,pool2d,softmax |
mobilenetv2(armv7) | 裁剪前--build_tailor=OFF | 938K | feed,fetch,conv2d,depthwise_conv2d,elementwise_add,fc,pool2d,relu6,softmax |
mobilenetv2(armv7) | 裁剪后--build_tailor=ON | 687K | feed,fetch,conv2d,depthwise_conv2d,elementwise_add,fc,pool2d,relu6,softmax |
实现过程:¶
1、转化模型时记录优化后模型信息¶
说明:使用model_optimize_tool转化模型时,选择 --record_tailoring_info =true
会将优化后模型的OP和kernel信息保存到输出文件夹,这些信息将用于编译裁剪后的动态库。
注意:需要使用Paddle-Lite 最新版本(release/v2.0.0之后)代码编译出的model_optimize_tool
例如:
./model_optimize_tool --model_dir=./mobilenet_v1 --optimize_out_type=naive_buffer --optimize_out=mobilenet_v1NB --record_tailoring_info =true --valid_targets=arm
效果:优化后模型使用的OP和kernel信息被保存在 mobilenet_v1NB
文件夹中的隐藏文件里了
2、根据模型信息编译裁剪后的预测库¶
说明:编译Paddle-Lite时选择--build_tailor=ON
,并且用 –-opt_model_dir=
指定优化后的模型的地址
例如:
./lite/tools/build.sh --arm_os=android --arm_abi=armv7 --arm_lang=gcc --android_stl=c++_static --build_extra=ON --build_tailor=ON --opt_model_dir=../mobilenet_v1NB tiny_publish
注意:上面命令中的../mobilenet_v1NB
是第1步得到的转化模型的输出路径
效果:编译出来的动态库文件变小,且可以运行优化后的模型。
编译出的C++预测库文件位于 :
build.lite.android.armv7.gcc/inference_lite_lib.android.armv7/cxx/lib/
编译出的Java预测库文件位于:
build.lite.android.armv7.gcc/inference_lite_lib.android.armv7/java/so/
3、运行裁剪后的预测库文件¶
注意:基于某一模型裁剪出的预测库只能支持优化工具转化后的该模型,例如根据mobilenetV1裁剪出的 full_api预测库只能运行以protobuf格式转化出的模型mobilenetV1_opt_nb, 裁剪出的light_api预测库只能运行以naive_buffer格式转化出的模型mobilenetV1_opt_nb, 运行其他模型可能会出现segementation fault:undifined op or kernel
。 模型转化方法参考:使用opt转化模型)。
示例1:使用裁剪后的light_api预测库运行mobilenetv1
1、执行第二步编译后,light_api的C++ 示例位于
/Paddle-Lite/build.lite.android.armv7.gcc/inference_lite_lib.android.armv7/demo/cxx/mobile_light
输入make
命令执行编译可编译出可执行文件mobilenetv1_light_api
2、使用adb将mobilenetV1_NB模型和mobilenetv1_light_api传到手机后执行demo:
./mobilenetv1_light_api --model_dir=./mobilenetV1_NB
注意:mobilenetV1_NB
是用mobilenetV1
模型转化的naive_buffer格式模型(不需要设置--record_tailoring_info =true
,转化流程参考:使用opt转化模型)。
示例2:使用裁剪后的full_api预测库运行mobilenetv1
1、执行第二步编译后,full_api的C++ 示例位于
/Paddle-Lite/build.lite.android.armv7.gcc/inference_lite_lib.android.armv7/demo/cxx/mobile_light
替换mobilenetv1_full_api.cc代码内容:
#include <gflags/gflags.h>
#include <stdio.h>
#include <vector>
#include "paddle_api.h" // NOLINT
using namespace paddle::lite_api; // NOLINT
DEFINE_string(model_dir, "", "Model dir path.");
int64_t ShapeProduction(const shape_t& shape) {
int64_t res = 1;
for (auto i : shape) res *= i;
return res;
}
void RunModel() {
// 1. Set CxxConfig
CxxConfig config;
config.set_model_file(FLAGS_model_dir + "model");
config.set_param_file(FLAGS_model_dir + "params");
std::vector<Place> valid_places{Place{TARGET(kARM), PRECISION(kFloat)}};
config.set_valid_places(valid_places);
// 2. Create PaddlePredictor by CxxConfig
std::shared_ptr<PaddlePredictor> predictor =
CreatePaddlePredictor<CxxConfig>(config);
// 3. Prepare input data
std::unique_ptr<Tensor> input_tensor(std::move(predictor->GetInput(0)));
input_tensor->Resize(shape_t({1, 3, 224, 224}));
auto* data = input_tensor->mutable_data<float>();
for (int i = 0; i < ShapeProduction(input_tensor->shape()); ++i) {
data[i] = 1;
}
// 4. Run predictor
predictor->Run();
// 5. Get output
std::unique_ptr<const Tensor> output_tensor(
std::move(predictor->GetOutput(0)));
printf("Output dim: %d\n", output_tensor->shape()[1]);
for (int i = 0; i < ShapeProduction(output_tensor->shape()); i += 100) {
printf("Output[%d]: %f\n", i, output_tensor->data<float>()[i]);
}
}
int main(int argc, char** argv) {
google::ParseCommandLineFlags(&argc, &argv, true);
RunModel();
return 0;
}
2、使用adb将mobilenetV1_PB模型和mobilenetv1_full_api传到手机后执行demo:
./mobilenetv1_full_api --model_dir=./mobilenetV1_PB
注意:mobilenetV1_PB
是用mobilenetV1
模型转化的protobuf格式模型(不需要设置--record_tailoring_info =true
,转化流程参考:使用opt转化模型)。
按模型集合裁剪预测库¶
为了方便用户使用,我们同时提供了按模型集合进行预测库裁剪的功能。用户可以提供一个模型集合,Model Optimize Tool会根据用户所指定的模型集合分析其优化后的模型所需要的算子信息对预测库进行裁剪。使用此功能用户根据自己的需要使用模型集合来对预测库中的算子进行任意裁剪。
使用方法如下所示:
# 非combined模型集合
./model_optimize_tool \
--model_set_dir=<your_model_set_dir> \
--optimize_out_type=naive_buffer \
--optimize_out=<output_model_set_dir> \
--record_tailoring_info=true \
--valid_targets=arm
# combined模型集合
./model_optimize_tool \
--model_set_dir=<your_model_set_dir> \
--optimize_out_type=naive_buffer \
--model_filename=<model_topo_filename> \
--param_filename=<model_param_filename> \
--optimize_out=<output_model_set_dir> \
--record_tailoring_info=true \
--valid_targets=arm
经过以上步骤后会在<output_model_set_dir>
中生成模型集合中各模型对应的NaiveBuffer格式的优化模型。此步会对模型集合中所需算子信息进行搜集并存储到<output_model_set_dir>
中。下一步编译预测库的流程与使用单模型进行预测库裁剪步骤相同。
注意:
- 模型集合必须均为combined参数模型或均为非combined参数模型。
- 使用非combined参数模型时,模型拓扑文件名应为
__model__
,使用非combined参数模型时,集合中各模型的拓扑与参数名应相同,分别由--model_filename
和--param_filename
指定。 - 模型集合必须均为INT8量化模型或均为非INT8量化模型。
- 需要使用Paddle-Lite
release/v2.1.0
之后版本代码编译出的模型优化工具。
C++ Demo¶
1. 下载最新版本预测库¶
预测库下载界面位于Paddle-Lite官方预编译库,可根据需求选择合适版本。
以Android-ARMv8架构为例,可以下载以下版本:
ARM Version | build_extra | arm_stl | target | 下载 |
---|---|---|---|---|
armv8 | OFF | c++_static | tiny_publish | release/v2.3 |
解压后内容如下图所示:
image
2. 转化模型¶
PaddlePaddle的原生模型需要经过opt工具转化为Paddle-Lite可以支持的naive_buffer格式。
以mobilenet_v1
模型为例:
(1)下载mobilenet_v1模型后解压:
wget http://paddle-inference-dist.bj.bcebos.com/mobilenet_v1.tar.gz
tar zxf mobilenet_v1.tar.gz
如下图所示:
image
(2)下载opt工具。放入同一文件夹,终端输入命令转化模型:
wget https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.3.0/opt
chmod +x opt
./opt --model_dir=./mobilenet_v1 --optimize_out_type=naive_buffer --optimize_out=./mobilenet_v1_opt
结果如下图所示:
image
3. 编写预测程序¶
准备好预测库和模型,我们便可以编写程序来执行预测。我们提供涵盖图像分类、目标检测等多种应用场景的C++示例demo可供参考,位于inference_lite_lib.android.armv8/demo/cxx
。
以mobile net_v1预测为例:mobile_light
为mobilenet_v1预测示例,可以直接调用。
示例如下图所示:
image
4. 编译¶
预测程序需要编译为Android可执行文件。
以mobilenet_v1模型为例,C++示例位于inference_lite_lib.android.armv8/demo/mobile_light
cd inference_lite_lib.android.armv8/demo/mobile_light
编译demo
make
结果如下图所示:
image
5. 执行预测¶
通过adb工具将可执行文件推送到手机上执行预测
(1)保证电脑已经安装adb工具,手机以"USB调试"、"文件传输模式"连接到电脑。
adb deveices #查看adb设备是否已被识别
连接如下图所示:
image
(2)准备预测库、模型和预测文件
1、将模型、动态库和预测文件放入同一文件夹:
image
注意:动态预测库文件位于: inference_lite_lib.android.armv8/cxx/liblibpaddle_light_api_shared.so
2、文件推送到手机:
chmod +x mobilenetv1_light_api
adb push mobilenet_v1_opt.nb /data/local/tmp
adb push libpaddle_light_api_shared.so /data/local/tmp
adb push mobilenetv1_light_api /data/local/tmp
效果如下图所示:
image
(3)执行预测
adb shell 'cd /data/local/tmp && export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/data/local/tmp && mobilenetv1_light_api ./mobilenet_v1_opt.nb'
结果如下图所示:
image
上图的Output
为mobilenet_v1模型在全1输入时,得到的预测输出。至此,Paddle-Lite的C++ demo执行完毕。
注:如何在代码中使用 API¶
C++代码调用Paddle-Lite执行预测库仅需以下五步:
(1)引用头文件和命名空间
#include "paddle_api.h"
using namespace paddle::lite_api;
(2)指定模型文件,创建Predictor
// 1. Set MobileConfig, model_file_path is
// the path to model model file.
MobileConfig config;
config.set_model_from_file(model_file_path);
// 2. Create PaddlePredictor by MobileConfig
std::shared_ptr<PaddlePredictor> predictor =
CreatePaddlePredictor<MobileConfig>(config);
(3)设置模型输入 (下面以全一输入为例)
std::unique_ptr<Tensor> input_tensor(std::move(predictor->GetInput(0)));
input_tensor->Resize({1, 3, 224, 224});
auto* data = input_tensor->mutable_data<float>();
for (int i = 0; i < ShapeProduction(input_tensor->shape()); ++i) {
data[i] = 1;
}
(4)执行预测
predictor->Run();
(5)获得预测结果
std::unique_ptr<const Tensor> output_tensor(
std::move(predictor->GetOutput(0)));
// 转化为数据
auto output_data=output_tensor->data<float>();
其他cxx_demo的编译与预期结果¶
Light API Demo¶
cd ../mobile_light
make
adb push mobilenetv1_light_api /data/local/tmp/
adb shell chmod +x /data/local/tmp/mobilenetv1_light_api
adb shell "/data/local/tmp/mobilenetv1_light_api --model_dir=/data/local/tmp/mobilenet_v1.opt "
图像分类 Demo¶
cd ../mobile_classify
wget http://paddle-inference-dist.bj.bcebos.com/mobilenet_v1.tar.gz
tar zxvf mobilenet_v1.tar.gz
make
adb push mobile_classify /data/local/tmp/
adb push test.jpg /data/local/tmp/
adb push labels.txt /data/local/tmp/
adb push ../../../cxx/lib/libpaddle_light_api_shared.so /data/local/tmp/
adb shell chmod +x /data/local/tmp/mobile_classify
adb shell "export LD_LIBRARY_PATH=/data/local/tmp/:$LD_LIBRARY_PATH && /data/local/tmp/mobile_classify /data/local/tmp/mobilenet_v1.opt /data/local/tmp/test.jpg /data/local/tmp/labels.txt"
目标检测 Demo¶
cd ../mobile_detection
wget https://paddle-inference-dist.bj.bcebos.com/mobilenetv1-ssd.tar.gz
tar zxvf mobilenetv1-ssd.tar.gz
make
adb push mobile_detection /data/local/tmp/
adb push test.jpg /data/local/tmp/
adb push ../../../cxx/lib/libpaddle_light_api_shared.so /data/local/tmp/
adb shell chmod +x /data/local/tmp/mobile_detection
adb shell "export LD_LIBRARY_PATH=/data/local/tmp/:$LD_LIBRARY_PATH && /data/local/tmp/mobile_detection /data/local/tmp/mobilenetv1-ssd /data/local/tmp/test.jpg"
adb pull /data/local/tmp/test_detection_result.jpg ./
light API Demo 运行结果¶
运行成功后 ,将在控制台输出预测结果的前10个类别的预测概率:
Output dim: 1000
Output[0]: 0.000191
Output[100]: 0.000160
Output[200]: 0.000264
Output[300]: 0.000211
Output[400]: 0.001032
Output[500]: 0.000110
Output[600]: 0.004829
Output[700]: 0.001845
Output[800]: 0.000202
Output[900]: 0.000586
图像分类 Demo 运行结果¶
运行成功后 ,将在控制台输出预测结果的前5个类别的类型索引、名字和预测概率:
parameter: model_dir, image_path and label_file are necessary
parameter: topk, input_width, input_height, are optional
i: 0, index: 285, name: Egyptian cat, score: 0.482870
i: 1, index: 281, name: tabby, tabby cat, score: 0.471593
i: 2, index: 282, name: tiger cat, score: 0.039779
i: 3, index: 287, name: lynx, catamount, score: 0.002430
i: 4, index: 722, name: ping-pong ball, score: 0.000508
目标检测 Demo 运行结果¶
运行成功后 ,将在控制台输出检测目标的类型、预测概率和坐标:
running result:
detection image size: 935, 1241, detect object: person, score: 0.996098, location: x=187, y=43, width=540, height=592
detection image size: 935, 1241, detect object: person, score: 0.935293, location: x=123, y=639, width=579, height=597
Java Demo¶
本节中,Java demo 完整代码位于 demo/java 。
要编译和跑起Android demo 程序 PaddlePredictor,你需要准备:
- 一台能运行安卓程序的安卓手机
- 一台带有AndroidStudio的开发机
编译¶
首先在PaddleLite的开发 Docker镜像 中,拉取最新PaddleLite代码,编译对应你手机架构的预测库, 下面我们以arm8 架构举例。进入paddlelite 目录,运行以下命令:
./lite/tools/build.sh \
--arm_os=android \
--arm_abi=armv8 \
--arm_lang=gcc \
--android_stl=c++_static \
tiny_publish
命令完成后查看要存在
./build.lite.android.armv8.gcc/inference_lite_lib.android.armv8/java/so/libpaddle_lite_jni.so
./build.lite.android.armv8.gcc/inference_lite_lib.android.armv8/java/jar/PaddlePredictor.jar
./build.lite.android.armv8.gcc/inference_lite_lib.android.armv8/demo/java/android
libpaddle_lite_jni.so为 PaddleLite c++ 动态链接库,PaddlePredictor.jar为 Java jar 包,两者包含 PaddleLite Java API,接下来 Android Java 代码会使用这些api。android文件夹中则是Android demo。
准备 demo 需要的其他文件¶
Demo 除了代码,还需要准备在Android工程目录下配置好JNI .so 库(上节提到的libpaddle_lite_jni.so
),Java .jar 包(上文提到的PaddlePredictor.jar
),和模型文件。我们提供了自动化的脚本和手动拷贝两种方法,用户可以根据自己需要选择:
脚本方法¶
进入 build.lite.android.armv8.gcc/inference_lite_lib.android.armv8/demo/java/android
,我们准备了一个脚本prepare_demo.bash
,脚本输入一个参数,为你要拷贝的.so 对应的架构文件夹名。
例如运行
bash prepare_demo.bash arm8
该脚本自动下载并解压缩模型文件,拷贝了 .jar 包进demo,还有生成的.so包进PaddlePredictor/app/src/main/jinLibs/架构文件夹下
,
在我们这个例子里,armv8 就是架构文件夹。备注:这种方式构建的 demo 在 armv8 手机运行正常。如果要demo 程序在别的手机架构(如 armv7)上也运行正常,需要添加别的架构。
手动拷贝方法¶
接下来我们介绍手动拷贝,如果使用了脚本,那么可以跳过以下手动方法的介绍。
把 .so 动态库和 .jar 拷贝进安卓demo程序:¶
- 将PaddlePredictor 载入到AndroidStudio。
- 将
libpaddle_lite_jni.so
拷贝进PaddlePredictor/app/src/main/jinLibs/架构文件夹下
,比如文件夹arm8里要包含该 .so文件。 - 将
PaddlePredictor.jar
拷贝进PaddlePredictor/app/libs
下
把demo使用到的模型文件拷贝进安卓程序:¶
下载我们的5个模型文件,并解压缩到 PaddlePredictor/app/src/main/assets
这个文件夹中
需要拷贝的模型文件和下载地址:
inception_v4_simple_opt.nb http://paddle-inference-dist.bj.bcebos.com/inception_v4_simple_opt.nb.tar.gz
lite_naive_model_opt.nb http://paddle-inference-dist.bj.bcebos.com/lite_naive_model_opt.nb.tar.gz
mobilenet_v1_opt.nb http://paddle-inference-dist.bj.bcebos.com/mobilenet_v1_opt.nb.tar.gz
mobilenet_v2_relu_opt.nb http://paddle-inference-dist.bj.bcebos.com/mobilenet_v2_relu_opt.nb.tar.gz
resnet50_opt.nb http://paddle-inference-dist.bj.bcebos.com/resnet50_opt.nb.tar.gz
下载完后,assets文件夹里要包含解压后的上面五个模型文件夹,但demo里不需要保存原压缩.tar.gz 文件。
注意:输入的模型要求为naive buffer存储格式,您可以通过 Model Optimize Tool 将fluid模型转为naive buffer存储格式。
运行 Android 程序结果¶
以上准备工作完成,就可以开始Build 、安装、和运行安卓demo程序。当你运行PaddlePredictor 程序时,大概会等10秒,然后看到类似以下字样:
lite_naive_model output: 50.213173, -28.872887
expected: 50.2132, -28.8729
inception_v4_simple test:true
time: xxx ms
resnet50 test:true
time: xxx ms
mobilenet_v1 test:true
time: xxx ms
mobilenet_v2 test:true
time: xxx ms
该 demo 程序跑我们的 5 个模型,第一个模型结果将真正的头两个数字输出,并在第二行附上期望的正确值。你应该要看到他们的误差小于0.001。后面四个模型如果你看到 test:true
字样,说明模型输出通过了我们在 demo 程序里对其输出的测试。time 代表该测试花费的时间。
Android Demo¶
多种应用场景¶
我们提供的Paddle-Lite示例工程Paddle-Lite-Demo,其中包含Android、iOS和Armlinux平台的示例工程。涵盖人脸识别、人像分割、图像分类、目标检测4个应用场景。
2. 人像分割¶
人像分割是Paddle-Lite 提供的图像分割demo ,在移动端上提供了实时的人像分割能力,可以应用证件照自动抠图、面积测量、智能交通(标记车道和交通标志)等场景。 在移动端预测的效果图如下:
3. 图像分类¶
图像分类是Paddle-Lite 提供的图像处理demo ,在移动端上提供了实时的物体识别能力,可以应用到生产线自动分拣或质检、识别医疗图像、辅助医生肉眼诊断等场景。在移动端预测的效果图如下:
4. 物体检测¶
物体检测是Paddle-Lite 提供的图像识别demo ,在移动端上提供了检测多个物体的位置、名称、位置及数量的能力。可以应用到视频监控(是否有违规物体或行为)、工业质检(微小瑕疵的数量和位置)、医疗诊断(细胞计数、中药识别)等场景。在移动端预测的效果图如下:
Android demo部署方法¶
下面我们以 目标检测示例(object_detection_demo) 为例讲解如何部署。
目的:将基于Paddle-Lite预测库的Android APP 部署到手机,实现物体检测
需要的环境: Android Studio、Android手机(开启USB调试模式)、下载到本地的Paddle-Lite-Demo工程
部署步骤:
1、 目标检测的Android示例位于 Paddle-Lite-Demo\PaddleLite-android-demo\object_detection_demo
2、用Android Studio 打开object_detection_demo工程 (本步骤需要联网)。
3、手机连接电脑,打开USB调试和文件传输模式,在Android Studio上连接自己的手机设备(手机需要开启允许从 USB安装软件权限)
Android_studio
4、按下 Run按钮,自动编译APP并安装到手机。(该过程会自动下载Paddle-Lite预测库和模型,需要联网)
成功后效果如下,图一:APP安装到手机 图二: APP打开后的效果,会自动识别图片中的物体并标记
Android demo结构讲解¶
Android 示例的代码结构如下图所示:
1、 Predictor.java: 预测代码
# 位置:
object_detection_demo/app/src/main/java/com/baidu/paddle/lite/demo/object_detection/Predictor.java
2、 model.nb : 模型文件 (opt 工具转化后Paddle-Lite模型);pascalvoc_label_list:训练模型时的labels
文件
# 位置:
object_detection_demo/app/src/main/assets/models/ssd_mobilenet_v1_pascalvoc_for_cpu/model.nb
object_detection_demo/app/src/main/assets/labels/pascalvoc_label_list
3、 libpaddle_lite_jni.so、PaddlePredictor.jar:Paddle-Lite Java 预测库与Jar包
# 位置
object_detection_demo/app/src/main/jniLibs/arm64-v8a/libpaddle_lite_jni.so
object_detection_demo/app/libs/PaddlePredictor.jar
4、 build.gradle : 定义编译过程的 gradle 脚本。(不用改动,定义了自动下载Paddle-Lite预测和模型的过程)
# 位置
object_detection_demo/app/build.gradle
代码讲解 (使用Paddle-Lite Java API 执行预测)¶
Android 示例基于Java API 开发,调用Paddle-Lite Java API包括以下五步。更详细的API 描述参考: Paddle-Lite Java API。
// 导入Java API
import com.baidu.paddle.lite.MobileConfig;
import com.baidu.paddle.lite.Tensor;
import com.baidu.paddle.lite.Predictor;
import com.baidu.paddle.lite.PowerMode;
// 1. 写入配置:设置MobileConfig
MobileConfig config = new MobileConfig();
config.setModelFromFile(<modelPath>); // 设置Paddle-Lite模型路径
config.setPowerMode(PowerMode.LITE_POWER_NO_BIND); // 设置CPU运行模式
config.setThreads(4); // 设置工作线程数
// 2. 创建 PaddlePredictor
PaddlePredictor predictor = PaddlePredictor.createPaddlePredictor(config);
// 3. 设置输入数据
long[] dims = {100, 100};
float[] inputBuffer = new float[10000];
for (int i = 0; i < 10000; ++i) {
inputBuffer[i] = i;
}
Tensor input = predictor.getInput(0);
input.resize(dims);
input.setData(inputBuffer);
// 4. 执行预测
predictor.run();
// 5. 获取输出数据
Tensor result = predictor.getOutput(0);
float[] output = result.getFloatData();
for (int i = 0; i < 1000; ++i) {
System.out.println(output[i]);
}
iOS Demo¶
多种应用场景¶
我们提供Paddle-Lite示例工程Paddle-Lite-Demo,其中包含Android、iOS和Armlinux平台的示例工程。iOS demo涵盖图像分类、目标检测2个应用场景。
1. 图像分类¶
图像分类是Paddle-Lite 提供的图像处理demo ,在移动端上提供了实时的物体识别能力,可以应用到生产线自动分拣或质检、识别医疗图像、辅助医生肉眼诊断等场景。在移动端预测的效果图如下:
2. 物体检测¶
物体检测是Paddle-Lite 提供的图像识别demo ,在移动端上提供了检测多个物体的位置、名称、位置及数量的能力。可以应用到视频监控(是否有违规物体或行为)、工业质检(微小瑕疵的数量和位置)、医疗诊断(细胞计数、中药识别)等场景。在移动端预测的效果图如下:
iOS demo部署方法¶
下面我们以**目标检测(object_detection_demo)**为例讲解如何部署iOS工程。
目的:将基于Paddle-Lite预测库的iOS APP部署到苹果手机,实现物体检测。
需要的环境:Mac 电脑上安装Xcode、苹果手机、下载到本地的Paddle-Lite-Demo工程
部署步骤:
1、 目标检测的iOS示例位于 Paddle-Lite-Demo\PaddleLite-ios-demo\object_detection_demo
2、终端中执行 download_dependencies.sh
脚本自动下载模型和Paddle-Lite预测库
cd PaddleLite-ios-demo # 1. 终端中进入 Paddle-Lite-Demo\PaddleLite-ios-demo
sh download_dependencies.sh # 2. 执行脚本下载依赖项 (需要联网)
下载完成后会出现提示: Extract done
3、用Xcode打开object_detection_demo/detection_demo.xcodeproj
文件,修改工程配置。
依次修改 General/Identity
和Signing&Capabilities
属性,替换为自己的工程代号和团队名称。(必须修改,不然无法通过编译)
Xcode1
Xcode2
4、 IPhone手机连接电脑,在Xcode中连接自己的手机 (第一次连接IPhone到电脑时,需要在IPhone的设置->通用->设备管理
中选择本电脑并信任)
5、按下左上角的 Run按钮,自动编译APP并安装到手机。在苹果手机中设置信任该APP(进入设置->通用->设备管理
,选中新安装的APP并验证该应用
)
成功后效果如下,图一:APP安装到手机 图二: APP打开后的效果,会自动识别图片中的物体并标记
iOS demo结构讲解¶
iOS 示例的代码结构如下图所示:
1、 mobilenetv1-ssd: 模型文件 (opt 工具转化后Paddle-Lite模型)
# 位置:
ios-detection_demo/detection_demo/models/mobilenetv1-ssd
2、 libpaddle_api_light_bundled.a、paddle_api.h : Paddle-Lite C++ 预测库和头文件
# 位置:
# iOS预测库
ios-detection_demo/detection_demo/lib/libpaddle_api_light_bundled.a
# 预测库头文件
ios-detection_demo/detection_demo/include/paddle_api.h
ios-detection_demo/detection_demo/include/paddle_use_kernels.h
ios-detection_demo/detection_demo/include/paddle_use_ops.h
3、 ViewController.mm:主要预测代码
# 位置
ios-detection_demo/detection_demo/ViewController.mm
代码讲解 (如何使用Paddle-Lite C++ API 执行预测)¶
IOS 示例基于C++ API 开发,调用Paddle-Lite C++ API包括以下五步。更详细的API 描述参考: Paddle-Lite C++ API。
#include <iostream>
// 引入C++ API
#include "paddle_lite/paddle_api.h"
#include "paddle_lite/paddle_use_ops.h"
#include "paddle_lite/paddle_use_kernels.h"
// 1. 设置MobileConfig
MobileConfig config;
config.set_model_from_file(<modelPath>); // 设置NaiveBuffer格式模型路径
config.set_power_mode(LITE_POWER_NO_BIND); // 设置CPU运行模式
config.set_threads(4); // 设置工作线程数
// 2. 创建PaddlePredictor
std::shared_ptr<PaddlePredictor> predictor = CreatePaddlePredictor<MobileConfig>(config);
// 3. 设置输入数据
std::unique_ptr<Tensor> input_tensor(std::move(predictor->GetInput(0)));
input_tensor->Resize({1, 3, 224, 224});
auto* data = input_tensor->mutable_data<float>();
for (int i = 0; i < ShapeProduction(input_tensor->shape()); ++i) {
data[i] = 1;
}
// 4. 执行预测
predictor->run();
// 5. 获取输出数据
std::unique_ptr<const Tensor> output_tensor(std::move(predictor->GetOutput(0)));
std::cout << "Output shape " << output_tensor->shape()[1] << std::endl;
for (int i = 0; i < ShapeProduction(output_tensor->shape()); i += 100) {
std::cout << "Output[" << i << "]: " << output_tensor->data<float>()[i]
<< std::endl;
}
PaddleLite使用X86预测部署¶
Paddle-Lite 支持在Docker或Linux环境编译x86预测库。环境搭建参考环境准备。
编译¶
1、 下载代码
# 下载Paddle-Lite源码
git clone https://github.com/PaddlePaddle/Paddle-Lite.git
# 切换到release分支
git checkout release/v2.6.0
2、 源码编译
cd Paddle-Lite
./lite/tools/build.sh x86
# 其他可选择编译选项
# --with_log=OFF 关闭LOG信息输出
编译结果说明¶
x86编译结果位于 build.lite.x86/inference_lite_lib
具体内容说明:
1、 bin
文件夹:可执行工具文件 test_model_bin
2、 cxx
文件夹:包含c++的库文件与相应的头文件
include
: 头文件lib
: 库文件- 静态库文件:
libpaddle_api_full_bundled.a
:full_api 静态库libpaddle_api_light_bundled.a
:light_api 静态库
- 动态库文件:
libpaddle_full_api_shared.so
:full_api 动态库libpaddle_light_api_shared.so
:light_api 动态库
- 静态库文件:
3、 third_party
文件夹:依赖的第三方预测库mklml
- mklml : Paddle-Lite预测库依赖的mklml数学库
4、 demo/cxx
文件夹:x86预测库的C++ 示例demo
mobilenetv1_full
:使用full_api 执行mobilenet_v1预测的C++ demomobilenetv1_light
:使用light_api 执行mobilenet_v1预测的C++ demo
x86预测API使用示例¶
1、mobilenetv1_full
目录结构
mobilenetv1_full/
|-- CMakeLists.txt
|-- build.sh
`-- mobilenet_full_api.cc
本demo使用cmake构建CMakeLists.txt
为cmake脚本,mobilenet_full_api.cc
是x86示例的源代码、build.sh
为编译的脚本。
2、demo使用方法
# 1、编译
cd mobilenetv1_full
sh build.sh
编译结果为当前目录下的 mobilenet_full_api
# 2、执行预测
./mobilenet_full_api ./mobilenet_v1
下载并解压模型mobilenet_v1
到当前目录,执行以上命令进行预测。
# 3、执行demo后输出结果如下,全一输入下mobilenet_v1的预测结果
Output shape 1000
Output[0]: 0.000191312
Output[100]: 0.000159713
Output[200]: 0.000264313
Output[300]: 0.000210793
Output[400]: 0.00103236
Output[500]: 0.000110071
Output[600]: 0.00482924
Output[700]: 0.00184533
Output[800]: 0.000202116
Output[900]: 0.000585591
3、示例源码mobilenet_full_api.cc
#include <iostream>
#include <vector>
#include "paddle_api.h"
using namespace paddle::lite_api; // NOLINT
int64_t ShapeProduction(const shape_t& shape) {
int64_t res = 1;
for (auto i : shape) res *= i;
return res;
}
void RunModel(std::string model_dir) {
// 1. Create CxxConfig
CxxConfig config;
config.set_model_dir(model_dir);
config.set_valid_places({
Place{TARGET(kX86), PRECISION(kFloat)},
Place{TARGET(kHost), PRECISION(kFloat)}
});
// 2. Create PaddlePredictor by CxxConfig
std::shared_ptr<PaddlePredictor> predictor =
CreatePaddlePredictor<CxxConfig>(config);
// 3. Prepare input data
std::unique_ptr<Tensor> input_tensor(std::move(predictor->GetInput(0)));
input_tensor->Resize({1, 3, 224, 224});
auto* data = input_tensor->mutable_data<float>();
for (int i = 0; i < ShapeProduction(input_tensor->shape()); ++i) {
data[i] = 1;
}
// 4. Run predictor
predictor->Run();
// 5. Get output
std::unique_ptr<const Tensor> output_tensor(
std::move(predictor->GetOutput(0)));
std::cout << "Output shape " << output_tensor->shape()[1] << std::endl;
for (int i = 0; i < ShapeProduction(output_tensor->shape()); i += 100) {
std::cout << "Output[" << i << "]: " << output_tensor->data<float>()[i]
<< std::endl;
}
}
int main(int argc, char** argv) {
if (argc < 2) {
std::cerr << "[ERROR] usage: ./" << argv[0] << " naive_buffer_model_dir\n";
exit(1);
}
std::string model_dir = argv[1];
RunModel(model_dir);
return 0;
}
PaddleLite使用CUDA预测部署¶
Lite支持在x86_64,arm64架构上(如:TX2)进行CUDA的编译运行。
编译¶
NOTE: 如果是在TX2等NVIDIA嵌入式硬件上编译,请使用最新的Jetpack 安装依赖库。
一: 下载代码
git clone https://github.com/PaddlePaddle/Paddle-Lite.git
二:编译
# 进入代码目录
cd Paddle-Lite
# 运行编译脚本
# 编译结束会在本目录下生成 build_cuda 目录
# 编译过程中如果提示找不到CUDA,CUDNN,请在环境变量设置CUDA_TOOLKIT_ROOT_DIR, CUDNN_ROOT
# CUDA_TOOLKIT_ROOT_DIR,CUDNN_ROOT分别表示CUDA,CUDNN的根目录
./lite/tools/build.sh cuda
# 如果使用python接口,需要打开build_python选项
./lite/tools/build.sh --build_python=ON cuda
编译结果说明¶
cuda的编译结果位于 build_cuda/inference_lite_lib
具体内容说明:
1、 bin
文件夹:可执行工具文件,目前为空
2、 cxx
文件夹:包含c++的库文件与相应的头文件
include
: 头文件lib
: 库文件- 打包的静态库文件:
libpaddle_api_full_bundled.a
:包含 full_api 和 light_api 功能的静态库
- 打包的动态态库文件:
libpaddle_full_api_shared.so
:包含 full_api 和 light_api 功能的动态库
- 打包的静态库文件:
3、 third_party
文件夹:第三方库文件
4、 demo
文件夹:c++ demo.
如果编译打开了python选项,则会在 build_cuda/inference_lite_lib/python/lib/
目录下生成 lite.so
。
运行¶
以下以Yolov3模型为例,介绍如何在Nvidia GPU硬件上运行模型。
一: 下载darknet_yolov3模型,模型信息请参考这里
# 下载模型
wget https://paddle-inference-dist.cdn.bcebos.com/PaddleLite/yolov3_infer.tar.gz
tar -zxf yolov3_infer.tar.gz
# 下载图片样例
wget https://paddle-inference-dist.cdn.bcebos.com/PaddleLite/kite.jpg
二: 运行
NOTE: 此处示例使用的是python接口。
#-*- coding: utf-8 -*-
from __future__ import print_function
import sys
import numpy as np
import cv2
sys.path.append('build_cuda/inference_lite_lib/python/lib')
from lite import *
def read_img(im_path, resize_h, resize_w):
im = cv2.imread(im_path).astype('float32')
im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
h, w, _ = im.shape
im_scale_x = resize_h / float(w)
im_scale_y = resize_w / float(h)
out_img = cv2.resize(im, None, None, fx=im_scale_x, fy=im_scale_y, interpolation=cv2.INTER_CUBIC)
mean = np.array([0.485, 0.456, 0.406]).reshape((1, 1, -1))
std = np.array([0.229, 0.224, 0.225]).reshape((1, 1, -1))
out_img = (out_img / 255.0 - mean) / std
out_img = out_img.transpose((2, 0, 1))
return out_img
# 配置config
a = CxxConfig()
a.set_model_file('./yolov3_infer/__model__') # 指定模型文件路径
a.set_param_file('./yolov3_infer/__params__') # 指定参数文件路径
place_cuda = Place(TargetType.CUDA)
a.set_valid_places([place_cuda])
# 创建predictor
predictor = create_paddle_predictor(a)
# 设置输入
input_tensor = predictor.get_input(0);
height, width = 608, 608
input_tensor.resize([1, 3, height, width])
data = read_img('./kite.jpg', height, width).flatten()
input_tensor.set_float_data(data, TargetType.CUDA)
in2 = predictor.get_input(1);
in2.resize([1, 2])
in2.set_int32_data([height, width], TargetType.CUDA)
# 运行
predictor.run()
# 获取输出
output_tensor = predictor.get_output(0);
print (output_tensor.shape())
# [100L, 6L]
print (output_tensor.target())
# TargetType.Host
print (output_tensor.float_data()[:6])
# [0.0, 0.9862784743309021, 98.51927185058594, 471.2381286621094, 120.73092651367188, 578.33251953125]
NOTE: 此处示例使用的是C++接口。
cd build_cuda/inference_lite_lib/demo/cxx/
mkdir build && cd build
cmake ..
make
wget https://paddle-inference-dist.cdn.bcebos.com/PaddleLite/yolov3_infer.tar.gz
tar -zxf yolov3_infer.tar.gz
./demo yolov3_infer
PaddleLite使用OpenCL预测部署¶
Lite支持在Android系统上运行基于OpenCL的程序,目前支持Ubuntu环境下armv8、armv7的交叉编译。
编译¶
编译Paddle-Lite OpenCL库范例¶
注:以android-armv8-opencl的目标、Docker容器的编译开发环境为例,CMake3.10,android-ndk-r17c位于/opt/
目录下。
针对 Lite 用户的编译命令(无单元测试,有编译产物)¶
arm_os
:[android]
,目前不支持linux;arm_abi
:[armv7 | armv8]
;arm_lang
:[gcc]
,目前不支持clang;build_extra
:[OFF | ON]
,编译全量op和kernel,体积会大,编译时间长;build_cv
:[OFF | ON]
,编译arm cpu neon实现的的cv预处理模块;android_stl
:[c++_shared | c++_static]
,paddlelite的库以何种方式链接android_stl
,选择c++_shared
得到的动态库体积更小,但使用时候记得上传paddlelite所编译版本(armv7或armv8)一致的libc++_shared.so
(来自Android-NDK); 注:调用./lite/tools/build.sh
执行编译。
# 假设当前位于处于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/build.sh \
--arm_os=android \
--arm_abi=armv8 \
--arm_lang=gcc \
--build_extra=OFF \
--build_cv=OFF \
--android_stl=c++_shared \
opencl
针对 Lite 开发者的编译命令(有单元测试,编译产物)¶
注:调用./lite/tools/ci_build.sh
执行编译,该命令会编译armv7和armv8的opencl库。虽然有编译产物,但因编译单元测试,编译产物包体积可能较大,不推荐使用。
# 假设当前位于处于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_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 中或者对应单测可执行文件中。
编译产物说明¶
编译产物位于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
实现的相关kernel已经打包到动态库中。
.
|-- 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
调用libpaddle_api_full_bundled.a
和libpaddle_api_light_bundled.a
见下一部分运行示例。
运行示例¶
下面以android、ARMv8、gcc的环境为例,介绍3个示例,分别如何在手机上执行基于OpenCL的ARM GPU推理过程。
运行示例1: 编译产物demo示例¶
######################################################################
# 编译mobile_light的demo #
######################################################################
# 步骤: #
# 0.确保编译Paddle-Lite时编译了OpenCL; #
# 1.编译model_optimize_tool并对模型优化, `targets`参数为`opencl`; #
# 2.在产物目录`demo/cxx/mobile_light`下编译`mobile_light`的demo; #
# 3.上传demo, 模型文件到手机; #
# 4.运行demo得到预期结果. #
######################################################################
# 在/data/local/tmp目录下创建OpenCL文件目录
adb shell mkdir -p /data/local/tmp/opencl
# 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/mobilenetv1_opt \
--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/mobilenetv1_opt.nb /data/local/tmp/opencl/
# use mobile_light run mobilenet_v1
adb shell "export GLOG_v=1; \
/data/local/tmp/opencl/mobilenetv1_light_api \
/data/local/tmp/opencl/mobilenetv1_opt.nb"
注: GLOG_v
是指定需要显示VLOG的日志级别,默认为0。权重参数会在第一次运行时加载,所以第一次执行时间略长。一般将warmup的值设为10,repeats值设为多次。
运行示例2: test_mobilenetv1单元测试¶
- 运行文件准备
# 在/data/local/tmp目录下创建OpenCL文件目录
adb shell mkdir -p /data/local/tmp/opencl
# 将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推理过程
adb shell chmod +x /data/local/tmp/opencl/test_mobilenetv1
adb shell "export GLOG_v=1; \
/data/local/tmp/opencl-image/test_mobilenetv1 \
--model_dir=/data/local/tmp/opencl-image/mobilenetv1_fluid/ \
--warmup=10 \
--repeats=100"
运行示例3: test_layout_opencl单元测试¶
adb shell mkdir -p /data/local/tmp/opencl
adb shell chmod +x /data/local/tmp/opencl/test_layout_opencl
adb shell "export GLOG_v=4; \
/data/local/tmp/opencl/test_layout_opencl"
如何在Code中使用¶
见运行示例1的demo代码:
- ./lite/demo/cxx/mobile_light/mobilenetv1_light_api.cc;
- ./lite/demo/cxx/mobile_full/mobilenetv1_full_api.cc.
注:这里给出的链接会跳转到线上最新develop分支的代码,很可能与您本地的代码存在差异,建议参考自己本地位于lite/demo/cxx/
目录的代码,查看如何使用。
NOTE: 对OpenCL的支持还在持续开发中。
PaddleLite使用FPGA预测部署¶
Paddle Lite支持基于arm的FPGA zu3/zu5/zu9的模型预测,提供armv8的交叉编译
Lite基于FPGA运行模型需要相应的FPGA驱动,目前只支持百度Edgeboard开发板
Lite实现FPGA简介¶
Lite支持FPGA作为后端硬件进行模型推理,其主要特性如下:
- Lite中FPGA的kernel(feed、fetch除外)均以FP16、NHWC的格式作为输入输出格式,所有的weights和bias仍为FP32、NCHW的格式,feed的输入和fetch的输出均为FP32、NCHW格式的数据,在提升计算速度的同时能做到用户对数据格式无感知
- 对于FPGA暂不支持的kernel,均会切回arm端运行,实现arm+FPGA混合布署运行
- 目前FPGA成本功耗都较低,Lite基于FPGA的模型性能远远好于arm端,可作为边缘设备首选硬件
编译¶
需要提前准备带有FPGAdrv.ko的FPGA开发板(如edgeboard开发板)和Lite代码
CMAKE编译选项:
- 设置
LITE_WITH_FPGA=ON
和LITE_WITH_ARM=ON
其他编译选项与ARM编译相同,可以参考“Paddle Lite在Docker下的ARM编译”。 示例如下:
cmake .. \
-DWITH_GPU=OFF \
-DWITH_MKL=OFF \
-DWITH_LITE=ON \
-DLITE_WITH_CUDA=OFF \
-DLITE_WITH_X86=OFF \
-DLITE_WITH_ARM=ON \
-DLITE_WITH_OPENMP=ON \
-DLITE_WITH_LIGHT_WEIGHT_FRAMEWORK=ON \
-DWITH_TESTING=OFF \
-DLITE_WITH_FPGA=ON \
-DARM_TARGET_OS=armlinux
make publish_inference -j2
Lite提供FPGA编译脚本,位于lite/tools/build_FPGA.sh,在Lite根目录执行该脚本即可编译
运行示例¶
- 运行文件准备
下面以Resnet50模型为例,介绍如何使用edgeboard开发板实现模型运行
#连接开发板,并利用screen命令启动 [本机执行]
screen /dev/cu.SLAB_USBtoUART 115200
#查看开发板ip并ssh登录到开发板,假设开发板ip为192.0.1.1 [本机执行]
ssh root@192.0.1.1
#在开发板上建立目录workspace,拷贝FPGA驱动FPGAdrv.ko到workspace目录 [开发板执行]
mkdir workspace && scp $DRIVER_PATH/FPGAdrv.ko workspace
#将Lite中编译好的测试程序拷贝到开发板workspace目录 [本机执行]
scp $LITE_ROOT/build_FPGA/lite/api/test_resnet50_FPGA root@$EDGEBOARD_IP:workspace/
#把Resnet50的模型和参数scp到开发板workspace目录 [本机执行]
scp -r $LITE_ROOT/build_FPGA/lite/third_party/install/resnet50/ root@$EDGEBOARD_IP:workspace/
#在运行模型前需要加载FPGA驱动 [开发板执行]
insmod FPGAdrv.ko
#给测试程序添加可运行权限 [开发板执行]
chmod +x test_resnet50_FPGA
- 使用FPGA进行模型预测
#以下命令均在开发板上运行
#直接运行单测程序
./test_resnet50_FPGA --model_dir=resnet50
#如果需要测试性能,可以用repeats参数设置模型运行次数(如1000),同时可以设置预热次数(如10)来让硬件事先运行到稳定水平
./test_resnet50_FPGA --model_dir=resnet50 --repeats=1000 --warmup=10
如何在Code中使用¶
在Lite中使用FPGA与ARM相似,具体的区别如下:
- 由于fpga运行模式为fp16精度、nhwc布局,所以需要修改相应的
valid_place
- fpga不需要device的初始化和运行模式设置
代码示例:
lite::Predictor predictor;
std::vector<Place> valid_places(
{Place{TARGET(kFPGA), PRECISION(kFP16), DATALAYOUT(kNHWC)},Place{TARGET(kARM)});
predictor.Build(model_dir, "", "", valid_places);
auto* input_tensor = predictor.GetInput(0);
input_tensor->Resize(DDim(std::vector<DDim::value_type>({1, 3, 224, 224})));
auto* data = input_tensor->mutable_data<float>();
auto item_size = input_tensor->dims().production();
//假设设置输入数据全为1
for (int i = 0; i < item_size; i++) {
data[i] = 1;
}
predictor.Run();
auto* out = predictor.GetOutput(0);
PaddleLite使用NPU(华为)预测部署¶
Paddle Lite是首款支持华为自研达芬奇架构NPU(Kirin 810/990 SoC搭载的NPU)的预测框架。 原理是在线分析Paddle模型,将Paddle算子转成HiAI IR后,调用HiAI IR/Builder/Runtime APIs生成并执行HiAI模型。
已支持的设备¶
- 华为nova5、nova5i pro、mate30、mate30 pro、mate30 5G、荣耀v30,以及即将推出的mate40、p40。据华为透露,今后上市的大部分手机都会搭载其自研达芬奇架构NPU。
已支持的模型¶
- MobileNetV1
- MobileNetV2
- ResNet-18/50
- ShuffleNetV2
- CycleGAN (暂时需要华为内部rom的支持)
- 百度内部业务模型(由于涉密,不方便透露具体细节)
已支持(或部分支持)的Paddle算子¶
- sigmoid
- relu
- tanh
- relu_clipped
- leaky_relu
- softsign
- hard_sigmoid
- batch_norm
- concat
- conv2d
- depthwise_conv2d
- conv2d_transpose
- dropout
- elementwise_add
- elementwise_sub
- elementwise_mul
- elementwise_div
- fusion_elementwise_add_activation
- fusion_elementwise_sub_activation
- fusion_elementwise_mul_activation
- fusion_elementwise_div_activation
- fc
- bilinear_interp
- nearest_interp
- matmul
- mul
- pad2d
- pool2d
- reduce_mean
- reshape
- reshape2
- scale
- shuffle_channel
- softmax
- split
- sqrt
- square
- transpose
- transpose2
- unsqueeze
- unsqueeze2
- instance_norm (暂时需要华为内部rom的支持)
- layer_norm (暂时需要华为内部rom的支持)
编译支持NPU的Paddle Lite库¶
- 从https://developer.huawei.com/consumer/cn/hiai/下载华为HiAI DDK后解压到任意路径(注意:华为提供了多个版本的DDK,我们需要下载针对麒麟810/990芯片HiAI Foundation开发套件,例如最新的DDK V310版本)。
- 将HiAI DDK中的ai_ddk_lib目录拷贝至Paddle Lite源码根目录后,使用NPU编译脚本编译full_publish和tiny_publish。
注意:以下是HiAI DDK V310版解压后的目录结构,需要将ai_ddk_lib目录拷贝至Paddle Lite源码根目录。
- app_sample
- ddk
- ai_ddk_lib
- include
- lib # for armv7
- lib64 # for armv8
- document
- tools
- full_publish and tiny_publish for armv8,由于HiAI DDK的armv7和armv8的so库均基于c++_shared构建,因此,建议使用c++_shared编译Paddle Lite。
$ ./lite/tools/build_npu.sh --arm_os=android --arm_abi=armv8 --arm_lang=gcc --android_stl=c++_shared full_publish
$ ./lite/tools/build_npu.sh --arm_os=android --arm_abi=armv8 --arm_lang=gcc --android_stl=c++_shared tiny_publish
- full_publish and tiny_publish for armv7
$ ./lite/tools/build_npu.sh --arm_os=android --arm_abi=armv7 --arm_lang=gcc --android_stl=c++_shared full_publish
$ ./lite/tools/build_npu.sh --arm_os=android --arm_abi=armv7 --arm_lang=gcc --android_stl=c++_shared tiny_publish
注意:为了保证编译环境一致,建议参考源码编译中的Docker开发环境进行配置,然后再执行上述命令。
优化生成NPU模型¶
- model_optimize_tool工具已经支持生成NPU模型,仅需要将valid_targets设置为npu,arm即可,具体参考模型转化方法。
./model_optimize_tool --model_dir=<model_param_dir> \
--model_file=<model_path> \
--param_file=<param_path> \
--optimize_out_type=(protobuf|naive_buffer) \
--optimize_out=<output_optimize_model_dir> \
--valid_targets=npu,arm \
--record_tailoring_info =(true|false)
- model_optimize_tool生成的模型只是标记了NPU支持的Paddle算子,并没有真正生成NPU HiAI模型,只有在执行时才会将标记的Paddle算子转成HiAI IR,最终生成并执行HiAI模型,具体实现参考PR2576。
- 不同模型,不同型号(ROM版本)的华为手机,在执行阶段,由于某些Paddle算子无法完全转成HiAI IR,或目标手机的HiAI版本过低等原因,可能导致HiAI模型无法成功生成,在这种情况下,Paddle Lite会调用CPU版算子进行运算完成整个预测任务。
通过JAVA接口加载并执行NPU模型¶
- 使用方法和Java实例一致,无需额外设置任何参数,只需将模型换成NPU模型即可。Paddle-Lite-Demo中的Image Classification Demo for Android是同时支持CPU和NPU两种模型的图像分类Demo。
注意:在拷贝libpaddle_lite_jni.so的时候,由于依赖HiAI DDK so和libc++_shared.so库,需要将HiAI DDK中ai_ddk_lib/lib或ai_ddk_lib/lib64目录下的所有so和libc++_shared.so,拷到libpaddle_lite_jni.so同级目录下。
通过C++接口加载并执行NPU模型¶
- 使用方法和C++实例一致,同样无需额外设置任何参数,只需将模型换成NPU模型即可。
注意:1)不能使用安卓模拟器,需要使用真实设备,且必须是支持NPU的华为手机。2)在使用adb push命令向手机推送目标程序时,需要将HiAI DDK中ai_ddk_lib/lib或ai_ddk_lib/lib64目录下的所有so和libc++_shared.so,推送到目标程序同级目录下。
其它说明¶
- 华为达芬奇架构的NPU内部大量采用float16进行运算,因此,预测结果会存在偏差,但大部分情况下精度不会有较大损失,可参考Paddle-Lite-Demo中Image Classification Demo for Android对同一张图片CPU与NPU的预测结果。
- 华为Kirin 810/990 Soc搭载的自研达芬奇架构的NPU,与Kirin 970/980 Soc搭载的寒武纪NPU不一样,同样的,与Hi3559A、Hi3519A使用的NNIE也不一样,Paddle Lite只支持华为自研达芬奇架构NPU。
- 我们正在持续增加能够适配HiAI IR的Paddle算子bridge/converter,以便适配更多Paddle模型,同时华为研发同学也在持续对HiAI IR性能进行优化。
PaddleLite使用RK NPU预测部署¶
Paddle Lite已支持RK NPU的预测部署。 其接入原理是与之前华为NPU类似,即加载并分析Paddle模型,将Paddle算子转成RK组网API进行网络构建,在线生成并执行模型。
支持现状¶
已支持的芯片¶
- RK1808, RK1806
- RK1126, RK1109
已支持的设备¶
- RK1808 EVB,暂时不支持RK3399Pro。
已支持的Paddle模型¶
已支持(或部分支持)的Paddle算子¶
- relu
- conv2d
- depthwise_conv2d
- pool2d
- fc
- softmax
- batch_norm
- concat
- elementwise_add
- elementwise_sub
- elementwise_mul
- elementwise_div
参考示例演示¶
准备设备环境¶
- 需要依赖特定版本的firmware,请参照rknpu_ddk的说明对设备进行firmware的更新;
- 由于RK1808 EVB在刷firmware后,只是一个纯净的Linux系统,无法像Ubuntu那样使用apt-get命令方便的安装软件,因此,示例程序和PaddleLite库的编译均采用交叉编译方式;
- 将MicroUSB线插入到设备的MicroUSB OTG口,就可以使用Android的adb命令进行设备的交互,再也不用配置网络使用ssh或者通过串口的方式访问设备了,这个设计非常赞!
运行图像分类示例程序¶
- 从https://paddlelite-demo.bj.bcebos.com/devices/rockchip/PaddleLite-armlinux-demo.tar.gz下载示例程序,解压后清单如下:
- PaddleLite-armlinux-demo
- image_classification_demo
- images
- tabby_cat.jpg # 测试图片
- tabby_cat.raw # 已处理成raw数据的测试图片
- labels
- synset_words.txt # 1000分类label文件
- models
- mobilenet_v1_int8_224_for_rknpu.nb # 已通过opt转好的mobilenetv1全量化模型
- CMakeLists.txt # 示例程序CMake脚本
- build
- image_classification_demo # 已编译好的示例程序
- image_classification_demo.cc # 示例程序源码
- convert_to_raw_image.py # 将测试图片保存为raw数据的python脚本
- build.sh # 示例程序编译脚本
- run.sh # 示例程序运行脚本
- Paddle-Lite
- include # PaddleLite头文件
- libs
- armv8
- libGAL.so # RK DDK库
- libOpenVX.so
- libVSC.so
- librknpu_ddk.so
- libgomp.so.1 # gnuomp库
- libpaddle_light_api_shared.so # 预编译PaddleLite库
- 进入PaddleLite-armlinux-demo/image_classification_demo,再定频后直接执行./run.sh即可,注意:run.sh的执行不能在docker环境,否则无法找到设备;
$ cd PaddleLite-armlinux-demo/image_classification_demo
$ adb shell
/ # echo userspace > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
/ # echo 1608000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed
/ # echo userspace > /sys/devices/system/cpu/cpu1/cpufreq/scaling_governor
/ # echo 1608000 > /sys/devices/system/cpu/cpu1/cpufreq/scaling_setspeed
$ ./run.sh
...
warmup: 5 repeat: 10, average: 6.486600 ms, max: 6.544000 ms, min: 6.450000 ms
results: 3
Top0 tabby, tabby cat - 0.438732
Top1 Egyptian cat - 0.438732
Top2 tiger cat - 0.116995
Preprocess time: 2.447000 ms
Prediction time: 6.486600 ms
Postprocess time: 0.101000 ms
- 如果需要更改测试图片,可通过convert_to_raw_image.py工具生成;
- 如果需要重新编译示例程序,直接运行./build.sh即可,注意:build.sh的执行必须在docker环境中,否则可能编译出错。
更新模型¶
- 通过Paddle Fluid训练,或X2Paddle转换得到MobileNetv1 foat32模型mobilenet_v1_fp32_224_fluid;
- 参考模型量化-有校准数据训练后量化使用PaddleSlim对float32模型进行量化(注意:由于RK NPU只支持tensor-wise的全量化模型,在启动量化脚本时请注意相关参数的设置),最终得到全量化MobileNetV1模型mobilenet_v1_int8_224_fluid;
- 参考模型转化方法,利用opt工具转换生成RKNPU模型,仅需要将valid_targets设置为rknpu,arm即可。
$ ./opt --model_dir=mobilenet_v1_int8_224_fluid \
--optimize_out_type=naive_buffer \
--optimize_out=mobilenet_v1_int8_224_for_rknpu \
--valid_targets=rknpu,arm
- 注意:opt生成的模型只是标记了RKNPU支持的Paddle算子,并没有真正生成RK NPU模型,只有在执行时才会将标记的Paddle算子转成RK NPU组网API,最终生成并执行HiAI模型。
更新支持RK NPU的Paddle Lite库¶
- 下载PaddleLite源码和RK DDK;
$ git clone https://github.com/PaddlePaddle/Paddle-Lite.git
$ cd Paddle-Lite
$ git checkout <release-version-tag>
$ git clone https://github.com/airockchip/rknpu_ddk.git
- 编译full_publish and tiny_publish for armv8(注意:RKNPU_DDK只支持armv8)
$ ./lite/tools/build.sh --arm_os=armlinux --arm_abi=armv8 --arm_lang=gcc --build_extra=ON --shutdown_log=OFF --build_rknpu=ON --rknpu_ddk_root=./rknpu_ddk full_publish
$ ./lite/tools/build.sh --arm_os=armlinux --arm_abi=armv8 --arm_lang=gcc --build_extra=ON --shutdown_log=OFF --build_rknpu=ON --rknpu_ddk_root=./rknpu_ddk tiny_publish
- 将编译生成的build.lite.armlinux.armv8.gcc/inference_lite_lib.armlinux.armv8.rknpu/cxx/include替换PaddleLite-armlinux-demo/Paddle-Lite/include目录;
- 将编译生成的build.lite.armlinux.armv8.gcc/inference_lite_lib.armlinux.armv8.rknpu/cxx/lib/libpaddle_light_api_shared.so替换PaddleLite-armlinux-demo/Paddle-Lite/libs/armv8/libpaddle_light_api_shared.so文件。
其它说明¶
- RK研发同学正在持续增加用于适配Paddle算子bridge/converter,以便适配更多Paddle模型。
C++ API¶
CreatePaddlePredictor¶
template <typename ConfigT>
std::shared_ptr<PaddlePredictor> CreatePaddlePredictor(const ConfigT&);
CreatePaddlePredictor
用来根据MobileConfig
构建预测器。
示例:
// 设置MobileConfig
MobileConfig config;
config.set_model_dir(FLAGS_model_dir);
// 根据MobileConfig创建PaddlePredictor
std::shared_ptr<PaddlePredictor> predictor = CreatePaddlePredictor<MobileConfig>(config);
参数:
config(MobileConfig)
- 用于构建Predictor的配置信息。
返回:PaddlePredictor
指针
返回类型:std::shared_ptr<PaddlePredictor>
CxxConfig¶
class CxxConfig;
CxxConfig
用来配置构建CxxPredictor的配置信息,如protobuf格式的模型地址、能耗模式、工作线程数、place信息等等。
示例:
config = CxxConfig()
# 设置模型目录,加载非combined模型时使用
config.set_model_dir(<your_model_dir_path>)
# 设置工作线程数
config.set_threads(4);
# 设置能耗模式
config.set_power_mode(PowerMode.LITE_POWER_NO_BIND)
# 设置valid places
places = [Place(TargetType.ARM, PrecisionType.FP32)]
config.set_valid_places(places)
# 根据CxxConfig创建CxxPredictor
predictor = create_paddle_predictor(config)
set_model_dir(model_dir)
¶
设置模型文件夹路径,当需要从磁盘加载非combined模型时使用。
参数:
model_dir(str)
- 模型文件夹路径
返回:None
返回类型:None
set_valid_places(valid_places)
¶
设置可用的places列表。
参数:
valid_places(list)
- 可用place列表。
返回类型:None
示例:
config = CxxConfig()
# 设置模型目录,加载非combined模型时使用
config.set_model_dir(<your_model_dir_path>)
# 设置valid places
# 注意,valid_places列表中Place的排序表明了用户对Place的偏好程度,如用户想优先使用ARM上Int8精度的
# kernel,则应把Place(TargetType.ARM, PrecisionType.INT8)置于valid_places列表的首位。
places = [Place(TargetType.ARM, PrecisionType.INT8),
Place(TargetType.ARM, PrecisionType.FP32)]
config.set_valid_places(places)
# 根据CxxConfig创建CxxPredictor
predictor = create_paddle_predictor(config)
set_power_mode(mode)
¶
设置CPU能耗模式。若不设置,则默认使用PowerMode.LITE_POWER_HIGH
。
注意:只在开启OpenMP
时生效,否则系统自动调度。此函数只在使用LITE_WITH_ARM
编译选项下生效。
参数:
mode(PowerMode)
- CPU能耗模式
返回:None
返回类型:None
set_threads(threads)
¶
设置工作线程数。若不设置,则默认使用单线程。
注意:只在开启OpenMP
的模式下生效,否则只使用单线程。此函数只在使用LITE_WITH_ARM
编译选项下生效。
参数:
threads(int)
- 工作线程数
返回:None
返回类型:None
set_x86_math_library_num_threads(threads)
¶
设置CPU Math库线程数,CPU核心数支持情况下可加速预测。默认为1,并且仅在x86下有效。
参数:
threads(int)
- CPU Math库线程数。
返回:None
返回类型:None
x86_math_library_num_threads()
¶
返回CPU Math库线程数,CPU核心数支持情况下可加速预测。仅在x86下有效。
参数:
None
返回:CPU Math库线程数。
返回类型:int
MobileConfig¶
class MobileConfig;
MobileConfig
用来配置构建轻量级PaddlePredictor的配置信息,如NaiveBuffer格式的模型地址、模型的内存地址(从内存加载模型时使用)、能耗模式、工作线程数等等。
注意:输入的模型需要使用Model Optimize Tool转化为NaiveBuffer格式的优化模型。
示例:
MobileConfig config;
// 设置NaiveBuffer格式模型目录,从文件加载模型时使用
config.set_model_from_file(<your_model_path>);
// 设置工作线程数
config.set_threads(4);
// 设置能耗模式
config.set_power_mode(LITE_POWER_HIGH);
// 根据MobileConfig创建PaddlePredictor
std::shared_ptr<PaddlePredictor> predictor = CreatePaddlePredictor<MobileConfig>(config);
set_model_from_file(model_file)
¶
设置模型文件,当需要从磁盘加载模型时使用。
参数:
model_file(std::string)
- 模型文件路径
返回:None
返回类型:void
set_model_dir(model_dir)
¶
注意:Lite模型格式在release/v2.3.0之后修改,本接口为加载老格式模型的接口,将在release/v3.0.0废弃。建议替换为set_model_from_file
接口。
设置模型文件夹路径,当需要从磁盘加载模型时使用。
参数:
model_dir(std::string)
- 模型文件夹路径
返回:None
返回类型:void
set_model_from_buffer(model_buffer)
¶
设置模型的内存数据,当需要从内存加载模型时使用。
参数:
model_buffer(std::string)
- 内存中的模型数据
返回:None
返回类型:void
set_model_buffer(model_buffer, model_buffer_size, param_buffer, param_buffer_size)
¶
注意:Lite模型格式在release/v2.3.0之后修改,本接口为加载老格式模型的接口,将在release/v3.0.0废弃。建议替换为set_model_from_buffer
接口。
设置模型、参数的内存地址,当需要从内存加载模型时使用。
示例:
// 读取模型文件到内存
std::string model_buffer = ReadFile(FLAGS_model_path);
std::string params_buffer = lite::ReadFile(FLAGS_params_path);
// 设置MobileConfig
lite_api::MobileConfig config;
config.set_model_buffer(model_buffer.c_str(), model_buffer.size(),
params_buffer.c_str(), params_buffer.size());
// 根据MobileConfig创建PaddlePredictor
std::shared_ptr<PaddlePredictor> predictor = CreatePaddlePredictor<MobileConfig>(config);
参数:
model_buffer(const char*)
- 内存中模型结构数据。model_buffer_size(size_t)
- 内存中模型结构数据的大小。param_buffer(const char*)
- 内存中模型参数数据。param_buffer_size(size_t)
- 内存中模型参数数据的大小。
返回:None
返回类型:Void
set_power_mode(mode)
¶
设置CPU能耗模式。若不设置,则默认使用LITE_POWER_HIGH
。
注意:只在开启OpenMP
时生效,否则系统自动调度。
参数:
mode(PowerMode)
- CPU能耗模式
返回:None
返回类型:void
set_threads(threads)
¶
设置工作线程数。若不设置,则默认使用单线程。
注意:只在开启OpenMP
的模式下生效,否则只使用单线程。
参数:
threads(int)
- 工作线程数
返回:None
返回类型:void
PaddlePredictor¶
class PaddlePredictor
PaddlePredictor
是Paddle-Lite的预测器,由CreatePaddlePredictor
根据MobileConfig
进行创建。用户可以根据PaddlePredictor提供的接口设置输入数据、执行模型预测、获取输出以及获得当前使用lib的版本信息等。
示例:
int64_t ShapeProduction(const shape_t& shape) {
int64_t res = 1;
for (auto i : shape) res *= i;
return res;
}
// 设置MobileConfig
MobileConfig config;
config.set_model_dir(FLAGS_model_dir);
// 根据MobileConfig创建PaddlePredictor
std::shared_ptr<PaddlePredictor> predictor = CreatePaddlePredictor<MobileConfig>(config);
// 获得模型的输入和输出名称
std::vector<std::string> input_names = predictor->GetInputNames();
for (int i = 0; i < input_names.size(); i ++) {
printf("Input name[%d]: %s\n", i, input_names[i].c_str());
}
std::vector<std::string> output_names = predictor->GetOutputNames();
for (int i = 0; i < output_names.size(); i ++) {
printf("Output name[%d]: %s\n", i, output_names[i].c_str());
}
// 准备输入数据
// (1)根据index获取输入Tensor
std::unique_ptr<Tensor> input_tensor(std::move(predictor->GetInput(0)));
// (2)根据名称获取输入Tensor
// std::unique_ptr<Tensor> input_tensor(std::move(predictor->GetInputByName(input_names[0])));
input_tensor->Resize({1, 3, 224, 224});
auto* data = input_tensor->mutable_data<float>();
for (int i = 0; i < ShapeProduction(input_tensor->shape()); ++i) {
data[i] = 1;
}
// 执行预测
predictor->Run();
// 获取输出
// (1)根据index获取输出Tensor
std::unique_ptr<const Tensor> output_tensor(std::move(predictor->GetOutput(0)));
// (2)根据名称获取输出Tensor
// std::unique_ptr<const Tensor> output_tensor(std::move(predictor->GetOutput(output_names[0])));
printf("Output dim: %d\n", output_tensor->shape()[1]);
for (int i = 0; i < ShapeProduction(output_tensor->shape()); i += 100) {
printf("Output[%d]: %f\n", i, output_tensor->data<float>()[i]);
}
GetInput(index)
¶
获取输入Tensor指针,用来设置模型的输入数据。
参数:
index(int)
- 输入Tensor的索引
返回:第index
个输入Tensor
的指针
返回类型:std::unique_ptr<Tensor>
GetOutput(index)
¶
获取输出Tensor的指针,用来获取模型的输出结果。
参数:
index(int)
- 输出Tensor的索引
返回:第index
个输出Tensor`的指针
返回类型:std::unique_ptr<Tensor>
GetInputByName(name)
¶
根据名称获取输出Tensor的指针,用来获取模型的输出结果。
参数:
name(const std::string)
- 输入Tensor的名称
返回:输入Tensor`的指针
返回类型:std::unique_ptr<Tensor>
GetTensor(name)
¶
根据名称获取输出Tensor的指针。
注意:GetTensor
接口是为开发者设计的调试接口,可以输出转化后模型中的任一节点。如果出现GetTensor(InputName)
返回值为空Tensor
,可能原因是以该InputName
命名的Tensor在模型转化的子图融合过程被融合替换了。
参数:
name(const std::string)
- Tensor的名称
返回:指向const Tensor
的指针
返回类型:std::unique_ptr<const Tensor>
GetVersion()
¶
用于获取当前lib使用的代码版本。若代码有相应tag则返回tag信息,如v2.0-beta
;否则返回代码的branch(commitid)
,如develop(7e44619)
。
参数:
None
返回:当前lib使用的代码版本信息
返回类型:std::string
TargetType¶
class TargetType;
TargetType
为目标设备硬件类型,用户可以根据应用场景选择硬件平台类型。
枚举型变量TargetType
的所有可能取值包括:
{X86, CUDA, ARM, OpenCL, FPGA, NPU}
PrecisionType¶
class PrecisionType {FP32};
PrecisionType
为模型中Tensor的数据精度,默认值为FP32(float32)。
枚举型变量PrecisionType
的所有可能取值包括:
{FP32, INT8, INT32, INT64}
DataLayoutType¶
class DataLayoutType {NCHW};
DataLayoutType
为Tensor的数据格式,默认值为NCHW(number, channel, height, weigth)。
枚举型变量DataLayoutType
的所有可能取值包括:
{NCHW, NHWC}
Place¶
class Place{
TargetType target;
PrecisionType precision{FP32};
DataLayoutType layout{NCHW}
}
Place
是TargetType
、PrecisionType
和DataLayoutType
的集合,说明运行时的设备类型、数据精度和数据格式。
示例:
Place{TargetType(ARM), PrecisionType(FP32), DataLayoutType(NCHW)}
PowerMode¶
enum PowerMode;
PowerMode
为ARM CPU能耗模式,用户可以根据应用场景设置能耗模式获得最优的能效比。
示例:
MobileConfig config;
// 设置NaiveBuffer格式模型目录
config.set_model_dir(FLAGS_model_dir);
// 设置能耗模式
config.set_power_mode(LITE_POWER_HIGH);
// 根据MobileConfig创建PaddlePredictor
std::shared_ptr<PaddlePredictor> predictor = CreatePaddlePredictor<MobileConfig>(config);
PowerMode详细说明如下:
选项 | 说明 |
---|---|
LITE_POWER_HIGH | 绑定大核运行模式。如果ARM CPU支持big.LITTLE,则优先使用并绑定Big cluster。如果设置的线程数大于大核数量,则会将线程数自动缩放到大核数量。如果系统不存在大核或者在一些手机的低电量情况下会出现绑核失败,如果失败则进入不绑核模式。 |
LITE_POWER_LOW | 绑定小核运行模式。如果ARM CPU支持big.LITTLE,则优先使用并绑定Little cluster。如果设置的线程数大于小核数量,则会将线程数自动缩放到小核数量。如果找不到小核,则自动进入不绑核模式。 |
LITE_POWER_FULL | 大小核混用模式。线程数可以大于大核数量。当线程数大于核心数量时,则会自动将线程数缩放到核心数量。 |
LITE_POWER_NO_BIND | 不绑核运行模式(推荐)。系统根据负载自动调度任务到空闲的CPU核心上。 |
LITE_POWER_RAND_HIGH | 轮流绑定大核模式。如果Big cluster有多个核心,则每预测10次后切换绑定到下一个核心。 |
LITE_POWER_RAND_LOW | 轮流绑定小核模式。如果Little cluster有多个核心,则每预测10次后切换绑定到下一个核心。 |
Tensor¶
class Tensor
Tensor是Paddle-Lite的数据组织形式,用于对底层数据进行封装并提供接口对数据进行操作,包括设置Shape、数据、LoD信息等。
注意:用户应使用PaddlePredictor
的GetInput
和GetOuput
接口获取输入/输出的Tensor
。
示例:
int64_t ShapeProduction(const shape_t& shape) {
int64_t res = 1;
for (auto i : shape) res *= i;
return res;
}
// 设置MobileConfig
MobileConfig config;
config.set_model_dir(FLAGS_model_dir);
// 根据MobileConfig创建PaddlePredictor
std::shared_ptr<PaddlePredictor> predictor = CreatePaddlePredictor<MobileConfig>(config);
// 准备输入数据, 获取输入Tensor
std::unique_ptr<Tensor> input_tensor(std::move(predictor->GetInput(0)));
// 设置输入Tensor维度信息
input_tensor->Resize({1, 3, 224, 224});
// 设置输入数据
auto* data = input_tensor->mutable_data<float>();
for (int i = 0; i < ShapeProduction(input_tensor->shape()); ++i) {
data[i] = 1;
}
// 执行预测
predictor->Run();
// 获取输出Tensor
std::unique_ptr<const Tensor> output_tensor(std::move(predictor->GetOutput(0)));
// 获取输出Tensor维度
printf("Output dim: %d\n", output_tensor->shape()[1]);
// 获取输出Tensor数据
for (int i = 0; i < ShapeProduction(output_tensor->shape()); i += 100) {
printf("Output[%d]: %f\n", i, output_tensor->data<float>()[i]);
}
data<T>()
¶
template <typename T>
const T* data() const;
获取Tensor的底层数据的常量指针,根据传入的不同模型类型获取相应数据。用于读取Tensor数据。
示例:
std::unique_ptr<const Tensor> output_tensor(std::move(predictor->GetOutput(0)));
// 如果模型中输出为float类型
output_tensor->data<float>()
参数:
None
返回:Tensor
底层数据常量指针
返回类型:const T*
mutable_data<T>()
¶
template <typename T>
T* mutable_data() const;
获取Tensor的底层数据的指针,根据传入的不同模型类型获取相应数据。用于设置Tensor数据。
示例:
std::unique_ptr<Tensor> input_tensor(std::move(predictor->GetInput(0)));
// 如果模型中输出为float类型
auto* data = input_tensor->mutable_data<float>();
// 设置Tensor数据
for (int i = 0; i < ShapeProduction(input_tensor->shape()); ++i) {
data[i] = 1;
}
参数:
None
返回:Tensor
底层数据指针
返回类型:T*
SetLoD(lod)
¶
设置Tensor的LoD信息。
参数:
lod(std::vector<std::vector<uint64_t>>)
- Tensor的LoD信息
返回:None
返回类型:void
Java API¶
MobileConfig¶
public class MobileConfig extends ConfigBase;
MobileConfig
用来配置构建轻量级PaddlePredictor的配置信息,如NaiveBuffer格式的模型地址、能耗模式、工作线程数等等。
注意:输入的模型需要使用Model Optimize Tool转化为NaiveBuffer格式的优化模型。
示例:
MobileConfig config = new MobileConfig();
// 设置NaiveBuffer格式模型目录
config.setModelFromFile(modelfile);
// 设置能耗模式
config.setPowerMode(PowerMode.LITE_POWER_HIGH);
// 设置工作线程数
config.setThreads(1);
// 根据MobileConfig创建PaddlePredictor
PaddlePredictor predictor = PaddlePredictor.createPaddlePredictor(config);
setModelDir(model_dir)
¶
注意:Lite模型格式在release/v2.3.0之后修改,本接口为加载老格式模型的接口,将在release/v3.0.0废弃。建议替换为setModelFromFile
接口。
设置模型文件夹路径。
参数:
model_dir(String)
- 模型文件夹路径
返回:None
返回类型:void
setModelFromBuffer(model_buffer)
¶
设置模型的内存数据,当需要从内存加载模型时使用。
参数:
model_buffer(str)
- 内存中的模型数据
返回:None
返回类型:void
setPowerMode(mode)
¶
设置CPU能耗模式。若不设置,则默认使用LITE_POWER_HIGH
。
注意:只在开启OpenMP
时生效,否则系统自动调度。
参数:
mode(PowerMode)
- CPU能耗模式。
返回:None
返回类型:void
setThreads(threads)
¶
设置工作线程数。若不设置,则默认使用单线程。
注意:只在开启OpenMP
的模式下生效,否则只使用单线程。
参数:
threads(int)
- 工作线程数。默认为1。
返回:None
返回类型:void
PaddlePredictor¶
public class PaddlePredictor;
PaddlePredictor
是Paddle-Lite的预测器。用户可以根据PaddlePredictor提供的接口使用MobileConfig创建新的预测器、设置输入数据、执行模型预测、获取输出以及获得当前使用lib的版本信息等。
示例:
// 设置MobileConfig
MobileConfig config = new MobileConfig();
config.setModelDir(modelPath);
// 创建PaddlePredictor
PaddlePredictor predictor = PaddlePredictor.createPaddlePredictor(config);
// 设置输入数据
long[] dims = {100, 100};
float[] inputBuffer = new float[10000];
for (int i = 0; i < 10000; ++i) {
inputBuffer[i] = i;
}
Tensor input = predictor.getInput(0);
input.resize(dims);
input.setData(inputBuffer);
// 执行预测
predictor.run();
// 获取输出数据
Tensor output = predictor.getOutput(0);
float[] output = result.getFloatData();
for (int i = 0; i < 1000; ++i) {
System.out.println(output[i]);
}
CreatePaddlePredictor(config)
¶
public static PaddlePredictor createPaddlePredictor(ConfigBase config);
CreatePaddlePredictor
用来根据ConfigBase
动态创建预测器,目前Java API支持使用MobileConfig`。框架会根据您在config中指定的模型路径、能耗模型、工作线程数等自动创建一个预测器。
参数:
config(ConfigBase,目前应使用MobileConfig)
- 创建预测器的配置信息
返回:根据config创建完成的预测器
返回类型:PaddlePredictor
getInput(index)
¶
获取输入Tensor,用来设置模型的输入数据。
参数:
index(int)
- 输入Tensor的索引
返回:第index
个输入Tensor
返回类型:Tensor
getOutput(index)
¶
获取输出Tensor,用来获取模型的输出结果。
参数:
index(int)
- 输出Tensor的索引
返回:第index
个输出Tensor
返回类型:Tensor
getVersion()
¶
用于获取当前lib使用的代码版本。若代码有相应tag则返回tag信息,如v2.0-beta
;否则返回代码的branch(commitid)
,如develop(7e44619)
。
参数:
None
返回:当前lib使用的代码版本信息
返回类型:String
PowerMode¶
public enum PowerMode;
PowerMode
为ARM CPU能耗模式,用户可以根据应用场景设置能耗模式获得最优的能效比。
示例:
MobileConfig config = new MobileConfig();
// 设置NaiveBuffer格式模型目录
config.setModelDir(modelPath);
// 设置能耗模式
config.setPowerMode(PowerMode.LITE_POWER_HIGH);
// 根据MobileConfig创建PaddlePredictor
PaddlePredictor predictor = PaddlePredictor.createPaddlePredictor(config);
PowerMode详细说明如下:
选项 | 说明 |
---|---|
LITE_POWER_HIGH | 绑定大核运行模式。如果ARM CPU支持big.LITTLE,则优先使用并绑定Big cluster。如果设置的线程数大于大核数量,则会将线程数自动缩放到大核数量。如果系统不存在大核或者在一些手机的低电量情况下会出现绑核失败,如果失败则进入不绑核模式。 |
LITE_POWER_LOW | 绑定小核运行模式。如果ARM CPU支持big.LITTLE,则优先使用并绑定Little cluster。如果设置的线程数大于小核数量,则会将线程数自动缩放到小核数量。如果找不到小核,则自动进入不绑核模式。 |
LITE_POWER_FULL | 大小核混用模式。线程数可以大于大核数量。当线程数大于核心数量时,则会自动将线程数缩放到核心数量。 |
LITE_POWER_NO_BIND | 不绑核运行模式(推荐)。系统根据负载自动调度任务到空闲的CPU核心上。 |
LITE_POWER_RAND_HIGH | 轮流绑定大核模式。如果Big cluster有多个核心,则每预测10次后切换绑定到下一个核心。 |
LITE_POWER_RAND_LOW | 轮流绑定小核模式。如果Little cluster有多个核心,则每预测10次后切换绑定到下一个核心。 |
Tensor¶
public class Tensor;
Tensor是Paddle-Lite的数据组织形式,用于对底层数据进行封装并提供接口对数据进行操作,包括设置维度、数据等。
注意:用户应使用PaddlePredictor
的getInput
和getOuput
接口获取输入/输出的Tensor
。
示例:
// 导入Java API
import com.baidu.paddle.lite.MobileConfig;
import com.baidu.paddle.lite.Tensor;
import com.baidu.paddle.lite.Predictor;
import com.baidu.paddle.lite.PowerMode;
// 设置MobileConfig
MobileConfig config = new MobileConfig();
config.setModelDir(modelPath);
// 创建PaddlePredictor
PaddlePredictor predictor = PaddlePredictor.createPaddlePredictor(config);
// 设置输入数据
long[] dims = {100, 100};
float[] inputBuffer = new float[10000];
for (int i = 0; i < 10000; ++i) {
inputBuffer[i] = i;
}
// 获取输入Tensor
Tensor input = predictor.getInput(0);
// 设置输入维度
input.resize(dims);
// 设置输入数据
input.setData(inputBuffer);
// 执行预测
predictor.run();
// 获取输出Tensor
Tensor result = predictor.getOutput(0);
// 获取输出数据
float[] output = result.getFloatData();
for (int i = 0; i < 1000; ++i) {
System.out.println(output[i]);
}
Python API¶
create_paddle_predictor¶
CxxPredictor create_paddle_predictor(config); # config为CxxConfig类型
LightPredictor create_paddle_predictor(config); # config为MobileConfig类型
create_paddle_predictor
函数用来根据CxxConfig
或MobileConfig
构建预测器。
示例:
from lite_core import *
# 设置CxxConfig
config = CxxConfig()
config.set_model_dir(<your_model_dir_path>)
places = [Place(TargetType.ARM, PrecisionType.FP32)]
config.set_valid_places(places)
# 根据CxxConfig创建CxxPredictor
predictor = create_paddle_predictor(config)
参数:
config(CxxConfig或MobileConfig)
- 用于构建Predictor的配置信息。
返回:预测器predictor
返回类型:CxxPredictor
或LightPredictor
CxxConfig¶
class CxxConfig;
CxxConfig
用来配置构建CxxPredictor的配置信息,如protobuf格式的模型地址、能耗模式、工作线程数、place信息等等。
示例:
from lite_core import *
config = CxxConfig()
# 设置模型目录,加载非combined模型时使用
config.set_model_dir(<your_model_dir_path>)
# 设置工作线程数
config.set_threads(4);
# 设置能耗模式
config.set_power_mode(PowerMode.LITE_POWER_NO_BIND)
# 设置valid places
places = [Place(TargetType.ARM, PrecisionType.FP32)]
config.set_valid_places(places)
# 根据CxxConfig创建CxxPredictor
predictor = create_paddle_predictor(config)
set_model_dir(model_dir)
¶
设置模型文件夹路径,当需要从磁盘加载非combined模型时使用。
参数:
model_dir(str)
- 模型文件夹路径
返回:None
返回类型:None
set_valid_places(valid_places)
¶
设置可用的places列表。
参数:
valid_places(list)
- 可用place列表。
返回类型:None
示例:
from lite_core import *
config = CxxConfig()
# 设置模型目录,加载非combined模型时使用
config.set_model_dir(<your_model_dir_path>)
# 设置valid places
# 注意,valid_places列表中Place的排序表明了用户对Place的偏好程度,如用户想优先使用ARM上Int8精度的
# kernel,则应把Place(TargetType.ARM, PrecisionType.INT8)置于valid_places列表的首位。
places = [Place(TargetType.ARM, PrecisionType.INT8),
Place(TargetType.ARM, PrecisionType.FP32)]
config.set_valid_places(places)
# 根据CxxConfig创建CxxPredictor
predictor = create_paddle_predictor(config)
set_power_mode(mode)
¶
设置CPU能耗模式。若不设置,则默认使用PowerMode.LITE_POWER_HIGH
。
注意:只在开启OpenMP
时生效,否则系统自动调度。此函数只在使用LITE_WITH_ARM
编译选项下生效。
参数:
mode(PowerMode)
- CPU能耗模式
返回:None
返回类型:None
set_threads(threads)
¶
设置工作线程数。若不设置,则默认使用单线程。
注意:只在开启OpenMP
的模式下生效,否则只使用单线程。此函数只在使用LITE_WITH_ARM
编译选项下生效。
参数:
threads(int)
- 工作线程数
返回:None
返回类型:None
MobileConfig¶
class MobileConfig;
MobileConfig
用来配置构建LightPredictor的配置信息,如NaiveBuffer格式的模型地址、能耗模式、工作线程数等等。
示例:
from lite_core import *
config = MobileConfig()
# 设置NaiveBuffer格式模型目录
config.set_model_from_file(<your_model_path>)
# 设置工作线程数
config.set_threads(4);
# 设置能耗模式
config.set_power_mode(PowerMode.LITE_POWER_NO_BIND)
# 根据MobileConfig创建LightPredictor
predictor = create_paddle_predictor(config)
set_model_from_file(model_file)
¶
注意:model_file
应该是经过opt
优化后产生的NaiveBuffer
格式的模型。
设置模型文件夹路径。
参数:
model_file(str)
- 模型文件路径
返回:None
返回类型:None
set_model_dir(model_dir)
¶
注意:Lite模型格式在release/v2.3.0之后修改,本接口为加载老格式模型的接口,将在release/v3.0.0废弃。建议替换为setModelFromFile
接口。model_dir
应该是经过Model Optimize Tool
优化后产生的NaiveBuffer
格式的模型。
设置模型文件夹路径。
参数:
model_dir(str)
- 模型文件夹路径
返回:None
返回类型:None
set_model_from_buffer(model_buffer)
¶
设置模型的内存数据,当需要从内存加载模型时使用。
参数:
model_buffer(str)
- 内存中的模型数据
返回:None
返回类型:void
set_power_mode(mode)
¶
设置CPU能耗模式。若不设置,则默认使用PowerMode.LITE_POWER_HIGH
。
注意:只在开启OpenMP
时生效,否则系统自动调度。此函数只在使用LITE_WITH_ARM
编译选项下生效。
参数:
mode(PowerMode)
- CPU能耗模式
返回:None
返回类型:None
set_threads(threads)
¶
设置工作线程数。若不设置,则默认使用单线程。
注意:只在开启OpenMP
的模式下生效,否则只使用单线程。此函数只在使用LITE_WITH_ARM
编译选项下生效。
参数:
threads(int)
- 工作线程数
返回:None
返回类型:None
CxxPredictor¶
class CxxPredictor
CxxPredictor
是Paddle-Lite的预测器,由create_paddle_predictor
根据CxxConfig
进行创建。用户可以根据CxxPredictor提供的接口设置输入数据、执行模型预测、获取输出以及获得当前使用lib的版本信息等。
示例:
from __future__ import print_function
from lite_core import *
# 1. 设置CxxConfig
config = CxxConfig()
if args.model_file != '' and args.param_file != '':
config.set_model_file(args.model_file)
config.set_param_file(args.param_file)
else:
config.set_model_dir(args.model_dir)
places = [Place(TargetType.ARM, PrecisionType.FP32)]
config.set_valid_places(places)
# 2. 创建CxxPredictor
predictor = create_paddle_predictor(config)
# 3. 设置输入数据
input_tensor = predictor.get_input(0)
input_tensor.resize([1, 3, 224, 224])
input_tensor.set_float_data([1.] * 3 * 224 * 224)
# 4. 运行模型
predictor.run()
# 5. 获取输出数据
output_tensor = predictor.get_output(0)
print(output_tensor.shape())
print(output_tensor.float_data()[:10])
get_input(index)
¶
获取输入Tensor,用来设置模型的输入数据。
参数:
index(int)
- 输入Tensor的索引
返回:第index
个输入Tensor
返回类型:Tensor
get_output(index)
¶
获取输出Tensor,用来获取模型的输出结果。
参数:
index(int)
- 输出Tensor的索引
返回:第index
个输出Tensor
返回类型:Tensor
get_version()
¶
用于获取当前lib使用的代码版本。若代码有相应tag则返回tag信息,如v2.0-beta
;否则返回代码的branch(commitid)
,如develop(7e44619)
。
参数:
None
返回:当前lib使用的代码版本信息
返回类型:str
LightPredictor¶
class LightPredictor
LightPredictor
是Paddle-Lite的预测器,由create_paddle_predictor
根据MobileConfig
进行创建。用户可以根据LightPredictor提供的接口设置输入数据、执行模型预测、获取输出以及获得当前使用lib的版本信息等。
示例:
from __future__ import print_function
from lite_core import *
# 1. 设置MobileConfig
config = MobileConfig()
config.set_model_dir(args.model_dir)
# 2. 创建LightPredictor
predictor = create_paddle_predictor(config)
# 3. 设置输入数据
input_tensor = predictor.get_input(0)
input_tensor.resize([1, 3, 224, 224])
input_tensor.set_float_data([1.] * 3 * 224 * 224)
# 4. 运行模型
predictor.run()
# 5. 获取输出数据
output_tensor = predictor.get_output(0)
print(output_tensor.shape())
print(output_tensor.float_data()[:10])
get_input(index)
¶
获取输入Tensor,用来设置模型的输入数据。
参数:
index(int)
- 输入Tensor的索引
返回:第index
个输入Tensor
返回类型:Tensor
get_output(index)
¶
获取输出Tensor,用来获取模型的输出结果。
参数:
index(int)
- 输出Tensor的索引
返回:第index
个输出Tensor
返回类型:Tensor
get_version()
¶
用于获取当前lib使用的代码版本。若代码有相应tag则返回tag信息,如v2.0-beta
;否则返回代码的branch(commitid)
,如develop(7e44619)
。
参数:
None
返回:当前lib使用的代码版本信息
返回类型:str
TargetType¶
class TargetType;
TargetType
为目标设备硬件类型,用户可以根据应用场景选择硬件平台类型。
枚举型变量TargetType
的所有可能取值包括:
{X86, CUDA, ARM, OpenCL, FPGA, NPU}
PrecisionType¶
class PrecisionType {FP32};
PrecisionType
为模型中Tensor的数据精度,默认值为FP32(float32)。
枚举型变量PrecisionType
的所有可能取值包括:
{FP32, INT8, INT32, INT64}
DataLayoutType¶
class DataLayoutType {NCHW};
DataLayoutType
为Tensor的数据格式,默认值为NCHW(number, channel, height, weigth)。
枚举型变量DataLayoutType
的所有可能取值包括:
{NCHW, NHWC}
Place¶
class Place{
TargetType target;
PrecisionType precision{FP32};
DataLayoutType layout{NCHW}
}
Place
是TargetType
、PrecisionType
和DataLayoutType
的集合,说明运行时的设备类型、数据精度和数据格式。
示例:
from lite_core import *
Place{TargetType(ARM), PrecisionType(FP32), DataLayoutType(NCHW)}
PowerMode¶
class PowerMode;
PowerMode
为ARM CPU能耗模式,用户可以根据应用场景设置能耗模式获得最优的能效比。
示例:
from lite_core import *
config = MobileConfig()
# 设置NaiveBuffer格式模型目录
config.set_model_dir(<your_model_dir_path>)
# 设置能耗模式
config.set_power_mode(PowerMode.LITE_POWER_NO_BIND)
# 根据MobileConfig创建LightPredictor
predictor = create_paddle_predictor(config)
PowerMode详细说明如下:
选项 | 说明 |
---|---|
LITE_POWER_HIGH | 绑定大核运行模式。如果ARM CPU支持big.LITTLE,则优先使用并绑定Big cluster。如果设置的线程数大于大核数量,则会将线程数自动缩放到大核数量。如果系统不存在大核或者在一些手机的低电量情况下会出现绑核失败,如果失败则进入不绑核模式。 |
LITE_POWER_LOW | 绑定小核运行模式。如果ARM CPU支持big.LITTLE,则优先使用并绑定Little cluster。如果设置的线程数大于小核数量,则会将线程数自动缩放到小核数量。如果找不到小核,则自动进入不绑核模式。 |
LITE_POWER_FULL | 大小核混用模式。线程数可以大于大核数量。当线程数大于核心数量时,则会自动将线程数缩放到核心数量。 |
LITE_POWER_NO_BIND | 不绑核运行模式(推荐)。系统根据负载自动调度任务到空闲的CPU核心上。 |
LITE_POWER_RAND_HIGH | 轮流绑定大核模式。如果Big cluster有多个核心,则每预测10次后切换绑定到下一个核心。 |
LITE_POWER_RAND_LOW | 轮流绑定小核模式。如果Little cluster有多个核心,则每预测10次后切换绑定到下一个核心。 |
Tensor¶
class Tensor
Tensor是Paddle-Lite的数据组织形式,用于对底层数据进行封装并提供接口对数据进行操作,包括设置Shape、数据、LoD信息等。
注意:用户应使用CxxPredictor
或LightPredictor
的get_input
和get_output
接口获取输入/输出的Tensor
。
示例:
from __future__ import print_function
from lite_core import *
# 1. 设置CxxConfig
config = CxxConfig()
if args.model_file != '' and args.param_file != '':
config.set_model_file(args.model_file)
config.set_param_file(args.param_file)
else:
config.set_model_dir(args.model_dir)
places = [Place(TargetType.ARM, PrecisionType.FP32)]
config.set_valid_places(places)
# 2. 创建CxxPredictor
predictor = create_paddle_predictor(config)
# 3. 设置输入数据
input_tensor = predictor.get_input(0)
input_tensor.resize([1, 3, 224, 224])
input_tensor.set_float_data([1.] * 3 * 224 * 224)
# 4. 运行模型
predictor.run()
# 5. 获取输出数据
output_tensor = predictor.get_output(0)
print(output_tensor.shape())
print(output_tensor.float_data()[:10])
float_data()
¶
获取Tensor的持有的float型数据。
示例:
output_tensor = predictor.get_output(0)
print(output_tensor.shape())
print(output_tensor.float_data()[:10])
参数:
None
返回:Tensor
持有的float型数据
返回类型:list
set_float_data(float_data)
¶
设置Tensor持有float数据。
示例:
input_tensor = predictor.get_input(0)
input_tensor.resize([1, 3, 224, 224])
input_tensor.set_float_data([1.] * 3 * 224 * 224)
参数:
float_data(list)
- 待设置的float型数据
返回:None
返回类型:None
CV图像预处理API¶
请把编译脚本Paddle-Lite/lite/too/build.sh
中BUILD_CV
变量设置为ON
, 其他编译参数设置请参考源码编译, 以确保 Lite 可以正确编译。这样CV
图像的加速库就会编译进去,且会生成paddle_image_preprocess.h
的API文件
- 硬件平台:
ARM
- 操作系统:
MAC
和LINUX
CV 图像预处理功能¶
Lite 支持不同颜色空间的图像相互转换 Convert
、缩放 Resize
、翻转 Flip
、旋转 Rotate
和图像数据转换为 Tensor
存储ImageToTensor
功能,下文将详细介绍每个功能的API接口。
CV 枚举变量和结构体变量¶
- 颜色空间
enum ImageFormat {
RGBA = 0,
BGRA,
RGB,
BGR,
GRAY,
NV21 = 11,
NV12,
};
- 翻转参数
enum FlipParam {
X = 0, // flip along the X axis
Y, // flip along the Y axis
XY // flip along the XY axis
};
- 转换参数
typedef struct {
int ih; // input height
int iw; // input width
int oh; // outpu theight
int ow; // output width
FlipParam flip_param; // flip, support x, y, xy
float rotate_param; // rotate, support 90, 180, 270
} TransParam;
ImagePreprocess 类的成员变量¶
ImagePreprocess
类含有以下三个私有成员变量,通过构造函数进行初始化。
private:
ImageFormat srcFormat_; // input image color format
ImageFormat dstFormat_; // output image color format
TransParam transParam_; // image transform parameter
// init
ImagePreprocess::ImagePreprocess(ImageFormat srcFormat, ImageFormat dstFormat, TransParam param) {
this->srcFormat_ = srcFormat;
this->dstFormat_ = dstFormat;
this->transParam_ = param;
}
颜色空间转换 Convert¶
Convert
函数支持颜色空间:GRAY、NV12(NV21)、RGB(BGR)和RGBA(BGRA)
目前支持以下颜色空间的相互转换:
- GRAY2BGR
- GRAY2RGB
- BGR2RGB
- BGRA2BGR
- BGRA2RGB
- RGBA2RGB
- RGBA2BGR
- BGRA2RGBA
目前支持以下颜色空间的单向转换:
- NV12—BGR
- NV21—BGR
- NV12—RGB
- NV21—RGB
- NV12—BGRA
- NV21—BGRA
- NV12—RGBA
- NV21—RGBA
Convert
功能的API接口// 方法一 void ImagePreprocess::imageCovert(const uint8_t* src, uint8_t* dst); // 方法二 void ImagePreprocess::imageCovert(const uint8_t* src, uint8_t* dst, ImageFormat srcFormat, ImageFormat dstFormat);
- 第一个
imageCovert
接口,缺省参数来源于ImagePreprocess
类的成员变量。故在初始化ImagePreprocess
类的对象时,必须要给以下成员变量赋值:- param srcFormat:
ImagePreprocess
类的成员变量srcFormat_
- param dstFormat:
ImagePreprocess
类的成员变量dstFormat_
- param srcFormat:
- 第二个
imageCovert
接口,可以直接使用
- 第一个
缩放 Resize¶
Resize
功能支持颜色空间:GRAY、NV12(NV21)、RGB(BGR)和RGBA(BGRA)
Resize
功能目前支持的方法:bilinear
Resize
功能的API接口// 方法一 void ImagePreprocess::imageResize(const uint8_t* src, uint8_t* dst); // 方法二 void ImagePreprocess::imageResize(const uint8_t* src, uint8_t* dst, ImageFormat srcFormat, ImageFormat srcFormat, int srcw, int srch, int dstw, int dsth);
- 第一个
imageResize
接口,缺省参数来源于ImagePreprocess
类的成员变量。故在初始化ImagePreprocess
类的对象时,必须要给以下成员变量赋值:- param srcFormat:
ImagePreprocess
类的成员变量dstFormat_
- param srcw:
ImagePreprocess
类的成员变量transParam_.iw
- param srch:
ImagePreprocess
类的成员变量transParam_.ih
- param dstw:
ImagePreprocess
类的成员变量transParam_.ow
- param dsth:
ImagePreprocess
类的成员变量transParam_.ow
- param srcFormat:
- 第二个
imageResize
接口,可以直接使用
- 第一个
旋转 Rotate¶
Rotate
功能支持颜色空间:GRAY、RGB(BGR)和RGBA(BGRA)
Rotate
功能目前支持的角度:90、180 和 270
Rotate
功能的API接口// 方法一 void ImagePreprocess::imageRotate(const uint8_t* src, uint8_t* dst); // 方法二 void ImagePreprocess::imageRotate(const uint8_t* src, uint8_t* dst, ImageFormat srcFormat, ImageFormat srcFormat, int srcw, int srch, float degree);
- 第一个
imageRotate
接口,缺省参数来源于ImagePreprocess
类的成员变量。故在初始化ImagePreprocess
类的对象时,必须要给以下成员变量赋值:- param srcFormat:
ImagePreprocess
类的成员变量dstFormat_
- param srcw:
ImagePreprocess
类的成员变量transParam_.ow
- param srch:
ImagePreprocess
类的成员变量transParam_.oh
- param degree:
ImagePreprocess
类的成员变量transParam_.rotate_param
- param srcFormat:
- 第二个
imageRotate
接口,可以直接使用
- 第一个
翻转 Flip¶
Flip
功能支持颜色空间:GRAY、RGB(BGR)和RGBA(BGRA)
Flip
功能目前支持的功能:沿X轴翻转、沿Y轴翻转和沿XY轴翻转
Flip
功能的API接口// 方法一 void ImagePreprocess::imageFlip(const uint8_t* src, uint8_t* dst); // 方法二 void ImagePreprocess::imageFlip(const uint8_t* src, uint8_t* dst, ImageFormat srcFormat, ImageFormat srcFormat, int srcw, int srch, FlipParam flip_param);
- 第一个
imageFlip
接口,缺省参数来源于ImagePreprocess
类的成员变量。故在初始化ImagePreprocess
类的对象时,必须要给以下成员变量赋值:- param srcFormat:
ImagePreprocess
类的成员变量dstFormat_
- param srcw:
ImagePreprocess
类的成员变量transParam_.ow
- param srch:
ImagePreprocess
类的成员变量transParam_.oh
- param flip_param:
ImagePreprocess
类的成员变量transParam_.flip_param
- param srcFormat:
- 第二个
imageFlip
接口,可以直接使用
- 第一个
Image2Tensor¶
Image2Tensor
功能支持颜色空间:RGB(BGR)和RGBA(BGRA)
Image2Tensor
功能目前支持的Layout:NCHW
和 NHWC
Image2Tensor
不仅完成图像转换为Tensor
数据处理,而且还完成了图像数据的归一化处理
Image2Tensor
功能的API接口// 方法一 void ImagePreprocess::image2Tensor(const uint8_t* src, Tensor* dstTensor, LayoutType layout, float* means, float* scales); // 方法二 void ImagePreprocess::image2Tensor(const uint8_t* src, Tensor* dstTensor, ImageFormat srcFormat, srcw, int srch, LayoutType layout, float* means, float* scales;
- 第一个
image2Tensor
接口,缺省参数来源于ImagePreprocess
类的成员变量。故在初始化ImagePreprocess
类的对象时,必须要给以下成员变量赋值:- param srcFormat:
ImagePreprocess
类的成员变量dstFormat_
- param srcw:
ImagePreprocess
类的成员变量transParam_.ow
- param srch:
ImagePreprocess
类的成员变量transParam_.oh
- param srcFormat:
- 第二个
image2Tensor
接口,可以直接使用
- 第一个
CV 图像预处理 Demo 示例¶
例子:输入 1920x1080
大小的 NV12
图像src,输出 960x540
大小 RGB
格式的图像dst;然后,完成 90
度旋转和沿 X
轴翻转功能;最后,用 NHWC
格式存储在Tensor里。
定义 ImagePreprocess
类的对象,初始化成员变量
// init
srcFormat = ImageFormat::NV12;
dstFormat = ImageFormat::RGB;
srch = 1920;
srcw = 1080;
dsth = 960;
dstw = 540;
flip_param = FlipParam::X;
degree = 90;
layout = LayoutType::NHWC
// 方法一:
TransParam tparam;
tparam.ih = srch;
tparam.iw = srcw;
tparam.oh = dsth;
tparam.ow = dstw;
tparam.flip_param = flip_param;
tparam.rotate_param = degree;
ImagePreprocess image_preprocess(srcFormat, dstFormat, tparam);
// 方法二:
ImagePreprocess image_preprocess();
imageConvert Demo¶
// 方法一:
image_preprocess.imageCovert(src, lite_dst);
// 方法二:
image_preprocess.imageCovert(src, lite_dst, (ImageFormat)srcFormat, (ImageFormat)dstFormat);
imageResize Demo¶
// 方法一:
image_preprocess.imageResize(lite_dst, resize_tmp);
// 方法二:
image_preprocess.imageResize(lite_dst,resize_tmp, (ImageFormat)dstFormat, srcw,
srch, dstw, dsth);
imageRotate Demo¶
// 方法一:
image_preprocess.imageRotate(resize_tmp, tv_out_ratote);
// 方法二:
image_preprocess.imageRotate(resize_tmp,tv_out_ratote, (ImageFormat)dstFormat, dstw, dsth, degree);
imageFlip Demo¶
// 方法一:
image_preprocess.imageFlip(tv_out_ratote, tv_out_flip);
// 方法二:
image_preprocess.imageFlip(tv_out_ratote, tv_out_flip, (ImageFormat)dstFormat, dstw, dsth, flip_param);
image2Tensor Demo¶
// 方法一:
image_preprocess.image2Tensor(tv_out_flip, &dst_tensor, layout, means, scales);
// 方法二:
image_preprocess.image2Tensor(tv_out_flip, &dst_tensor,(ImageFormat)dstFormat, dstw, dsth, layout, means, scales);
开发基础须知¶
可以参考 Paddle 开发者文档。
提交PR¶
需要在 commit message 里加上 test=develop
才能触发 CI
版本发布检查清单¶
- 所有 feature 梳理,确认状态
- 所有 QA 测试结果梳理,确认版本可靠
- Release note 确认 review 通过
- 确认需要 release 的 binary 编译完毕
架构详解¶
这篇文档会从开发者角度详细介绍开发 Paddle-Lite 需要的相关信息。
设计及思考¶
近年来,各种深度学习预估硬件称出不穷,从手机APP到车载设备,再到音箱,均需要部署深度学习预测,且有如下共性需求:
- 高性能
- 硬件支持和扩展容易
- 轻量级部署
Paddle-Lite 的架构方面便是定向参考如上需求设计实现的,具体地
- 高性能方面
- 通过 MIR(Machine IR) 实现精细复杂的计算图的分析和优化
- 执行期 Kernel 的简单设计,几乎没有额外调度开销
- 适当的硬件层抽象,框架支持各个硬件后端中做特定的调度实现
- 轻量级部署方面
- 拆分分析和执行两个阶段,执行阶段轻量级实现,可以单独部署
- 轻量级 Op 和 Kernel 设计
- 硬件支持和扩展方面
- 通过 MIR 支撑带硬件和执行信息的宏观分析优化
- TypeSystem 抽象带硬件的不同计算模式的表示,实现整个计算图的强类型推导,以及执行状态机的静态分析
Paddle-Lite 的架构尝试从强类型推导的角度建模支持多硬件,多种计算模式(不同量化精度、不同的 data layout等)的混合计算,从而实现宏观上的各异硬件和计算模式的混合。
框架部分已经经过 FPGA,GPU,NPU 等异构硬件的打磨,各项能力也在完善中。
重要模块介绍¶
OpLite¶
OpLite 是 Paddle-Lite 中的 Operator,用户扩展单个硬件时,最多的就是扩展 Op 和 Kernel。
重要方法如下:
class OpLite : public Registry {
public:
// Check the shape.
virtual bool CheckShape() const { return true; }
// Inference the outputs' shape.
virtual bool InferShape() const { return true; }
// Link the external execution environ to internal context.
bool AttachImpl(const cpp::OpDesc &opdesc, lite::Scope *scope);
};
其中,分析期执行
AttachImpl
执行期执行
CheckShape
InferShape
扩展须知:
CheckShape
只在第一个 batch 执行,所以耗时不敏感InferShape
需要在每个 batch 执行,应该严格耗时- 可以通过添加 member variable 的方式,对其中一部分信息增加 cache,比如
class XXOp : public OpLite { void InferShape() { int batch_size = param().input.shape[0]; if (!shape_cache_.empty()) { shape_cache_[0] = batch_size; param().output->Resize(shape_cache_); } } private: shape_t shape_cache_; }
OpParam¶
OpParam 用于存储执行期 Kernel 需要的各项参数。 所有字段可以直接存储(比如指针或者 int
),以避免执行中获取参数的延迟。
因为没有需求,OpParam 暂时没有设置基类。
实际例子:
// For Softmax op
struct SoftmaxParam {
lite::Tensor* x{};
lite::Tensor* output{};
int axis{-1};
};
OpLite 的 AttachImpl
方法就用于构建 OpParam
,复制传递给 Kernel
用于执行。
OpParam 是执行期的重要模块,需要严格保证性能,相应的扩展要求:
- 字段的获取必须是低延迟的,可以直接用指针,或者直接复制值
- 避免执行无关信息混入,包括 debug 信息
- 命名需要与 Paddle OpDesc 中的信息严格一致,以降低功能对齐和理解的难度
Kernel¶
template <TargetType Target,
PrecisionType Precision,
DataLayoutType DataLayout = DataLayoutType::kNCHW>
class KernelLite : public KernelBase {
public:
// Run the kernel.
virtual void Run() { CHECK(false) << "Not Implemented"; }
TargetType target() const override { return Target; }
PrecisionType precision() const override { return Precision; }
DataLayoutType layout() const override { return DataLayout; }
Place place() const override { return Place{Target, Precision, DataLayout}; }
std::string name() const override;
};
由于是执行期的重要概念,因此 Kernel 设计地非常简单高效。
其中,执行期的 Run
是其唯一重要的接口,其中包含具体的计算逻辑。
模板中的参数主要用于方便多硬件编译,以及自解释:
- Target: 执行硬件
- Precision: 主要的计算精度
- DataLayout:主要计算的 data layout
这部分信息用于帮助挑选 kernel,具体的值并不严格。
Kernel 的注册需要用到 TypeSystem,不光对 Kernel 本身的特性进行描述,对其输入和输出均进行详尽的定义。
例如 FullyConnected 的注册
REGISTER_LITE_KERNEL(
fc, kARM, kFloat, kNCHW, paddle::lite::kernels::arm::FcCompute, def)
.BindInput("Input", {LiteType::GetTensorTy(TARGET(kARM), PRECISION(kFloat), LAYOUT(kNCHW))})
.BindInput("Bias", {LiteType::GetTensorTy(TARGET(kARM))})
.BindInput("W", {LiteType::GetTensorTy(TARGET(kARM))})
.BindOutput("Out", {LiteType::GetTensorTy(TARGET(kARM))})
.Finalize();
Kernel自身定义是 kARM
的,也就是ARM上的kernel,主要的计算精度是 kFloat
,主要的 Data layout 是 kNCHW
。
接着会对其所有的输入和输出做详细定义,比如看 Input
输入的定义是 LiteType::GetTensorTy(TARGET(kARM), PRECISION(kFloat), LAYOUT(kNCHW))
,也就是声明其 Target 是 kARM
, PRECISION 是 kFloat
,Data Layout 是 kNCHW
。
这里的设计思想是类似C++中的函数重载,同一个 Kernel(的名字),在重载了其输入输出的类型之后可以是不同的kernel。
扩展须知¶
- 模板参数选用计算中主要的来表示
- 比如,scale kernel,同时能接受
float
和int
的输入,但其不算量化 kernel,那应该设置为Precision=float
,代表常规的计算精度中使用
- 比如,scale kernel,同时能接受
- Kernel 输入输出的定义需要足够精确,是什么类型就是什么类型;框架会根据其输入输出的定义来动态构建状态机,否则会出现分析期和执行期的状态机不一致,造成未定义行为
MIR¶
MIR 类似于 LLVM 里的 IR,只是加上了硬件和执行期的信息参与分析优化。
Pass 是MIR中的模块化策略,其输入和输出都是 SSA Graph.
框架会自动基于模型的Program 构建 SSA Graph,之后按 Optimizer 中定义的pass的顺序调用一系列 Pass。
Op Fusion¶
MIR 中的 PatternMacher 实现了简单有效的基于图的模板识别的算法,相关的 op fusion 的图操作可以基于此实现。
实际的例子可以参考 fc_fuse_pass.h。
TypeSystem¶
TypeSystem 是 Paddle-Lite 中构建复杂计算图的基础模块,核心思想是协助 SSA Graph 构建一个状态机,表示其中不同的状态。
这里的 Type 主要包含下面四组信息,更多的信息可以按需扩展:
- TargetType
- Precision
- DataLayout
- device id,用于表示卡号
状态机的表示:
Tensor0(kARM, kFloat, kNCHW) --pass--> Tensor1(kOpenCL, kFloat, kNCHW)
MIR 会识别出,Tensor0 和 Tensor1 的硬件位置不同,因此触发相依的 Pass 插入对应的 cast op 来进行 type cast,比如
Tensor0(kARM, kFloat, kNCHW) --pass-> IoCopyOp(kARM, kOpenCL) --pass-> Tensor1(kOpenCL, kFloat, kNCHW)
KernelContext¶
KernelContext 是硬件支持的核心封装,主要用于为 Kernel 提供执行期的硬件上下文。
KernelContext 的设计类似于 OpParam,两者均没有基类;对于 KernelContext,其假定是,不同的硬件间的接口和逻辑可能完全不同,比如 kARM 和 kCUDA,因此不设定基类,也不需要提供统一的接口来封装不同硬件行为。
不同硬件的 KernelContext 直接与该硬件对应的 Kernel 对接。
KernelContext 的行为可以被 MIR 在分析期确定和调度。
注意事项:
- 由于是执行期概念,KernelContext 也需要注意性能和轻量化
- 移动端部署时只会部署执行期,因此 MIR 和 KernelContext 会拆开,因此 KernelContext 相应的设置需要能够序列化到 ProgramDesc 中,以便执行期载入和执行
扩展硬件后端¶
扩展现有的硬件后端¶
主要是扩充 Op 和 Kernel 的工作,如果需要 fuse,则参考 MIR 章节,增加相应的fuse pass便可,具体地,可以参考
- fc_op 实现类似的 Op
- fc_compute 实现类似的 Kernel
- fc_fuse_pass 实现fuse逻辑,并注册到 optimizer
扩展全新硬件后端¶
需要额外扩充如下模块,让框架能够支撑硬件执行:
- TypeSystem,需要扩充其中相关的 type
- 相关 enum
- MIR,需要扩展其中的 type cast 相关的 pass
- TargetType cast pass 用于拷贝不同硬件上的tensor
- Data layout cast pass 用于转化不同的 data layout
- Precision cast pass 用于转化不同 tensor 的量化精度
- KernelContext,具体地可以参考
- ARM context
- 需要注意的是,硬件 context 的接口只服务于该硬件的 kernel
- context 有分析期和执行期两个阶段,如果分析期没有特殊的优化,则无需考虑;否则,需要注意将分析期的信息整理并序列化到离线模型中,用于执行期直接加载。
新增OP¶
以下以添加argmax为例,详细说明新增op的方法。
1. 添加OpParam 结构体以传导 Op 的输入和输出¶
这里命名为
ArgmaxParam
在
paddlelite/lite/operators/op_params.h
中添加ArgmaxParam
结构体,代码如下:struct ArgmaxParam { lite::Tensor* X{}; lite::Tensor* Out{}; int Axis{0}; };
2. 添加 Argmax Op 并注册¶
在paddlelite/lite/operators/目录下新建argmax_op.h文件,主要代码如下:
class ArgmaxOpLite : public OpLite { public: ArgmaxOpLite() {} explicit ArgmaxOpLite(const std::string &op_type) : OpLite(op_type) {} bool CheckShape() const override; bool InferShape() const override; bool AttachImpl(const cpp::OpDesc &opdesc, lite::Scope *scope) override; void AttachKernel(KernelBase *kernel) override { kernel->SetParam(param_); } std::string DebugString() const override { return "argmax"; } private: mutable ArgmaxParam param_; };
ArgmaxOpLite
继承OpLite
,成员变量包括ArgmaxParam
结构体,需要实现的接口包括CheckShape()
、InferShape()
、AttachImp()
、AttachKernel()
和DebugString()
函数。AttachKernel()
和DebugString()
函数较为简单,此处直接实现;在
paddlelite/lite/operators/
目录下新建argmax_op.cc文件,需要具体实现CheckShape()
、InferShape()
和AttachImp()
函数。CheckShape()
函数检查输入是否符合要求,InferShape()
函数基于输入推断得到输出的维度,AttachImp()
函数绑定Op的输入输出。然后在argmax_op.cc文件中注册argmax,核心代码如下:bool ArgmaxOpLite::CheckShape() const { CHECK_OR_FALSE(param_.X); CHECK_OR_FALSE(param_.Out); CHECK_OR_FALSE(param_.Axis < (param_.X)->dims().size()); return true; } bool ArgmaxOpLite::InferShape() const { auto x_dims = param_.X->dims(); int x_rank = x_dims.size(); int axis = param_.Axis; if (axis < 0) axis += x_rank; std::vector<int64_t> out_dims; for (int64_t i = 0; i < axis; i++) { out_dims.push_back(x_dims[i]); } for (int64_t i = axis + 1; i < x_rank; i++) { out_dims.push_back(x_dims[i]); } // Set output dims param_.Out->Resize(lite::DDim(out_dims)); return true; } bool ArgmaxOpLite::AttachImpl(const cpp::OpDesc &op_desc, lite::Scope *scope) { auto x = op_desc.Input("X").front(); auto out = op_desc.Output("Out").front(); param_.X = scope->FindVar(x)->GetMutable<lite::Tensor>(); param_.Out = scope->FindVar(out)->GetMutable<lite::Tensor>(); param_.Axis = op_desc.GetAttr<int>("Axis"); return true; } REGISTER_LITE_OP(argmax, paddle::lite::operators::ArgmaxOpLite);
在paddlelite/lite/operators/CMakeLists.txt中添加
add_operator(argmax_op basic SRCS argmax_op.cc DEPS ${op_DEPS})
3. 添加Argmax Kernel并绑定¶
以下以arm端argmax实现为例说明
在paddlelite/lite/kernels/arm/目录下新建argmax_compute.h文件,声明ArgmaxCompute类,并继承KernelLite,主要代码如下:
class ArgmaxCompute : public KernelLite<TARGET(kARM), PRECISION(kFloat)> { public: using param_t = operators::ArgmaxParam; void Run() override; virtual ~ArgmaxCompute() = default; };
在paddlelite/lite/kernels/arm/目录下新建argmax_compute.cc文件,主要实现Run函数。
Run()
函数调用paddlelite/lite/bachends/arm/math/argmax.h中的argmax_func()
函数,根据输入计算输出。最后在argmax_compute.cc文件中,我们绑定argmax的输入输出(为tensor的输入参数都需要绑定),代码如下:void ArgmaxCompute::Run() { auto& param = Param<operators::ArgmaxParam>(); lite::Tensor* input = param.X; lite::Tensor* output = param.Out; int axis = param.Axis; lite::arm::math::argmax_func(input, axis, output); return; } REGISTER_LITE_KERNEL( argmax, kARM, kFloat, kNCHW, paddle::lite::kernels::arm::ArgmaxCompute, def) .BindInput("X", {LiteType::GetTensorTy(TARGET(kARM))}) .BindOutput("Out", {LiteType::GetTensorTy(TARGET(kARM))}) .Finalize();
在paddlelite/lite/kernels/arm/CMakeLists.txt中添加
add_kernel(argmax_compute_arm ARM basic SRCS argmax_compute.cc DEPS ${lite_kernel_deps} math_arm)
4. 添加Argmax实现¶
在paddlelite/lite/backends/arm/math/目录下新建argmax.h文件,声明
argmax_func()
函数,代码如下:void argmax_func(const lite::Tensor* input, const int axis, lite::Tensor* output);
在paddlelite/lite/backends/arm/math/目录下新建argmax.cc文件,具体实现
argmax_func()
函数,代码如下:void argmax_func(const lite::Tensor *input, const int axis, lite::Tensor *output) { auto input_ddim = input->dims(); auto output_ddim = output->dims(); const int size = input_ddim[axis]; const int in_channel = input_ddim.count(axis, input_ddim.size()); const int out_channel = output_ddim.count(axis, output_ddim.size()); const int in_stride = input_ddim.count(axis + 1, input_ddim.size()); const int out_stride = input_ddim.count(0, axis); for (int n = 0; n < out_stride; n++) { for (int k = 0; k < in_stride; k++) { const float *in_ptr = input->data<float>() + n * in_channel + k; std::vector<std::pair<float, int>> vec; vec.resize(size); for (int i = 0; i < size; i++) { vec[i] = std::make_pair(in_ptr[i * in_stride], i); } // sort std::partial_sort(vec.begin(), vec.begin() + 1, vec.end(), std::greater<std::pair<float, int>>()); // out float *out_ptr = output->mutable_data<float>() + n * out_channel + k; *out_ptr = vec[0].second; } } }
在paddlelite/lite/backends/arm/math/CMakeFile.txt中的
math_arm library
中添加argmax.cc,在paddlelite/lite/backends/arm/math/funcs.h中添加#include "lite/arm/math/argmax.h"
5. 添加Argmax单测¶
在paddlelite/lite/tests/kernels目录下新建argmax_compute_test.cc文件,声明并实现ArgmaxComputeTester类;
ArgmaxComputeTester类中主要包括PrepareOpDesc、PrepareData和RunBaseline函数。PrepareOpDesc函数设定单测op的类型和输入输出参数,PrepareData函数对输入tensor进行初始化,RunBaseline是基于输入计算得到输出,用于和框架计算的输出进行对比;
使用gtest添加单测,代码如下:
TEST(Argmax, precision) { #ifdef LITE_WITH_ARM LOG(INFO) << "test argmax arm"; Place place(TARGET(kARM)); for (int axis : {0, 1, 2, 3}) { for (int n : {1, 3}) { for (int c : {3, 6}) { for (int h : {9, 18}) { for (int w : {9, 18}) { std::unique_ptr<arena::TestCase> tester( new ArgmaxComputeTester(place, "def", axis, n, c, h, w)); arena::Arena arena(std::move(tester), place, 2e-5); arena.TestPrecision(); } } } } } #endif }
在paddlelite/lite/tests/kernels/CMakeLists.txt中添加
lite_cc_test(test_kernel_argmax_compute SRCS argmax_compute_test.cc DEPS arena_framework ${x86_kernels} ${arm_kernels} ${lite_ops} ${host_kernels})
6. 编译运行¶
- 在paddlelite目录中,执行
./lite/tools/ci_build.sh build_test_arm
,该脚本会创建手机模拟器,并编译运行所有单测(花费时间较久)。如果运行无误,则表明添加argmax成功。
新增Layout¶
Paddle-Lite中Place包含了Target、Layout、Precision信息,用来注册和选择模型中的具体Kernel。下面以增加Place中的layout:ImageDefault
、ImageFolder
、ImageNW
为例,讲解如何增加新Layout。
根据在lite/core/
、lite/api
目录下以NHWC
为关键词检索代码,发现需要分别在以下的文件中加入Layout内容:
- lite/api/paddle_place.h
- lite/api/paddle_place.cc
- lite/api/python/pybind/pybind.cc
- lite/core/op_registry.h
- lite/core/op_registry.cc
1. lite/api/paddle_place.h¶
在enum class DataLayoutType
中加入对应的Layout,注意已有的Layout不能改变值,增加新Layout递增即可:
enum class DataLayoutType : int {
kUnk = 0,
kNCHW = 1,
kNHWC = 3,
kImageDefault = 4, // for opencl image2d
kImageFolder = 5, // for opencl image2d
kImageNW = 6, // for opencl image2d
kAny = 2, // any data layout
NUM = 7, // number of fields.
};
2. lite/api/paddle_place.cc¶
本文件有3处修改,注意在DataLayoutToStr
函数中加入对应Layout的字符串名,顺序为lite/api/paddle_place.h
中枚举值的顺序:
// 该文件第1处
const std::string& DataLayoutToStr(DataLayoutType layout) {
static const std::string datalayout2string[] = {
"unk", "NCHW", "any", "NHWC", "ImageDefault", "ImageFolder", "ImageNW"};
auto x = static_cast<int>(layout);
CHECK_LT(x, static_cast<int>(DATALAYOUT(NUM)));
return datalayout2string[x];
}
// 该文件第2处
const std::string& DataLayoutRepr(DataLayoutType layout) {
static const std::string datalayout2string[] = {"kUnk",
"kNCHW",
"kAny",
"kNHWC",
"kImageDefault",
"kImageFolder",
"kImageNW"};
auto x = static_cast<int>(layout);
CHECK_LT(x, static_cast<int>(DATALAYOUT(NUM)));
return datalayout2string[x];
}
// 该文件第3处
std::set<DataLayoutType> ExpandValidLayouts(DataLayoutType layout) {
static const std::set<DataLayoutType> valid_set({DATALAYOUT(kNCHW),
DATALAYOUT(kAny),
DATALAYOUT(kNHWC),
DATALAYOUT(kImageDefault),
DATALAYOUT(kImageFolder),
DATALAYOUT(kImageNW)});
if (layout == DATALAYOUT(kAny)) {
return valid_set;
}
return std::set<DataLayoutType>({layout});
}
3. lite/api/python/pybind/pybind.cc¶
// DataLayoutType
py::enum_<DataLayoutType>(*m, "DataLayoutType")
.value("NCHW", DataLayoutType::kNCHW)
.value("NHWC", DataLayoutType::kNHWC)
.value("ImageDefault", DataLayoutType::kImageDefault)
.value("ImageFolder", DataLayoutType::kImageFolder)
.value("ImageNW", DataLayoutType::kImageNW)
.value("Any", DataLayoutType::kAny);
4. lite/core/op_registry.h¶
找到KernelRegister final中的using any_kernel_registor_t =
,加入下面修改信息:
// 找到KernelRegister final中的`using any_kernel_registor_t =`
// 加入如下内容:
KernelRegistryForTarget<TARGET(kOpenCL),
PRECISION(kFP16),
DATALAYOUT(kNCHW)> *, //
KernelRegistryForTarget<TARGET(kOpenCL),
PRECISION(kFP16),
DATALAYOUT(kNHWC)> *, //
KernelRegistryForTarget<TARGET(kOpenCL),
PRECISION(kFP16),
DATALAYOUT(kImageDefault)> *, //
KernelRegistryForTarget<TARGET(kOpenCL),
PRECISION(kFP16),
DATALAYOUT(kImageFolder)> *, //
KernelRegistryForTarget<TARGET(kOpenCL),
PRECISION(kFP16),
DATALAYOUT(kImageNW)> *, //
KernelRegistryForTarget<TARGET(kOpenCL),
PRECISION(kFloat),
DATALAYOUT(kImageDefault)> *, //
KernelRegistryForTarget<TARGET(kOpenCL),
PRECISION(kFloat),
DATALAYOUT(kImageFolder)> *, //
KernelRegistryForTarget<TARGET(kOpenCL),
PRECISION(kFloat),
DATALAYOUT(kImageNW)> *, //
KernelRegistryForTarget<TARGET(kOpenCL),
PRECISION(kAny),
DATALAYOUT(kImageDefault)> *, //
KernelRegistryForTarget<TARGET(kOpenCL),
PRECISION(kAny),
DATALAYOUT(kImageFolder)> *, //
KernelRegistryForTarget<TARGET(kOpenCL),
PRECISION(kAny),
DATALAYOUT(kImageNW)> *, //
5. lite/core/op_registry.cc¶
该文件有2处修改:
// 该文件第1处
#define CREATE_KERNEL1(target__, precision__) \
switch (layout) { \
case DATALAYOUT(kNCHW): \
return Create<TARGET(target__), \
PRECISION(precision__), \
DATALAYOUT(kNCHW)>(op_type); \
case DATALAYOUT(kAny): \
return Create<TARGET(target__), \
PRECISION(precision__), \
DATALAYOUT(kAny)>(op_type); \
case DATALAYOUT(kNHWC): \
return Create<TARGET(target__), \
PRECISION(precision__), \
DATALAYOUT(kNHWC)>(op_type); \
case DATALAYOUT(kImageDefault): \
return Create<TARGET(target__), \
PRECISION(precision__), \
DATALAYOUT(kImageDefault)>(op_type); \
case DATALAYOUT(kImageFolder): \
return Create<TARGET(target__), \
PRECISION(precision__), \
DATALAYOUT(kImageFolder)>(op_type); \
case DATALAYOUT(kImageNW): \
return Create<TARGET(target__), \
PRECISION(precision__), \
DATALAYOUT(kImageNW)>(op_type); \
default: \
LOG(FATAL) << "unsupported kernel layout " << DataLayoutToStr(layout); \
}
// 该文件第2处
// 找到文件中的下面的函数
KernelRegistry::KernelRegistry()
: registries_(static_cast<int>(TARGET(NUM)) *
static_cast<int>(PRECISION(NUM)) *
static_cast<int>(DATALAYOUT(NUM)))
// 在该函数中加入新增Layout的下面内容
INIT_FOR(kOpenCL, kFP16, kNCHW);
INIT_FOR(kOpenCL, kFP16, kNHWC);
INIT_FOR(kOpenCL, kFP16, kImageDefault);
INIT_FOR(kOpenCL, kFP16, kImageFolder);
INIT_FOR(kOpenCL, kFP16, kImageNW);
INIT_FOR(kOpenCL, kFloat, kImageDefault);
INIT_FOR(kOpenCL, kFloat, kImageFolder);
INIT_FOR(kOpenCL, kFloat, kImageNW);
INIT_FOR(kOpenCL, kAny, kImageDefault);
INIT_FOR(kOpenCL, kAny, kImageFolder);
INIT_FOR(kOpenCL, kAny, kImageNW);
新增Pass¶
本文从三个方面介绍了Lite
中的Pass
结构:Pass是什么、Pass的实现与接口、Pass的一般注册流程。最后以Fc_fuse_pass
为例介绍了fusion_pass
的作用与注册方法。
前述:Pass是什么?¶
CxxPredictor加载模型后,在执行预测前会先优化模型。模型优化过程是通过Pass实现的。
具体调用关系如下:
图片
CreatePredictor(CxxConfig)
函数调用了Predictor->Build(CxxConfig)- CxxPredictor的构建过程(Build)分为两步:
- Predictor->LoadModel() 加载模型文件到program中
- Predicotr->optimizer_.Run() 对Program中的原始图形结构进行优化
- 对图结构的优化是通过调用
Pass->Apply(const std::unique_ptr<SSAGraph>& graph)
方法实现的。
- 对图结构的优化是通过调用
- CxxPredictor的构建过程(Build)分为两步:
每一类Pass定义了一种优化过程,包括:原模型中的kernel选取、OP融合、冗余OP去除、子图创建、内存优化、类型推导、类型转换等。
Pass的实现与接口 :Pass基类、PassManager和Pass注册¶
1、Pass基类:paddle::lite::mir::Pass
¶
class Pass {
public:
// Pass的类型,Pass按照作用的不同可以分为三种
enum class Kind { //种类的作用不太清楚
// 1. 修改模型中的图拓扑结构的Pass
kProgramWise = 0,
// 2. 不修改图结构,修改状态的Pass
kStmtWise,
// 3. 不修改 IR,用于搜集信息和可视化信息的Pass.
kDebug,
};
// 主要实现函数:Apply 函数定义了 Pass 运行时执行的操作
virtual void Apply(const std::unique_ptr<SSAGraph>& graph) = 0;
bool is_program_pass() const { return kind_ == Kind::kProgramWise; }
bool is_stmt_pass() const { return kind_ == Kind::kStmtWise; }
virtual ~Pass() = default;
private:
const Kind kind_; // pass 的种类
std::string name_; // pass 的名称
std::set<TargetType> bound_targets_; // 指定了Pass运行的硬件平台,模型优化过程会根据当前硬件平台是否匹配筛选Pass。
std::unordered_map<std::string, std::set<lite_api::Place>> bound_kernels_; // 绑定的kernel
};
// Different kinds.
class ProgramPass : public Pass {
public:
ProgramPass() : Pass(Kind::kProgramWise) {}
};
class StmtPass : public Pass {
public:
StmtPass() : Pass(Kind::kStmtWise) {}
};
class DebugPass : public Pass {
public:
DebugPass() : Pass(Kind::kDebug) {}
};
代码位置:lite/core/mir/pass.h
主要类成员:
const Kind kind_
: Pass类型。pass 有三种基本基本类型 :修改图结构的ProgramPass
、修改状态量的StmtPass
和Debug过程采集信息与控制可视化的DebugPass
。std::string name_
:pass 的名称
std::set<TargetType> bound_targets_
: Pass运行的硬件平台,optimizer.Run()优化过程会根据硬件平台选择匹配的Pass。------根据硬件平台自动选择需要的pass
std::unordered_map<std::string, std::set<lite_api::Place>> bound_kernels_
: Pass 绑定的kernel (what's this used for)
主要接口:
Pass::Apply(const std::unique_ptr& graph)
: Pass优化过程的具体操作,是新注册Pass需要实现的接口。输入为SSAGraph
型指针,是对模型结构的拓扑表示。
2、Pass管理 paddle::lite::mir::PassManager
¶
class PassManager {
public:
// 内部静态变量PassManager,用来存储使用的Pass和图优化操作
static PassManager& Global() {
static PassManager x;
return x;
}
// 执行所有的 Pass
void Run(const std::unique_ptr<SSAGraph>& graph) {
for (auto& pass : passes_) {
LOG(INFO) << "Running MIR pass " << pass->name();
pass->Apply(graph);
}
private:
std::list<std::unique_ptr> passes_; //存储所有的 Pass
std::map<std::string, mir::Pass*> pass_map_; //使用map变量存储 PassName::Pass
}
代码位置:lite/core/mir/pass_manager.h
主要类成员:
std::list:unique_ptr> passes_;
: List类型,存储了所有已注册Pass。
std::map<std::string, mir::Pass*> pass_map_;
: Map类型,存储了所有"Pass名称-Pass类"键对,用于根据名称查找Pass。
主要接口:
static PassManager& Global()
返回PassManager全局静态变量,该变量存储了所有已注册的Pass
bool AddNewPass(const std::string& name, Pass* pass)
添加新的Pass到PassManager中
3、 Pass 注册 paddle::lite::mir::PassRegistry
¶
代码位置:lite/core/mir/pass_registry.h
主要接口:
REGISTER_MIR_PASS(name__, class__)
:宏定义函数,用于注册Pass。注册Pass过程实现的是 PassManager::Global().AddNewPass(name__, class__)
,将新注册Pass添加到全局变量PassManager
中。
Pass的一般注册流程与使用方法¶
1. Pass 注册流程¶
在lite/core/mir
或其子目录下继承Pass基类
,实现Pass::Apply
接口,并使用宏REGISTER_MIR_PASS(name__, class__)
将Pass注册到PassManager
即完成了新Pass注册。
**以新建 **new_demo_pass
为例,具体流程如下:
(1)在lite/core/mir
路径下新建example_pass.cc
和 new_demo_pass.h
文件
(2)在example_pass.h
文件中继承Pass基类(ProgramPass、StmtPass或DebugPass)定义自己的Pass类。
#include "lite/core/mir/pass.h"
namespace paddle {
namespace lite {
namespace mir {
class ExamplePass : public ProgramPass {
void Apply(const std::unique_ptr<SSAGraph> &graph) override {}
...
};
} // namespace mir
} // namespace lite
} // namespace paddle
(3)在example_pass.cc
文件中实现ExamplePass::Apply()
接口,并注册ExamplePass
#include "lite/core/mir/pass_registry.h"
#include "lite/core/mir/example_pass.h"
namespace paddle {
namespace lite {
namespace mir {
void ExamplePass::Apply(const std::unique_ptr<SSAGraph>& graph) {
...
}
} // namespace mir
} // namespace lite
} // namespace paddle
REGISTER_MIR_PASS(example_pass, paddle::lite::mir::ExamplePass)
.BindTargets({TARGET(kARM)}); // Pass执行的目标硬件平台
// .BindKernel("conv2d"); //Pass绑定的 kernel
(4)修改lite/core/mir/CMakeLists.txt
文件,将example_pass.cc
编译到mir_passes
库中
lite_cc_library(mir_passes
SRCS
demo_pass.cc // 新建的Pass文件
...
memory_optimize_pass.cc
DEPS mir_pass types context ${mir_fusers} ${subgraph_passes})
2. Pass使用流程¶
将Pass注册到PassManager后不会自动生效。需要在optimizer->run()
函数中添加该Pass才会在模型优化过程中调用。
(1)在paddle_use_passes.h
文件中调用该Pass
#include "paddle_lite_factory_helper.h" // NOLINT
...
USE_MIR_PASS(new_demo_pass); //调用 new_demo_pass
(2)要想在优化模型时调用该Pass,需要在optimizer->run()
函数中手动添加调用。
修改lite/core/optimizer.h
文件,添加new_demo_pass
到Optimizer::Run()
函数;
class Optimizer {
public:
void Run(...) {
...
if (passes.empty()) {
RunPasses(std::vector<std::string>{
{"new_demo_pass" //将新注册的Pass添加在这里
...
}
...
}
(3)只有CxxPredictor才会在模型加载后根据Pass优化模型。
...
#include "paddle_use_passes.h" // 引用Pass优化模型
void RunModel() {
// 1. 创建 CxxConfig
CxxConfig config;
config.set_model_dir(FLAGS_model_dir);
config.set_valid_places(Place{TARGET(kARM), PRECISION(kFloat)});
// 2. 创建CxxPredictor,该过程包括加载模型和用Pass优化模型
std::shared_ptr> predictor =
Creat<CxxConfig>(config);
}
Fusion Pass的定义与注册¶
Fusion Pass
是一种常见图结构优化Pass,可将多个连续OP融合成单个等效OP,减少数据交换并简化图结构。Pass运行时调用Fuser
自动查找并替换指定图结构,所以注册FuserPass
时还需要实现对应的Fuser类。
下面以fc_fuse_pass
为例,详细说明FusionPass
的效果和注册方法。
fc_fuse_pass
的作用¶
将相邻的mul
算子和 element_wise add
算子 融合成一个 FC
算子
mul(X) = X * W
elementwise_add( mul(x) ) = X * W + Bias
//----------> after fusion
FC(X) = X * W +Bias
Pass 运行效果如下:
图片
mul和elementwise_add的原有参数映射到FC的参数上:
图片
fc_fuse_pass
的注册方法¶
1、创建FcFuser¶
(1)在lite/core/mir/fusion
路径下新建fc_fuser.cc
和 fc_fuser.h
文件
(2)在fc_fuser.h
文件中继承FuseBase
定义自己的Fuser类。
#include "lite/core/mir/pattern_matcher_high_api.h"
namespace paddle {
namespace lite {
namespace mir {
namespace fusion {
class FcFuser : public FuseBase {
public:
void BuildPattern() override;
void InsertNewNode(SSAGraph* graph, const key2nodes_t& matched) override;
private:
cpp::OpDesc GenOpDesc(const key2nodes_t& matched) override;
};
} // namespace fusion
} // namespace mir
} // namespace lite
} // namespace paddle
主要接口:
FuseBase::BuildPattern
: 描述需要替换位置的图结构(pattern),Fuser运行时会自动查找并替换该pattern。
FuseBase::GenOpDesc
: 创建融合后的等效Fused_op。
FuseBase::InsertNewNode
:用Fused_op替换原始图结构(pattern)。
对于 FcFuser
:BuildPattern描述的Pattern是mul+elementwise add
,GenOpDesc创建的FC_op,InsertNewNode函数的效果是用新建的FC_op
替换模型中的mul+elementwise add
pattern。
(3) 在fc_fuser.cc
文件中实现 BuildPattern()
、GenOpDesc()
、InsertNewNode()
接口
下面以FcFuser为例介绍三种接口的实现:
// 1. BuildPattern函数,描述需要替换的图结构
// FcFuser::BuildPattern() 描述了 mul + element_wise add 图结构
void FcFuser::BuildPattern() {
// (1) 用OpNode描述和VarNode
// mul OP
auto* mul = OpNode("mul", "mul");
// mul OP 的输入和输出
auto* x = VarNode("x")->assert_is_op_input("mul", "X");
auto* W = VarNode("W")->assert_is_op_input("mul", "Y");
auto* mul_out = VarNode("mul_out");
// elementwise_add OP
auto* add = OpNode("add", "elementwise_add");
//elementwise_add 的输入
auto* b = VarNode("b")->assert_is_persistable_var();
// elementwise_add OP的输出(最终输出)
auto* Out = VarNode("Out");
//(2) 描述拓扑连接 (Fuse之前mul 和elementwise_add的连接)
std::vector<PMNode*> mul_inputs{W, x};
std::vector<PMNode*> add_inputs{mul_out, b};
mul_inputs >> *mul >> *mul_out;
add_inputs >> *add >> *Out;
//(3) 声明新的拓扑结构中将会被移除的节点,包括被fuse的OP和OP之间的中间变量
mul_out->AsIntermediate();
mul->AsIntermediate();
add->AsIntermediate();
}
// 2. GenOpDesc函数新建等效 Fused_op
// FcFuser::GenOpDesc() 新建了Fc_op
cpp::OpDesc FcFuser::GenOpDesc(const key2nodes_t& matched) {
// (1) 得到第一个OP节点的 OpDesc ,并清空输入输出信息
cpp::OpDesc op_desc = *matched.at("mul")->stmt()->op_info();
op_desc.mutable_inputs()->clear();
op_desc.mutable_outputs()->clear();
// (2) 修改OpDesc , 将OpType设置为 "fc" (FC OP 的OP_type),
op_desc.SetType("fc");
// (3) 设置OpDesc中的Input、Output、Attrbute。分别连接到BuildPattern()函数中创建的VarNode
op_desc.SetInput("Input", {matched.at("x")->arg()->name});
op_desc.SetInput("W", {matched.at("W")->arg()->name});
op_desc.SetInput("Bias", {matched.at("b")->arg()->name});
op_desc.SetOutput("Out", {matched.at("Out")->arg()->name});
op_desc.SetAttr(
"in_num_col_dims",
matched.at("mul")->stmt()->op_info()->GetAttr<int>("x_num_col_dims"));
return op_desc;
}
// 3. InsertNewNode函数用Fused OP 替换模型图中的原始 Pattern
// FcFuser::InsertNewNode() 用Fc_OP替换原始模型图中的 " mul + element_wise add "
void FcFuser::InsertNewNode(SSAGraph* graph, const key2nodes_t& matched) {
// (1) 创建FC OP的参数(OpDesc)
auto op_desc = GenOpDesc(matched);
// 创建一个 FC OP
auto fc_op = LiteOpRegistry::Global().Create("fc");
// 找到原拓扑结构中的scope (作用域)和 valid_places (可支持设备类型)
auto mul = matched.at("mul")->stmt()->op();
auto* scope = mul->scope();
auto& valid_places = mul->valid_places();
// (2) 将 FC OP的 scope和 valid_places设置与fuse前相同,并在图中创建该节点(node)
fc_op->Attach(op_desc, scope);
auto* new_op_node = graph->GraphCreateInstructNode(fc_op, valid_places);
// (3) 将FC节点连接到输入输出(var_node)
IR_NODE_LINK_TO(matched.at("W"), new_op_node);
IR_NODE_LINK_TO(matched.at("x"), new_op_node);
IR_NODE_LINK_TO(matched.at("b"), new_op_node);
IR_NODE_LINK_TO(new_op_node, matched.at("Out"));
}
2、注册fc_fuse_pass¶
(1)在lite/core/mir/fusion
路径下新建fc_fuse_pass.cc
和 fc_fuse_pass.h
文件
(2)在fc_fuse_pass.h
文件中,继承ProgramPass
定义FcFusePass
。
#include "lite/core/mir/pass.h"
namespace paddle {
namespace lite {
namespace mir {
class FcFusePass : public ProgramPass {
public:
void Apply(const std::unique_ptr<SSAGraph>& graph) override; namespace mir namespace lite namespace paddle
(3)在fc_fuse_pass.cc
文件中实现FcFusePass::Apply()
接口,并注册FcFusePass
#include "lite/core/mir/pass_registry.h"
#include "lite/core/mir/example_pass.h"
namespace paddle {
namespace lite {
namespace mir {
void FcFusePass::Apply(const std::unique_ptr<SSAGraph>& graph) {
fusion::FcFuser fuser;
fuser(graph.get());namespace mir
} // namespace lite
} // namespace paddle
REGISTER_MIR_PASS(lite_fc_fuse_pass, paddle::lite::mir::FcFusePass)
.BindTargets({TARGET(kAny)}) // FcFusePass 可以在任何硬件平台执行
.BindKernel("fc"); // FcFusePass 绑定 fc_kernel
(4)修改lite/core/mir/fusion/CMakeLists.txt
文件,将fc_fuser.cc
编译到mir_fusers
库
lite_cc_library(fuse_fc
SRCS fc_fuser.cc
DEPS pattern_matcher_high_api)
set(mir_fusers
fuse_fc
...
CACHE INTERNAL "fusers")
(5)修改lite/core/mir/CMakeLists.txt
文件,将fc_fuse_pass.cc
编译到mir_pass
库
lite_cc_library(mir_passes
SRCS
fusion/fc_fuse_pass.cc
...
DEPS mir_pass types context ${mir_fusers} ${subgraph_passes})
3、使用 fc_fuse_pass¶
(1) lite/api/paddle_use_passes.h
使用USE_LITE_PASS
宏来引入新加入的pass
USE_MIR_PASS(lite_fc_fuse_pass);
(2) 在lite/core/optimizer.h
文件的Optimizer::Run()
函数中添加新注册的pass
class Optimizer {
public:
void Run(Program&& program,
const std::vector<Place>& valid_places,
core::KernelPickFactor kernel_pick_factor,
const std::vector<std::string>& passes = {}) {
...
if (passes.empty()) {
RunPasses(std::vector<std::string>{
{"lite_fc_fuse_pass", // the newly registered pass
...
"argument_type_display_pass"}});
} else {
RunPasses(passes);
}
exec_scope_ = program.exec_scope();
}
(3) 以上修改完成后,在CreatePredictor(CxxConfig)创建CxxPredictor时,模型优化过程会调用lite_fc_fuse_pass
,扫描mul + element_wise add
结构并替换为等效的Fc_OP。
Road map¶
这篇文档会介绍 Paddle-Lite 近期对外的开源版本和计划。
其中包含的 feature 为最小集合,按最终发布的版本为准。
2.0.0-beta1-prerelease¶
预计发布 2019-8-26 ~ 2days
- 完善编译和 benchmark 文档
- 增加第三方依赖代码的离线下载功能,加速编译过程
- 去掉
tiny_publish
模式下无关的第三方代码下载,可以不依赖任何第三方
2.0.0-beta1¶
预计发布 2019-9-1~2days
model_optimize_tool
从 ARM 上执行修改为 Host 上执行,只从 kernel 分布来确定计算图优化;后续硬件针对优化会发布新的工具;- Paddle 模型支持参数 composed 的格式
- 增加分层编译来控制常用模型的部署库的大小,分两个模式
basic
,extra
;默认basic
模式只发布核心的op 和kernel;将控制流相关的Op和kernel 折叠进extra
按需编译 - 增加 INT8 量化,从 PaddleSlim 训练到 PaddleLite 部署完整案例
- 支持内存中加载模型,以支持 APP 的简易加密
2.3¶
2.6¶
FAQ 常见问题¶
问题或建议可以发Issue,为加快问题解决效率,可先检索是否有类似问题,我们也会及时解答! 欢迎加入Paddle-Lite百度官方QQ群:696965088
- 在Host端采用交叉编译方式编译PaddleLite,将编译后的libpaddle_light_api_shared.so和可执行程序放到板卡上运行,出现了如下图所示的错误,怎么解决?
host_target_compiling_env_miss_matched
- 原因是Host端的交叉编译环境与Target端板卡的运行环境不一致,导致libpaddle_light_api_shared.so链接的GLIBC库高于板卡环境的GLIBC库。目前有四种解决办法(为了保证编译环境与官方一致,推荐第一种方式):1)在Host端,参考源码编译中的Docker方式重新编译libpaddle_light_api_shared.so;2)在Host端,使用与Target端版本一致的ARM GCC和GLIBC库重新编译libpaddle_light_api_shared.so;3)在Target端板卡上,参考源码编译中的ARM Linux本地编译方式重新编译libpaddle_light_api_shared.so;4)在Target端板卡上,将GLIBC库升级到和Host端一致的版本,即GLIBC2.27。