
opset / IR / FP16 / dynamic batch,导出参数一个都不能错
开头:C++ 加载 ONNX 报错,你查了三天
你有没有过这种经历?
训练了一个精度 99% 的模型,导出了 ONNX。产线同事说:「C++ 加载报错。」
你查了三天,发现是 IR 版本过高(IR=11),旧版 ORT 不支持。锁定 IR=8 后,问题解决。
.onnx 训好了不算完,C++ 能跑才是真正的交付。
AOI 项目有一套统一的导出规范:opset=12、IR=8、FP16、dynamic batch。每个 ONNX 文件带时间戳和元信息。
不是因为 ONNX 有多难------而是因为导出参数一个都不能错。
一、AOI 项目的导出规范
统一参数
| 参数 | 推荐值 | 说明 |
|---|---|---|
| format | "onnx" | 导出格式 |
| opset | 12 | 操作集版本 |
| ir_version | 8 | 内部版本号(手动锁定) |
| half | True | FP16 |
| dynamic | True | 动态 batch |
| imgsz | 与训练一致 | 输入尺寸 |
为什么要锁定 IR?
新版 ultralytics 可能写出过高 IR 版本(如 IR=11)
旧版 ORT(C++ 端)可能不支持高版本 IR
解决方案:导出后手动锁定 IR=8
二、AOI 项目的导出脚本
核心函数
python
def export_compatible_onnx(weights, *, out_dir, run_id, imgsz=224, half=True):
"""
导出 C++ 链路可用的 ONNX
"""
model = YOLO(str(weights))
# 1. 导出 ONNX
exported = model.export(
format="onnx",
imgsz=imgsz,
dynamic=True,
half=half,
opset=12,
)
# 2. 锁定 IR 版本
onnx_model = onnx.load(str(exported))
onnx_model.ir_version = 8 # 锁定 IR=8
onnx.save(onnx_model, str(exported))
# 3. 产出文件管理
stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
tagged = f"{run_id}_opset12_ir8"
# 主产出:带时间戳,不覆盖
stamped = out_dir / f"{tagged}_{stamp}.onnx"
shutil.copy2(exported, stamped)
# 指针文件:可覆盖
latest = out_dir / f"{tagged}_latest.onnx"
shutil.copy2(exported, latest)
# 元信息
meta = out_dir / f"{tagged}_{stamp}.txt"
meta.write_text(f"run_id={run_id}\nonnx={stamped}\nir_version=8\nopset=12")
return stamped
文件命名规范
{run_id}_opset12_ir8_{timestamp}.onnx # 主产出,不覆盖
{run_id}_opset12_ir8_latest.onnx # 指针文件,可覆盖
{run_id}_opset12_ir8_{timestamp}.txt # 元信息
三、AOI 项目的三种导出场景
场景1:极性分类 ONNX
python
# 极性分类
exported = export_compatible_onnx(
weights="runs/classify/train-yolo12n-cls-cpp224v2/weights/best.pt",
out_dir=Path("onnx"),
run_id="yolo12n-cls-cpp224v2",
imgsz=224,
half=True,
)
# 产出:yolo12n-cls-cpp224v2_opset12_ir8_20260808_113805.onnx
场景2:缺陷分类 ONNX
python
# 缺陷分类 V4
exported = export_compatible_onnx(
weights="runs/train/defect_cls_v4_safe/weights/best.pt",
out_dir=Path("onnx"),
run_id="defect_cls_v4",
imgsz=224,
half=True,
)
# 产出:defect_cls_v4_opset12_ir8_20260808_123740.onnx
场景3:灯组检测 ONNX
python
# 灯组检测(已锁定 IR=8,跳过重新锁定)
exported = export_compatible_onnx(
weights="runs/detect/train-yolo11s/weights/best.pt",
out_dir=Path("onnx"),
run_id="yolo11s-det",
imgsz=640,
half=True,
)
# 产出:yolo11s-det_opset12_ir8_20260808_123812.onnx
四、ONNX 文件管理
防覆盖策略
错误做法:
直接导出到 model.onnx
下次导出覆盖了上次的结果
正确做法:
带时间戳的主产出:model_20260808_113805.onnx(不覆盖)
指针文件:model_latest.onnx(可覆盖)
元信息:model_20260808_113805.txt
元信息记录
run_id=yolo12n-cls-cpp224v2
onnx=onnx/yolo12n-cls-cpp224v2_opset12_ir8_20260808_113805.onnx
ir_version=8
opset=12
用途:追溯每个 ONNX 文件的来源和参数。
五、避坑指南
坑1:IR 版本过高
python
# 错误:用默认 IR 版本
exported = model.export(format="onnx") # IR=11,旧 ORT 加载失败!
# 正确:手动锁定 IR 版本
onnx_model = onnx.load(str(exported))
onnx_model.ir_version = 8 # 锁定 IR=8
onnx.save(onnx_model, str(exported))
坑2:imgsz 与训练不一致
python
# 错误:训练用 224,导出用 640
model.train(imgsz=224)
model.export(imgsz=640) # 输入尺寸不一致,推理结果错误!
# 正确:导出尺寸与训练一致
model.train(imgsz=224)
model.export(imgsz=224)
坑3:覆盖已有 ONNX
python
# 错误:导出到已有文件,覆盖了旧版本
shutil.move(exported, "model.onnx") # 覆盖了!
# 正确:带时间戳,不覆盖
stamped = f"model_{timestamp}.onnx"
shutil.move(exported, stamped)
坑4:不记录元信息
现象 :ONNX 文件太多,不知道哪个是哪个版本。
解法 :每个 ONNX 文件带 .txt 元信息。
坑5:FP16 精度损失未验证
python
# 错误:直接用 FP16 导出,不验证精度
exported = model.export(format="onnx", half=True) # 可能有精度损失!
# 正确:用 FP32 对比验证
exported_fp16 = model.export(format="onnx", half=True)
exported_fp32 = model.export(format="onnx", half=False)
# 对比两个模型的精度,确认 FP16 没有精度损失
AOI 项目优秀实践
- 统一导出规范:opset=12、IR=8、FP16、dynamic batch
- 时间戳防覆盖:每个 ONNX 文件带时间戳,不会覆盖旧版本
- 元信息记录 :每个 ONNX 文件有对应的
.txt元信息
AOI 项目可改进之处
- 缺少导出后精度验证工具:没有自动对比 FP16 和 FP32 精度
- 缺少多格式导出支持:只支持 ONNX,不支持 TensorRT / OpenVINO
- 缺少导出日志:没有记录每次导出的参数和结果
结尾
说回那个「C++ 加载 ONNX 报错」的故事。
后来我定了一套导出规范:opset=12、IR=8、FP16、dynamic batch。每个 ONNX 文件带时间戳和元信息。产线同事再也没报过加载错误。
不是因为 ONNX 有多难------而是因为导出参数一个都不能错。
翻翻你的项目:
- 你的 ONNX 导出用的是哪个 opset 版本?
- 你的 IR 版本是多少?旧版 ORT 能加载吗?
- 你的 ONNX 文件有元信息记录吗?
评论区说说你在 ONNX 导出上踩过最深的坑。
觉得有用?
收藏这篇,下次导出 ONNX 时翻出来对照。转发给你团队里那个天天被 ONNX 折磨的同事。
下期预告:
训练报告 99%,产线上只有 80%?
裁图验证 / Holdout 盲测 / 整板 E2E,三级验证体系全拆解。
下一篇,我们聊三级验证体系------「训练准 不等于 产线准」。
本文是《AOI 芯片焊盘缺陷检测实战》系列第六篇。