GE 在编译计算图时,需要回答一系列问题:这个算子的输入 Tensor 形状是什么、输出 Tensor 形状是什么、数据类型是 FP16 还是 INT8、参数里有几个属性。这些信息的结构化描述就是元数据(metadata)。CANN 用 metadef 仓库来定义和管理这些元数据。

metadef 不参与图编译的逻辑——它不管"怎么融合"“怎么优化”。它的职责是给 GE 和 Graph Compiler 提供一套统一的算子描述语言,让不同模块之间的信息传递有一套标准格式。


metadef 在 CANN 中的定位

在 CANN 的仓库架构中,metadef 是最底层的基础设施之一:

应用层(AscendCL / PyTorch)
  ↓
图引擎(GE)——依赖 metadef 的算子定义
  ↓
Graph Compiler——依赖 metadef 的 Tensor 描述
  ↓
Runtime——依赖 metadef 的任务描述
  ↓
硬件驱动

opbase 提供 Tensor 和 Buffer 的运行时基础设施,metadef 提供编译时的算子描述基础设施。两者位置平行但职责不同:opbase 管运行时的数据,metadef 管编译时的元信息。


为什么图编译需要元数据

GE 在收到 ONNX 模型后,需要把 ONNX 的算子描述翻译成 CANN 内部的算子描述。一个 Conv 算子在 ONNX 里可能是:

Conv(input, weight, strides=[1,1], pads=[0,0,0,0], dilations=[1,1])

GE 需要把这个描述转成 CANN 内部格式:

OpType: Conv
Inputs:
  - name: x, shape: [1,3,224,224], dtype: float16
  - name: w, shape: [64,3,3,3], dtype: float16
Outputs:
  - name: y, shape: [1,64,224,224], dtype: float16
Attrs:
  - strides: [1,1]
  - pads: [0,0,0,0]
  - dilations: [1,1]

这个结构化描述就是 metadef 定义的格式。每个算子类型(OpType)在 metadef 中有唯一的定义——输入参数有哪些、输出参数有哪些、属性有哪些、参数的数据类型和默认值。


Tensor Shape 如何被管理

Tensor 的形状在推理过程中可能变化。metadef 用 ShapeShapeRange 两种方式描述:

// metadef 中的 Shape 定义
struct Shape {
    std::vector<int64_t> dims;
    Format format;        // ND, NZ, NHWC 等
};

struct ShapeRange {
    std::vector<std::pair<int64_t, int64_t>> dim_ranges;
    // dim_ranges[0] 表示第一个维度从 min 到 max
};

固定 Shape 用 Shape 描述。动态 Shape 用 ShapeRange 描述。

GE 在编译时遍历每个 Tensor 的 Shape 信息,判断哪些优化可以提前做、哪些要留到运行时。比如算子融合——如果 Tensor 的 ShapeRange 某些维度跨度过大、L1 容量无法覆盖所有情况,GE 选择不做融合或做"分情况融合"(运行时根据 Shape 选择不同的执行计划)。


Graph Compiler 如何依赖 metadef

Graph Compiler 的每个优化 Pass 都需要查询算子的元数据:

// 融合 Pass 中查询算子元数据
bool CanBeFused(const OpDesc& op_a, const OpDesc& op_b) {
    // 检查 op_a 的输出类型是否匹配 op_b 的输入类型
    if (op_a.output(0).dtype != op_b.input(0).dtype) return false;
    
    // 检查 Tensor 格式是否兼容
    if (op_a.output(0).format != op_b.input(0).format) return false;
    
    // 检查 Shape 推导是否确定
    if (!op_b.output(0).shape.IsStatic()) return false;
    
    return true;
}

op_a.output(0).dtypeop_a.output(0).formatop_b.output(0).shape.IsStatic()——这些信息都是 metadef 定义的数据结构。没有 metadef 的统一定义,每个 Pass 都要自己解析 ONNX 的原始格式,优化 Pass 的代码会变得又长又散。


Transformer 推理中的元数据流转

以 LLaMA-7B 在 GE 中的编译过程为例,metadef 的参与路径:

  1. 模型加载。 ATC 转换后的 OM 文件中,每个算子都附带了 metadef 格式的元描述。GE 解析 OM 时直接读这些描述,不需要重新从 ONNX 解析。
  2. Shape 传播。 GE 从输入 Tensor 的 Shape 开始,沿计算图正向传播每个算子的输出 Shape。Self-Attention 的 Q 投影输出 Shape 是 [1, 32, n, 128],K 投影相同。这些 Shape 信息存储在 metadef 的 TensorDesc 中。
  3. 融合评估。 graph-autofusion 根据 metadef 提供的算子类型和输入输出 Shape,判断 Attention 子图是否可以替换为 FlashAttention 融合算子。
  4. 执行计划生成。 Task 的输入输出 Buffer 地址和 Shape 信息以 metadef 格式序列化到 OM 中。Runtime 加载 OM 时直接读取这些描述来创建 Task。

metadef 的数据结构

metadef 中几个核心的数据结构:

  • TensorDesc:描述 Tensor 的 Shape、DataType、Format、存储地址
  • OpDesc:描述算子的类型、输入输出 Tensor 列表、属性参数
  • GraphDesc:描述整个计算图的节点(算子)和边(Tensor 依赖)

GE 在编译时使用这些描述来遍历图、分析依赖、做优化决策。Runtime 的 Task 描述也基于 metadef 的数据结构——Task 包含 OpDesc 的执行配置和 TensorDesc 的地址信息。

metadef 的序列化与反序列化

OM 模型中包含序列化的 metadef 数据。模型加载时 GE 反序列化这些数据重建计算图。序列化格式是 protobuf——TensorDesc、OpDesc、GraphDesc 都有对应的 proto 定义。

序列化格式的兼容性保证了不同 CANN 版本之间的 OM 模型互通。CANN 8.0 编译的 OM 模型可以加载到 CANN 8.5 的 Runtime 上运行——metadef 的新增字段不会破坏旧版本的解析逻辑。

参考仓库

metadef 元数据定义库

GE 图执行引擎

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