ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Paddle Lite 端侧部署实战:从 Paddle 动态图训练到 Android ARM C++ 推理

Paddle Lite 端侧部署实战:从 Paddle 动态图训练到 Android ARM C++ 推理 Paddle Lite 端侧部署实战从 Paddle 动态图训练到 Android ARM C 推理【免费下载链接】Paddle-LitePaddlePaddle High Performance Deep Learning Inference Engine for Mobile and Edge (飞桨高性能深度学习端侧推理引擎项目地址: https://gitcode.com/GitHub_Trending/pa/Paddle-Lite本篇技术指南围绕训练 → 动转静保存 → 端侧部署的完整链路展开以 LeNet 在 MNIST 上的图像分类任务为例介绍如何使用 Paddle 2.0 及以上的新接口训练一个简单模型将其保存为预测部署格式再借助 Paddle Lite 的 C 接口部署到 Android ARM 设备上执行推理。读完本文你将掌握预测格式模型与训练格式模型的差异、paddle.jit.to_static与paddle.jit.save的动转静用法、Paddle Python 推理接口的调用方式以及 Paddle LiteMobileConfigPaddlePredictor在移动端的五步推理流程并能在真实安卓设备上跑通一个分类 Demo。Paddle Lite 端侧推理的完整流程配置 → 模型加载 → 创建 Predictor → 输入数据 → 执行推理 → 输出结果图片来源[C 完整示例](https://link.gitcode.com/i/5b24f153bd3b9af087a26bfe2f7435be)一、从训练到端侧部署的整体链路一个模型要最终跑在移动端通常需要经历以下环节Paddle 训练使用 Paddle 动态图Dygraph新接口构建网络、训练模型此时得到的是带完整拓扑含训练辅助算子的动态图模型动转静保存通过InputSpec标记输入用paddle.jit.to_static将动态图转为静态图再用paddle.jit.save保存为预测格式模型。预测格式相对训练格式在拓扑上进行了裁剪去除了预测不需要的算子Python 端验证使用 Paddle 自带的paddle.inferencePython 接口加载预测模型做一次推理确认模型正确可用opt 模型转换使用 Paddle Lite 的 opt 工具将 Paddle 原生模型转化为移动端可执行的naive_buffer.nb格式并完成量化、算子融合、Kernel 优选等优化C 端侧部署在 Android ARM 设备上通过 Paddle Lite 的 C 接口MobileConfigCreatePaddlePredictor加载.nb模型执行推理。本文对应仓库中的原始说明文档为 docs/introduction/training_to_deployment.md并补充了 docs/user_guides/cpp_demo.md、docs/user_guides/model_optimize_tool.md 以及lite/api、lite/demo/cxx等源码目录中的实现细节。二、使用 Paddle 新接口训练一个简单模型本节参考 LeNet 的 MNIST 数据集图像分类任务使用 Paddle 动态图接口训练一个简单模型。核心目标是理解如何生成可供部署的模型文件。2.1 环境要求与版本检查开始前需要完成 Paddle 的安装要求 Paddle 版本号 2.0.0。安装完成后先检查版本print(paddle.__version__)依赖包导入如下import paddle import paddle.nn.functional as F from paddle.nn import Layer from paddle.vision.datasets import MNIST from paddle.metric import Accuracy from paddle.nn import Conv2D, MaxPool2D, Linear from paddle.static import InputSpec from paddle.jit import to_static from paddle.vision.transforms import ToTensor其中InputSpec与to_static是后面动转静保存的关键模块InputSpec用于声明模型输入张量的 shape 与 nameto_static负责把动态图 Layer 转换为静态图程序。2.2 数据集准备使用 Paddle 内置的 MNIST 数据集mode分别指定训练集与测试集transform使用ToTensor()将数据转换为张量train_dataset MNIST(modetrain, transformToTensor()) test_dataset MNIST(modetest, transformToTensor())2.3 构建 LeNet 网络LeNet 是经典的手写数字识别卷积网络依次包含两个卷积 池化阶段和三个全连接层class LeNet(paddle.nn.Layer): def __init__(self): super(LeNet, self).__init__() self.conv1 paddle.nn.Conv2D(in_channels1, out_channels6, kernel_size5, stride1, padding2) self.max_pool1 paddle.nn.MaxPool2D(kernel_size2, stride2) self.conv2 paddle.nn.Conv2D(in_channels6, out_channels16, kernel_size5, stride1) self.max_pool2 paddle.nn.MaxPool2D(kernel_size2, stride2) self.linear1 paddle.nn.Linear(in_features16*5*5, out_features120) self.linear2 paddle.nn.Linear(in_features120, out_features84) self.linear3 paddle.nn.Linear(in_features84, out_features10) def forward(self, x): x self.conv1(x) x F.relu(x) x self.max_pool1(x) x F.relu(x) x self.conv2(x) x self.max_pool2(x) x paddle.flatten(x, start_axis1,stop_axis-1) x self.linear1(x) x F.relu(x) x self.linear2(x) x F.relu(x) x self.linear3(x) return x需要注意在forward的展平阶段paddle.flatten(x, start_axis1, stop_axis-1)将[N, C, H, W]展平为[N, C*H*W]保证展平后的维度16*5*5 400与linear1的in_features严格对应。MNIST 输入是单通道1×28×28因此conv1的in_channels1两次 2×2 stride2 池化后conv2输出的空间尺寸恰好为 5×5。2.4 模型训练使用DataLoader组织数据、Adam优化器更新参数训练 2 个 epochtrain_loader paddle.io.DataLoader(train_dataset, batch_size64, shuffleTrue) model LeNet() optim paddle.optimizer.Adam(learning_rate0.001, parametersmodel.parameters()) def train(model, optim): model.train() epochs 2 for epoch in range(epochs): for batch_id, data in enumerate(train_loader()): x_data data[0] y_data data[1] predicts model(x_data) loss F.cross_entropy(predicts, y_data) acc paddle.metric.accuracy(predicts, y_data) loss.backward() if batch_id % 300 0: print(epoch: {}, batch_id: {}, loss is: {}, acc is: {}.format(epoch, batch_id, loss.numpy(), acc.numpy())) optim.step() optim.clear_grad() train(model, optim)训练过程的要点model.train()将模型置于训练模式与model.eval()相对每个 batch 依次完成前向计算、cross_entropy损失计算、loss.backward()反向传播、optim.step()参数更新与optim.clear_grad()梯度清零paddle.metric.accuracy用于在训练循环中打印当前 batch 的准确率方便观测收敛情况。三、训练部署场景的模型与参数保存载入3.1 动转静从动态图到预测格式模型实际部署时必须使用预测格式的模型。预测格式模型相对训练格式模型而言在拓扑上进行了裁剪去除了预测不需要的算子。实现动转静只需两步用InputSpec标记模型的输入再调用paddle.jit.to_static和paddle.jit.savenet to_static(model, input_spec[InputSpec(shape[None, 1, 28, 28], namex)]) paddle.jit.save(net, inference_model/lenet)也可以直接写成一步完成paddle.jit.save(model, inference_model/lenet, input_spec[InputSpec(shape[None, 1, 28, 28], namex)])InputSpec中的shape[None, 1, 28, 28]表示批维度为动态None表示任意 batch 大小通道数为 1宽高为 28×28这与 MNIST 输入完全一致namex为输入张量命名后续部署侧可通过该名称访问输入。3.2 保存产物的文件格式Paddle 2.0 及之后的版本默认保存的权重格式为*.pdiparams后缀的文件。执行保存后会在当前路径inference_model/目录下生成三个文件文件含义lenet.pdmodel模型文件网络结构预测格式lenet.pdiparams权重文件参数数据lenet.pdiparams.info参数信息文件而 Paddle 1.8 及更早版本以权重分离的方式保存模型权重参数信息分开保存在多个参数文件中模型保存在文件__model__中。这两种格式在后文使用Config加载时的写法不同见 4.1 节。3.3 完整参考代码以下为从数据准备、网络构建、训练到保存预测模型的完整可运行代码import paddle import paddle.nn.functional as F from paddle.nn import Layer from paddle.vision.datasets import MNIST from paddle.metric import Accuracy from paddle.nn import Conv2D, MaxPool2D, Linear from paddle.static import InputSpec from paddle.jit import to_static from paddle.vision.transforms import ToTensor class LeNet(paddle.nn.Layer): def __init__(self): super(LeNet, self).__init__() self.conv1 paddle.nn.Conv2D(in_channels1, out_channels6, kernel_size5, stride1, padding2) self.max_pool1 paddle.nn.MaxPool2D(kernel_size2, stride2) self.conv2 paddle.nn.Conv2D(in_channels6, out_channels16, kernel_size5, stride1) self.max_pool2 paddle.nn.MaxPool2D(kernel_size2, stride2) self.linear1 paddle.nn.Linear(in_features16 * 5 * 5, out_features120) self.linear2 paddle.nn.Linear(in_features120, out_features84) self.linear3 paddle.nn.Linear(in_features84, out_features10) def forward(self, x): x self.conv1(x) x F.relu(x) x self.max_pool1(x) x F.relu(x) x self.conv2(x) x self.max_pool2(x) x paddle.flatten(x, start_axis1, stop_axis-1) x self.linear1(x) x F.relu(x) x self.linear2(x) x F.relu(x) x self.linear3(x) return x def train(model, optim): model.train() epochs 2 for epoch in range(epochs): for batch_id, data in enumerate(train_loader()): x_data data[0] y_data data[1] predicts model(x_data) loss F.cross_entropy(predicts, y_data) acc paddle.metric.accuracy(predicts, y_data) loss.backward() if batch_id % 300 0: print(epoch: {}, batch_id: {}, loss is: {}, acc is: {}.format( epoch, batch_id, loss.numpy(), acc.numpy())) optim.step() optim.clear_grad() if __name__ __main__: # paddle version print(paddle.__version__) # prepare datasets train_dataset MNIST(modetrain, transformToTensor()) test_dataset MNIST(modetest, transformToTensor()) # load dataset train_loader paddle.io.DataLoader(train_dataset, batch_size64, shuffleTrue) # build network model LeNet() # prepare optimizer optim paddle.optimizer.Adam(learning_rate0.001, parametersmodel.parameters()) # train network train(model, optim) # save inferencing format model net to_static(model, input_spec[InputSpec(shape[None, 1, 28, 28], namex)]) paddle.jit.save(net, inference_model/lenet)四、使用 Paddle Python 接口预测部署在进入移动端之前建议先用 Paddle 自带的 Python 推理接口验证已保存的预测模型是否正确。这一环节的 API 来自paddle.inference模块。4.1 加载预测模型并进行预测配置通过Config加载模型文件和参数文件并创建预测引擎config Config( inference_model/lenet/lenet.pdmodel, inference_model/lenet/lenet.pdiparams ) # 通过模型和参数文件路径加载 config.disable_gpu() # 使用 cpu 预测 predictor create_predictor(config) # 根据预测配置创建预测引擎 predictor注意如果模型是以权重分离的方式保存的即 Paddle 1.8 风格的__model__ 多个参数文件Config需要按如下方式设置——直接传入保存模型的目录路径config Config(inference_model/) # 通过路径加载路径下保存着模型文件和多个权重信息文件 config.disable_gpu() # 使用 cpu 预测 predictor create_predictor(config) # 根据预测配置创建预测引擎 predictorconfig.disable_gpu()表示使用 CPU 执行推理更多配置选项可参考 Paddle 官方 Python 推理Config文档。4.2 设置输入推理前需要获取输入 Tensor 的名称再根据名称取得输入句柄然后准备数据并拷贝到待预测的设备上# 获取输入变量名称 input_names predictor.get_input_names() input_handle predictor.get_input_handle(input_names[0]) ### 设置输入 fake_input np.random.randn(1, 1, 28, 28).astype(float32) input_handle.reshape([1, 1, 28, 28]) input_handle.copy_from_cpu(fake_input)这里使用了随机数据填充[1, 1, 28, 28]的输入实际应用中应将其替换为预处理后的真实图片如归一化到[0,1]的灰度图像。4.3 运行预测并获取输出predictor.run() # 获取输出变量名称 output_names predictor.get_output_names() output_handle predictor.get_output_handle(output_names[0]) output_data output_handle.copy_to_cpu()获取输出句柄的方式与输入类似最终得到的output_data是numpy.ndarray类型方便后续用 numpy 处理例如取argmax得到分类结果。4.4 完整可运行代码import numpy as np from paddle.inference import Config from paddle.inference import create_predictor config Config(inference_model/lenet.pdmodel, inference_model/lenet.pdiparams) config.disable_gpu() # 创建 PaddlePredictor predictor create_predictor(config) # 获取输入的名称 input_names predictor.get_input_names() input_handle predictor.get_input_handle(input_names[0]) # 设置输入 fake_input np.random.randn(1, 1, 28, 28).astype(float32) input_handle.reshape([1, 1, 28, 28]) input_handle.copy_from_cpu(fake_input) # 运行 predictor predictor.run() # 获取输出 output_names predictor.get_output_names() output_handle predictor.get_output_handle(output_names[0]) output_data output_handle.copy_to_cpu() # numpy.ndarray 类型 print(output_data)五、使用 Paddle Lite 预测库与 C 接口部署到 Android ARM存储好的模型可以使用 Paddle Lite 的 C 接口在 Android ARM 设备上执行预测部署。完整流程可参考 C 完整示例本节结合仓库源码lite/demo/cxx/mobile_light/mobilenetv1_light_api.cc 与 lite/api/paddle_api.h展开讲解。5.1 前置步骤用 opt 将模型转换为.nb格式Paddle Lite 并不能直接执行 Paddle 原生模型需要通过opt 工具将模型转化为 Paddle Lite 支持的naive_buffer.nb格式。opt 工具会自动执行一系列优化包括存储格式转换有效降低模型体积、量化、子图融合、混合调度、Kernel 优选等见 模型优化工具 opt。安装 Paddle Lite 后即可通过命令行调用 optpip install paddlelite paddle_lite_opt --model_dir./lenet_inference \ --valid_targetsarm \ --optimize_out_typenaive_buffer \ --optimize_out./lenet_opt更完整的 opt 参数说明如下参考 opt Python 调用文档选项说明--model_dir待优化的 PaddlePaddle 模型非 combined 形式的路径--model_file待优化的 PaddlePaddle 模型combined 形式的网络结构文件路径--param_file待优化的 PaddlePaddle 模型combined 形式的权重文件路径--optimize_out_type输出模型类型protobuf或naive_buffer移动端执行预测需选naive_buffer默认protobuf--optimize_out优化模型的输出路径--valid_targets指定模型可执行的 backend默认为arm可同时指定多个逗号分隔opt 自动选择最佳方式如npu,arm--enable_fp16是否启用 Float16 低精度量化可提速并降低内存占用但预测精度会有降低--quant_model是否启用 opt 的动态离线量化功能--quant_type动态离线量化的类型QUANT_INT8体积约减 4 倍、精度略有影响或QUANT_INT16体积约减 2 倍、精度基本无影响转换成功后在输出目录生成lenet_opt.nb单文件模型。需要注意非 combined 模型设置--model_dir忽略--model_file/--param_filecombined 模型即.pdmodel.pdiparams则设置--model_file/--param_file忽略--model_dirnaive_buffer优化结果为.nb结尾的单个文件protobuf优化结果为model与params两个文件将model重命名为__model__可用 Netron 可视化模型结构opt 还支持统计模型算子信息--print_model_opstrue与打印 Paddle Lite 算子支持情况--print_all_opstrue/--print_supported_opstrue用于判断模型是否能在目标硬件上运行。5.2 C API 推理的五步流程使用 Paddle Lite C 接口执行预测只需五步。完整流程可参考 C 完整示例相关 API 定义在 lite/api/paddle_api.h 中。第一步引用头文件和命名空间#include paddle_api.h using namespace paddle::lite_api;第二步指定模型文件创建 Predictor// 1. Set MobileConfig MobileConfig config; // 2. Set the path to the model generated by opt tools config.set_model_from_file(model_file_path); // 3. Create PaddlePredictor by MobileConfig std::shared_ptrPaddlePredictor predictor CreatePaddlePredictorMobileConfig(config);MobileConfig继承自ConfigBaselite/api/paddle_api.h支持配置线程数、功耗模式、OpenCL 参数、XPU 参数、Metal 参数等。常用配置接口包括config.set_threads(int threads)设置 CPU 线程数config.set_power_mode(PowerMode mode)设置 ARM 功耗模式config.set_opencl_binary_path_name(path, name)/config.set_opencl_tune(...)/config.set_opencl_precision(...)OpenCL GPU 相关的 kernel 缓存、算法调优与精度设置。第三步设置模型输入以全一输入为例std::unique_ptrTensor input_tensor(std::move(predictor-GetInput(0))); input_tensor-Resize({1, 3, 224, 224}); auto* data input_tensor-mutable_datafloat(); for (int i 0; i ShapeProduction(input_tensor-shape()); i) { data[i] 1; }其中Tensor是 Paddle Lite 的输入输出数据载体lite/api/paddle_api.h核心操作包括Resize(shape)分配空间、mutable_dataT()获取可写数据指针、dataT()获取只读数据、shape()查询形状等。如果模型有多个输入每一个输入都需要准确设置 shape 和 data。第四步执行预测predictor-Run();第五步获得预测结果std::unique_ptrconst Tensor output_tensor( std::move(predictor-GetOutput(0))); // 转化为数据 auto output_data output_tensor-datafloat();除了上述接口PaddlePredictorlite/api/paddle_api.h还提供了GetInputNames()/GetOutputNames()按名称获取输入输出、Clone()创建共享持久化变量的多实例、TryShrinkMemory()压缩内存池、SaveOptimizedModel()持久化优化后模型等能力。更完整的 C API 说明见 C API 文档。5.3 使用仓库自带的 mobile_light Demo 在安卓设备上跑通仓库中的 lite/demo/cxx/mobile_light/mobilenetv1_light_api.cc 给出了一个可直接编译运行的完整示例。该程序的主流程即上文五步构造MobileConfig、加载.nb模型、按命令行传入的 shape 填充全一输入、Run()循环执行推理并统计耗时支持warmup与repeats参数、最后打印输出 tensor 的 shape、均值与标准差。其命令行参数说明见 mobilenetv1_light_api.cc./mobilenetv1_light_api naive_buffer_model_dir raw_input_shapes eg: 1,3,224,224多输入用冒号分隔 repeats eg: 100 warmup eg: 10 power_mode 0: big cluster, high performance 1: little cluster 2: all cores 3: no bind thread_num eg: 1 accelerate_opencl 0: 关闭 opencl kernel cache tuning 1: 开启 opencl kernel cache tuning print_output 0/1: 是否打印输出到 stdout在安卓设备上部署的完整操作参考 C 完整示例准备预测库下载或编译 Paddle Lite 预测库如inference_lite_lib.android.armv8其中cxx/include为头文件cxx/lib/libpaddle_light_api_shared.so为动态预测库demo/cxx/mobile_light为示例工程准备部署模型用 opt 工具将 Paddle 原生模型转为.nb文件mobilenet_v1_opt.nb编译示例程序在预测库目录的demo/cxx/mobile_light下执行make生成可执行文件mobilenetv1_light_api推送并执行用 adb 将.nb模型、动态库与可执行文件推送到手机的/data/local/tmp设置LD_LIBRARY_PATH后执行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 adb shell cd /data/local/tmp export LD_LIBRARY_PATH$LD_LIBRARY_PATH:/data/local/tmp ./mobilenetv1_light_api mobilenet_v1_opt.nb按文档示例运行成功后控制台会输出每轮推理耗时如 33~34 ms 量级具体数值取决于设备与配置、benchmark 汇总input_shape、warmup、repeats、max/min/avg_duration以及输出 tensor 的 shape、元素数量、标准差与均值可用于快速验证端侧推理链路是否打通。除了基准性能验证用的 mobile_light 外仓库 lite/demo/cxx 还提供了更贴近业务场景的示例包括使用 OpenCV 读取真实图片并输出 top-5 分类结果的mobile_classify、目标检测的ssd_detection、yolov3 检测、mask_detection 等均可按模型下载 → opt 转换 → make 编译 → adb 推送 → 执行的相同模式运行。六、小结本文完整走通了一条Paddle 训练 → 动转静保存 → Python 验证 → opt 转换 → C 端侧部署的端侧 AI 落地链路训练侧使用 Paddle 2.0 动态图接口构建 LeNet 并训练理解了InputSpecto_staticpaddle.jit.save的动转静保存方式以及.pdmodel/.pdiparams/.pdiparams.info三件套与 Paddle 1.8 权重分离格式的差异验证侧掌握paddle.inference的Config、create_predictor、输入输出句柄等 Python 推理 API部署侧掌握 opt 工具的参数含义与.nb模型生成方法以及 Paddle Lite C 接口MobileConfig→CreatePaddlePredictor→GetInput→Run→GetOutput的五步推理流程并能在 Android ARM 设备上跑通仓库自带的 mobile_light 示例。对于需要部署第三方框架TensorFlow、Caffe、ONNX、PyTorch模型的场景可借助 X2Paddle 先将第三方模型转为 PaddlePaddle 格式再经 opt 转为 Paddle Lite 支持的格式X2Paddle 也已集成 opt提供convert_to_liteTrue的一键转换能力详见 模型优化工具 opt。从训练到端侧推理的完整流程可进一步参考仓库中的 quick_start/tutorial 与 模型优化工具 等文档。【免费下载链接】Paddle-LitePaddlePaddle High Performance Deep Learning Inference Engine for Mobile and Edge (飞桨高性能深度学习端侧推理引擎项目地址: https://gitcode.com/GitHub_Trending/pa/Paddle-Lite创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进