目录

46. 问题:当出现Unsupported: ONNX export failed on an operator错误时,你的标准排查流程是什么?

47. 问题:你都知道哪些工具可以可视化和检查ONNX模型结构(如Netron)?

48. 问题:有哪些工具或库(如ONNX Runtime, Polygraphy)可以帮助你验证模型和调试性能?

1. ONNX Runtime

2. Polygraphy

3. TensorRT trtexec

4. Nsight Systems

49. 问题:你遇到过RuntimeError: ONNX export failed:没能导出Python操作符错误吗?它的根本原因是什么?

50. 问题:错误信息Expected all tensors to be on the same device出现在转换过程中,问题出在哪?

51. 问题:转换成功,但推理时出现[ONNXRuntimeError] : Non-zero status code returned,你的调试思路是什么?

52. 问题:你知道哪些常见的、在转换时容易出问题的PyTorch算子?

53. 问题:遇到不支持的PyTorch算子时,你有哪几种标准应对方案?

54. 问题:如何为一个PyTorch操作实现自定义的ONNX导出(重写symbolic方法)?

55. 问题:如何通过寻找等价算子组合来绕过不支持的操作?

56. 问题:什么是ONNX图的形状推断?一个模型缺少形状信息会导致什么问题?

57. 问题:你知道ONNX有哪些内置的图优化和简化工具(如onnx-simplifier)?它们能解决什么问题?

58. 问题:当你导出一个高opset版本的模型,但目标推理引擎只支持低版本时,降级策略是什么?


46. 问题:当出现Unsupported: ONNX export failed on an operator错误时,你的标准排查流程是什么?

当出现"Unsupported: ONNX export failed on an operator"错误时,我的标准排查流程如下:

  1. 确认错误类型和上下文:首先获取完整的错误信息,确定是ONNX本身不支持该算子,还是特定执行提供者(如TensorRT)不支持。
  2. 检查ONNX算子支持情况:查阅ONNX官方算子支持列表,确认该算子在目标opset版本中是否被支持。
  3. 验证模型结构合法性:使用ONNX Checker工具验证模型结构是否符合规范。
  4. 分析动态轴设置:检查torch.onnx.export()中的dynamic_axes参数是否正确配置。
  5. 尝试不同opset版本:调整opset_version参数,尝试不同版本的ONNX算子集。
  6. 查找等价算子组合:如果算子确实不支持,寻找等价算子组合实现相同功能。
  7. 实现自定义ONNX导出方法:为自定义算子实现symbolic函数并注册。
  8. 简化模型结构:使用onnx-simplifier移除冗余节点,修复可能的结构问题。
  9. 检查框架版本兼容性:确保PyTorch、ONNX和ONNX Runtime的版本兼容。
  10. 逐层验证输出:使用Polygraphy工具对比不同后端的逐层输出差异。

47. 问题:你都知道哪些工具可以可视化和检查ONNX模型结构(如Netron)?

以下是常用的ONNX模型可视化和检查工具:

工具 主要功能 使用场景
Netron 可视化模型结构、检查输入输出维度和数据类型 快速查看模型整体结构和算子类型
ONNX Checker 验证模型结构合法性,检查算子兼容性 确保模型符合ONNX规范
ONNX Shape Inference 分析模型形状信息,解决动态轴问题 验证动态维度设置是否正确
Polygraphy 检查模型算子兼容性,对比不同后端输出 分析TensorRT等推理引擎的兼容性
TensorRT解析器 在转换ONNX时抛出详细错误信息 调试TensorRT转换过程中的问题
AI Toolkit验证脚本 自动检测兼容性问题,生成报告 快速评估模型在不同平台上的兼容性
ONNX-GraphSurgeon 编辑和优化ONNX模型结构 手动修改模型结构以解决兼容性问题
ONNX计算器 验证模型计算的正确性 检查模型输出是否与预期一致

48. 问题:有哪些工具或库(如ONNX Runtime, Polygraphy)可以帮助你验证模型和调试性能?

以下是用于验证和调试ONNX模型性能的常用工具和库:

1. ONNX Runtime

功能:提供高性能的ONNX模型推理

性能验证示例

import onnxruntime as ort
import numpy as np
import time

# 加载模型
session = ort.InferenceSession("model.onnx")

