跳到主要内容

常见问题解决

本章汇总了 ONNX 模型导出和部署过程中的常见问题及其解决方案。

导出阶段问题

问题:不支持的操作 (Unsupported Operator)

错误信息

RuntimeError: ONNX export failed on an operator: xxx

原因:模型中使用了 ONNX 标准算子集中不存在或当前 Opset 版本不支持的操作。

解决方案

  1. 提高 Opset 版本
torch.onnx.export(model, dummy_input, "model.onnx", opset_version=17)
  1. 用基础算子组合替代
# 原始:自定义激活函数
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)
  1. 注册自定义算子导出规则
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 模式,只记录实际执行的路径。

解决方案

  1. 将条件逻辑移到模型外部
# 问题代码
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)
  1. 使用 Scripting 模式
scripted_model = torch.jit.script(model)
torch.onnx.export(scripted_model, dummy_input, "model.onnx")
  1. 使用新的 dynamo 导出器(PyTorch 2.6+):
torch.onnx.export(model, dummy_input, "model.onnx", dynamo=True)

问题:动态维度设置无效

错误信息

InvalidArgument: Got invalid dimensions for input

原因dynamic_axes 配置不正确或与某些算子不兼容。

解决方案

  1. 检查配置语法
# 正确的 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
)
  1. 使用 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]

原因:导出时未配置动态维度,或配置错误。

解决方案

  1. 检查模型输入形状
session = ort.InferenceSession("model.onnx")
for inp in session.get_inputs():
print(f"输入 {inp.name}: {inp.shape}")
  1. 使用正确的输入大小
# 如果模型固定了 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)
  1. 重新导出时添加动态维度
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

原因

  1. 安装的是 CPU 版本的 onnxruntime
  2. CUDA 版本不匹配

解决方案

  1. 安装 GPU 版本
pip uninstall onnxruntime
pip install onnxruntime-gpu
  1. 检查 CUDA 版本兼容性
import onnxruntime as ort

print(f"可用提供者: {ort.get_available_providers()}")

# 如果 CUDAExecutionProvider 不在列表中,检查 CUDA 安装
  1. 验证 CUDA 环境
nvidia-smi  # 检查 GPU 状态
nvcc --version # 检查 CUDA 版本

问题:内存不足

错误信息

RuntimeError: CUDA out of memory

原因:GPU 显存不足,或存在内存泄漏。

解决方案

  1. 减小 batch size
batch_size = 1  # 从小批量开始
  1. 使用 IOBinding 管理 GPU 内存
io_binding = session.io_binding()
# 只将必要的输入输出放在 GPU 上
io_binding.bind_input(...)
io_binding.bind_output(...)
session.run_with_iobinding(io_binding)
  1. 释放未使用的内存
import gc
import torch

gc.collect()
torch.cuda.empty_cache()

问题:推理结果不一致

症状:ONNX 模型输出与 PyTorch 模型有显著差异。

排查步骤

  1. 检查 eval 模式
model.eval()  # 必须在导出前调用
  1. 检查 BatchNorm
# 确保使用推理模式的统计量
for m in model.modules():
if isinstance(m, nn.BatchNorm2d):
m.eval()
  1. 检查 Dropout
# Dropout 应该被跳过
for m in model.modules():
if isinstance(m, nn.Dropout):
assert not m.training
  1. 检查数值精度
# 使用相同的输入对比
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 算子的某些参数组合在目标推理引擎中不支持。

解决方案

  1. 降低 Opset 版本
torch.onnx.export(model, dummy_input, "model.onnx", opset_version=11)
  1. 修改插值方式
# 避免使用 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 更有限。

解决方案

  1. 使用 TensorRT 兼容的算子

    • 避免使用动态 reshape
    • 避免复杂的索引操作
    • 使用 TensorRT 支持的激活函数
  2. 简化模型

# 使用 onnx-simplifier 简化
python -m onnxsim model.onnx model_simplified.onnx
  1. 使用 ONNX-TensorRT 版本矩阵:检查 ONNX Opset 和 TensorRT 版本的兼容性。

性能问题

问题:推理速度慢

排查方向

  1. 确认使用了正确的执行提供者
print(f"当前提供者: {session.get_providers()}")
# 应该是 ['CUDAExecutionProvider', 'CPUExecutionProvider']
  1. 启用图优化
options = ort.SessionOptions()
options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
session = ort.InferenceSession("model.onnx", sess_options=options)
  1. 考虑量化
from onnxruntime.quantization import quantize_dynamic
quantize_dynamic("model.onnx", "model_int8.onnx")
  1. 检查输入预处理
    • 是否有不必要的 CPU-GPU 数据传输?
    • 预处理是否可以并行化?

问题:吞吐量低

解决方案

  1. 增加 batch size
# 找到合适的 batch size
for bs in [1, 2, 4, 8, 16, 32]:
# 测试吞吐量
pass
  1. 使用多线程/异步推理
# 异步推理
session.run_async(["output"], {"input": input_data}, callback, None)
  1. 使用动态批处理(服务端):
    • 等待多个请求后批量处理
    • 使用 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})")

总结

遇到问题时,按以下步骤排查:

  1. 检查错误信息:仔细阅读错误消息,通常包含关键线索
  2. 简化问题:创建最小复现示例
  3. 查阅文档:检查算子和 Opset 兼容性
  4. 使用工具:Netron、onnx-simplifier、onnx.checker
  5. 社区搜索:GitHub Issues、Stack Overflow

大多数问题都源于:

  • 模型未设置为 eval 模式
  • 动态维度配置错误
  • 数据类型不匹配
  • Opset 版本不兼容