2. ONNX模型简化与验证

任务目标

对YOLO模型导出的ONNX文件执行计算图优化(常量折叠、冗余节点消除等),并在简化前后分别用相同输入运行推理,对比输出张量以验证数值一致性,确保简化不改变模型行为。

环境依赖

Python 3.8+,安装以下包:

pip install onnx onnxruntime onnx-simplifier numpy

完整实现

import onnx
import numpy as np
import onnxruntime as ort
from onnxsim import simplify

# 1. 加载原始模型
model_path = "yolov8n.onnx"
model = onnx.load(model_path)

# 2. 执行简化
model_sim, check_ok = simplify(model)
if not check_ok:
    raise RuntimeError("ONNX简化验证失败(shape/值检查未通过)")
onnx.save(model_sim, "yolov8n_sim.onnx")
print(f"简化完成,节点数: {len(model.graph.node)} -> {len(model_sim.graph.node)}")

# 3. 构建推理会话
ort_session_orig = ort.InferenceSession(model_path, providers=["CPUExecutionProvider"])
ort_session_sim = ort.InferenceSession("yolov8n_sim.onnx", providers=["CPUExecutionProvider"])

# 4. 生成固定随机输入(与模型输入shape匹配)
input_name = ort_session_orig.get_inputs()[0].name
input_shape = ort_session_orig.get_inputs()[0].shape
# 将动态维度替换为具体值
input_shape = [1 if dim in ("batch", "N") else (640 if dim == 640 else dim) for dim in input_shape]
# 若仍有动态维度,手动指定
input_shape = [1, 3, 640, 640] if "dynamic" in str(input_shape) else input_shape
np.random.seed(0)
dummy_input = np.random.rand(*input_shape).astype(np.float32)

# 5. 分别推理
outputs_orig = ort_session_orig.run(None, {input_name: dummy_input})
outputs_sim = ort_session_sim.run(None, {input_name: dummy_input})

# 6. 逐输出对比
print(f"输出数量: {len(outputs_orig)}")
for idx, (o1, o2) in enumerate(zip(outputs_orig, outputs_sim)):
    if o1.shape != o2.shape:
        print(f"输出{idx} shape不一致: {o1.shape} vs {o2.shape}")
        continue
    max_diff = np.max(np.abs(o1 - o2))
    mean_diff = np.mean(np.abs(o1 - o2))
    print(f"输出{idx} shape={o1.shape} max_diff={max_diff:.6e} mean_diff={mean_diff:.6e}")

# 7. 一致性判定
all_close = all(
    np.allclose(o1, o2, rtol=1e-4, atol=1e-5)
    for o1, o2 in zip(outputs_orig, outputs_sim)
)
print("一致性判定:", "通过" if all_close else "失败")

运行方式

  1. 将待验证的ONNX文件命名为yolov8n.onnx放在脚本同目录(或修改model_path变量指向实际文件);
  2. 执行脚本:python simplify_verify.py。

验证方法与通过标准

  • 简化成功标志:脚本打印节点数变化,且check_ok=True(onnx-simplifier内部已做shape与值校验);
  • 数值一致性:所有输出的max_diff在1e-4量级以下,且最终打印“一致性判定: 通过”;
  • 输出文件:同目录生成yolov8n_sim.onnx,可用Netron打开确认结构已简化。

常见问题

  • 动态维度报错:若模型输入含动态batch或动态分辨率,脚本中已做基础替换;若仍失败,手动将input_shape改为模型实际接受的固定shape(如[1,3,640,640])。
  • 简化后无法加载:某些自定义算子不被onnx-simplifier支持,此时check_ok会返回False,可改用skip_fuse_patterns参数或直接使用onnxruntime的GraphOptimizationLevel做运行时优化。
  • 输出顺序差异:若模型有多个输出,onnxruntime返回顺序与模型定义一致,对比时按索引对应即可。