1、PyTorch模型导出基础:理解torch.onnx.export的核心参数与导出流程

说实话,模型部署这件事,我踩过的坑比走过的路还多。刚开始做PyTorch转ONNX时,总觉得不就是一行代码的事吗?后来才发现,这里面的门道真不少。今天咱们就从头捋一捋,把torch.onnx.export这个核心函数彻底搞明白。

1.1 为什么需要ONNX?

你想想看,PyTorch模型训练好了,总不能一直活在Python环境里吧?生产环境可能是C++、Java,甚至跑在手机或嵌入式设备上。ONNX就是那个「通用翻译官」——它定义了一套标准化的模型表示格式,让不同框架之间能互相转换。

我个人习惯把ONNX比作「模型界的PDF」。你写Word文档,别人不一定有Word,但PDF几乎谁都能打开。ONNX也是这个道理,PyTorch训练完,导出成ONNX,然后TensorRT、OpenVINO、ONNX Runtime这些推理引擎都能直接吃进去。

核心价值:一次导出,到处推理。跨平台、跨语言、跨硬件。

1.2 torch.onnx.export 的核心参数

这个函数我用了不下几百次了。每次新项目,第一件事就是调它。咱们直接看最常用的参数:

import torch
import torch.onnx

# 假设我们有一个训练好的模型
model = torch.load('my_model.pth')
model.eval()

# 构造一个 dummy input
dummy_input = torch.randn(1, 3, 224, 224)

# 导出
torch.onnx.export(
    model,               # 要导出的模型
    dummy_input,         # 示例输入
    'model.onnx',        # 输出文件名
    export_params=True,  # 是否导出模型参数
    opset_version=11,    # ONNX算子集版本
    do_constant_folding=True,  # 是否做常量折叠优化
    input_names=['input'],     # 输入张量名称
    output_names=['output'],   # 输出张量名称
    dynamic_axes={
        'input': {0: 'batch_size'},
        'output': {0: 'batch_size'}
    }
)

嗯,这里要注意几个关键点:

参数 作用 我的建议
model 要导出的模型实例 记得先调 model.eval(),去掉Dropout和BatchNorm的训练行为
dummy_input 一个形状正确的示例输入 形状要和实际推理时一致,否则导出后可能报错
opset_version ONNX算子集版本号 我一般用11或13,太老不支持新算子,太新有些推理引擎不兼容
dynamic_axes 指定哪些维度是动态的 如果推理时batch size会变,这个必须配

1.3 导出流程:从模型到ONNX

整个流程其实就三步,但每一步都有坑。我当年第一次导出时,卡在第二步整整两天。

  1. 准备模型和输入:加载模型,调成eval模式,构造一个dummy input
  2. 调用export函数:传入模型、输入、输出路径和参数
  3. 验证ONNX模型:用onnx.checker检查,再用ONNX Runtime跑一遍看看结果对不对

小技巧:导出后一定要做验证。我曾经遇到过导出成功但推理结果完全不对的情况,就是因为某个自定义算子没注册好。

1.4 避坑指南:我踩过的那些坑

我曾经在一个图像分割项目里,模型导出后死活跑不通。查了两天,最后发现是torch.nn.functional.interpolate这个函数在ONNX里不支持某些参数组合。后来我换成了torch.nn.Upsample层,问题就解决了。

还有一次,我忘了调model.eval(),结果导出的ONNX模型里包含了Dropout层,推理时每次结果都不一样。你说这坑不坑?

常见坑点:

  • 忘记调 model.eval() —— 导出结果包含训练行为
  • dummy_input 形状和实际不一致 —— 导出后推理报错
  • 使用了ONNX不支持的算子 —— 需要手动替换或自定义
  • 动态轴没配好 —— 推理时batch size固定死

1.5 验证导出的ONNX模型

导出完别急着走,一定要验证。我习惯写个脚本:

import onnx
import onnxruntime as ort
import numpy as np

# 1. 检查ONNX模型结构
onnx_model = onnx.load('model.onnx')
onnx.checker.check_model(onnx_model)
print('ONNX模型结构检查通过')

# 2. 用ONNX Runtime推理
ort_session = ort.InferenceSession('model.onnx')
ort_inputs = {ort_session.get_inputs()[0].name: np.random.randn(1, 3, 224, 224).astype(np.float32)}
ort_outputs = ort_session.run(None, ort_inputs)
print('ONNX Runtime推理成功,输出形状:', ort_outputs[0].shape)

说白了,这一步就是给导出的模型做个「体检」。结构没问题,推理能跑通,这才算真正导出成功。

1.6 总结一下

PyTorch转ONNX,核心就是torch.onnx.export这个函数。你只要记住:

  • 模型要调eval()
  • dummy input形状要对
  • opset版本选合适
  • 动态轴按需配置
  • 导出后一定要验证

做到这几点,基本就不会出大问题。下一章咱们聊聊更复杂的场景——带自定义算子的模型怎么导出。那个才是真正考验功力的时候。