TVM IR 核心架构:Expr 与 Type 的统一表达
TVM 的编译栈(涵盖高层 Relay/Relax 到低层 TIR)均构建在tvm.ir基础设施之上,其核心抽象为Expr(表达式)与Type(类型)。所有中间表示最终都归结为这两类基础节点的组合与继承 。
1. 核心基类继承体系
无论是用于图优化的 Relay/Relax,还是用于代码生成的 TIR,其算子(Op)定义均统一继承自RelayExprNode。这种设计确保了不同层级 IR 在元数据管理和类型推断上的一致性 。
| 组件层级 | 对应 C++ 基类 | 主要特征 | 源码位置参考 |
|---|---|---|---|
| 通用表达 | BaseExprNode | 所有表达式的最顶层基类 | include/tvm/ir/expr.h |
| 底层原语 | PrimExprNode | 处理 POD 数据类型,无生命周期管理,用于 TIR 及整数分析 | include/tvm/ir/expr.h |
| 高层算子 | RelayExprNode | 支持张量、函数等一等公民,包含类型推断结果 (checked_type_) | include/tvm/ir/expr.h |
| 算子定义 | OpNode | 继承自RelayExprNode,定义算子元数据(名称、签名、属性) | include/tvm/ir/op.h |
2. OpNode 结构与 Support Level 解析
OpNode是 TVM 中定义算子元数据的核心结构。除了常规的算子名称、类型签名和参数描述外,support_level字段用于标识算子的优先级或成熟度 。
C++ 结构定义示例:
/*!\ * \brief Primitive Op(builtin intrinsics)\ *\ * This data structure stores the meta-data\ * about primitive operators that can be invoked via Call.\ */\ class OpNode : public RelayExprNode {\ public:\ /*! \brief name of the operator */\ String name;\ /*! \brief the type of the operator */\ mutable FuncType op_type;\ /*! \brief detailed description of the operator */\ String description;\ /*! \brief Information of input arguments to the operator */\ Array<AttrFieldInfo> arguments;\ /*! \brief The type key of the attribute field */\ String attrs_type_key;\ /*! \brief number of input arguments to the operator, -1 means variable length */\ int32_t num_inputs = -1;\ /*! \brief support level of the operator, The lower the more priority it contains. */\ int32_t support_level = 10;\ // ... 其他字段如 attrs_type_index等\ };注:以上代码逻辑基于 TVM 源码include/tvm/ir/op.h整理 。
support_level的具体作用:
该字段的设计灵感来源于BLAS (Basic Linear Algebra Subprograms)的分级标准。
- 数值含义:值越小,表示算子的优先级越高,通常意味着该算子越基础、越稳定或被硬件支持得越好 。
- 应用场景:
- 算子选择策略:在编译器进行算子融合或 lowering 时,若存在多种实现方式(例如一个通用的 Python 实现和一个高度优化的 C++/CUDA 实现),编译器倾向于优先选择
support_level更低的实现。
2.稳定性标识:Level 1 通常代表最核心、最稳定的内置算子,而较高的 Level 可能代表实验性算子或特定后端的扩展算子。
3.文档生成:该字段也可用于自动生成算子文档,提示用户该算子的成熟度。
3. Type 系统的演进:从静态到动态
TVM 的类型系统直接决定了其对动态形状(Dynamic Shape)的支持能力,这也是 Relay向 Relax 演进的关键驱动力。
| 特性维度 | Relay / TIR (传统) | Relax (新一代) | 差异解析 |
|---|---|---|---|
| 张量类型定义 | TensorTypeNode | DynTensorTypeNode | Relay 将具体 Shape 表达式嵌入类型中 。 |
| Shape 存储 | Array<PrimExpr> shape | int ndim | Relax 将 Shape 的具体值从 Type 中解耦,Type 仅保留秩 (ndim) 和 dtype 。 |
| 动态形状支持 | 困难 | 原生支持 | 在 Relay 中Tensor[(m, n)]与Tensor[(m, 4)]被视为不同类型;而在 Relax 中,只要秩相同,它们属于同一类型DynTensor,Shape 作为独立的数据流处理 。 |
| 类型推断 | 编译期严格推断 | 运行时/混合推断 | 解耦后避免了因 Shape 符号表达式复杂导致的类型推断失败。 |
代码对比示例(概念性):
# Relay/TIR 风格:Shape 是类型的一部分 # TensorType(shape=[m, n], dtype="float32") # 若 m, n 变化,类型即变化,导致多态处理困难 # Relax 风格:Shape 与类型分离 # DynTensorType(ndim=2, dtype="float32") # 具体的 shape [m, n] 作为独立的 Expr 在数据流中传递4. 编译单元:IRModule
所有上述的 Expr 和 Type 最终被封装在IRModule中,这是 TVM 编译的最小完整单元。IRModule本质上是一个映射容器,存储了全局函数(BaseFunc,如PrimFunc或Relay Function)和类型定义 。
- TIR视角:
IRModule包含多个PrimFunc,每个函数对应一个可编译的低级内核。 - Relay/Relax 视角:
IRModule包含高层函数,描述了计算图的结构。 - 统一性:无论前端如何变化,最终都通过
IRModule传递给 Pass 机制进行优化和 Codegen 。
参考来源
- TVM 学习指南(个人版)
- 深度学习编译器Relax入门:从TVM到动态图编译实战
- TVM 模块部署实战指南:基于 C++ 与 Python 的完整部署示例(apps/howto_deploy 解析)
- Apache TVM 源码编译安装实战:版本选择、依赖配置与踩坑完全指南
- TVM 代码规范与贡献指南:编码风格、测试编写与常见实践要点
- Apache TVM 贡献者指南:从提交 PR、代码评审到版本发布的完整协作流程