# 准备输入数据
inputs = {"input": np.random.rand(1,3,224,224).astype(np.float32)}

# 多次推理取平均
num_trials = 100
total_time = 0

for _ in range(num_trials):
    start = time.time()
    session.run(None, inputs)
    end = time.time()
    total_time += end - start

avg_latency = total_time / num_trials
print(f"Average latency: {avg_latency:.4f} seconds")

2. Polygraphy

功能:NVIDIA开发的模型调试工具链

多后端验证

polygraphy run model.onnx --onnxrt --trt --validate --atol=1e-3 --rtol=1e-3

3. TensorRT trtexec

功能:TensorRT自带的命令行性能测试工具

性能测试

trtexec --onnx=model.onnx --fp16 --batch=8 --timing --吞吐量

4. Nsight Systems

功能:NVIDIA的性能分析工具,可视化GPU执行情况

用法

nsight-systems -o profile痕迹 model.onnx

49. 问题:你遇到过RuntimeError: ONNX export failed:没能导出Python操作符错误吗?它的根本原因是什么?

是的,我遇到过类似的错误。根本原因是PyTorch无法将特定的Python操作符映射到ONNX标准算子

具体来说,当出现"RuntimeError: ONNX export failed:没能导出Python操作符"错误时,通常是因为:

  1. 自定义操作符:用户在模型中定义了自定义的Python操作符,而PyTorch没有为其注册对应的ONNX导出方法。
  2. 框架不支持的算子:某些PyTorch算子在目标opset版本中没有对应的ONNX实现。
  3. 动态控制流:如iffor等Python控制流结构在ONNX中需要特殊处理。
  4. 未实现的符号函数:PyTorch的symbolic函数未实现该操作符。

解决方案包括:

  • 注册自定义算子:为自定义操作符实现symbolic函数并注册
  • 寻找等价算子组合:用支持的算子组合实现相同功能
  • 提高opset版本:某些算子在高版本opset中才被支持
  • 简化模型结构:使用onnx-simplifier移除冗余节点

50. 问题:错误信息Expected all tensors to be on the same device出现在转换过程中,问题出在哪?

当出现"Expected all tensors to be on the same device"错误时,问题出在模型或输入张量的设备不一致

具体来说,这个错误通常发生在以下情况:

  1. 模型参数与输入张量不在同一设备:例如模型在GPU上,但输入张量在CPU上。
  2. 模型内部张量设备不一致:某些层的参数可能被意外移动到不同设备。
  3. 动态计算过程中设备切换:例如在forward函数中混合使用CPU和GPU张量。
  4. 未初始化的张量:某些层可能在forward函数中动态创建张量,而这些张量默认在CPU上。

解决方法

# 确保输入张量与模型在同一设备
input = input.to(device)

# 将整个模型移动到同一设备
model = model.to(device)

# 在forward函数中显式指定设备
def forward(self, x):
    ones = torch.ones(x.shape, device=x.device)
    return x * ones

关键点

  • ONNX导出过程需要所有张量在同一设备上,通常为CPU
  • 如果模型在GPU上,导出前应先移动到CPU
  • forward函数中显式指定张量设备
  • 使用model.eval()并确保所有张量都已正确初始化

51. 问题:转换成功,但推理时出现[ONNXRuntimeError] : Non-zero status code returned,你的调试思路是什么?

当出现"[ONNXRuntimeError] : Non-zero status code returned"错误时,我的调试思路如下:

  1. 获取详细错误信息:通过设置SessionOptions.log_severity_level启用详细日志
  2. 验证模型合法性:使用ONNX Checker验证模型结构是否符合规范
  3. 检查输入输出形状:确保输入张量的形状与模型定义的dynamic_axes一致
  4. 分析算子兼容性:确认所有算子在目标执行提供者(EP)中被支持
  5. 检查数据类型:确保输入张量的数据类型与模型定义一致
  6. 简化模型结构:使用onnx-simplifier移除冗余节点
  7. 逐层验证输出:使用Polygraphy工具对比不同后端的逐层输出差异
  8. 检查执行提供者:确保正确配置了目标硬件的执行提供者

示例代码

# 启用详细日志
options = SessionOptions()
options.log_severity_level = 0
session = InferenceSession("model.onnx", options)

