常见问题解决
本章汇总了 ONNX 模型导出和部署过程中的常见问题及其解决方案。
导出阶段问题
问题:不支持的操作 (Unsupported Operator)
错误信息:
RuntimeError: ONNX export failed on an operator: xxx
原因:模型中使用了 ONNX 标准算子集中不存在或当前 Opset 版本不支持的操作。
解决方案:
- 提高 Opset 版本:
torch.onnx.export(model, dummy_input, "model.onnx", opset_version=17)
- 用基础算子组合替代:
# 原始:自定义激活函数
class CustomActivation(nn.Module):
def forward(self, x):
return torch.special.my_custom_func(x)
# 替代:用基础算子实现等效逻辑
class EquivalentActivation(nn.Module):
def forward(self, x):
# 用标准 PyTorch 操作重写
return torch.where(x > 0, x, 0.1 * x)
- 注册自定义算子导出规则:
from torch.onnx import register_custom_op_symbolic
def custom_op_symbolic(g, input):
return g.op("MyDomain::CustomOp", input)
register_custom_op_symbolic("my_namespace::custom_op", custom_op_symbolic, 1)
问题:Tracing 无法捕获动态行为
错误信息:模型导出后,条件分支总是执行同一个路径。
原因:torch.onnx.export 默认使用 Tracing 模式,只记录实际执行的路径。
解决方案:
- 将条件逻辑移到模型外部:
# 问题代码
class Model(nn.Module):
def forward(self, x, mode):
if mode == "train":
return self.train_head(x)
else:
return self.inference_head(x)
# 解决方案:拆分为两个模型
class TrainModel(nn.Module):
def forward(self, x):
return self.train_head(x)
class InferenceModel(nn.Module):
def forward(self, x):
return self.inference_head(x)
- 使用 Scripting 模式:
scripted_model = torch.jit.script(model)
torch.onnx.export(scripted_model, dummy_input, "model.onnx")
- 使用新的 dynamo 导出器(PyTorch 2.6+):
torch.onnx.export(model, dummy_input, "model.onnx", dynamo=True)
问题:动态维度设置无效
错误信息:
InvalidArgument: Got invalid dimensions for input
原因:dynamic_axes 配置不正确或与某些算子不兼容。
解决方案:
- 检查配置语法:
# 正确的 dynamic_axes 格式
dynamic_axes = {
"input": {0: "batch_size"}, # 字典形式
# 或
"input": [0], # 列表形式
}
# 确保名称与 input_names 匹配
torch.onnx.export(
model, dummy_input, "model.onnx",
input_names=["input"],
dynamic_axes=dynamic_axes
)
- 使用 dynamic_shapes(PyTorch 2.6+):
from torch.export import Dim
batch_size = Dim("batch_size", min=1, max=64)
torch.onnx.export(
model, dummy_input, "model.onnx",
dynamo=True,
dynamic_shapes={"input": {0: batch_size}}
)
问题:大型模型导出失败
错误信息:
RuntimeError: serialized model exceeds 2GB
原因:ONNX 协议缓冲区格式的文件大小限制为 2GB。
解决方案:
# 使用外部数据存储
torch.onnx.export(
large_model,
dummy_input,
"large_model.onnx",
dynamo=True,
external_data=True, # 权重存储在外部文件
)
# 会生成两个文件:
# - large_model.onnx (模型结构)
# - large_model.onnx.data (权重数据)
推理阶段问题
问题:输入形状不匹配
错误信息:
InvalidArgument: Got invalid dimensions for input: input
Expected: [1,3,224,224], Got: [2,3,224,224]
原因:导出时未配置动态维度,或配置错误。
解决方案:
- 检查模型输入形状:
session = ort.InferenceSession("model.onnx")
for inp in session.get_inputs():
print(f"输入 {inp.name}: {inp.shape}")
- 使用正确的输入大小:
# 如果模型固定了 batch_size=1
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
# 如果支持动态维度
input_data = np.random.randn(batch_size, 3, 224, 224).astype(np.float32)
- 重新导出时添加动态维度:
torch.onnx.export(
model, dummy_input, "model.onnx",
dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}}
)
问题:数据类型不匹配
错误信息:
Type Error: Input type tensor(double) is not compatible with expected type tensor(float)
原因:NumPy 默认创建 float64 数组,但模型期望 float32。
解决方案:
# 始终显式指定数据类型
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
# 或在 PyTorch 导出时
dummy_input = torch.randn(1, 3, 224, 224, dtype=torch.float32)
问题:GPU 推理不可用
错误信息:
CUDAExecutionProvider not found
原因:
- 安装的是 CPU 版本的 onnxruntime
- CUDA 版本不匹配
解决方案:
- 安装 GPU 版本:
pip uninstall onnxruntime
pip install onnxruntime-gpu
- 检查 CUDA 版本兼容性:
import onnxruntime as ort
print(f"可用提供者: {ort.get_available_providers()}")
# 如果 CUDAExecutionProvider 不在列表中,检查 CUDA 安装
- 验证 CUDA 环境:
nvidia-smi # 检查 GPU 状态
nvcc --version # 检查 CUDA 版本
问题:内存不足
错误信息:
RuntimeError: CUDA out of memory
原因:GPU 显存不足,或存在内存泄漏。
解决方案:
- 减小 batch size:
batch_size = 1 # 从小批量开始
- 使用 IOBinding 管理 GPU 内存:
io_binding = session.io_binding()
# 只将必要的输入输出放在 GPU 上
io_binding.bind_input(...)
io_binding.bind_output(...)
session.run_with_iobinding(io_binding)
- 释放未使用的内存:
import gc
import torch
gc.collect()
torch.cuda.empty_cache()
问题:推理结果不一致
症状:ONNX 模型输出与 PyTorch 模型有显著差异。
排查步骤:
- 检查 eval 模式:
model.eval() # 必须在导出前调用
- 检查 BatchNorm:
# 确保使用推理模式的统计量
for m in model.modules():
if isinstance(m, nn.BatchNorm2d):
m.eval()
- 检查 Dropout:
# Dropout 应该被跳过
for m in model.modules():
if isinstance(m, nn.Dropout):
assert not m.training
- 检查数值精度:
# 使用相同的输入对比
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
# PyTorch
with torch.no_grad():
pt_output = model(torch.from_numpy(input_data)).numpy()
# ONNX
ort_output = session.run(None, {"input": input_data})[0]
diff = np.abs(pt_output - ort_output).max()
print(f"最大差异: {diff}")
if diff > 1e-5:
print("存在显著差异,需要进一步排查")
算子兼容性问题
问题:Resize 算子不支持
错误信息:
NotImplemented: Resize with coordinate_transformation_mode=align_corners is not supported
原因:Resize 算子的某些参数组合在目标推理引擎中不支持。
解决方案:
- 降低 Opset 版本:
torch.onnx.export(model, dummy_input, "model.onnx", opset_version=11)
- 修改插值方式:
# 避免使用 align_corners=True
x = F.interpolate(x, size=(224, 224), mode='bilinear', align_corners=False)
问题:Gather/ScatterND 不支持
错误信息:
Unsupported operator: ScatterND
原因:某些推理引擎(如 TensorRT 的某些版本)不支持这些索引操作。
解决方案:将后处理逻辑移到模型外部:
# 原模型包含 NMS
class DetectionModel(nn.Module):
def forward(self, x):
boxes, scores = self.backbone(x)
return self.nms(boxes, scores) # NMS 使用了 ScatterND
# 拆分模型
class DetectionBackbone(nn.Module):
def forward(self, x):
return self.backbone(x) # 只导出主干网络
# 在推理代码中实现 NMS
def post_process(boxes, scores):
# 用 Python/NumPy 实现 NMS
pass
问题:TensorRT 转换失败
错误信息:
[TensorRT] ERROR: xxx operation not supported
原因:TensorRT 支持的算子集比 ONNX Runtime 更有限。
解决方案:
-
使用 TensorRT 兼容的算子:
- 避免使用动态 reshape
- 避免复杂的索引操作
- 使用 TensorRT 支持的激活函数
-
简化模型:
# 使用 onnx-simplifier 简化
python -m onnxsim model.onnx model_simplified.onnx
- 使用 ONNX-TensorRT 版本矩阵:检查 ONNX Opset 和 TensorRT 版本的兼容性。
性能问题
问题:推理速度慢
排查方向:
- 确认使用了正确的执行提供者:
print(f"当前提供者: {session.get_providers()}")
# 应该是 ['CUDAExecutionProvider', 'CPUExecutionProvider']
- 启用图优化:
options = ort.SessionOptions()
options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
session = ort.InferenceSession("model.onnx", sess_options=options)
- 考虑量化:
from onnxruntime.quantization import quantize_dynamic
quantize_dynamic("model.onnx", "model_int8.onnx")
- 检查输入预处理:
- 是否有不必要的 CPU-GPU 数据传输?
- 预处理是否可以并行化?
问题:吞吐量低
解决方案:
- 增加 batch size:
# 找到合适的 batch size
for bs in [1, 2, 4, 8, 16, 32]:
# 测试吞吐量
pass
- 使用多线程/异步推理:
# 异步推理
session.run_async(["output"], {"input": input_data}, callback, None)
- 使用动态批处理(服务端):
- 等待多个请求后批量处理
- 使用 Triton Inference Server
调试技巧汇总
1. 查看模型结构
import onnx
model = onnx.load("model.onnx")
print(f"IR 版本: {model.ir_version}")
print(f"节点数: {len(model.graph.node)}")
# 打印所有算子
ops = set(node.op_type for node in model.graph.node)
print(f"使用的算子: {sorted(ops)}")
2. 使用 Netron 可视化
pip install netron
netron model.onnx
3. 启用详细日志
import onnxruntime as ort
options = ort.SessionOptions()
options.log_severity_level = 0 # Verbose
session = ort.InferenceSession("model.onnx", sess_options=options)
4. 逐步排除法
# 创建最小复现示例
import torch
import torch.nn as nn
class MinimalModel(nn.Module):
def __init__(self):
super().__init__()
self.conv = nn.Conv2d(3, 32, 3)
def forward(self, x):
return self.conv(x)
model = MinimalModel().eval()
dummy = torch.randn(1, 3, 224, 224)
torch.onnx.export(model, dummy, "minimal.onnx")
# 如果这个能工作,逐步添加原始模型的组件来定位问题
5. 验证工具函数
def diagnose(pytorch_model, onnx_path, input_shape=(1, 3, 224, 224)):
"""快速诊断工具"""
import torch
import onnx
import onnxruntime as ort
import numpy as np
# 1. 检查 ONNX 文件
try:
model = onnx.load(onnx_path)
onnx.checker.check_model(model)
print("✓ ONNX 文件有效")
except Exception as e:
print(f"✗ ONNX 文件问题: {e}")
return
# 2. 检查推理
try:
session = ort.InferenceSession(onnx_path)
input_data = np.random.randn(*input_shape).astype(np.float32)
output = session.run(None, {session.get_inputs()[0].name: input_data})
print("✓ ONNX 推理正常")
except Exception as e:
print(f"✗ ONNX 推理失败: {e}")
return
# 3. 检查一致性
pytorch_model.eval()
with torch.no_grad():
pt_output = pytorch_model(torch.from_numpy(input_data)).numpy()
diff = np.abs(pt_output - output[0]).max()
if diff < 1e-5:
print(f"✓ 输出一致 (差异: {diff:.2e})")
else:
print(f"✗ 输出不一致 (差异: {diff:.2e})")
总结
遇到问题时,按以下步骤排查:
- 检查错误信息:仔细阅读错误消息,通常包含关键线索
- 简化问题:创建最小复现示例
- 查阅文档:检查算子和 Opset 兼容性
- 使用工具:Netron、onnx-simplifier、onnx.checker
- 社区搜索:GitHub Issues、Stack Overflow
大多数问题都源于:
- 模型未设置为 eval 模式
- 动态维度配置错误
- 数据类型不匹配
- Opset 版本不兼容