4. 模型文件规范

ONNX模型在工程交付中往往被视为“可运行的二进制产物”,但实际项目中,模型文件本身承载的信息密度远高于权重数值本身。一个缺乏规范的ONNX文件,可能在交付后数周内就变得不可维护:无法确认它由哪个框架导出、对应哪个训练版本、输入张量的语义是什么、是否经过量化或图优化。模型文件规范的核心目标,是让模型文件在脱离原始训练环境后,依然具备可读性、可追溯性、可复现性。本章围绕命名规则、目录结构、版本管理、元数据填写四个维度展开,建立一套可直接落地的ONNX工程交付标准。

ONNX模型文件规范四维框架 命名规则 · 模型名_版本号.onnx · 小写+下划线 · 不含空格/特殊字符 · 示例:resnet50_v1.2.onnx 目录结构 · models/原始模型 · optimized/优化后 · quantized/量化版 · test_cases/验证用例 版本管理 · 语义化版本号 · 与训练代码Git同步 · 变更记录CHANGELOG · 哈希校验SHA256 元数据 · producer_name · description · custom_metadata_map · 输入输出语义标注 四维 一体 交付前检查流程 ① 命名合规 ② 目录归位 ③ 版本核对 ④ 元数据完整 ⑤ 校验通过 关键原则 模型文件是交付物,不是中间产物 —— 每个字段都应为后续维护者降低认知成本

4.1 命名规则:让文件名自解释

ONNX模型文件的命名是工程交付的第一道门面。一个规范的命名应当让团队成员在不打开文件的情况下,就能获取三个关键信息:模型身份、版本状态、适用场景。实践中推荐采用以下模式:

格式:{模型家族}_{变体}_{精度或优化标记}_v{主版本}.{次版本}.onnx
示例:resnet50_imagenet_fp32_v1.2.onnx
      bert_base_uncased_quant_v2.0.onnx
      yolo5s_coco_int8_v3.1.onnx

命名规则的具体约束如下表所示:

组成部分 规则要求 合法示例 非法示例
模型家族 小写字母+数字,不含下划线 resnet50 ResNet_50
变体/数据集 小写+下划线,不超过20字符 imagenet ImageNet-2012
精度标记 fp32/fp16/int8/bf16 fp32 float32
版本号 v主版本.次版本,主版本从1开始 v1.2 V1.2.3
扩展名 统一小写.onnx .onnx .ONNX
风险提示:避免在文件名中使用日期(如resnet50_20240115.onnx),因为日期无法表达语义化版本关系,且容易造成多个同日文件无法区分。同样,避免使用“final”“latest”“最终版”等模糊词汇,这些词汇在版本迭代中必然产生歧义。

4.2 目录结构:建立可预测的交付布局

一个ONNX项目的交付目录应当让新成员在5分钟内找到所有关键资产。推荐采用以下分层结构:

model_delivery/
├── models/                    # 原始导出模型
│   ├── resnet50_imagenet_fp32_v1.0.onnx
│   └── resnet50_imagenet_fp32_v1.1.onnx
├── optimized/                 # 图优化/算子融合后
│   └── resnet50_imagenet_fp32_v1.1_opt.onnx
├── quantized/                 # 量化模型
│   └── resnet50_imagenet_int8_v1.1.onnx
├── test_cases/                # 验证用例
│   ├── input_001.npy
│   ├── output_001.npy
│   └── config.json
├── scripts/                   # 推理/转换脚本
│   ├── inference.py
│   └── verify_onnx.py
├── docs/                      # 文档
│   ├── model_card.md
│   └── compatibility_report.md
└── CHANGELOG.md               # 版本变更记录

目录设计遵循三个原则:按处理阶段分目录(原始/优化/量化)、测试资产与模型分离、文档与代码分离。其中test_cases目录尤为重要,它保存了每次验证使用的输入输出对,确保模型可复现性。

实践建议:对于超过10个模型的交付项目,建议在models目录下按模型家族分子目录,例如models/resnet/、models/bert/。同时,在根目录维护一个MANIFEST.json文件,记录每个模型的路径、版本、哈希值、依赖环境,作为机器可读的交付清单。

4.3 版本管理:语义化版本与可追溯性

ONNX模型的版本管理不能简单依赖文件名后缀,必须建立语义化版本体系。推荐遵循以下规则:

  • 主版本号(Major):模型架构变化、输入输出张量形状改变、算子集版本升级导致不兼容时递增。
  • 次版本号(Minor):权重更新、训练数据扩充、精度提升但接口不变时递增。
  • 补丁版本号(Patch):修复推理bug、优化图结构但不改变数值结果时递增。

版本管理的关键操作流程如下:

  1. 每次导出模型时,从Git标签或CI流水线获取当前代码版本号,自动写入文件名和元数据。
  2. 在CHANGELOG.md中记录每个版本的变更内容、导出环境(PyTorch/TensorFlow版本)、onnxruntime版本。
  3. 对每个发布模型计算SHA256哈希值,存入MANIFEST.json,防止文件被意外篡改。
  4. 旧版本模型保留至少两个迭代周期,不立即删除,以便回滚。
# CHANGELOG.md 示例
## [1.1.0] - 2025-03-15
### 变更
- 使用新增的5000张工业缺陷图像重新训练
- Top-1准确率从92.3%提升至93.1%
- 输入张量形状不变,保持[1,3,224,224]
### 导出环境
- PyTorch 2.1.0
- onnx 1.14.0
- onnxruntime 1.16.0
版本管理核心原则:模型版本必须与训练代码版本一一对应。如果无法从模型文件追溯到训练脚本的Git提交号,那么这个模型就不具备可复现性。建议在元数据的custom_metadata_map中直接记录git_commit_hash字段。

