PyTorch模型导出ONNX是OpenVINO部署必经步骤,需用torch.onnx.export(非torch.export)、固定shape、opset13/14、补全shape;mo转换须指定--input_shape等参数;量化必须用POT而非mo--quantize,并严格对齐校准与推理预处理。

PyTorch 模型导出为 ONNX 是必须的前置步骤
OpenVINO 不直接读取 torch.nn.Module,必须先转成 ONNX。关键不是“能不能转”,而是“怎么转才不会在 OpenVINO 里报错”。常见坑是动态轴没标、torch.export(新 API)导出的格式 OpenVINO 2024.0 还不支持,得用传统 torch.onnx.export。
实操建议:
- 固定输入 shape,避免
dynamic_axes过度泛化;如只允许 batch 维动态,就只写{"x": {0: "batch"}},别把 height/width 也设成动态 - 用
opset_version=13或14,避开 opset 15+ 中某些 OpenVINO 尚未实现的算子(比如aten::scaled_dot_product_attention) - 导出前调用
model.eval()并禁用 dropout/batchnorm 更新,否则 IR 推理时行为不一致 - 导出后用
onnx.shape_inference.infer_shapes_path补全 shape 信息,OpenVINO 的mo工具对缺失 shape 的 ONNX 容错差
用 mo.py 转 ONNX 到 IR 时参数不能省
mo.py 是 Model Optimizer,不是“丢进去就完事”的黑盒。默认参数大概率导致转换失败或 IR 不可用——尤其当模型含自定义 op、非标准归一化、或输入预处理逻辑嵌在模型里。
关键参数组合:
- 必须指定
--input_shape,即使 ONNX 有 shape;例如[1,3,224,224],不能写[?,3,224,224](?会被 mo 忽略) - 若模型输入是
uint8(如某些部署友好的 CNN),加--input_model_precision FP32并配合--scale_values手动还原归一化,否则 IR 会错误假设输入是 float - 遇到
Unsupported primitive,先试--disable_fusing和--disable_gfusing,关掉图融合常能绕过 unsupported op - 输出目录用
--output_dir显式指定,别依赖当前路径;IR 文件(.xml+.bin)必须成对存在才能加载
Post-training quantization 必须用 POT,不能靠 mo.py
mo.py 的 --quantize 参数只是占位符,实际不生效。真量化得用 pot(Post-training Optimization Tool),它依赖校准数据集和量化配置。
容易卡住的点:
- 校准数据必须和训练/部署时的预处理完全一致:同样的 resize、mean/std、BGR/RGB 顺序;哪怕
cv2.imread默认 BGR,而 PyTorch 训练用 RGB,这里错一点,量化权重就崩 -
pot配置里的target_device得设成VPU或CPU,不同设备的量化策略(如对称/非对称、激活 bit 数)默认值不同 - 别跳过
accuracy_aware模式验证;有些模型量化后 top1 掉 5% 以上,pot默认不报错,但 IR 推理结果已不可用 - 量化后生成的 IR 仍是
.xml+.bin,但.bin里权重变成 int8;用ie_core.read_network加载时无需改代码,OpenVINO 运行时自动 dispatch
C++ 加载量化 IR 和原始 IR 写法一样,但数据类型要对齐
IR 文件格式本身不暴露是否量化,C++ 侧调用流程完全一致:读 network → 设置 input info → infer request → run。真正影响结果的是输入 tensor 的 precision 和预处理逻辑。
必须检查:
- 如果量化时用了
uint8输入(常见于 INT8 对称量化),C++ 里infer_request.set_tensor的 input tensor 必须是U8类型,不能传F32再靠 runtime 转——会静默截断 -
input_info.set_layout和set_precision要跟 IR 中定义的一致;可用network.input("input_name").get_element_type()运行时确认 - 量化模型对输入范围更敏感,建议在 C++ 里加 assert:比如要求输入像素值在 [0, 255],超出就抛异常,而不是让 inference 返回 NaN
最常被忽略的是校准数据预处理和 C++ 推理预处理的微小差异——比如一处用了 std::round,另一处用了 floor,INT8 量化下这点误差会被放大。动手前,先拿同一张图,在 Python POT 校准脚本和 C++ infer 代码里分别 dump 输入 tensor,逐元素比对。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











