CANN 组织链接 : https://atomgit.com/cann
Metadef 仓库链接 : https://atomgit.com/cann/metadef
在深度学习编译器的宏大叙事中,如果说编译器(Compiler)是工匠,运行时(Runtime)是监工,那么 Metadef (Metadata Definition) 就是那张至关重要的"蓝图"。它是整个异构计算软件栈中关于"计算图"和"算子"的通用定义集合。metadef 仓库不包含具体的计算逻辑,而是定义了数据结构的标准协议------即 Intermediate Representation (IR)。无论是上层的 TensorFlow/PyTorch 适配,还是下层的图优化引擎,都必须遵循这套元数据标准来进行信息的传递与交换。
本文将深入剖析 Metadef 的六大核心架构设计,揭示它是如何统一异构算力世界的"度量衡"。
一、 图(Graph)的拓扑结构与节点语义
Metadef 的核心职能是定义一张计算图。在 C++ 的对象模型中,这张图并非简单的邻接矩阵,而是由 Graph、Node、Edge 和 Anchor 构成的精密网络。
1.1 节点(Node)与算子(Op)的映射
在 Metadef 中,图中的每一个节点(Node)都是对算子(Operator)的一次实例化。
- OpDesc:描述了算子的静态属性,如算子类型(Conv2D, MatMul)、算子名称以及预定义的参数。
- Node:赋予了 OpDesc 在图中的拓扑属性,记录了它在这个特定网络结构中的位置、它与谁相连、以及它的输入输出依赖。
1.2 锚点(Anchor)机制
为了精确控制数据流和控制流,Metadef 引入了锚点设计。不同于简单的边连接,锚点区分了数据的流向与控制依赖:
- Data Anchor:承载 Tensor 数据的流动,必须严格匹配数据类型与 Shape。
- Control Anchor:仅用于同步执行顺序,不产生实际的数据拷贝,常用于控制流算子(如 If/Loop)或副作用算子(如 Assign)。
二、 张量描述符(TensorDesc)与数据排布
数据是流淌在计算图血管中的血液。Metadef 通过 GeTensor 和 GeTensorDesc 类,对数据的元信息进行了极致的抽象,使其能够适应不同硬件对内存布局的严苛要求。
2.1 形状(Shape)的动态性
在静态图时代,Shape 往往是固定的。但在大模型与动态 Batch 场景下,Metadef 支持范围定义:
- Static Shape :编译期确定的固定维度,如
[1, 3, 224, 224]。 - Dynamic Shape :使用
-1或范围区间(Range)来表示未知的维度,允许编译器在运行时根据真实输入进行推导。
2.2 格式(Format)的转换与兼容
不同的硬件单元偏好不同的数据排布。CPU 习惯 NCHW,而专用的矩阵计算单元可能需要 NC1HWC0 这种分块格式以优化存取带宽。Metadef 定义了丰富的 Format 枚举值,并允许在图编译阶段插入"转排算子"(TransData)来自动适配这种差异。
三、 算子原型注册(Operator Prototype)
如何让编译器认识一个新的算子?Metadef 提供了一套宏大的注册机制(Registry Mechanism)。这不仅仅是名称的注册,更是算子"契约"的签订。
3.1 属性推导与约束
注册机制允许开发者定义算子的输入输出数量、必选属性以及默认值。更重要的是,它包含了 InferShape 和 Verify 回调函数的接口定义。
- InferShape:根据输入的 Shape 推导输出的 Shape,这是图编译阶段内存规划的基础。
- Verify:对算子的参数进行合法性校验(例如卷积核大小不能为负数),在构图初期就拦截错误。
3.2 结构化定义示例
以下展示了 Metadef 中用于定义算子原型的结构化概念。这种设计采用了构建者模式(Builder Pattern),使得算子的定义具备高度的可扩展性,且与具体的实现代码解耦:
cpp
// 概念性结构定义:算子原型注册
// 这种设计允许编译器在不加载算子实现库(Kernel)的情况下
// 仅通过原型库就能完成图的校验与推导
class OpRegistrationData {
public:
// 1. 定义算子类型标识符
explicit OpRegistrationData(const char* om_op_type);
// 2. 声明输入端口与其依赖的数据类型
OpRegistrationData& Input(const char* name, TensorType type);
// 3. 声明输出端口
OpRegistrationData& Output(const char* name, TensorType type);
// 4. 定义算子特有的属性(如 stride, dilation)
// 支持设置默认值与属性类型检查
template<typename T>
OpRegistrationData& Attr(const char* name, T default_value);
// 5. 绑定 Shape 推导函数,用于静态内存规划
OpRegistrationData& InferShape(InferShapeFunc func);
// 6. 绑定数据格式推导函数
OpRegistrationData& InferFormat(InferFormatFunc func);
};
四、 序列化与模型持久化(Model Serialization)
计算图最终需要脱离 Python 前端,以文件的形式存储或部署。Metadef 定义了基于 Protocol Buffers 的序列化协议,这是模型"硬化"为离线模型(Offline Model)的基石。
4.1 协议缓冲区的分层
Metadef 的 proto 文件定义了从 Graph 到 Node 再到 Attribute 的全套层级结构。
- Def 层面:定义了基本的 Key-Value 属性存储方式。
- Model 层面:定义了由多个 Graph 组成的拓扑结构(例如包含主图与子图)。
4.2 跨版本兼容性
由于 AI 框架迭代极快,Metadef 在设计序列化格式时高度重视兼容性。通过 Protobuf 的 Optional 字段特性,新版本的编译器可以读取旧版本的模型文件,反之亦然,确保了算子库升级后,已部署的模型依然可用。
五、 属性(Attribute)的泛型存储系统
在计算图中,除了 Tensor 数据外,还存在大量的配置信息(如学习率、超参数、模式开关)。Metadef 设计了一个类似于 std::any 或 Variant 的泛型属性存储系统。
5.1 异构属性容器
AttrHolder 类通过 TypeId 机制支持存储任意类型的属性值,涵盖了从基础的 int, float, string 到复杂的 List<Tensor>, NamedAttrs。
5.2 属性的传递与重写
在图优化(Graph Optimization)过程中,优化器经常需要修改算子的属性。例如,将 BatchNorm 的参数融合进 Conv2D 的权重中。Metadef 的属性系统支持高效的就地修改(In-place Modification),并提供了序列化接口,确保优化后的属性能够被正确写入到最终的 OM 模型中。
六、 错误处理与调试信息的元数据
Metadef 不仅仅定义了"正确"的图,也定义了当图出错时如何描述错误。它是整个编译栈错误报告机制的数据源头。
6.1 节点定位信息
为了在发生错误时能够追溯到用户的源代码,Metadef 在 Node 定义中预留了 SourceInfo 字段,记录了该节点起源于 Python 脚本的哪一行。
6.2 编译期与运行时的上下文
它定义了用于传递上下文信息的结构体,使得错误信息可以在 Graph Builder、Graph Optimizer 和 Runtime 之间流转。
- Context ID:关联特定的执行流。
- Task ID:关联底层的硬件任务。
通过这些元数据,当系统抛出 "Op Execute Failed" 时,开发者不仅能看到底层的错误码,还能看到是图中的哪个节点、哪条边导致了问题,从而实现快速定位。