3. 模型部署流程

模型部署不是“把权重拷到板子上”这么简单。它是一条从训练产物到端侧推理结果的完整工程链路,中间要经过模型导出、格式转换、图优化、运行时选择与部署验证五个关键环节。任何一个环节的参数不匹配,都会导致推理结果错误、性能不达标甚至根本无法加载。理解这条链路,是嵌入式 AI 工程化的第一步。

核心认知:训练框架关心“能不能算对”,部署链路关心“能不能在目标硬件上、以可接受的延迟和内存算对”。两者之间的桥梁,就是导出与转换。

一、完整链路总览

一条典型的端侧部署链路可以抽象为:训练检查点 → 推理图导出 → 中间格式转换 → 图优化与量化 → 运行时加载 → 部署验证。下图展示了各环节的输入输出关系与关键产物。

训练检查点 .pt / .pth / .ckpt 含优化器状态 推理图导出 ONNX / TorchScript 固定输入形状 格式转换 TFLite / RKNN / TensorRT 图优化与量化 算子融合 / INT8 常量折叠 运行时加载 NPU / GPU / CPU 部署验证 精度对齐 · 延迟测试 · 内存占用 · 数值一致性 不达标则回溯调整 关键产物:.onnx → .tflite / .rknn / .engine → 端侧推理结果 每一环节都需校验张量形状、数据类型与算子支持列表

二、模型导出:从训练图到推理图

训练框架中的模型包含大量推理不需要的信息:优化器状态、梯度计算图、Dropout 与 BatchNorm 的训练分支等。导出的目标是把模型固化为静态计算图,并固定输入输出张量形状。

以 PyTorch 为例,常用两种导出方式:

# 方式一:TorchScript 追踪导出
model.eval()
example = torch.randn(1, 3, 224, 224)
traced = torch.jit.trace(model, example)
traced.save("model_ts.pt")

# 方式二:ONNX 导出(推荐跨框架转换)
torch.onnx.export(
    model, example, "model.onnx",
    input_names=["input"], output_names=["output"],
    opset_version=13, dynamic_axes=None
)
常见误区:导出时忘记调用 model.eval(),导致 BatchNorm 使用训练时的滑动统计量、Dropout 仍然随机丢弃,推理结果与训练验证集精度严重不一致。

三、格式转换与图优化

ONNX 是中间表示,真正部署到端侧还需要转换为目标运行时支持的格式。转换器在转换过程中会执行图优化,主要包括:

  • 算子融合:将 Conv + BN + ReLU 合并为单个算子,减少内存访问与调度开销。
  • 常量折叠:把编译期可确定的计算提前算完,减小图规模。
  • 死代码消除:移除推理不需要的分支与节点。
  • 布局转换:将 NCHW 转为 NHWC 以适配某些 NPU 的数据排布偏好。
  • 量化插入:在图中插入 Quantize / Dequantize 节点,或直接生成全整型图。

不同目标平台的转换命令差异较大,以下为典型示例:

# TensorFlow Lite 转换(含 INT8 量化)
tflite_convert \
  --saved_model_dir=./saved_model \
  --output_file=model_int8.tflite \
  --quantize_weights=INT8 \
  --representative_dataset=rep_data

# RKNN 转换(瑞芯微 NPU)
rknn.config(mean_values=[[127.5]], std_values=[[127.5]], target_platform='rk3588')
rknn.load_onnx(model='model.onnx')
rknn.build(do_quantization=True, dataset='calib.txt')
rknn.export_rknn('model.rknn')

四、运行时选择与部署验证

运行时是真正执行推理的引擎,选择时需综合考虑硬件加速器支持、算子覆盖率、内存占用与调度开销。下表对比了常见运行时方案。

运行时 目标硬件 典型格式 优势 注意事项
TensorFlow Lite ARM CPU / GPU / EdgeTPU .tflite 生态成熟,量化工具链完整 部分自定义算子需手动注册
ONNX Runtime CPU / GPU / NPU .onnx 跨平台,Execution Provider 丰富 端侧包体较大,需裁剪
TensorRT NVIDIA GPU / Jetson .engine 极致延迟优化,FP16/INT8 支持好 绑定 NVIDIA 硬件,转换耗时
RKNN 瑞芯微 NPU .rknn 低功耗,INT8 性能突出 算子支持列表有限,需查文档
NCNN / MNN 移动端 ARM CPU .param + .bin 轻量无依赖,启动快 GPU 加速支持因平台而异

部署验证是链路的最后一道关卡,也是最重要的一环。验证至少应覆盖四个维度:

  1. 数值一致性:用同一组输入分别跑训练框架与端侧运行时,比较输出最大绝对误差。FP32 通常要求误差小于 1e-4,INT8 量化后允许放宽到 1e-2 量级。
  2. 精度指标:在完整验证集上比较 Top-1 / mAP 等指标,确认量化后精度下降在可接受范围内。
  3. 性能指标:测量单帧延迟、吞吐量、首帧加载时间,确认满足实时性要求。
  4. 资源占用:记录峰值内存、模型文件大小、NPU 占用率,避免运行时 OOM。
实用技巧:在转换前先保存一组“黄金输入”和对应的“黄金输出”,部署后逐层对比中间张量,可以快速定位是哪个算子或哪次融合引入了误差。

五、案例复盘:一次典型的部署失败

某团队将 YOLOv5s 部署到 RK3588 平台,转换成功、模型能加载,但检测框全部偏移。排查过程如下:

  • 检查输入预处理:训练时使用 letterbox 填充,部署时误用了直接 resize,导致坐标映射关系改变。
  • 检查输出解码:模型导出时未包含后处理,端侧自行实现时 anchor 顺序与训练代码不一致。
  • 检查量化校准:校准集仅用了 20 张纯色图,导致激活值分布估计严重偏差。

最终修正预处理逻辑、对齐 anchor 顺序并补充 500 张真实场景校准图后,精度恢复至 FP32 的 98.5%。这个案例说明:部署链路的每个环节都必须与训练侧严格对齐,任何“想当然”的假设都可能成为精度损失的根源。

部署流程的本质是“契约传递”:训练侧定义输入输出契约,导出环节固化契约,转换环节保持契约,运行时遵守契约,验证环节校验契约。契约一旦断裂,推理结果就不可信。

掌握这条完整链路后,面对任何新的硬件平台或运行时,都可以按照“导出 → 转换 → 优化 → 选择运行时 → 验证”的框架逐步推进,把不可控的部署问题转化为可定位、可复现的工程步骤。