3、模型格式基础:掌握ONNX、Caffe、TFLite等常见模型格式的特点,以及算能TPU支持的输入格式和转换入口
在深度学习模型部署到算能TPU(如BM1684X、BM1688、CV183x等系列)之前,我们首先需要理解一个核心问题:训练框架产出的模型文件(如PyTorch的.pt、TensorFlow的.pb)并不能直接被TPU运行时加载执行。TPU的编译器(如TPU-MLIR、BMNETC等)需要将模型解析为统一的中间表示(IR),再经过图优化、量化、算子映射等步骤,最终生成TPU可执行的BModel。因此,掌握常见模型格式(ONNX、Caffe、TFLite)的结构特点、适用场景以及它们与算能TPU工具链的对接方式,是部署工作的第一道关卡。本章将系统梳理这些格式的底层逻辑,并给出算能TPU的输入格式要求与转换入口。
3.1 核心原理:模型格式的本质与设计动机
模型格式本质上是一种序列化协议,它将神经网络的计算图结构(节点、边、权重、属性)以及必要的元数据(版本、作者、输入输出信息)编码为特定文件结构。不同框架的格式设计差异,源于其设计目标的不同:PyTorch的.pt文件是Python对象的序列化,包含完整的训练状态;TensorFlow的.pb是GraphDef的protobuf序列化,偏重静态图执行;而ONNX的设计初衷就是成为框架之间的“通用语言”。
从部署视角看,我们关注三个核心要素:图结构表达能力(能否描述复杂控制流、动态shape)、算子覆盖度(是否包含目标硬件支持的算子)、量化支持(是否携带量化参数或易于插入量化节点)。算能TPU的编译器前端正是基于这些要素来设计解析逻辑的。
| 格式 | 底层序列化 | 图类型 | 动态shape | 量化支持 | 主要场景 |
|---|---|---|---|---|---|
| ONNX | Protobuf | 静态图(可动态) | 支持(dynamic_axes) | QDQ节点 / 直接量化 | 跨框架转换、推理部署 |
| Caffe | Protobuf + Binary | 静态图 | 不支持 | 需手动插入 | 早期CV模型、老项目 |
| TFLite | FlatBuffers | 静态图 | 部分支持 | 内置量化(INT8/FP16) | 移动端、嵌入式 |
| PyTorch (.pt) | Pickle + Zip | 动态图(TorchScript) | 支持 | 需通过ONNX导出 | 训练、研究 |
Top IR 表示。无论输入是ONNX还是Caffe,都会先被解析为Top IR,再进行后续优化。这意味着,ONNX是推荐的中间格式,因为它能覆盖绝大多数训练框架的导出需求,且TPU-MLIR对ONNX的算子支持最完善。
3.2 判断方法与操作流程:如何选择正确的转换路径
面对一个训练好的模型,我们需要根据其来源框架和目标部署精度,选择不同的转换入口。以下是算能TPU支持的三种主要转换路径,以及它们的适用条件。
路径一:PyTorch → ONNX → BModel(推荐)
这是最常用的路径。PyTorch模型通过 torch.onnx.export 导出为ONNX文件,然后使用TPU-MLIR的 model_transform.py 工具转换为Top IR,再经过 model_deploy.py 生成BModel。关键操作如下:
# 1. PyTorch导出ONNX
import torch
dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(model, dummy_input, "model.onnx",
input_names=["input"], output_names=["output"],
dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}})
# 2. TPU-MLIR转换(Docker环境)
# model_transform.py:ONNX → Top IR(mlir文件)
python3 model_transform.py \
--model_name resnet50 \
--model_def resnet50.onnx \
--input_shapes [[1,3,224,224]] \
--pixel_format rgb \
--keep_aspect_ratio \
--output_dir ./tmp
# 3. model_deploy.py:Top IR → BModel(FP32/FP16/INT8)
python3 model_deploy.py \
--mlir resnet50.mlir \
--quantize F32 \
--chip bm1684x \
--output resnet50_f32.bmodel
路径二:Caffe → BModel(直接转换)
算能提供了 bmnetc 工具,可以直接解析Caffe的prototxt和caffemodel。但需要注意,Caffe模型中的自定义层(如某些检测头)需要手动实现或替换。转换命令示例:
# 使用bmnetc转换Caffe模型
bmnetc --model=deploy.prototxt \
--weights=model.caffemodel \
--outdir=./output \
--target=BM1684X \
--shapes=1,3,224,224 \
--precision=FP32
路径三:TFLite → ONNX → BModel(间接转换)
TFLite模型通常来自TensorFlow Lite或Edge TPU场景。由于TPU-MLIR对TFLite的直接支持尚不完善,推荐先通过 tf2onnx 或 onnx-tensorflow 转换为ONNX,再走路径一。转换时需注意TFLite中量化算子的映射问题。
3.3 案例拆解:一个YOLOv5s检测模型的完整转换实战
我们以一个实际项目为例:将PyTorch训练的YOLOv5s模型部署到BM1684X芯片,目标精度为INT8,要求mAP@0.5下降不超过2%。
初始状态:模型文件为 yolov5s.pt,训练时输入尺寸640×640,类别80类。项目约束:部署环境无GPU,仅CPU进行转换;需要支持batch=1的实时推理。
操作步骤:
- 导出ONNX:使用YOLOv5官方脚本
export.py,设置--include onnx,并指定--dynamic以保留batch维度动态性。导出后使用onnxsim进行图简化,减少冗余节点。 - 检查ONNX算子:使用
onnxruntime加载模型,确认所有算子版本在TPU-MLIR支持列表中。YOLOv5的Focus层在ONNX中会被拆解为Slice+Concat,TPU-MLIR能自动优化。 - 执行转换:在TPU-MLIR Docker容器中,先执行
model_transform.py生成Top IR,再执行model_deploy.py进行INT8量化。量化时需要准备校准数据集(约200张代表图像),使用--calibration_table参数。 - 验证精度:转换完成后,使用
bmrt_test工具加载BModel,对验证集进行推理,对比原始PyTorch模型的mAP。若精度下降超过阈值,需调整量化策略(如使用混合精度、增加校准集数量)。
结果对比:
| 指标 | PyTorch FP32 | BModel FP32 | BModel INT8 |
|---|---|---|---|
| mAP@0.5 | 0.742 | 0.741 | 0.728 |
| 单帧延迟(ms) | — | 8.2 | 3.1 |
| 模型大小(MB) | 14.1 | 14.1 | 3.6 |
复盘经验:INT8量化后mAP下降1.4%,在可接受范围内。关键教训是:校准数据集必须覆盖目标场景的分布(如白天/夜晚、不同光照),否则量化参数会偏移。此外,YOLOv5的检测头(Detect层)包含大量拼接和Reshape操作,在转换时建议使用 --fuse_preprocess 选项将预处理(归一化、缩放)融入模型,减少运行时开销。
3.4 常见误区与总结复盘
在模型格式转换的实践中,工程师常遇到以下问题,需要特别注意:
- 误区一:ONNX导出后直接转换,不做图优化。很多PyTorch模型导出后包含大量Shape、Gather等辅助算子,这些算子会阻碍TPU的算子融合。建议先用
onnxsim或onnxoptimizer简化图结构。 - 误区二:忽略动态shape的影响。如果模型在导出时未指定
dynamic_axes,则转换后的BModel只能接受固定尺寸输入。对于检测模型,建议保留batch维度的动态性,但不要将宽高设为动态,否则TPU内存规划会变得低效。 - 误区三:Caffe模型直接转换时忽略自定义层。Caffe的某些层(如Faster R-CNN的Proposal层)在TPU上无对应实现,需要先替换为ONNX标准算子或使用TPU的CPU fallback机制。
- 误区四:TFLite模型直接转换,忽略量化信息丢失。如前所述,TFLite的量化参数在转换过程中可能丢失,导致精度异常。务必检查转换后的ONNX是否包含QDQ节点。
onnx.checker 验证ONNX文件完整性;③在转换前用 onnxruntime 跑一遍推理,确保ONNX输出与原始框架一致(误差<1e-4);④记录转换日志,包括算子映射警告、量化敏感层信息,便于后续调优。