hixiong123头像
ONNX 合并工具(纯 C#)封面图

ONNX 合并工具(纯 C#)

ONNX 合并工具(纯 C#)

把网上下载的 .onnx + .data 合并成一个自包含的 .onnx,或者反过来把内嵌权重拆成外部数据。
纯托管 C# 实现,自己手写 ONNX 的 protobuf 读写,零 NuGet 依赖(不需要 onnx、onnxruntime、Google.Protobuf)。


为什么需要它

从 HuggingFace / Model Zoo 下载的模型经常是"导出时拆开"的两个文件:

model.onnx         ← 计算图 + 元数据(很小)
model.onnx.data    ← 权重(很大,GB 级)

.onnx 里用 TensorProto.data_location = EXTERNAL 加 external_data 的
location / offset / length 指向 .data 里的某个区间。这带来几个麻烦:

  • 只拿到 .onnx 或只拿到 .data 就没法用(模型打不开);
  • 两个文件放不同目录、.data 被浏览器改名,工具就找不到数据;
  • 有些运行时/框架只接受单文件模型;
  • 而超过 2GB 的模型反而不能合并(见下面「2GB 上限」)。

本工具解决这些情况。

快速开始

# 合并(默认模式):自动在同目录找 .data
onnxmerge model.onnx                       # → model.merged.onnx

# 数据文件不在旁边,显式指定
onnxmerge model.onnx --data D:\downloads\model.onnx.data

# 数据在别的目录(按文件名去找)
onnxmerge model.onnx --data-dir D:\downloads

# 先看看这个模型到底需要哪些数据文件
onnxmerge --inspect model.onnx

# 反向:把内嵌权重拆成 .onnx + .data
onnxmerge --split model.onnx --data-name weights.data

图形界面:双击 OnnxMergeGui.exe,把 .onnx(连同 .data)拖进窗口即可。

关键设计

1. 零依赖手写 protobuf

ONNX 就是 protobuf。这里没有引入 Google.Protobuf,而是按 onnx.proto 的字段号
自己实现编解码(字段号已与本机 onnx 1.17.0 的 descriptor 逐一核对):

消息只递归展开有哪些字段其余字段
ModelProtograph(7)、functions(25)、training_info(20)原样保留字节
GraphProtonode(1)、initializer(5)、sparse_initializer(15)同上
NodeProtoattribute(5)同上
AttributeProtot(5)、tensors(10)、g(6)、graphs(11)、sparse_tensor(22/23)同上
TensorProtoexternal_data(13)同上

只解析需要的结构,其余原样保留,所以重写后的模型在语义和未知字段上都不丢信息。
递归会走完子图(If/Loop 的 then/else 分支)、FunctionProto、训练信息里的两个图,
以及 Constant 节点那种存在 node attribute 里的张量——这几点是很多简单实现会漏掉的。

2. 内存与模型大小无关

GB 级权重不会被读进托管堆:

  • 解析时把 raw_data 和外部数据都表示成 FileSlice(文件路径 + offset + length);
  • 写出时直接从源文件按块(4MB)拷贝到输出流。

实测:合并 256 MB 模型峰值 37 MB;重打包 2.13 GB 权重峰值同样 37 MB。
进程内存只与张量个数/元数据有关。

3. 三种数据表示都支持

TensorProto 里的权重可以是 raw_data(字段 9),也可以是类型化字段
float_data(4) / int32_data(5) / int64_data(7) / double_data(10) / uint64_data(11)。
小张量常被导出成后者——官方 mobilenetv2-7 的偏置就是这样。工具都认,并且 split 时
会先把类型化字段转成 raw_data 再写进 .data。

4. 校验与友好的报错

合并前逐张量校验,而不是等到运行时才发现问题:

  • 数据切片越界 → 指出文件实际大小与需要的 offset/length,并提示"通常是下载不完整";
  • 字节数与 dims × dtype 推算不符 → 默认报错(--allow-size-mismatch 可跳过);
  • checksum(SHA1)与文件实际不符 → 警告;
  • .data 找不到 → 按 location 原路径 → --data-dir → 同名回退 → 常见命名(x.onnx.data / x.data / x.bin)→ 目录内唯一数据文件 的顺序兜底,找不到才报错并给出可用选项。

5. 2GB 上限

protobuf 单个消息上限约 2GB,所以超过 2GB 的模型无法合成一个 .onnx。
工具在写盘之前就算总长度并拦住:

错误: 合并后的 .onnx 约为 2.13 GB,超过 protobuf 单消息 2GB 上限,无法写出。
这种模型必须保留 external data 形式:请改用 split 模式(或 --keep-external)。

这样不会先写 2GB 废文件再失败。

项目结构

src/OnnxMerge.Core/          纯逻辑库(无 UI、无依赖)
  Proto/Model.cs             protobuf 数据结构 + varint 编码 + 长度缓存
  Proto/Parser.cs            内存字节解析
  Proto/PbStreamParser.cs    流式解析(大权重记成文件切片)+ ByteReader
  Proto/Writer.cs            流式写出(切片按块拷贝)
  Onnx/OnnxSchema.cs         字段号与递归规则
  Onnx/TensorShape.cs        元素字节数与形状校验
  Onnx/TensorData.cs         raw_data ↔ 类型化字段互转
  Onnx/ExternalData.cs       external_data 解析与文件定位
  Onnx/OnnxInspector.cs      只读诊断
  Pass/TensorWalker.cs       递归遍历所有张量(含子图/函数/属性)
  Merge/OnnxMerger.cs        合并 / 拆分的核心流程
src/OnnxMergeCli/            命令行
src/OnnxMergeGui/            WinForms 图形界面(可拖拽)
tests/                       端到端测试(14 项)
tools/*.py                   用真实 onnx / onnxruntime 做独立参照验证

构建

dotnet build OnnxMergeTool.sln -c Release

# 单文件、自包含(不要求目标机器装 .NET)
dotnet publish src/OnnxMergeCli -c Release -r win-x64 --self-contained true \
  -p:PublishSingleFile=true -o publish/cli
dotnet publish src/OnnxMergeGui -c Release -r win-x64 --self-contained true \
  -p:PublishSingleFile=true -o publish/gui

验证

测试不是"自己验自己",而是用 Python onnx / onnxruntime 作为独立参照:

python tools/make_fixtures.py          # 用官方 onnx 库生成 .onnx+.data 夹具
dotnet run --project tests             # 14 项端到端测试

覆盖:

项验证内容
roundtrip5 种夹具合并后与 onnx 库自己生成的内嵌版结构等价
weights逐张量 np.array_equal,数值完全一致
multi / nested每张量一个 .data、数据在 data/ 子目录
override只给 .onnx,靠 --data 指定数据文件
subgraph / constant_attrIf 子图 initializer、Constant 节点的属性张量都参与合并
fp16 / uint8半精度与量化 dtype 的字节数校验
split→merge内嵌→拆分→再合并,数值回到原样
errors缺文件 / 截断 / location 失效三种报错的文案与可操作性
inspect能正确识别外部数据张量
mobilenetv2-7真实模型 267 个张量往返;用 ONNX Runtime 加载并推理,输出与原始模型一致
大模型256 MB 与 2.13 GB:峰值内存 37 MB;2.13 GB 数据重打包 SHA256 一致;超 2GB 时在写盘前拦截

已知边界

  • 单个张量 >2GB 或整体 >2GB:无法合成单文件,只能保持 external 形式(工具的 split 模式可以重打包)。
  • 4bit 类型(UINT4/INT4)按 bit 打包,不做字节数校验(--allow-size-mismatch 可兜底)。
  • 模型若同时有 .onnx.data 和自定义外部文件,按 location 优先;冲突时以能解析到的那个为准并写进报告。
  • 输出的字段顺序会按"删除后追加"重排(语义不变;与 onnx 库的字节级顺序可能不同,测试用语义等价比较而非逐字节)。

下载链接

通过网盘分享的文件:onnxmerge.exe
链接: https://pan.baidu.com/s/1V3HFxCeG8xO-51GjDwDd2w?pwd=v77s 提取码: v77s

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/hixiong123/article/details/170280923

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--