# 验证模型合法性
import onnx
model = onnx.load("model.onnx")
try:
    onnx.checker.check_model(model)
    print("模型结构合法")
except onnx.checker.ValidationError as e:
    print(f"模型验证失败: {e}")

52. 问题:你知道哪些常见的、在转换时容易出问题的PyTorch算子?

以下是一些在PyTorch到ONNX转换过程中容易出问题的算子:

算子 问题描述 解决方案
AdaptiveAvgPool2d 需替换为AvgPool2d并指定具体输出尺寸 使用固定尺寸的池化层替代
ScatterND/Scatter 某些推理引擎不支持,需用ScatterElements替代 使用等价算子组合实现
grid_sample 需用bilinear_grid_sample替代 使用自定义实现或替代算子
NonMaxSuppression 需手动实现或使用torchvision ops中的替代方案 使用等价算子组合
Mod/fmod TensorRT不支持 AddDiv组合实现
MaxPool3D 需替换为2D池化或使用特定形状的输入 调整模型结构
Where 需确保条件张量的维度与输入匹配 调整输入形状
Loop 需手动拆分循环逻辑为子图 使用For循环替代

53. 问题:遇到不支持的PyTorch算子时,你有哪几种标准应对方案?

遇到不支持的PyTorch算子时,有以下几种标准应对方案:

  1. 提高opset版本
    torch.onnx.export(model, dummy_input, "model.onnx", opset_version=16)
  2. 寻找等价算子组合
    • ScatterND → ScatterElements+Where组合
    • Hardswish → Multiply+Divide+Clip组合
    • NonMaxSuppression → Sort+Where+ScatterND组合
  3. 自定义ONNX导出方法
    from torch.onnx import register_custom_op
    
    def my_op_symbolic(g, input):
        return g.op("MyCustomOp", input)
    
    register_custom_op("MyCustomOp", my_op_symbolic)
  4. 使用ONNX工具链优化
    • 使用onnx-simplifier简化模型结构
    • 使用onnxruntimeModel Optimizer优化模型结构
    • 使用Polygraphy工具分析模型并替换不支持的算子
  5. 修改模型结构
    • 替换为支持的算子,如将GroupNorm改为BatchNorm
    • 固定输入形状以避免动态轴问题
    • 拆分复杂控制流为支持的算子组合

54. 问题:如何为一个PyTorch操作实现自定义的ONNX导出(重写symbolic方法)?

为PyTorch操作实现自定义ONNX导出(重写symbolic方法)的步骤如下:

  1. 导入必要模块
    from torch.onnx import register_custom_op
    from torch.onnx象征性 import symbolic
  2. 定义symbolic函数
    @symbolic('MyCustomOp')
    def my_custom_op_symbolic(g, input, alpha=1.0, inplace=False):
        # 在这里实现ONNX算子的映射
        return g.op("MyCustomOp", input, alpha_f=alpha)
  3. 注册自定义算子
    register_custom_op('MyCustomOp', my_custom_op_symbolic)
  4. 在模型中使用自定义算子
    class MyModel(nn.Module):
        def forward(self, x):
            return my_custom_op(x, alpha=0.5)
  5. 导出模型时启用自定义算子
    torch.onnx.export(model, input, "model.onnx",
                      custom_ops=[my_custom_op_symbolic],
                      opset_version=13)

关键点

  • symbolic函数的第一个参数必须是g(ONNX图对象)
  • 参数名称必须与PyTorch接口一致
  • 使用g.op()创建ONNX算子节点
  • 对于复杂操作,可能需要组合多个ONNX算子

55. 问题:如何通过寻找等价算子组合来绕过不支持的操作?

通过寻找等价算子组合绕过不支持的操作的步骤如下:

  1. 理解操作功能:明确不支持的算子的具体数学操作和逻辑流程
  2. 查阅ONNX算子文档:查找可实现相同功能的ONNX算子
  3. 设计等价组合:用支持的算子组合实现相同功能
  4. 实现替代逻辑:在PyTorch模型中替换为等价算子组合
  5. 验证输出一致性:使用测试数据验证替换前后的输出结果是否一致
  6. 简化模型:导出后使用onnx-simplifier简化模型结构

具体案例

对于不支持的ScatterND操作,可以用以下等价组合实现:

