Mini-Infer (18): 编排导入流程 — `ModelImporter` 与 `AttributeHelper`
Mini-Infer (18): 编排导入流程 — ModelImporter 与 AttributeHelper
引言:从架构到实现
在之前的博客中,我们设计了 OnnxParser 的顶层入口,定义了 ImporterContext 和 OperatorRegistry 的接口,并实现了 WeightImporter 来解析数据。
现在,我们需要将这些组件真正“运转”起来。
本篇,我们将实现两个核心组件:
AttributeHelper:一个极其使用的工具类,用于解决 ONNX Protobuf 属性访问繁琐的问题。ModelImporter:整个导入过程的“总指挥”。它负责按照正确的顺序(权重 -> 输入 -> 节点 -> 输出)编排导入流程,并将解析任务分发给注册表。
1. AttributeHelper:优雅地解析属性
ONNX 的 NodeProto 使用 Key-Value 的列表来存储算子属性(如卷积的 strides, pads)。使用原生的 Protobuf API 来查找和读取这些属性非常啰嗦且容易出错。
我们需要一个包装器来简化这个过程。
封装痛点
在原生 Protobuf 中,获取一个名为 kernel_shape 的属性可能需要写十几行代码来遍历 attribute 列表、检查名字、检查类型。
优雅实现
AttributeHelper 将这些逻辑封装在内部,对外提供了极其简洁的接口。
1 | // mini_infer/importers/attribute_helper.cpp |
有了这个工具,未来的算子导入代码将变得非常干净: int stride = attrs.get_int("stride", 1);
2. ModelImporter:导入流程的总指挥
ModelImporter 是解析器的核心引擎。它的 import_model 方法负责协调所有的资源,确保图构建的正确性。
我们将详细分析 import_graph 的四个关键步骤。
步骤 1:导入 Initializers (权重)
权重必须最先导入,因为后续算子(如 Conv)在创建时就需要访问它们。
1 | // mini_infer/importers/model_importer.cpp |
步骤 2:导入 Inputs & Outputs
这一步主要是在 Context 中注册占位符 Tensor。
- Inputs: 遍历
graph.input。如果一个输入名字不是权重(!ctx.is_weight(name)),那它就是模型的真实输入,我们需要创建一个空的 Tensor 并注册。 - Outputs: 遍历
graph.output,同样注册空的 Tensor 占位符。
1 | // mini_infer/importers/model_importer.cpp |
步骤 3:导入 Nodes (核心循环)
这是将 ONNX NodeProto 转换为 Mini-Infer 节点的关键步骤。
1 | // mini_infer/importers/model_importer.cpp |
步骤 4:图的收尾工作
在所有节点导入完成后,我们需要确保 Graph 对象的状态是完整的:
- 创建节点占位符:对于 Graph 的 Input/Output 名字,如果在导入过程中没有对应的
Node生成(虽然这很少见,但为了鲁棒性),我们需要手动调用graph->create_node()。 - 设置 Graph 输入输出:调用
graph->set_inputs()和graph->set_outputs(),确立图的边界。
1 | // mini_infer/importers/model_importer.cpp :: import_graph 尾部 |
3. 总结
本篇我们完成了 Mini-Infer 模型导入功能的逻辑闭环:
AttributeHelper:为后续的具体算子导入提供了极其便利的工具,让我们不用再和复杂的 Protobuf API 纠缠。ModelImporter:实现了严谨的导入流程控制。它像一个流水线管理员,确保了权重、输入、算子按照正确的顺序被解析和注册。
至此,我们的 ONNX Parser 已经具备了处理完整模型文件的能力——除了具体算子的转换逻辑。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 James的成长之路!
评论





