ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Expr与Type:TVM底层统一表达基石

Expr与Type:TVM底层统一表达基石

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)的分级标准。

  • 数值含义:值越小,表示算子的优先级越高,通常意味着该算子越基础、越稳定或被硬件支持得越好 。
  • 应用场景:
  1. 算子选择策略:在编译器进行算子融合或 lowering 时,若存在多种实现方式(例如一个通用的 Python 实现和一个高度优化的 C++/CUDA 实现),编译器倾向于优先选择support_level更低的实现。
    2.稳定性标识:Level 1 通常代表最核心、最稳定的内置算子,而较高的 Level 可能代表实验性算子或特定后端的扩展算子。
    3.文档生成:该字段也可用于自动生成算子文档,提示用户该算子的成熟度。

3. Type 系统的演进:从静态到动态

TVM 的类型系统直接决定了其对动态形状(Dynamic Shape)的支持能力,这也是 Relay向 Relax 演进的关键驱动力。

特性维度Relay / TIR (传统)Relax (新一代)差异解析
张量类型定义TensorTypeNodeDynTensorTypeNodeRelay 将具体 Shape 表达式嵌入类型中 。
Shape 存储Array<PrimExpr> shapeint ndimRelax 将 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、代码评审到版本发布的完整协作流程
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进