 was false“ when loading model 解决方案)
【Bug已解决】exception !utils::HasExternalDataInMemory(tensor_proto) was false when loading model 解决方案一、现象长什么样加载一个把权重拆到外部文件的 ONNX 模型大模型权重在.data外部数据文件里时构造InferenceSession直接抛异常onnxruntime.capi.onnxruntime_pybind11_state.RuntimeException: [ONNXRuntimeError] : 1 : FAIL : !utils::HasExternalDataInMemory(tensor_proto) was false最小触发import onnxruntime as ort # 模型 xxx.onnx 引用了外部数据 xxx.onnx.data权重在外部文件 sess ort.InferenceSession(big_model.onnx, providers[CPUExecutionProvider]) # 抛 !utils::HasExternalDataInMemory(tensor_proto) was false注意模型文件在、.data文件也在同一目录但就是加载失败。这是外部数据external data加载配置/路径的问题。二、背景ONNX 模型超过 2 GB 时protobuf 单文件放不下权重于是用“外部数据”机制模型.onnx里每个大 tensor 只记录一个引用data_location EXTERNALexternal_data字段包含location相对路径和offset/length真实权重存在同名的.data文件里。加载时ORT 需要读.onnx主文件发现 tensor 是EXTERNAL根据external_data.location解析出.data文件的路径相对于模型文件所在目录或配置的 base dir把外部数据读进内存或 mmap再组装成张量。HasExternalDataInMemory(tensor_proto)这个检查的含义是“这个 tensor_proto 标记了外部数据那它对应的外部数据应该已经被加载进内存了”。抛was false表示tensor_proto 说自己有外部数据但加载器在内存里找不到对应的已加载数据—— 也就是外部数据没被成功读进来而加载器却走到了“假定已加载”的分支。三、根因根因是外部数据没有被成功加载进内存路径解析失败 / 未开启外部数据加载 /.data文件缺失或改名但加载器仍按“外部数据已就位”继续触发HasExternalDataInMemory断言失败外部数据加载未开启或被跳过ORT 某些加载路径需要显式允许读外部数据尤其通过特定 API / session options如果没开tensor 的EXTERNAL标记还在但数据没读走到校验就was false。相对路径解析错external_data.location是相对路径基准目录应该是模型文件所在目录。如果加载器用了错误的基准比如当前工作目录、或某个临时解压目录.data文件找不到数据没加载。.data文件名/位置不对实际数据文件被改名、挪走或location指向的名字和实际文件不一致。加载时序问题先尝试“从内存 tensor_proto 取数据”假定已加载但外部数据根本没被LoadExternalData步骤读入于是内存里没有断言失败。所以这不是模型算错而是外部数据的加载链路断了配置/路径/文件导致“标记为外部数据却无内存数据”的矛盾。四、最小可运行复现下面用伪代码 Python 模拟“标记为外部数据但没加载进内存”的矛盾不依赖真实 ORT但精准复现断言触发的逻辑import os def load_model(model_path, allow_externalTrue, base_dirNone): 模拟 ORT 加载标记外部数据的 tensor 必须已加载进内存。 base_dir base_dir or os.path.dirname(model_path) tensors { # 主文件里标记了外部数据但内存中是否就位取决于是否成功读 .data weight: {external: True, location: big_model.onnx.data}, } # 步骤按 allow_external 与路径决定是否把外部数据读进内存 for name, t in tensors.items(): if t[external]: data_path os.path.join(base_dir, t[location]) in_memory allow_external and os.path.exists(data_path) # 加载器假定标记了外部数据 - 必须已在内存 if t[external] and not in_memory: raise RuntimeError(!HasExternalDataInMemory(tensor_proto) was false) return loaded if __name__ __main__: # 情况 A外部数据加载开启且 .data 在同目录 - 成功 try: print(load_model(big_model.onnx, allow_externalTrue, base_dir/models/big)) # 假定 .data 在此 except RuntimeError as e: print(A 失败:, e) # 情况 Ballow_externalFalse 或 base_dir 错 - 触发断言 try: load_model(big_model.onnx, allow_externalFalse, base_dir/wrong) except RuntimeError as e: print(B 触发断言:, e) # 正好复现 was false跑出来情况 B 会抛出!HasExternalDataInMemory... was false正是“标记为外部数据却没加载进内存”的精简复现。五、解决方案第一层最小直接修复最小修复确保外部数据被正确加载——开启外部数据加载并让.data文件路径可被正确解析。对使用者import onnxruntime as ort so ort.SessionOptions() # 关键允许从外部数据文件加载某些 ORT 构建/API 需要显式开启 so.add_session_config_entry(session.load_external_data, 1) # 关键保证 .data 文件与 .onnx 在同一目录且 location 名字一致 # 若 .data 在别的目录用对应 API 指定 base dir视 ORT 版本 sess ort.InferenceSession( big_model.onnx, so, providers[CPUExecutionProvider], # 部分 ORT 版本支持sess_options 指定 external_data 目录 )如果.data文件名被改了改回external_data.location指向的名字或把.data移到模型同目录。这一层立刻消除断言失败。六、解决方案第二层结构性改进把“外部数据如何加载、从哪里加载”收口成唯一的配置对象OrtExternalDataLoadPolicy所有加载逻辑读它from dataclasses import dataclass, field from typing import Tuple, Literal dataclass(frozenTrue) class OrtExternalDataLoadPolicy: ONNX 外部数据加载的单一事实来源。 # 是否允许加载外部数据 allow_external_data: bool True # 外部数据基准目录解析策略相对模型文件所在目录推荐 base_dir_strategy: Literal[model_dir, cwd, explicit] model_dir # 显式基准目录base_dir_strategyexplicit 时用 explicit_base_dir: str # .data 文件命名必须与 external_data.location 一致 require_matching_location: bool True # 加载失败时给出明确错误而非静默走到断言 fail_loud: bool True def resolve_base_dir(self, model_path: str) - str: import os if self.base_dir_strategy model_dir: return os.path.dirname(os.path.abspath(model_path)) if self.base_dir_strategy cwd: return os.getcwd() return self.explicit_base_dir def describe(self) - str: return 外部数据相对模型目录加载开启开关文件命名须与 location 一致 POLICY OrtExternalDataLoadPolicy() def plan_external_load(model_path: str, policy: OrtExternalDataLoadPolicy POLICY) - dict: return { allow: policy.allow_external_data, base_dir: policy.resolve_base_dir(model_path), fail_loud: policy.fail_loud, }所有加载逻辑读同一份POLICY外部数据加载配置被固化不会再“标记外部却没加载”。七、解决方案第三层断言 / CI 守护把“外部数据就位后才通过校验”做成断言。下面用 pytest 风格守护复用第四节逻辑import os import pytest def test_external_allowed(policy): assert policy.allow_external_data is True def test_base_dir_is_model_dir(policy): assert policy.base_dir_strategy model_dir def test_load_succeeds_when_data_present(tmp_path, policy): # 构造 .onnx .data 同目录应加载成功 (tmp_path / m.onnx.data).write_bytes(b\x00 * 16) base policy.resolve_base_dir(str(tmp_path / m.onnx)) assert os.path.exists(os.path.join(base, m.onnx.data)) def test_fail_loud_when_missing(policy): assert policy.fail_loud is True这四组断言锁住(1) 外部数据加载开启(2) 基准目录是模型目录(3).data同目录时存在可被解析(4) 缺失时明确报错。CI 跑通即代表外部数据加载链路被守护。八、排查清单遇到!HasExternalDataInMemory(tensor_proto) was false确认.data文件在不在和.onnx同目录名字是否等于external_data.location看加载是否允许外部数据session options 有没有开load_external_data。看基准目录.data的相对路径是相对模型目录解析的确认加载器用的基准对。改名/挪回把.data放模型同目录、名字对齐location或显式指定 base dir。统一策略对象用OrtExternalDataLoadPolicy固化加载配置。CI 守护断言外部数据开启、基准正确、缺失明确报错。不要嵌回大权重保持外部数据拆分但确保加载链路通。九、小结exception !utils::HasExternalDataInMemory(tensor_proto) was false when loading model的根因是模型把权重存成外部数据.data文件tensor_proto 标记了EXTERNAL但外部数据没有被成功加载进内存未开启外部数据加载、相对路径解析错、或.data文件缺失/改名加载器却走到“假定外部数据已就位”的分支触发HasExternalDataInMemory断言失败。最小修复是开启外部数据加载开关、保证.data与.onnx同目录且名字匹配external_data.location结构性改进是用唯一的OrtExternalDataLoadPolicy固化加载配置CI 用四组断言守护“外部数据开启、基准为模型目录、缺失明确报错”。记住标记了外部数据的 tensor加载器要求它已在内存链路断了就会抛这个断言——先确认.data能被找到并加载。