4. ONNX与模型转换:PyTorch/TensorFlow模型转ONNX、ONNX Runtime推理优化
模型训练出来只是第一步,真正要让它跑在田间的边缘设备上,中间还有一道绕不过去的坎——模型转换。说白了,就是把PyTorch或TensorFlow的模型,变成一种通用的、能被各种推理引擎识别的格式。ONNX就是目前最主流的中间格式。
我个人习惯把ONNX比作「模型界的普通话」。不管你的模型是用什么方言(框架)写的,只要转成ONNX,就能在支持ONNX的设备上跑起来。这章我们就来聊聊怎么把训练好的模型转成ONNX,以及怎么用ONNX Runtime做推理优化。
4.1 为什么需要ONNX?
你想想看,农业场景下的设备五花八门。有的用Jetson Nano,有的用树莓派,还有的用手机芯片。每个设备支持的推理框架都不一样。如果每次部署都要重新写一遍模型,那工程师得累死。
ONNX(Open Neural Network Exchange)解决了这个问题。它定义了一套统一的算子集和计算图格式。只要你的模型能转成ONNX,就能在支持ONNX Runtime、TensorRT、OpenVINO等引擎的设备上运行。
核心优势:
- 一次转换,多处部署:ONNX格式可以在不同硬件和推理引擎间复用
- 算子标准化:避免了框架间算子不兼容的问题
- 推理优化:ONNX Runtime等引擎会对计算图做自动优化
- 量化支持:ONNX原生支持INT8、FP16等低精度推理
4.2 PyTorch模型转ONNX
PyTorch转ONNX相对简单,因为PyTorch官方提供了torch.onnx.export接口。我在项目中遇到过最坑的事情是——动态轴没设置对,导致推理时输入尺寸变了就报错。
下面是一个标准的转换流程:
import torch
import torchvision.models as models
# 1. 加载模型
model = models.resnet18(pretrained=True)
model.eval()
# 2. 创建示例输入
dummy_input = torch.randn(1, 3, 224, 224)
# 3. 导出ONNX
torch.onnx.export(
model, # 模型
dummy_input, # 示例输入
"resnet18.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'}
}
)
print("模型转换完成:resnet18.onnx")
避坑指南:
我曾经因为没设置dynamic_axes,导致部署时只能跑固定batch size的输入。后来在田间测试时,图片尺寸稍微一变就崩了。记住:只要你的推理场景可能用到不同尺寸的输入,一定要设置动态轴。
4.3 TensorFlow模型转ONNX
TensorFlow转ONNX稍微麻烦一点,因为TF的图结构和PyTorch的动态图不太一样。我推荐用tf2onnx这个工具,它支持TF 1.x和2.x。
先安装工具:
pip install tf2onnx onnx onnxruntime
然后执行转换:
import tensorflow as tf
import tf2onnx
# 1. 加载TF模型
model = tf.keras.applications.MobileNetV2(weights='imagenet')
# 2. 指定输入签名
spec = (tf.TensorSpec((None, 224, 224, 3), tf.float32, name="input"),)
# 3. 转换
output_path = "mobilenetv2.onnx"
model_proto, _ = tf2onnx.convert.from_keras(
model,
input_signature=spec,
opset=13,
output_path=output_path
)
print(f"转换完成:{output_path}")
注意:
TF转ONNX时,如果模型里用了自定义层或TF特有的算子(比如tf.image.resize),可能会转换失败。我建议先检查算子兼容性,必要时用ONNX支持的算子替换。
4.4 ONNX模型验证与可视化
转换完成后,别急着部署。先验证一下ONNX模型是否正确。我习惯用onnx.checker做完整性检查,再用netron可视化计算图。
import onnx
import onnxruntime as ort
# 1. 加载ONNX模型
onnx_model = onnx.load("resnet18.onnx")
# 2. 检查模型结构
onnx.checker.check_model(onnx_model)
print("模型结构检查通过")
# 3. 用ONNX Runtime做推理验证
ort_session = ort.InferenceSession("resnet18.onnx")
# 准备输入数据
import numpy as np
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
# 执行推理
outputs = ort_session.run(
None,
{"input": input_data}
)
print(f"推理输出shape:{outputs[0].shape}")
可视化的话,我推荐用Netron(https://netron.app),直接把ONNX文件拖进去就能看到完整的计算图。这在排查模型结构问题时特别有用。
4.5 ONNX Runtime推理优化
ONNX Runtime(ORT)是微软开源的推理引擎,专门为ONNX模型做了大量优化。说白了,它能把你的模型跑得更快、更省内存。
ORT的优化主要体现在几个方面:
| 优化类型 | 说明 | 效果 |
|---|---|---|
| 图优化 | 合并相邻算子、消除冗余计算 | 减少推理延迟10%-30% |
| 算子融合 | 将多个小算子合并为一个大算子 | 减少内存访问次数 |
| 内存优化 | 复用中间张量、减少内存分配 | 降低峰值内存占用 |
| 量化 | FP32转INT8/FP16 | 推理速度提升2-4倍 |
| 执行提供者 | 支持CUDA、TensorRT、OpenVINO等后端 | 充分利用硬件加速 |
下面是一个使用ORT进行推理优化的示例:
import onnxruntime as ort
import numpy as np
import time
# 1. 创建推理会话(开启所有优化)
options = ort.SessionOptions()
options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
options.enable_profiling = True
# 2. 选择执行提供者(优先使用CUDA)
providers = [
'CUDAExecutionProvider',
'CPUExecutionProvider'
]
ort_session = ort.InferenceSession(
"resnet18.onnx",
sess_options=options,
providers=providers
)
# 3. 准备输入
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
# 4. 预热(第一次推理通常较慢)
_ = ort_session.run(None, {"input": input_data})
# 5. 正式推理并计时
start = time.time()
for _ in range(100):
outputs = ort_session.run(None, {"input": input_data})
end = time.time()
avg_time = (end - start) / 100 * 1000
print(f"平均推理时间:{avg_time:.2f} ms")
我的经验:
在农业场景下,我建议优先使用ORT_ENABLE_ALL级别的图优化。对于边缘设备,如果内存紧张,可以关闭enable_profiling来减少额外开销。另外,如果设备支持CUDA,一定要把CUDAExecutionProvider放在前面,GPU推理速度通常是CPU的5-10倍。
4.6 量化:让模型在边缘设备上跑得更快
量化是部署到边缘设备的关键技术。说白了,就是把模型参数从FP32压缩到INT8或FP16。精度会损失一点点,但推理速度能翻好几倍。
ONNX Runtime支持两种量化方式:
- 动态量化:推理时动态将权重转为INT8,简单但效果一般
- 静态量化:用校准数据集预先统计量化参数,效果更好
静态量化的示例:
from onnxruntime.quantization import quantize_static, QuantType
from onnxruntime.quantization import CalibrationMethod
# 准备校准数据(从训练集中取100张图片)
calibration_data = np.random.randn(100, 3, 224, 224).astype(np.float32)
# 执行静态量化
quantize_static(
model_input="resnet18.onnx",
model_output="resnet18_int8.onnx",
calibration_data_reader=calibration_data,
quant_format=QuantType.QInt8,
per_channel=True,
calibration_method=CalibrationMethod.MinMax
)
print("量化完成:resnet18_int8.onnx")
注意:
量化后的模型精度可能会下降1%-3%。我建议在量化前后用验证集做一次精度对比。如果精度下降太多,可以尝试只量化部分层,或者使用混合精度(部分层FP16,部分层INT8)。
4.7 本章知识体系
下面这张图总结了ONNX模型转换与优化的完整流程:
嗯,到这里,ONNX模型转换与优化的核心内容就讲完了。从PyTorch/TensorFlow转ONNX,到验证、量化、推理优化,每一步都有坑,但也都有对应的解决方案。我个人觉得,掌握好ONNX这套工具链,是农业AI模型部署的必修课。毕竟,模型再好,跑不起来也是白搭。