4.4 元数据填写:让模型自述其身份

ONNX的元数据是模型文件内部的结构化信息,通过ModelProto的字段承载。正确填写元数据,相当于给模型附上了“身份证”和“说明书”。关键字段及填写规范如下:

字段名 必填性 填写规范 示例
producer_name 必填 导出框架名称+版本 PyTorch-2.1.0
producer_version 必填 框架具体版本号 2.1.0
description 必填 模型用途、输入输出语义、训练数据概要 ResNet50图像分类模型,输入RGB图像[1,3,224,224],输出1000类ImageNet概率分布
model_version 必填 语义化版本号,与文件名一致 1
domain 推荐 组织域名反写 com.company.vision
custom_metadata_map 强烈推荐 扩展字段:git_commit、训练数据集、量化参数、opset版本等 {"git_commit": "a1b2c3d", "dataset": "imagenet2012"}

在Python中设置元数据的标准方式如下:

import onnx
from onnx import helper

# 假设已有model对象
model = onnx.load("resnet50_imagenet_fp32_v1.0.onnx")

# 设置元数据
model.producer_name = "PyTorch-2.1.0"
model.producer_version = "2.1.0"
model.model_version = 1
model.domain = "com.company.vision"
model.description = (
    "ResNet50图像分类模型,输入RGB图像[1,3,224,224],"
    "输出1000类ImageNet概率分布。训练数据为ImageNet-2012训练集。"
)

# 自定义元数据
metadata = model.metadata_props.add()
metadata.key = "git_commit"
metadata.value = "a1b2c3d4e5f6"

metadata2 = model.metadata_props.add()
metadata2.key = "opset_version"
metadata2.value = "17"

onnx.save(model, "resnet50_imagenet_fp32_v1.0.onnx")
常见错误:许多团队只填写producer_name而忽略description。实际上,description是排查推理问题时的第一线索。例如,当输入张量的数值范围(0-1 vs 0-255)不匹配时,清晰的description能立即定位问题。此外,custom_metadata_map中的opset_version字段对兼容性判断至关重要,因为不同onnxruntime版本支持的opset上限不同。

4.5 实战案例:工业质检模型交付规范落地

某智能制造企业需要交付一个PCB缺陷检测ONNX模型给产线集成商。初始交付时,模型文件名为pcb_det.onnx,无任何元数据,目录混乱,导致集成商无法判断模型输入格式,连续三天调试失败。以下是规范化的完整过程:

初始状态:

  • 文件名:pcb_det.onnx(无版本、无精度信息)
  • 元数据:producer_name为空,description为空
  • 目录:所有文件堆放在一个共享文件夹中
  • 集成商报错:输入张量shape不匹配,且不确定是否需要归一化

规范化操作步骤:

  1. 重命名:根据模型实际架构和训练数据,命名为pcb_yolov5s_fp32_v1.0.onnx,明确是YOLOv5s架构、FP32精度、首个发布版本。
  2. 目录重构:建立models/、test_cases/、docs/三个子目录,将模型放入models/,同时生成一个包含10组输入输出对的test_cases/目录。
  3. 元数据补全:使用Python脚本设置producer_name为"Ultralytics-YOLOv5-v7.0",description写明“输入图像[1,3,640,640],RGB顺序,像素范围0-255,输出为[1,25200,6]的检测结果,格式为cx,cy,w,h,conf,class_id”。在custom_metadata_map中记录git_commit和训练数据集信息。
  4. 版本记录:创建CHANGELOG.md,记录v1.0的导出环境、验证精度(mAP@0.5=0.892)、已知限制(不支持动态batch)。
  5. 验证:运行verify_onnx.py脚本,加载test_cases中的输入,对比输出与预期结果的误差小于1e-4,确认模型可正常推理。

结果对比:

维度 规范化前 规范化后
集成商接入耗时 3天(反复沟通输入格式) 2小时(直接读取description)
排障定位时间 无法定位(无元数据) 通过git_commit回溯训练代码
版本回滚 不可行(无版本记录) 保留v0.9,可快速回退

复盘经验:模型文件规范不是“额外工作”,而是降低集成成本的最短路径。元数据中的description字段,本质上是在为未来的维护者(可能是陌生人)写“使用说明书”。该企业后续将模型文件规范纳入CI/CD流水线,每次导出自动检查元数据完整性,不通过则阻断发布。

4.6 常见误区与总结

在模型文件规范实践中,以下误区最为常见:

  • 误区一:元数据可有可无。实际上,onnxruntime的某些版本在加载模型时会读取producer_name来启用特定优化,缺失可能导致性能下降。
  • 误区二:版本号只写在文件名中。文件名可能被修改,但模型内部的model_version字段无法被外部篡改(除非重新导出),因此两者必须同时维护。
  • 误区三:目录结构过度设计。对于单一模型交付,不需要复杂的多级目录,保持models/、test_cases/、docs/三个目录即可,过度设计反而增加维护成本。
  • 误区四:忽略opset版本记录。opset版本决定了模型可用的算子集合,如果集成方的onnxruntime版本低于导出时的opset,模型将无法加载。务必在元数据中记录opset_version。
最终检查清单:在交付ONNX模型前,逐项确认——①文件名符合“家族_变体_精度_v版本”格式;②模型位于正确的目录层级;③model_version与文件名版本一致;④producer_name和description已填写;⑤custom_metadata_map包含git_commit和opset_version;⑥CHANGELOG.md已更新;⑦SHA256哈希已记录。完成这七项,模型文件才具备工程交付的资格。