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 逐一核对):
| 消息 | 只递归展开有哪些字段 | 其余字段 |
|---|---|---|
ModelProto | graph(7)、functions(25)、training_info(20) | 原样保留字节 |
GraphProto | node(1)、initializer(5)、sparse_initializer(15) | 同上 |
NodeProto | attribute(5) | 同上 |
AttributeProto | t(5)、tensors(10)、g(6)、graphs(11)、sparse_tensor(22/23) | 同上 |
TensorProto | external_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 项端到端测试
覆盖:
| 项 | 验证内容 |
|---|---|
| roundtrip | 5 种夹具合并后与 onnx 库自己生成的内嵌版结构等价 |
| weights | 逐张量 np.array_equal,数值完全一致 |
| multi / nested | 每张量一个 .data、数据在 data/ 子目录 |
| override | 只给 .onnx,靠 --data 指定数据文件 |
| subgraph / constant_attr | If 子图 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




