4、模型文件规范:命名规则、目录结构、版本管理、元数据(producer_name、description等)填写
ONNX模型在工程交付中往往被视为“可运行的二进制产物”,但实际项目中,模型文件本身承载的信息密度远高于权重数值本身。一个缺乏规范的ONNX文件,可能在交付后数周内就变得不可维护:无法确认它由哪个框架导出、对应哪个训练版本、输入张量的语义是什么、是否经过量化或图优化。模型文件规范的核心目标,是让模型文件在脱离原始训练环境后,依然具备可读性、可追溯性、可复现性。本章围绕命名规则、目录结构、版本管理、元数据填写四个维度展开,建立一套可直接落地的ONNX工程交付标准。
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 |
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目录尤为重要,它保存了每次验证使用的输入输出对,确保模型可复现性。
models/resnet/、models/bert/。同时,在根目录维护一个MANIFEST.json文件,记录每个模型的路径、版本、哈希值、依赖环境,作为机器可读的交付清单。
4.3 版本管理:语义化版本与可追溯性
ONNX模型的版本管理不能简单依赖文件名后缀,必须建立语义化版本体系。推荐遵循以下规则:
- 主版本号(Major):模型架构变化、输入输出张量形状改变、算子集版本升级导致不兼容时递增。
- 次版本号(Minor):权重更新、训练数据扩充、精度提升但接口不变时递增。
- 补丁版本号(Patch):修复推理bug、优化图结构但不改变数值结果时递增。
版本管理的关键操作流程如下:
- 每次导出模型时,从Git标签或CI流水线获取当前代码版本号,自动写入文件名和元数据。
- 在
CHANGELOG.md中记录每个版本的变更内容、导出环境(PyTorch/TensorFlow版本)、onnxruntime版本。 - 对每个发布模型计算SHA256哈希值,存入
MANIFEST.json,防止文件被意外篡改。 - 旧版本模型保留至少两个迭代周期,不立即删除,以便回滚。
# 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
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不匹配,且不确定是否需要归一化
规范化操作步骤:
- 重命名:根据模型实际架构和训练数据,命名为
pcb_yolov5s_fp32_v1.0.onnx,明确是YOLOv5s架构、FP32精度、首个发布版本。 - 目录重构:建立
models/、test_cases/、docs/三个子目录,将模型放入models/,同时生成一个包含10组输入输出对的test_cases/目录。 - 元数据补全:使用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和训练数据集信息。
- 版本记录:创建CHANGELOG.md,记录v1.0的导出环境、验证精度(mAP@0.5=0.892)、已知限制(不支持动态batch)。
- 验证:运行
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。