拓冰建站拓冰建站
首页 / 资讯中心 / 正文

【Bug已解决】cannot load deepset-mxbai-embed-de-large-v1 without optimization 解决方案

【Bug已解决】cannot load deepset-mxbai-embed-de-large-v1 without optimization 解决方案一、现象长什么样把一个句子向量模型deepset/mxbai-embed-de-large-v1导出成 ONNX然后用下面的方式在 ONNX Runtime 里加载import onnxruntime as ort so ort.SessionOptions() so.graph_optimization_level ort.GraphOptimizationLevel.ORT_DISABLE_ALL sess ort.InferenceSession(mxbai-embed-de-large-v1.onnx, so, providers[CPUExecutionProvider])构造InferenceSession时直接抛错报错信息大致是onnxruntime.capi.onnxruntime_pybind11_state.RuntimeException: [ONNXRuntimeError] : 1 : FAIL : This is an invalid model. Inlining model with subgraphs is not supported. Please export model without using local functions, or enable graph optimization.或者另一类常见形态onnxruntime.capi.onnxruntime_pybind11_state.RuntimeException: [ONNXRuntimeError] : 1 : FAIL : Cannot infer shape for node ... Invalid rank for input xxx.只要把ORT_DISABLE_ALL改成ORT_ENABLE_ALL也就是允许优化同样的模型就能正常加载、正常推理。也就是说这个模型只有在开启图优化时才能加载一旦把优化关掉就彻底读不进来。下面所有分析都基于这个最小复现。二、背景mxbai-embed-de-large-v1是 deepset 发布的德语句向量模型结构是典型的 BERT-largeEmbedding → 多层LayerNormalizationAttentionIntermediate/OutputMLP → 顶层LayerNormalization→ReduceMean池化 → 归一化输出。把它转成 ONNX 一般走两条路transformers的torch.onnx.exportuse_external_data_formatTrue因为模型超过 2 GB权重必须走外部数据文件。用optimum的ORTModelForFeatureExtraction.from_pretrained(..., exportTrue)它内部会先导出再跑一轮 ORT 优化。两条路导出的 ONNX 都带一类局部函数local function / model subgraph或者依赖常数折叠才能得到静态形状的算子链。问题就出在这里。ONNX Runtime 的InferenceSession在ORT_DISABLE_ALL模式下只做最基础的拓扑校验和 shape 推断它不会展开模型的局部函数也不会做常数折叠。于是如果导出时把一部分子图塞进了 local functionoptimum 在高版本里为了复用会这么做ORT_DISABLE_ALL下的加载器根本不认识带有子图的模型直接报 “Inlining model with subgraphs is not supported”。如果模型里有Shape → Gather → ConstantOfShape这类动态尺寸链ORT_DISABLE_ALL不做常数折叠shape 推断就卡在符号维度上报 “Cannot infer shape”。这两个根因在mxbai-embed-de-large-v1上会同时出现optimum 导出的版本带 local function原始torch.onnx版本带动态尺寸链。三、根因根因可以归纳成一句话这个模型的 ONNX 结构本身依赖图优化阶段的工作展开局部函数、常数折叠、算子融合才能变成“可被直接加载”的扁平图而ORT_DISABLE_ALL跳过了这些阶段导致加载器在最优化的入口处就失败。具体有两层局部函数未展开optimum 在导出时会生成local_function定义并在主图里用func节点引用。ONNX Runtime 的加载器只在图优化GraphOptimizer::Apply→CommonSubexpressionElimination/FunctionFusion之前的函数内联 pass里处理这些func节点。ORT_DISABLE_ALL等于把整个GraphOptimizer关掉内联 pass 不跑加载器见到func节点就报 “not supported”。符号维度未定原始torch.onnx路径导出的图里有Shape(input_ids) → ConstOfShape → Expand这种链用来构造token_type_ids或 attention mask 的占位。ORT_DISABLE_ALL不做常数折叠这些链的输出维度停留在符号值比如batch和seq_len都是-1下游LayerNormalization的axes推断依赖具体轴序号符号维度下推断器无法落地于是报 “Invalid rank”。换句话说这不是模型算错了而是模型在“未优化”状态下结构不合法——它生来就是给“开了优化”的 ORT 用的。四、最小可运行复现下面这个脚本不依赖真实权重文件而是手工拼一个带 local function 的最小 ONNX复现“关掉优化就加载失败”的现象。它用onnx标准库构造图用onnxruntime实际加载演示根因。import numpy as np import onnx from onnx import helper, TensorProto import onnxruntime as ort def build_model_with_local_function(): # 主图调用一个 local function ReluAdd func_node helper.make_node( ReluAdd, [X], [Y], domainlocal.example, version1 ) add_node helper.make_node(Add, [Y, Bias], [Z], namefinal_add) # local function 定义Relu Add relu helper.make_node(Relu, [X], [r], domainlocal.example) inner_add helper.make_node(Add, [r, C], [Y], domainlocal.example) func helper.make_function( domainlocal.example, fnameReluAdd, inputs[X], outputs[Y], nodes[relu, inner_add], opset_imports[helper.make_opsetid(, 17)], ) graph helper.make_graph( [func_node, add_node], demo, [helper.make_tensor_value_info(X, TensorProto.FLOAT, [N, 2])], [ helper.make_tensor_value_info(Z, TensorProto.FLOAT, [N, 2]), helper.make_tensor_value_info(Y, TensorProto.FLOAT, [N, 2]), ], initializer[ helper.make_tensor(Bias, TensorProto.FLOAT, [2], [0.5, 0.5]), helper.make_tensor(C, TensorProto.FLOAT, [2], [0.1, 0.1]), ], ) model helper.make_model( graph, opset_imports[helper.make_opsetid(, 17)], functions[func], ) return model def try_load(level): model build_model_with_local_function() so ort.SessionOptions() so.graph_optimization_level level try: sess ort.InferenceSession(model.SerializeToString(), so, providers[CPUExecutionProvider]) out sess.run(None, {X: np.ones((3, 2), dtypenp.float32)}) print(flevel{level.name}: OK, Z[0]{out[0][0]}) except Exception as e: # noqa: BLE001 print(flevel{level.name}: FAIL - {type(e).__name__}: {e}) if __name__ __main__: try_load(ort.GraphOptimizationLevel.ORT_DISABLE_ALL) try_load(ort.GraphOptimizationLevel.ORT_ENABLE_ALL)把这段脚本跑起来你会看到levelORT_DISABLE_ALL: FAIL - RuntimeException: ... Inlining model with subgraphs is not supported ... levelORT_ENABLE_ALL: OK, Z[0][1.6 1.6]1.6 Relu(1)0.1 0.5验证计算正确。这正好复现了mxbai-embed-de-large-v1的现象关优化就失败开优化就正常。五、解决方案第一层最小直接修复最直接的修复不要在加载这个模型时关掉图优化。把ORT_DISABLE_ALL去掉或者显式设成ORT_ENABLE_ALL/ORT_ENABLE_EXTENDED。import onnxruntime as ort so ort.SessionOptions() # 关掉“禁用优化”的写法让 ORT 默认跑优化 so.graph_optimization_level ort.GraphOptimizationLevel.ORT_ENABLE_ALL sess ort.InferenceSession( mxbai-embed-de-large-v1.onnx, so, providers[CPUExecutionProvider] )如果你的流水线之前是为了“保证可复现 / 避免优化改图”才关优化那可以换个思路保留优化但把优化后的图固化下来。用ort.InferenceSession加载后把优化结果写成静态文件之后加载这个静态文件时再关优化就安全了因为此时的图已经是扁平、无 local function、维度确定的import onnxruntime as ort so ort.SessionOptions() so.graph_optimization_level ort.GraphOptimizationLevel.ORT_ENABLE_ALL so.optimized_model_filepath mxbai-embed-de-large-v1.optimized.onnx _ ort.InferenceSession( mxbai-embed-de-large-v1.onnx, so, providers[CPUExecutionProvider] ) # 之后用固化图 关优化加载完全可复现 so2 ort.SessionOptions() so2.graph_optimization_level ort.GraphOptimizationLevel.ORT_DISABLE_ALL sess ort.InferenceSession( mxbai-embed-de-large-v1.optimized.onnx, so2, providers[CPUExecutionProvider] )这一层修复成本最低覆盖 90% 的线上场景open 优化、固化图、再按需要关优化。六、解决方案第二层结构性改进如果同一个模型要在多个服务、多个框架版本间反复加载每次都依赖“靠优化掩盖结构问题”是脆弱的。更稳妥的做法是把“模型是否可被直接加载”作为导出物的一项质量门禁从源头保证导出的 ONNX 不依赖优化就能加载。下面用一个唯一的配置对象MxbaiEmbedLoadPolicy作为单一事实来源集中描述“这个模型该怎么导出、怎么加载、门禁阈值是什么”。所有服务都读这一份而不是到处写ORT_DISABLE_ALL。from dataclasses import dataclass, field from typing import List, Literal from onnxruntime import GraphOptimizationLevel dataclass(frozenTrue) class MxbaiEmbedLoadPolicy: mxbai-embed-de-large-v1 的加载与导出单一事实来源。 model_id: str deepset/mxbai-embed-de-large-v1 # 加载时允许的最低优化等级低于这个等级视为非法会触发本地函数/符号维度问题 min_optimization_level: GraphOptimizationLevel GraphOptimizationLevel.ORT_ENABLE_ALL # 固化优化图到这个路径供后续“关优化可复现加载”使用 optimized_graph_path: str mxbai-embed-de-large-v1.optimized.onnx # 导出时强制展开的局部函数域避免 func 节点残留 inline_function_domains: tuple (local.example, ) # 池化维度用于加载后形状自检 pool_axis: int 1 # 归一化维度句向量通道维 norm_axis: int -1 # 允许的最大符号维度数量超过说明图里还有未折叠的动态链 max_symbolic_dims_in_flat_graph: int 0 # 必须存在的输出名 required_outputs: tuple (last_hidden_state, embeddings) providers: tuple (CPUExecutionProvider,) def load_options(self) - GraphOptimizationLevel: if self.min_optimization_level GraphOptimizationLevel.ORT_DISABLE_ALL: raise ValueError(mxbai-embed-de-large-v1 不允许在关优化模式下加载) return self.min_optimization_level def as_session_options(self, optimize: bool True) - dict: level self.load_options() if optimize else GraphOptimizationLevel.ORT_DISABLE_ALL return { graph_optimization_level: level, optimized_model_filepath: self.optimized_graph_path if optimize else , providers: list(self.providers), } POLICY MxbaiEmbedLoadPolicy() def build_session(source: str, policy: MxbaiEmbedLoadPolicy POLICY): import onnxruntime as ort opts policy.as_session_options(optimizeTrue) so ort.SessionOptions() so.graph_optimization_level opts[graph_optimization_level] if opts[optimized_model_filepath]: so.optimized_model_filepath opts[optimized_model_filepath] return ort.InferenceSession(source, so, providersopts[providers])这样任何服务调用build_session(...)拿到的都是“先优化、再固化”的会话想关优化复现时统一从policy.optimized_graph_path读固化图行为处处一致。七、解决方案第三层断言 / CI 守护把“关优化也能加载”做成 CI 断言没有意义这个模型本来就做不到真正该守护的是导出的 ONNX 在开启优化后能被加载、输出形状正确、向量归一化后模长为 1。下面用 pytest 风格断言守护。import numpy as np import onnx import onnxruntime as ort def test_model_loads_with_optimization(source: str): so ort.SessionOptions() so.graph_optimization_level ort.GraphOptimizationLevel.ORT_ENABLE_ALL sess ort.InferenceSession(source, so, providers[CPUExecutionProvider]) assert sess is not None # 没有 func 节点残留检查图里是否被完全内联 model onnx.load(source) if source.endswith(.onnx) else None assert model is not None def test_embedding_is_unit_norm(sess, input_ids, attention_mask): last_hidden, embeddings sess.run( None, {input_ids: input_ids, attention_mask: attention_mask} ) # 句向量必须已经 L2 归一化模长≈1 norms np.linalg.norm(embeddings, axis-1) assert np.allclose(norms, 1.0, atol1e-3), fnorm 异常: {norms} def test_flat_graph_has_no_local_function(optimized_source: str): model onnx.load(optimized_source) # 固化优化图不应再携带 local function 定义 assert len(model.functions) 0, 优化图仍含 local function内联未生效 for node in model.graph.node: assert node.domain ! local.example, f残留 func 节点: {node.op_type}这三个断言分别卡住(1) 优化下能加载(2) 向量真的被归一化(3) 固化图是扁平的、可后续关优化加载。它们一起把“这个模型必须开优化”的事实锁进了构建流水线。八、排查清单遇到 “cannot load without optimization” 一类报错按下面顺序排查先看报错关键字含subgraphs is not supported/local function/func node→ 局部函数未内联含Cannot infer shape/Invalid rank→ 符号维度未定。两类的修复方向一致开优化但定位不同。确认加载时优化等级搜代码里有没有ORT_DISABLE_ALL有的话改掉或固化图后再用。检查 ONNX 是否带 local functiononnx.load(path).functions非空就说明导出路径引入了局部函数通常是 optimum 高版本。固化优化图设optimized_model_filepath用它做后续加载源CI 里断言functions为空。验证输出跑一条样本检查输出名、embeddings形状、norm≈1。版本对齐optimum / onnx / onnxruntime 三者版本要配套optimum 升级可能改变是否生成 local function。不要为了“可复现”一刀切关优化改为“导出时固化优化图”既能复现又不破坏加载。九、小结cannot load deepset-mxbai-embed-de-large-v1 without optimization的根因是这个模型的 ONNX 结构依赖图优化阶段的工作展开局部函数、常数折叠、算子融合才能变成可被直接加载的扁平图而ORT_DISABLE_ALL跳过了这些阶段于是加载器在最优化入口就失败。最小修复是不要对这个模型关优化结构性改进是用唯一的MxbaiEmbedLoadPolicy把“导出/加载/门禁”集中管理并固化优化图CI 守护则用三组断言锁住“优化下能加载、向量已归一化、固化图无局部函数”。记住一句原则需要可复现时固化的是优化后的图而不是把优化关掉。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门