尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

PyTorch模型转换PaddleLite并集成Android完整实践指南

PyTorch模型转换PaddleLite并集成Android完整实践指南 将 PyTorch 模型转换到 PaddleLite 并集成进 Android 应用这趟流程我前前后后趟过不少水。很多朋友在服务器上训完模型第一步就卡在“怎么把模型塞进手机跑起来”这件事上尤其是从 PyTorch 生态切到 Paddle 生态中间涉及到的格式转换、算子兼容、预处理对齐每一个环节都有能让你挠头的地方。这篇内容我会把完整的链路拆开讲透从环境准备、ONNX 导出、PaddleLite 转换到 Android Studio 里的集成调用再到实际部署时的性能调优和踩坑笔记。我不会只给你能跑的代码还会解释每一步为什么这么做以及我在实际项目中遇到过的真实问题。不管你是第一次做端侧推理还是已经从其他框架转过模型这篇文章都能给你省下几天的摸索时间。1. 项目整体思路拆解PyTorch 训练好的模型要跑在 Android 上核心链条可以概括成四步导出、转换、集成、调优。下面这张流程图不是标准化的架构图而是我当时实际执行的路线图你可以直接照着走。PyTorch 训练好的模型 (.pth/.pt) ↓ 导出为 ONNX 格式 (.onnx) ↓ 使用 PaddleLite 的 opt 工具转换为 Paddle 格式 (.nb) ↓ 将 .nb 模型文件放入 Android 工程 assets 目录 ↓ 在 Java/Kotlin 层调用 PaddleLite 预测 API ↓ 完成预处理、推理、后处理输出结果我之所以选择 ONNX 作为中间桥梁而不是直接把 PyTorch 权重转成 PaddlePaddle 的权重格式主要原因是 ONNX 已经成为深度学习模型交换的事实标准。PyTorch 官方对 ONNX 导出的支持非常完善PaddleLite 对 ONNX 的支持也在持续迭代中这条链路踩坑最少、社区解决方案最丰富。另外需要提前说明的是PaddleLite 在 2.x 版本之后已经全面转向使用.nb文件作为端侧推理的模型格式。这个.nb文件是经过算子融合、内存优化、量化压缩之后的产物相比于直接加载原始 ONNX加载速度和推理性能都有明显提升。所以在转换这一步我们最终的目标文件一定是.nb格式。还有一个关键决策是使用 PaddleLite 而不是其他推理框架。如果你在 Android 端还考虑过 ncnn、MNN 或者 TensorFlow Lite那么我建议你根据当前项目的实际情况来选择。PaddleLite 在 ARM CPU 上的性能优化做得相当出色而且提供了完整的 Java API对 Android 开发者非常友好。后续需要支持 GPU 或 NPU 加速时PaddleLite 也有对应的硬件后端扩展性不用担心。2. 环境准备与关键工具选型2.1 Python 环境与依赖版本锁定整个转换流程基于 Python 完成所以我建议你新建一个干净的虚拟环境专门用来做模型转换不要和训练环境混在一起。训练环境里通常装着一堆版本敏感的库稍有不慎就会互相干扰。我在实际项目中使用的 Python 环境配置如下这个组合经过验证可以稳定工作工具/库版本说明Python3.8兼容性最好不建议用 3.10PyTorch1.12.1导出 ONNX 的稳定版本PaddlePaddle2.4.2与 PaddleLite 2.12 对应PaddleLite2.12opt 工具版本onnxruntime1.13.1用于验证 ONNX 输出正确性创建虚拟环境并安装依赖的命令我贴出来你可以直接复制运行conda create -n pytorch2paddle python3.8 conda activate pytorch2paddle pip install torch1.12.1 torchvision0.13.1 pip install paddlepaddle2.4.2 pip install paddlelite2.12 pip install onnxruntime1.13.1这里有一个特别容易踩的坑PaddlePaddle 和 PaddleLite 的版本必须匹配。如果你安装了最新版的 PaddlePaddle那么 opt 工具可能在某些算子转换时报错。我曾经因为安装了 PaddlePaddle 2.5 导致 MobileNet 里的某个算子转换失败回退到 2.4.2 就一切正常了。2.2 Android Studio 端准备工作Android 端的开发环境要求相对简单Android Studio 4.2 以上版本minSdkVersion 21 或以上gradle 版本 6.5 以上即可。PaddleLite 的 Android 库是通过aar方式集成的不需要额外下载 NDK也不需要引入 CMake这大大简化了工程的构建流程。不过我还是建议你在 Android Studio 里提前配置好对assets目录的访问权限。因为后续我们会把.nb模型文件放在assets目录下如果工程里还有其他资源文件需要注意资源混淆的问题。我之前遇到过 Release 构建下模型文件被混淆改名的情况排查了很久才发现是android:extractNativeLibs和资源压缩导致的。3. PyTorch 模型导出为 ONNX 格式3.1 导出前的模型准备在导出 ONNX 之前有一件非常重要的事情必须先做确定模型的输入输出格式。包括输入张量的维度、类型、归一化方式以及输出张量的含义和顺序。这些信息看起来简单但如果你在导出时没有明确记录下来到了 Android 端写预处理的时候就会抓瞎。我通常会在导出前把模型的信息打印出来确认输入的通道顺序是NCHW还是NHWC以及 Normalize 时使用的均值和标准差。PyTorch 默认是NCHW格式而很多移动端部署习惯用NHWC这中间如果衔接不好模型跑出来的结果就是完全错误的。这里有一个最大的坑预处理必须在导出前就融入模型或者与模型权重一起固化下来。我见过太多人把归一化、标准化这些操作留在 PyTorch 代码里导出模型时只导出了网络的主体部分。结果到了 Android 端他们又不得不手动实现归一化稍微有一个细节没对齐推理结果偏差就非常大。我的建议是将标准化操作直接嵌入模型的第一层或者干脆作为导出模型的一部分写进 ONNX 里。这样 Android 端只需要把原始像素值直接喂给模型就可以了。3.2 使用 TorchScript 导出 ONNX 的坑PyTorch 导出 ONNX 的标准方式是通过torch.onnx.export函数。大部分情况下使用torch.jit.trace模式就足够了因为 trace 方式只执行一次模型记录计算图的结构速度快且稳定性好。但对于包含动态控制流的模型比如 if-else 分支、循环trace 就无能为力了。我处理过的一个语义分割模型使用了条件判断来控制输出分支trace 导出的 ONNX 在转换为 Paddle 格式后推理结果完全错误。后来改用脚本模式script导出才解决。基本导出代码示例如下import torch # 加载训练好的模型 model YourModel() model.load_state_dict(torch.load(model.pth)) model.eval() # 定义输入张量注意维度必须和推理时一致 dummy_input torch.randn(1, 3, 224, 224) # 导出 ONNX torch.onnx.export( model, dummy_input, model.onnx, export_paramsTrue, opset_version11, do_constant_foldingTrue, input_names[input], output_names[output], dynamic_axes{input: {0: batch_size}, output: {0: batch_size}} )有几个参数需要单独说明opset_version11是我实测最稳定的版本。opset 版本太新可能导致 PaddleLite 的转换工具不认识某些算子版本太旧又可能丢失部分优化信息。dynamic_axes设置动态 batch 维度。虽然 Android 端通常都是单张图片推理但开启动态维度不会影响转换过程而且增加了灵活性我建议保留。do_constant_foldingTrue可以把一些常量计算提前固化减小模型体积同时去掉一些冗余节点。3.3 验证 ONNX 输出正确性导出完成之后绝对不要急着做下一步。先用 onnxruntime 验证一下导出的 ONNX 模型输出是否和 PyTorch 模型一致。这一步能帮你确定问题出在哪个环节省得后面排查时怀疑人生。import onnxruntime import numpy as np import torch # 准备同样的输入 test_input torch.randn(1, 3, 224, 224) # PyTorch 推理 model.eval() with torch.no_grad(): pytorch_output model(test_input).numpy() # ONNX Runtime 推理 ort_session onnxruntime.InferenceSession(model.onnx) ort_input {ort_session.get_inputs()[0].name: test_input.numpy()} ort_output ort_session.run(None, ort_input)[0] # 对比误差 print(Max abs diff:, np.abs(pytorch_output - ort_output).max())如果最大绝对误差在 1e-4 以下说明 ONNX 导出基本没有问题。如果误差太大先检查预处理是否对齐再检查模型中是否有不支持的算子。我遇到过一次误差较大的问题最后发现是nn.BatchNorm2d在 eval 和 train 模式下输出不同。模型在训练阶段使用了 batch 统计量而导出时虽然调用了model.eval()但如果模型结构里还有 Dropout 层某些情况下 trace 会把训练模式下特有的路径记录进去。解决方式是确保所有 BatchNorm 和 Dropout 都处于 eval 状态后再导出。4. 使用 PaddleLite 工具转换模型4.1 opt 工具的基本用法PaddleLite 提供了opt命令行工具用于将 ONNX 模型转换成端侧推理用的.nb格式。这个工具在你安装paddlelitePython 包之后会自动出现在环境中用法非常直接paddle_lite_opt --model_filemodel.onnx --model_typeonnx --optimize_outmodel --optimize_out_typenaive_buffer --valid_targetsarm参数含义如下--model_file输入模型文件路径这里指定为model.onnx--model_type模型类型因为输入是 ONNX 所以填onnx--optimize_out优化后输出文件的前缀--optimize_out_type输出文件类型naive_buffer是推荐选项支持流式加载--valid_targets指定目标平台arm表示 ARM CPU后续如果要支持 GPU 可以填opencl,arm执行完成之后在输出目录下会生成一个model.nb文件这个文件就是在 Android 端使用的模型文件。需要注意的是不同版本的 PaddleLite 对 ONNX 算子的支持程度有差异。建议在转换之前先查看 PaddleLite 官方文档里的算子支持目录确认模型里用到的算子都在支持列表内。如果不支持通常只能改模型结构或者用其他框架转换。4.2 模型量化的技术与选择如果你对模型的推理速度有要求手机端尤其明显那么量化基本是绕不开的一步。PaddleLite 提供了两种量化方式训练后量化Post-training Quantization和量化感知训练Quantization-aware Training。对大部分项目来说训练后量化是性价比最高的选择。你只需要准备一小部分校准数据通常是几百张代表真实分布的图片工具就会根据这些数据的激活值分布来确定量化参数。转换命令如下paddle_lite_opt --model_filemodel.onnx --model_typeonnx --optimize_outmodel_quant --optimize_out_typenaive_buffer --valid_targetsarm --quant_modeltrue --quant_typeQUANT_INT8这里有一个经验值如果你不知道要不要量化就先跑一遍 FP32 模型测量延迟和内存再量化成 INT8 对比一下精度损失。如果精度损失小于 1%优先选择 INT8 量化。我做过一个人脸识别项目在 MobileNetV3 上量化为 INT8 后精度几乎无损但推理速度提升了将近 3 倍内存占用也降低了 4 倍。不过量化也有适用范围尤其是对网络最后一层和注意力机制部分比较敏感。我的建议是如果模型输出是分类概率并且对 Top-1 准确率要求严苛那么建议保留最后一层不量化或者使用混合量化方案。我需要提醒一点INT8 量化需要硬件支持。老一点的三星、骁龙芯片大部分都支持 INT8 加速但还是要在真机上测试模拟器上的表现不代表真实设备水平。4.3 转换失败与算子兼容排查转换失败是家常便饭几乎每次项目都会遇到几个不兼容的算子。遇到这种情况首先打开 PaddlePaddle 官方提供的算子映射表看看当前的 ONNX 算子在 PaddleLite 2.12 中是否有对应实现。如果没有对应实现我有三个备选方案方案一升级/降级 PaddleLite 版本。不同版本支持的算子范围不一样我曾经遇到过 GroupNorm 算子在新版本中已经支持但旧版本不支持的情况升级后问题直接解决。方案二修改模型结构。把不支持的算子替换成等价组合。比如把某个自定义的 attention 模块前向传播改写为标准的矩阵乘法和 softmax 组合就能解决转换失败。这个方法对模型效果影响极小但需要你对模型结构非常熟悉。方案三使用 ONNX 算子集版本回退。某些算子在新的 opset 版本中行为变化较大PaddleLite 解析器来不及适配。把opset_version从 13 改回 11有时就能绕过问题。还有一个排查技巧转换失败时用 Netron 可视化 ONNX 模型定位到报错的节点附近检查输入输出 shape 是否正常。很多时候 PaddleLite 报错提示并不友好只告诉你某个 op 不支持但通过可视化就能快速定位是哪一个。5. Android 工程集成 PaddleLite 推理5.1 集成 PaddleLite Android SDKPaddleLite 官方提供了两种集成方式一种是通过 Maven 依赖自动下载另一种是手动下载aar文件放入工程。自动方式比较简单但我建议手动方式因为自动方式下载的版本有时候和你其他依赖冲突。手动方式步骤如下从 PaddleLite 官方 GitHub Release 页面下载对应版本的androidAAR 包将 AAR 文件放在app/libs目录下在app/build.gradle中添加依赖repositories { flatDir { dirs libs } } dependencies { implementation(name: paddlelite-android-2.12.0, ext: aar) }将生成的model.nb文件复制到app/src/main/assets/models/目录下我用的 AAR 包版本是 2.12.0 对应上文提到的 PaddleLite 2.12 工具链这套组合在主流机型上运行稳定没有遇到底层崩溃的问题。5.2 Java/Kotlin 推理代码实战PaddleLite 在 Android 端提供了非常简洁的 Java API。下面这段代码是我实际项目中的推理封装做了一个图片分类的完整流程你可以直接参考import com.baidu.paddle.lite.MobileConfig; import com.baidu.paddle.lite.PaddlePredictor; import com.baidu.paddle.lite.Tensor; public class PaddleLiteClassifier { private PaddlePredictor predictor; private float[] inputData; private long[] inputShape; public PaddleLiteClassifier(String modelPath) { MobileConfig config new MobileConfig(); config.setModelFromFile(modelPath); config.setPowerMode(PowerMode.LITE_POWER_HIGH); config.setThreads(4); predictor PaddlePredictor.createPaddlePredictor(config); // 获取输入 tensor记录 shape Tensor input predictor.getInput(0); inputShape new long[]{1, 3, 224, 224}; inputData new float[1 * 3 * 224 * 224]; } public float[] predict(float[] normalizedPixels) { // 填充输入数据 System.arraycopy(normalizedPixels, 0, inputData, 0, inputData.length); Tensor input predictor.getInput(0); input.resize(inputShape); input.setData(inputData); // 执行推理 predictor.run(); // 获取输出 Tensor output predictor.getOutput(0); float[] outputData output.getFloatData(); return outputData; } }这段代码有几个关键点值得展开说明setPowerMode设置的是功耗模式。LITE_POWER_HIGH表示高频运行适合需要低延迟的场景LITE_POWER_LOW则更省电适合长时间后台运行。我通常建议先使用LITE_POWER_HIGH测试性能上限再根据实际需求调整。setThreads(4)设置的是 CPU 线程数。这个参数的合理范围是 2 到 4线程太少性能上不去太多反而会增加调度开销。我的经验值是 4 线程在大部分手机上都能获得不错的性能超过 4 之后收益递减明显。Tensor 的setData方法接受的是一维 float 数组所以如果你手里是 Bitmap需要先做像素值到 float 数组的转换。这里特别提示一个细节inputData的填充顺序必须严格遵循模型的输入要求。如果你的模型期望NCHW格式那么数据要按通道顺序依次排列如果是NHWC则是每个像素的 RGB 依次排列。顺序搞错的后果很可怕模型不会报错但输出结果完全是乱的。5.3 图像预处理的正确姿势说到预处理这里还有一个很容易出问题的环节。PyTorch 模型通常有两个预训练步骤Resize 到固定尺寸然后 Normalize。其中 Normalize 使用的 mean 和 std 值每个模型可能都不一样。如果你在导出 ONNX 时没有把 Normalize 融进模型那么 Android 端的预处理就必须完全复刻这些数值。下面是一个 Android 端完整的预处理代码示例对应 ImageNet 的 mean/stdpublic float[] preprocess(Bitmap bitmap) { // 缩放到模型输入尺寸 Bitmap resized Bitmap.createScaledBitmap(bitmap, 224, 224, true); float[] floatValues new float[224 * 224 * 3]; // mean 和 std 来自训练阶段的归一化参数 float[] mean {0.485f, 0.456f, 0.406f}; float[] std {0.229f, 0.224f, 0.225f}; int[] pixels new int[224 * 224]; resized.getPixels(pixels, 0, 224, 0, 0, 224, 224); for (int i 0; i pixels.length; i) { int pixel pixels[i]; float r ((pixel 16) 0xFF) / 255.0f; float g ((pixel 8) 0xFF) / 255.0f; float b (pixel 0xFF) / 255.0f; // NCHW 排列所有 R 通道数据然后 G然后 B floatValues[i] (r - mean[0]) / std[0]; floatValues[i 224 * 224] (g - mean[1]) / std[1]; floatValues[i 224 * 224 * 2] (b - mean[2]) / std[2]; } return floatValues; }这段代码的关键在于把 RGB 数据按NCHW格式重新排列前 224×224 个值为 R 通道接下来为 G 通道最后为 B 通道。如果你模型输入布局是NHWC相应的排列方式需要调整。另外一个经常被忽略的细节是 Bitmap 的像素格式。从相机获取的 Bitmap 可能是ARGB_8888格式其中每个像素都包含透明度。在转换时既要注意 alpha 通道的忽略也要注意像素值范围和模型训练时的一致性。有些模型训练时直接用原始[0, 255]像素值有些则先除以 255 归一化到[0, 1]。这一点对最终输出结果影响是决定性的。5.4 多线程与内存管理的最佳实践移动端应用的环境非常特殊主线程不能执行耗时操作否则会触发 ANR。推理本身是计算密集型任务实测在主流机型上跑一个 MobileNetV3 大约需要 20~50 毫秒这不是一个可以在 UI 线程安全执行的操作。我的实践经验是使用线程池管理推理任务。推荐使用ExecutorService创建一个固定大小的线程池推理任务提交后通过Future或回调返回结果。示例代码ExecutorService executor Executors.newSingleThreadExecutor(); public void predictAsync(float[] input, final OnPredictListener listener) { executor.execute(() - { float[] result predictor.predict(input); runOnUiThread(() - listener.onSuccess(result)); }); } public interface OnPredictListener { void onSuccess(float[] result); }注意这里使用单线程池而不是多线程池的原因PaddleLite 的 predictor 对象并非线程安全多个线程同时调用同一个 predictor 的run()方法会导致崩溃或结果错乱。如果一定要做并发推理正确做法是创建多个 predictor 实例每个实例绑定一个线程。内存管理方面也有一个容易忽略的坑。PaddlePredictor底层持有的是 Native 内存Java 层看起来是普通对象但 GC 并不会及时回收 Native 侧的资源。如果频繁创建和销毁 Predictor 对象内存泄漏会非常明显。正确做法是一个 Predictor 实例在整个应用生命周期内尽量复用如果需要切换模型调用close()明确释放资源。6. 推理结果验证与性能调优6.1 Android 端输出与服务器端输出一致性检查集成完成后第一件事不是看速度而是验证精度。我在每次集成的项目中都会准备一张固定的测试图片先在服务器上用 PyTorch 跑一次得到标准输出再在 Android 端用同一个模型跑一次对比两者结果。由于浮点计算的微小差异和 CPU/GPU 指令集差异Android 端输出和 PyTorch 输出不可能完全一致但概率输出的相对排序应该保持不变。如果分类的 Top-1 结果不一致优先检查预处理是否对齐如果 Top-1 一致但概率差距较大考虑是量化导致的精度损失。我在某个目标检测项目里遇到过一个问题PyTorch 输出的检测框坐标在[0, 1]范围内而 Android 端直接输出后忘记了乘以图像宽高导致画框位置完全错乱。这种问题不是模型转换问题而是后处理逻辑没有对齐排查时要先问一句“输出数据的含义和范围是什么”。6.2 推理性能分析与调优如果你的模型在 Android 端跑得不够快通常有几个优化空间。我建议按照以下顺序逐一尝试第一步确认 CPU 线程数对性能的影响。用 Profiler 工具或者简单的System.currentTimeMillis()测量不同线程数下的推理延迟。线程数从 1 到 4 依次递增找到最优值。在城市地区的主流手机 CPU 上4 线程一般是最优解但某些中低端机型可能 2 线程表现更好。第二步检查是否有内存带宽瓶颈。模型参数量越大内存带宽消耗越大。如果你的模型是典型的大模型比如 ResNet152那么瓶颈往往不在计算而在内存访问。此时考虑 INT8 量化把模型参数量压缩 4 倍。第三步考虑使用 GPU 加速。PaddleLite 支持 OpenCL 后端使用起来并不复杂只需要在 MobileConfig 中指定。但 GPU 加速并非总是比 CPU 快很多情况下小型模型在 GPU 上反而更慢因为 GPU 的开机启动和数据传输开销占主导。MobileConfig config new MobileConfig(); config.setModelFromFile(modelPath); config.setOpenCL(true); // 启用 OpenCL GPU 加速需要提醒的是启用 OpenCL 后对设备的兼容性有要求。个别老旧 GPU 驱动可能存在兼容性问题建议做真机测试覆盖主要机型。第四步模型裁剪和蒸馏。如果量化已经做了GPU 也试过了还是达不到预期那就只能从模型结构上想办法了。轻量化网络MobileNetV3、ShuffleNetV2、EfficientNet-Lite通常比大模型在端侧表现好一个数量级精度损失可以在可接受范围内。知识蒸馏可以把大模型的知识迁移到小模型上这也是工业界非常成熟的方案。6.3 Android 端的耗电与发热问题跑深度学习模型是重负载任务特别是在移动设备上长时间推理会导致 SoC 发热和降频。如果你开发的是一个需要长时间运行的实时应用比如视频流检测那么必须考虑耗电和发热问题。我的经验是在性能达标的条件下优先选择LITE_POWER_LOW模式。这个模式会降低 CPU 频率但也能显著减少发热。另外不要在推理时同时开启过多后台任务CPU 资源竞争会导致 GPU 频繁拉升频率加剧耗电。如果你做的是实时性要求很高的应用可以考虑用媒体框架CameraX、MLKit和推理线程配合实现流式处理。但这就涉及更复杂的 pipeline 设计不在本文范围内了。7. 实际项目中的典型问题速查表为了让你遇到问题时能快速定位我把实践中遇到的高频问题整理成了一张速查表。这些问题我都在真实项目中踩过每个都能省你至少半天时间。问题现象可能原因解决思路ONNX 导出时报算子不支持PyTorch 版本过高或 opset 设置不合适回退到 1.12.x使用 opset 11必要时改写模型结构PaddleLite opt 工具转换失败某个 ONNX 算子在 PaddleLite 中尚无映射查看算子映射表升级 PaddleLite 或改写模型Android 端加载 .nb 文件崩溃模型文件被压缩或路径错误检查 assets 配置确保构建时不压缩 .nb 文件推理结果与 PyTorch 不一致预处理不匹配或数据排列顺序错误用固定测试图片逐环节对比检查 Mean/Std、NCHW/NHWC推理速度太慢CPU 占用高线程数设置不合理或未启用量化调整线程数到最优值尝试 INT8 量化内存占用持续增长Predictor 未复用或 Native 资源未释放复用 Predictor显式调用 close()模型文件太大APK 体积暴涨模型未量化且优化不充分使用 INT8 量化必要时用 NHWC 优化布局或者裁剪网络层这张表里面有一个问题值得展开讲assets 目录下的.nb文件默认会被 Android 构建工具压缩而 PaddleLite 加载时需要直接内存映射文件压缩导致文件无法被正确解析。解决方案是在 Gradle 文件中明确排除对特定后缀文件的压缩android { aaptOptions { noCompress nb, pb } }这个配置我在第一次集成时完全没有意识到直到 Release 包闪退而 Debug 包正常查了很久才找到原因。这里优先分享给你省得走弯路。还有一点如果你从相机获取图像做识别图像的旋转方向也是一个隐藏坑。手机相机的原图方向跟传感器安装方向有关很多模型是按正向图片训练的如果你直接拿旋转 90 度的图去推理准确率会大打折扣。最佳实践是在预处理前根据 EXIF 信息旋转 Bitmap。8. 进阶扩展PaddleLite 的其他能力如果你走通了最基本的 CPU 推理后面还可以根据项目需求尝试更多 PaddleLite 的新能力。异构计算是比较实用的方向。PaddleLite 支持在一个模型中同时使用 CPU、GPU 和 NPU 异构计算通过算子放置策略配置把适合 GPU 的算子调度到 GPU 上执行适合 NPU 的算子调度到 NPU其余留在 CPU。这样做的性能收益上限很高但配置复杂度也上来了。个人建议除非模型确实非常大否则先用纯 CPU 部署就足够了。PaddleSlim 联合部署是另一个值得关注的组合。PaddleSlim 做蒸馏和剪枝生成的小模型直接用 PaddleLite 部署两者天然兼容。如果你有精度和模型大小的双重约束这个组合几乎是最佳解。最后说一点关于持续集成和版本管理的体会模型本身是易变资产和代码一样需要版本管理。建议把训练脚本、导出脚本、转换脚本全部纳入 Git 仓库同时在模型文件名中带上版本号比如model_v3_int8.nb这样在出现回归问题时可以快速回溯到具体哪个版本导致的。我个人的习惯是建立一条简单的自动化流水线训练完成自动导出 ONNX自动转 PaddleLite 格式自动用黄金测试集验证输出一致性和精度全部通过后才通知我去更新 Android 工程。这个流水线让模型迭代速度提升了不少也避免了很多低级错误的传播。整个 PyTorch 模型到 PaddleLite Android 部署的流程到这一步已经基本完整了。从环境搭建、ONNX 导出、模型转换、Android 集成到性能调优每一步都有足够多的细节可以钻研文中提到的每个坑都是我亲身体验过的真实问题。希望这篇内容能在你的模型落地之路上省去一部分无用的工作少走几步弯路。
返回列表