def scatter_nd_alternative(input, indices, updates):
    # 使用ScatterElements和Where组合实现类似功能
    mask = (indices == torch.arange(indices.size(0))).all(dim=1)
    output = torch.where(mask.unsqueeze(1), updates, input)
    return output

常见等价组合

不支持算子 等价组合
ScatterND ScatterElements+Where
Hardswish Multiply+Divide+Clip
NonMaxSuppression Sort+Where+ScatterND

56. 问题:什么是ONNX图的形状推断?一个模型缺少形状信息会导致什么问题?

ONNX图的形状推断是指ONNX引擎在加载模型时,自动推断各节点张量形状的过程。ONNX模型本身可以包含形状信息(静态形状),也可以在某些维度上保留动态形状(如batch_size)。

模型缺少形状信息会导致的问题

  1. 推理时维度不匹配:下游算子无法处理不确定的输入形状
  2. 执行提供者(EP)优化受限:ONNX Runtime和TensorRT等EP需要形状信息进行优化
  3. 动态输入支持失败:无法正确处理可变batch_size或输入尺寸
  4. 模型验证失败onnx.checker.check_model()可能因形状不明确而报错
  5. 内存分配错误:推理引擎无法预分配足够内存,导致OOM错误

解决方法

  • 在导出时明确指定dynamic_axes参数
  • 使用onnx形状推理器推断并固定形状
  • 通过onnx-simplifier简化模型并修复形状信息
  • 在模型中添加明确的形状约束(如Reshape算子)

示例代码

from onnx import shape_inference

# 加载模型
model = onnx.load("model.onnx")

# 进行形状推断
inferred_model = shape_inference.infer_shapes(model)

# 保存推断后的模型
onnx.save(inferred_model, "model_with_shapes.onnx")

57. 问题:你知道ONNX有哪些内置的图优化和简化工具(如onnx-simplifier)?它们能解决什么问题?

ONNX内置的图优化和简化工具包括:

工具 功能 解决的问题 用法
onnx形状推理器 静态推断模型中所有张量的形状信息 修复动态轴或维度不明确的问题 shape_inference.infer_shapes(model)
onnx计算器 验证模型计算的正确性 检查模型输出是否与预期一致 reference_evaluator(model)
onnx-simplifier 简化ONNX模型结构,移除冗余节点 修复因冗余节点导致的转换问题 python -m onnxsim input.onnx output.onnx
onnxruntime优化器 自动优化模型图结构 消除未使用的节点,合并相邻的算子 sess_options.graph_optimization_level = ORT_ENABLE_ALL
TensorRT解析器 解析并优化ONNX模型为TensorRT引擎 自动融合支持的算子,优化显存使用 trt.OnnxParser(network, TRT_LOGGER)

onnx-simplifier使用示例

# 安装
pip install onnx-simplifier

# 使用
python -m onnxsim input.onnx output.onnx

onnxruntime优化器使用示例

from onnxruntime import InferenceSession, SessionOptions

sess_options = SessionOptions()
sess_options.graph_optimization_level = ORT_ENABLE_ALL
session = InferenceSession("model.onnx", sess_options)

58. 问题:当你导出一个高opset版本的模型,但目标推理引擎只支持低版本时,降级策略是什么?

当导出的高opset版本模型与目标推理引擎不兼容时,降级策略包括:

  1. 降低opset版本
    torch.onnx.export(model, dummy_input, "model.onnx", opset_version=11)
  2. 手动替换不支持的算子
    • 使用onnxruntimeonnx库加载模型后,遍历节点并替换不支持的算子为低版本等价实现
    • 示例:将AdaptiveAvgPool2d替换为固定尺寸的AvgPool2d
  3. 使用ONNX工具链降级
    • 使用onnx形状推理器推断并固定形状,减少对高版本opset的依赖
    • 使用onnx计算器验证模型计算的正确性
  4. 使用中间框架转换
    • 将高opset模型转换为中间框架(如PyTorch)后再导出为低opset版本
    • 示例:使用onnxruntime加载高opset模型,运行推理后重新导出
  5. 调整模型结构
    • 替换为支持的算子,如将GroupNorm改为BatchNorm
    • 固定输入形状以避免动态轴问题

关键点

  • 降级可能导致功能损失或精度下降,需进行充分测试
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