3、ONNX模型调试:Netron可视化工具、onnxruntime推理验证、onnxsim模型简化、onnx-graphsurgeon基础操作

好,咱们进入第三章。这一章我把它叫做「模型调试四件套」。说白了,就是拿到一个ONNX模型后,你该怎么看、怎么跑、怎么瘦身、怎么动手术。

我在项目里见过太多人,模型转完ONNX就直接扔给TensorRT了。结果一跑,精度不对,或者干脆跑不起来。然后开始怀疑人生。其实,问题往往出在ONNX本身。你想想看,源头的水都不干净,下游怎么可能清澈?

所以,这一章我们就把ONNX模型扒个底朝天。

3.1 Netron可视化工具:一眼看穿模型结构

Netron,这玩意儿我每天都在用。它就像一个X光机,能把ONNX模型的内部结构照得清清楚楚。

你不需要安装什么复杂的环境。直接打开浏览器,访问 netron.app 就行。或者,你也可以用pip安装本地版本:

pip install netron
netron your_model.onnx

我个人习惯用本地版本,因为有时候模型文件比较大,网页版加载会慢一些。

打开之后,你会看到什么?

  • 输入输出节点:最左边是输入,最右边是输出。名字、数据类型、形状,一目了然。
  • 中间算子:每个方框代表一个算子,比如Conv、Relu、Add。点击它,能看到算子的属性,比如卷积核大小、步长、padding。
  • 张量形状:每条连线代表一个张量。鼠标悬停上去,能看到它的形状和数据类型。
我的小技巧: 当你怀疑某个算子参数不对时,直接在Netron里点开看。我曾经排查过一个Resize算子的问题,就是因为mode参数从'bilinear'变成了'nearest',Netron里一眼就发现了。

嗯,这里要注意。Netron只是可视化工具,它不能帮你改模型。但它能帮你快速定位问题。比如,你发现输出形状不对,那八成是某个Reshape或者Gather算子搞的鬼。

3.2 onnxruntime推理验证:跑一把,看结果

光看结构还不够。你得跑一下,看看输出对不对。这就是onnxruntime的活儿。

onnxruntime是微软出的推理引擎。它不挑硬件,CPU、GPU都能跑。用它来验证ONNX模型,再合适不过了。

代码很简单,我直接贴出来:

import onnxruntime as ort
import numpy as np

# 创建推理会话
session = ort.InferenceSession("your_model.onnx")

# 获取输入输出信息
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name

# 构造随机输入(注意形状和数据类型要和模型匹配)
dummy_input = np.random.randn(1, 3, 224, 224).astype(np.float32)

# 推理
outputs = session.run([output_name], {input_name: dummy_input})

print("输出形状:", outputs[0].shape)
print("输出值:", outputs[0])

这段代码,我建议你每次转完ONNX都跑一遍。为什么?

  • 验证形状:输入输出形状对不对?有没有多出奇怪的维度?
  • 验证数值:输出值是不是在合理范围内?比如分类任务的输出应该是概率值,在0到1之间。
  • 验证精度:你可以把onnxruntime的输出和原始PyTorch模型的输出对比一下。如果差异很大,说明模型转换出了问题。
我曾经踩过的坑: 有一次,我转完模型后,用onnxruntime跑出来的结果全是NaN。排查了半天,发现是模型里有一个除零操作。在PyTorch里,那个分母永远不会是0,但转成ONNX后,输入变了,分母就变成0了。所以,一定要用真实数据或者模拟真实数据来验证。

3.3 onnxsim模型简化:给模型减减肥

模型转成ONNX后,往往会带一些冗余的算子。比如,一些恒等映射、不必要的Cast操作。这些冗余不仅让模型变大,还会拖慢推理速度。

onnxsim就是干这个的。它能简化模型结构,去掉那些没用的东西。

安装和使用都很简单:

pip install onnx-simplifier

python -m onnxsim your_model.onnx simplified_model.onnx

就这么一行命令。简化后的模型,体积可能小10%到30%,推理速度也能快一些。

你可能会问:「简化后精度会变吗?」

理论上不会。onnxsim只做数学上等价的变换。比如,Add(0, x) 直接变成 x。但为了保险起见,我建议简化后再用onnxruntime跑一遍,对比一下输出。

我的经验: 有些模型,比如用了大量if-else控制流的,onnxsim可能简化不了。这时候别强求,手动检查一下就好。

3.4 onnx-graphsurgeon基础操作:给模型动手术

如果说Netron是看病的,onnxruntime是体检的,onnxsim是减肥的,那onnx-graphsurgeon就是做手术的。

它能让你直接修改ONNX模型的计算图。你可以增删算子、修改参数、合并节点。这在做模型优化时特别有用。

举个例子。假设你的模型里有一个 Conv + BatchNorm + Relu 的结构。在TensorRT里,这三个算子可以融合成一个算子,速度更快。但有时候,TensorRT的自动融合会失败。这时候,你就可以用graphsurgeon手动把它们融合。

先看一个基础操作:删除一个节点。

import onnx_graphsurgeon as gs
import onnx

# 加载模型
graph = gs.import_onnx(onnx.load("your_model.onnx"))

# 找到要删除的节点(比如一个恒等映射的Add节点)
for node in graph.nodes:
    if node.op == "Add" and node.inputs[0].name == node.inputs[1].name:
        # 把这个节点的输入直接连接到它的输出
        node.outputs[0].inputs = node.inputs
        # 删除这个节点
        graph.cleanup().toposort()

# 导出模型
onnx.save(gs.export_onnx(graph), "modified_model.onnx")

这段代码做了什么?它找到了一个Add节点,这个节点的两个输入是同一个张量(即 x + x)。然后,它把Add节点的输入直接连到了Add节点的输出上,相当于跳过了这个Add操作。最后,清理掉无用的节点。

再来看一个更实用的操作:修改算子的属性。

import onnx_graphsurgeon as gs
import onnx
import numpy as np

graph = gs.import_onnx(onnx.load("your_model.onnx"))

# 找到第一个Conv节点
for node in graph.nodes:
    if node.op == "Conv":
        # 修改卷积核的权重(比如全部设为0.5)
        weights = node.inputs[1].values
        weights[:] = 0.5
        node.inputs[1].values = weights
        break

onnx.save(gs.export_onnx(graph), "modified_model.onnx")

这个例子展示了如何直接修改卷积核的权重。当然,实际项目中你不会随便改权重,但你可以用它来做一些特殊的优化,比如量化感知训练后的权重调整。

我的建议: graphsurgeon的学习曲线有点陡。刚开始,你只需要掌握三个操作就够了:删除节点添加节点修改属性。别贪多,先把这三个练熟。

3.5 四件套的配合使用

这四个工具,不是孤立的。我一般的工作流是这样的:

  1. Netron:先看一眼模型结构,确认输入输出对不对。
  2. onnxruntime:跑一次,验证数值和形状。
  3. onnxsim:简化模型,去掉冗余。
  4. onnx-graphsurgeon:如果还有问题,或者想做特殊优化,就动手术。

每一步做完,我都会再用onnxruntime验证一次。确保每一步都没有引入错误。

你想想看,如果连ONNX这关都没过,后面TensorRT的优化就是空中楼阁。所以,别嫌麻烦,把这四件套用好,能省你后面80%的调试时间。

好,这一章就到这里。下一章,我们开始真正进入TensorRT的世界。