- 人工智能
- 深度学习
- 机器学习
【免费下载链接】onnx
Open standard for machine learning interoperability
External Data 是 ONNX 标准中把张量数据从模型文件(.onnxprotobuf)中剥离、存放到独立外部文件的机制,用于突破 protobuf 序列化的 2GB 大小限制,并显著降低模型文件的加载内存占用。本文以 docs/ExternalData.md 为骨架,结合本仓库的 onnx/external_data_helper.py、onnx/init.py、onnx/serialization.py、onnx/checker.cc 与 onnx/onnx.in.proto 等源码,完整讲解外部数据的加载、转换、保存、校验流程以及TensorProto中相关字段的语义,帮助读者在实际工程中正确使用并安全处理外部数据模型。
一、为什么需要 External Data:2GB 边界与内存问题
ONNX 模型本质上是 protobuf 序列化的ModelProto。protobuf 的单个消息序列化大小上限为 2GB,这在 onnx/checker.py 中被常量MAXIMUM_PROTOBUF = 2147483647明确约束。当模型的权重(尤其是 LLM、大视觉模型的参数)逼近或超过该阈值时,onnx/serialization.py 中的序列化逻辑会直接抛出如下错误:
The proto size is larger than the 2 GB limit. Please use save_as_external_data to save tensors separately from the model file.External Data 机制正是为此而生:把张量的原始二进制数据写到独立文件,模型文件内只保留描述"数据在哪"的键值对,从而绕开 2GB 上限,也让模型文件本身变得轻量、可版本化管理。这也是 ONNX 官方设计(见 docs/ExternalData.md 末尾引用的 onnx/onnx#678 提案)所确立的通用做法。
二、加载带有 External Data 的模型
2.1 默认方式:外部数据与模型同目录
如果外部数据文件与模型文件位于同一目录,直接用onnx.load()即可,加载过程会自动读取同目录下的外部数据:
import onnx onnx_model = onnx.load("path/to/the/model.onnx")其底层行为可以在 onnx/init.py 的load_model中看到:load_external_data参数默认为True,加载时会取模型文件所在目录作为base_dir,并调用load_external_data_for_model(model, base_dir)把外部张量读入内存。
2.2 外部数据在其他目录:手动指定目录
若外部数据存放在与模型不同的目录,则需要两步完成加载:
import onnx from onnx.external_data_helper import load_external_data_for_model onnx_model = onnx.load("path/to/the/model.onnx", load_external_data=False) load_external_data_for_model(onnx_model, "data/directory/path/") # Then the onnx_model has loaded the external data from the specific directory这里onnx.load(..., load_external_data=False)先只解析模型结构、跳过外部数据,随后用load_external_data_for_model()显式指定数据目录完成装载。
load_external_data_for_model()的实现(onnx/external_data_helper.py)会遍历模型中的所有张量,对每一个uses_external_data(tensor)为真的张量调用load_external_data_for_tensor(),将外部文件中的字节读入tensor.raw_data,然后把data_location复位为DEFAULT并清空external_data字段——加载完成后,内存中的模型与普通内嵌模型无异。
三、将模型转换为 External Data 格式
3.1 两步式:先转换、后保存
convert_model_to_external_data()只修改内存中的ModelProto标记,真正写文件发生在save_model()阶段,因此必须紧跟一次保存调用:
import onnx from onnx.external_data_helper import convert_model_to_external_data onnx_model = ... # Your model in memory as ModelProto convert_model_to_external_data(onnx_model, all_tensors_to_one_file=True, location="filename", size_threshold=1024, convert_attribute=False) # Must be followed by save_model to save the converted model to a specific path onnx.save_model(onnx_model, "path/to/save/the/model.onnx") # Then the onnx_model has converted raw data as external data and saved to specific directory各参数语义(对应 onnx/external_data_helper.py 的实现):
| 参数 | 默认值 | 说明 |
|---|---|---|
all_tensors_to_one_file | True | 为True时所有张量写入由location指定的同一个外部文件;为False时每个张量单独一个文件,文件名为张量名(若张量名不是合法文件名,则回退为uuid.uuid1()生成的随机名) |
location | None | 外部文件相对模型文件的路径;不指定时使用uuid.uuid1()生成形如<uuid>.data的文件名。注意该路径必须是相对路径,传绝对路径会抛出ValueError;若目标文件已存在则抛出FileExistsError |
size_threshold | 1024 | 只有raw_data字节数≥ 该阈值的张量才会被转为外部数据;设为0可把所有带原始数据的张量全部外置 |
convert_attribute | False | 为False时只转换图初始器(initializer)张量;为True时连节点属性(attribute)中的张量也一并转换 |
3.2 一步式:保存时直接转外部数据
更常见的是把转换与保存合并为一次调用,使用onnx.save_model(..., save_as_external_data=True):
import onnx onnx_model = ... # Your model in memory as ModelProto onnx.save_model(onnx_model, "path/to/save/the/model.onnx", save_as_external_data=True, all_tensors_to_one_file=True, location="filename", size_threshold=1024, convert_attribute=False) # Then the onnx_model has converted raw data as external data and saved to specific directorysave_model的这些关键字参数(onnx/init.py)与convert_model_to_external_data完全对应。保存时真正执行落盘的是 onnx/external_data_helper.py 的write_external_data_tensors():它遍历所有uses_external_data且带raw_data的张量,调用save_external_data()把数据写入外部文件并回填offset/length,随后清空raw_data,使模型文件本体只保留描述信息。
四、onnx.checker 对 External Data 模型的校验
4.1 小于 2GB 的模型
当前 checker 支持直接校验带外部数据的模型,既可以传入已加载的ModelProto,也可以传入模型路径:
import onnx onnx.checker.check_model(onnx_model) # 传入已加载模型 onnx.checker.check_model("path/to/the/model.onnx") # 或传入模型路径4.2 大于 2GB 的模型:必须传路径
对于超过 2GB 的模型,必须使用模型路径调用onnx.checker.check_model("path/to/the/model.onnx"),且外部数据文件必须与模型位于同一目录:
import onnx onnx.checker.check_model("path/to/the/model.onnx") # onnx.checker.check_model(loaded_onnx_model) will fail if given >2GB model原因在于:大于 2GB 的模型无法整体反序列化进内存的ModelProto(受 protobuf 2GB 上限约束),checker 需要从路径直接读取模型、结合同目录外部数据做流式校验。相关检查逻辑位于 onnx/checker.cc(如张量stored_externally时要求external_data必须包含location,见 checker.cc 第 144-162 行附近)。
五、TensorProto 中的两个外部数据字段
外部数据的语义由TensorProto消息中的两个字段定义,详见 onnx/onnx.in.proto。
5.1 data_location 字段
data_location记录该张量数据的存放位置,取值必须为以下两者之一:
DEFAULT——数据存储在 protobuf 消息内部,存放在raw_data(若已设置)中,否则存放在类型专用字段(如float_data、int64_data等)中;EXTERNAL——数据存储在外部位置,具体位置由external_data字段描述。
若该字段未设置,则行为等同于DEFAULT(对应 proto 中的DataLocation枚举:DEFAULT = 0; EXTERNAL = 1;)。
5.2 external_data 字段
external_data是StringStringEntryProto类型的键值对列表(repeated),描述数据的外部位置。官方识别的键如下:
| 键 | 必选 | 说明 |
|---|---|---|
"location" | 是 | 相对 ONNX protobuf 模型文件所在目录的文件路径;禁止使用..等向上目录组件,解析时应剥离 |
"offset" | 否 | 数据起始字节位置,以字符串形式存储的整数。建议为页大小(通常 4KB)的整数倍以支持 mmap;Windows 上建议为VirtualAlloc分配粒度(通常 64KB)的整数倍以支持内存映射 |
"length" | 否 | 数据包含的字节数,以字符串形式存储的整数 |
"checksum" | 否 | "location"指定文件的 SHA1 摘要 |
模型被加载后,所有external_data条目可能被追加一个("basepath", ...)键,其值为 ONNX 模型文件被加载时所在目录的路径。
5.3 外部数据文件的格式
外部数据文件中的字节内容与当前 ONNX 实现中raw_data字段的二进制格式完全一致:按固定宽度、小端序存储元素,浮点类型遵循 IEEE 754(相关编码约定见 onnx/onnx.in.proto 中raw_data的注释)。因此外部文件本质上就是raw_data的"搬出"形态,读取后直接回填即可。
六、源码级原理与安全设计
6.1 核心 API 一览
onnx/external_data_helper.py 是本机制的全部 Python 实现,关键函数包括:
load_external_data_for_model(model, base_dir)/load_external_data_for_tensor(tensor, base_dir):从外部文件装载数据并复位张量状态;convert_model_to_external_data(...):内存内标记转换;save_external_data(tensor, base_path):把raw_data写入外部文件,并在写入后通过set_external_data()回填offset与length;set_external_data(tensor, location, offset, length, checksum, basepath):设置data_location = EXTERNAL并写入键值对;要求张量必须存在raw_data,否则抛ValueError;uses_external_data(tensor):判断张量data_location是否为EXTERNAL;remove_external_data_field(tensor, field_key):删除某条外部数据键值对;convert_model_from_external_data(model):反向操作,把外部数据张量改回内嵌存储。
save_external_data中还有一个值得注意的细节:多张量共享同一文件时,新张量的offset必须落在"当前文件末尾到末尾 +_MAX_EXTERNAL_DATA_PADDING(64KB)"之间,超出该区间会抛出ValidationError,以避免覆盖已有数据或无界填充零字节(onnx/external_data_helper.py)。
6.2 针对恶意外部数据的三层安全防御
外部数据机制涉及文件系统路径与字节读写,仓库为此实现了三层纵深防御(注释见 onnx/external_data_helper.py):
- 属性白名单(CWE-915):
ExternalDataInfo解析时只接受{"location", "offset", "length", "checksum", "basepath"}五个规范键,未知键会被告警并忽略,防止任意属性注入; - 边界校验(CWE-400):
offset、length在解析阶段即强制为非负整数,非法值直接抛ValueError; - 文件大小校验(CWE-400):
_validate_external_data_file_bounds()在读取前用fstat获取真实文件大小,确认offset不超过文件大小、length不超过可读字节数,从根上防止恶意构造的模型导致内存耗尽。
在 C++ 侧(onnx/checker.cc 与 1504-1687 行的open_external_data实现),加载还包含路径规范化与目录逃逸检查:外部数据路径必须能规范化为合法路径,且必须解析在模型目录之内;打开文件时会拒绝符号链接/重解析点、拒绝存在多个硬链接的文件,并通过 fd 与规范化路径的一致性检查抵御 TOCTOU 攻击。
七、实践注意事项
- location 必须是相对路径:
convert_model_to_external_data对绝对路径直接抛ValueError;且location中的路径相对于模型文件所在目录解析,..向上目录组件在规范层面被禁止。 - 外部数据文件需随模型一起分发:模型被移动时,需保持外部数据文件与模型的相对位置关系(或使用
load_external_data_for_model重新指定数据目录)。 - size_threshold 决定哪些张量外置:默认 1024 字节意味着小张量仍内嵌于模型文件,兼顾文件数量与加载性能;全量外置时设为 0。
- 2GB 以上模型的校验:只能传模型路径,且外部数据必须在同一目录,无法对加载进内存的超大模型做 checker 校验。
- 加载后张量状态复位:
load_external_data_for_model会把data_location复位为DEFAULT并清空external_data,因此加载后的模型再保存会重新内嵌数据;如需保持外部数据形态,应使用save_as_external_data=True重新保存。
外部数据机制是 ONNX 处理大模型的事实标准方案,理解其加载、转换、保存与校验的完整链路,以及TensorProto.data_location/external_data字段的精确语义,能帮助你在模型导出、跨平台分发与超大模型推理场景中避免大量踩坑。相关测试用例可进一步参考 tests/python/external_data_test.py 与 tests/python/serialization_test.py。
- 人工智能
- 深度学习
- 机器学习
【免费下载链接】onnx
Open standard for machine learning interoperability
相关推荐
Apache Airflow 集成 AWS SSM Parameter Store:Secrets Backend 配置与实战指南
Apache Airflow 集成 AWS SSM Parameter Store:Secrets Backend 配置与实战指南 Apache Airflow
人工智能深度学习机器学习curl 命令 `--data-binary` 完全指南:按原样 POST 二进制数据、保留换行与空字节的底层原理
curl 命令 data binary 完全指南:按原样 POST 二进制数据、保留换行与空字节的底层原理 data binary 是 curl(命令行工具与
CLI网络通信PyMC 数据接口完全指南:Data、get_data 与 Minibatch 的底层原理与实战用法
PyMC 数据接口完全指南:Data、get_data 与 Minibatch 的底层原理与实战用法 本文基于 PyMC 仓库的 API 参考文档 https:
人工智能机器学习科学计算
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考