4. 模型表示与中间格式

在多硬件平台视觉推理适配工程中,训练框架与部署硬件之间往往并不直接兼容。PyTorch 训练出的权重无法直接交给 OpenVINO 推理引擎,TensorFlow 的 SavedModel 也不能被 ONNX Runtime 直接加载。中间格式的核心价值,就是把“训练框架的表达”翻译成“部署运行时的表达”,并在翻译过程中保留计算图结构、算子语义、张量形状与数据类型等关键信息。理解这些格式的定位、结构与互转路径,是构建可移植视觉推理流水线的基础。

四种主流中间格式的定位与结构

ONNX(Open Neural Network Exchange)是一种开放的跨框架模型表示,核心是 protobuf 序列化的计算图。图中每个节点是一个算子(如 Conv、Relu、Resize),边表示张量,并携带 shape、dtype 等类型信息。ONNX 的强项是“框架中立”,PyTorch、TensorFlow、PaddlePaddle 等都能导出为 ONNX,再由 ONNX Runtime、TensorRT、OpenVINO 等后端加载。它的算子集按 opset 版本演进,例如 opset 11 引入了更完整的 Resize 语义,opset 13 调整了部分算子属性,因此导出时必须明确 opset 版本。

TorchScript是 PyTorch 自带的序列化格式,通过 tracing 或 scripting 把 Python 模型转为可脱离 Python 解释器运行的图表示。它保留了 PyTorch 的算子命名与语义,适合在 LibTorch(C++)环境中直接加载,也常作为导出 ONNX 的中间步骤。TorchScript 的优势是“同源保真”,对动态控制流的支持比 tracing 更好,但跨框架互操作性弱于 ONNX。

TensorFlow SavedModel是 TensorFlow 的标准部署格式,目录中包含 saved_model.pb(图定义)与 variables/(权重)。它通过 SignatureDef 声明输入输出签名,支持 serving、TFLite 转换以及 TF-TRT 优化。SavedModel 与 TensorFlow 生态绑定较深,跨到非 TF 运行时通常需要先转 ONNX。

OpenVINO IR是 Intel OpenVINO 的中间表示,由 .xml(网络拓扑)与 .bin(权重)两个文件组成。IR 针对 Intel CPU、集成 GPU、VPU 等硬件做了图层融合与精度优化,加载后由 Inference Engine 编译为设备相关执行计划。IR 通常由 ONNX、TensorFlow、PaddlePaddle 等格式经 Model Optimizer 转换而来,是 Intel 平台部署的最终形态。

视觉模型中间格式转换与互操作路径 PyTorch torch.onnx.export / trace TensorFlow / Keras tf.saved_model.save PaddlePaddle paddle2onnx ONNX opset 11/13/17 · .onnx 跨框架中立图表示 TorchScript .pt · trace / script LibTorch 直接加载 TF SavedModel saved_model.pb + variables/ SignatureDef 输入输出 ONNX Runtime CPU / CUDA / DirectML 跨平台通用推理 OpenVINO IR .xml + .bin Intel CPU / iGPU / VPU TensorRT / TFLite NVIDIA GPU / 移动端 引擎序列化 .plan / .tflite tf2onnx onnx-tf mo --input_model ort.InferenceSession 训练框架 中间格式 部署运行时 直接导出 互转工具

转换路径与关键操作

从 PyTorch 到 ONNX 是最常见的路径。以视觉分类模型为例,导出时需要提供示例输入张量,并指定输入输出名称与动态维度:

import torch, torchvision

model = torchvision.models.resnet18(weights="IMAGENET1K_V1").eval()
dummy = torch.randn(1, 3, 224, 224)

torch.onnx.export(
    model, dummy, "resnet18.onnx",
    input_names=["input"], output_names=["logits"],
    dynamic_axes={"input": {0: "batch"}, "logits": {0: "batch"}},
    opset_version=13
)

这里 dynamic_axes 把 batch 维标记为动态,使导出的 ONNX 能接受任意 batch 输入;opset_version=13 决定了 Resize、Slice 等算子的语义版本。导出后应使用 onnx.checker.check_model 校验图完整性,并用 onnxruntime 跑一次前向,与 PyTorch 输出做数值比对,通常要求最大绝对误差小于 1e-4。

从 TensorFlow SavedModel 到 ONNX 使用 tf2onnx,命令形式为 python -m tf2onnx.convert --saved-model ./saved_model --output model.onnx --opset 13。若模型包含 TF 特有算子(如某些自定义 Resize 或 NMS),转换可能失败或产生不支持的算子,此时需要注册自定义算子或改写图结构。

从 ONNX 到 OpenVINO IR 使用 Model Optimizer:mo --input_model resnet18.onnx --input_shape [1,3,224,224] --data_type FP16 --output_dir ir/。生成的 resnet18.xml 描述拓扑,resnet18.bin 存放权重。FP16 可减小体积并提升 Intel GPU 上的吞吐,但 CPU 上通常 FP32 更稳。转换后可用 benchmark_app -m ir/resnet18.xml -d CPU -niter 100 观察延迟与吞吐。

互操作中的典型问题与判断依据

格式转换并非总是无损。常见问题包括:算子不支持导致转换中断;动态 shape 在部分后端退化为静态;精度从 FP32 降到 FP16 后分类置信度偏移;以及 NMS、ROI Align 等检测后处理算子在跨框架时语义不一致。判断转换是否成功,不能只看文件是否生成,而应做三层验证:结构层用 onnx.checker 或 OpenVINO 的 mo 日志确认图完整;数值层用同一输入比对原模型与转换模型的输出;性能层用 benchmark 工具确认目标设备上的延迟与内存占用在可接受范围。

对于视觉模型,建议固定输入分辨率(如 640×640)导出,避免动态 shape 在边缘设备上触发重新编译;若必须支持多分辨率,应在 ONNX 中显式声明动态维,并在部署端限制取值范围。

选择哪种中间格式,取决于目标硬件与团队工具链。若面向 NVIDIA GPU,ONNX 转 TensorRT 是主流;若面向 Intel 平台,ONNX 转 OpenVINO IR 更直接;若只在 PyTorch C++ 环境部署,TorchScript 可省去跨框架转换。理解每种格式的图结构、算子集与转换工具,才能在多硬件适配中快速定位问题并选择最短路径。