《Relay IR的基石:expr.h 中的表达式类型系统剖析》
TVM Relay源码深度解读
文章目录
- TVM Relay源码深度解读
- 一 、从Constant看Relay表达式的设计哲学
- 1. 类定义概述
- 2. `ConstantNode` 详解
- 1. 核心成员
- 2. 关键方法
- 3. 类型系统注册
- 3. `Constant` 详解
- 1. 核心功能
- 二. 核心内容概述
- (1) Relay表达式基类
- 1. RelayExprNode 和 RelayExpr 的区别与用法
- 2. 主要区别
- 3. 使用模式
- 例子1:常量表达式
- 例子2:变量表达式
- 例子3:函数应用
- 4. 实际使用建议
- (2) 具体表达式类型
- 1. 表达式类型 VarNode举例子
- 1. 核心设计理念
- 2. 关键成员解析
- (1) 核心字段
- (2) 特殊方法
- 3. 变量标识系统
- (1) vid (Unique ID)
- (2) name_hint 与 vid 的关系
- 4. 类型系统整合
- (1) 类型注解流程
- (2) 类型推导规则
- 5. 内存模型与跨语言交互
- (1) C++ 层构造
- (2) Python 绑定
- **(3) 对象生命周期**
- 6. 关键应用场景
- (1) 函数参数定义
- (2) 优化 Pass 中的变量处理
- (3) 类型检查
- 7. 设计亮点总结
- 8. 典型问题分析
- (3) TVM_DECLARE_BASE_OBJECT_INFO 宏详解
- 1. 宏的参数
- 2. 静态断言检查(防止非法继承)
- 2. 运行时类型索引(RuntimeTypeIndex)
- 3. 动态分配类型索引(_GetOrAllocRuntimeTypeIndex)
- 通俗版解释:TVM的类型身份证系统
- 1. 为什么要办身份证?
- 2. 办证过程(宏的作用)
- 3. 特殊班级(FINAL版)
- 4. 实际有什么用?
- 举个栗子🌰
- 一句话总结
- (4) 遍历接口
- 1. C++ 场景示例
- (1) 模型序列化(保存为JSON)
- (2) 优化Pass中的常量修改
- (3) 调试打印
- 2. Python 场景示例
- (1) 直接属性访问
- (2) 模型保存与加载
- (3) 自定义属性访问器
一 、从Constant看Relay表达式的设计哲学
在TVM的Relay IR中,即使是看似简单的常量表达式relay.const(1),其背后也隐藏着整个类型系统的精妙设计。让我们从include/tvm/relay/expr.h中的Constant类入手,逐步拆解…"
1. 类定义概述
| 类名 | 继承关系 | 角色 | 关键特性 |
|---|---|---|---|
ConstantNode | public ExprNode | 常量表达式的实际数据存储 | 包含常量数据(NDArray)、类型信息,并实现属性访问、哈希和相等比较逻辑。 |
Constant | public RelayExpr | 常量表达式的智能指针封装 | 提供用户友好的构造函数和访问方法,隐藏内存管理细节。 |
2. ConstantNode 详解
class ConstantNode : public ExprNode {public:/*! \brief The data of the tensor */runtime::NDArray data;/*! \return The corresponding tensor type of the data */TensorType tensor_type() const;/*! \return Whether it is scalar(rank-0 tensor) */bool is_scalar() const { return data->ndim == 0; }void VisitAttrs(tvm::AttrVisitor* v) {v->Visit("data", &data);v->Visit("span", &span);v->Visit("mdata", &mdata);v->Visit("_checked_type_", &checked_type_);}bool SEqualReduce(const ConstantNode* other, SEqualReducer equal) const {return equal(data, other->data);}void SHashReduce(SHashReducer hash_reduce) const { hash_reduce(data); }static constexpr const char* _type_key = "relay.Constant";TVM_DECLARE_FINAL_OBJECT_INFO(ConstantNode, ExprNode);
};
1. 核心成员
-
data(runtime::NDArray)- 存储常量张量的实际数据(如权重、偏置等),TVM 使用
NDArray统一表示多维数组。 - 示例:卷积层的权重矩阵会被存储在这里。
- 存储常量张量的实际数据(如权重、偏置等),TVM 使用
-
tensor_type()- 根据
data的维度(shape)和数据类型(dtype)自动生成对应的TensorType。 - 用途:类型推断时确定常量的类型。
- 根据
-
is_scalar()- 判断常量是否为标量(0维张量),如
data->ndim == 0。
- 判断常量是否为标量(0维张量),如
2. 关键方法
-
VisitAttrs- 实现属性的序列化/反序列化,支持以下字段:
v->Visit("data", &data); // 张量数据 v->Visit("span", &span); // 源码位置信息 v->Visit("mdata", &mdata); // 元数据(如调试信息) v->Visit("_checked_type_", &checked_type_); // 类型检查后的类型
- 实现属性的序列化/反序列化,支持以下字段:
-
SEqualReduce和SHashReduce- 结构化相等比较:比较两个
ConstantNode的data是否相同(用于优化中的常量折叠)。 - 哈希计算:基于
data生成哈希值(用于快速查找重复常量)。
- 结构化相等比较:比较两个
3. 类型系统注册
TVM_DECLARE_FINAL_OBJECT_INFO(ConstantNode, ExprNode);
_type_key = "relay.Constant":唯一标识常量节点类型。FINAL:禁止继承,确保常量节点的行为不可被修改。
3. Constant 详解
class Constant : public Expr {public:/*!* \brief The constructor* \param data The data of the constant tensor.* \param span The source span of the expression.*/TVM_DLL explicit Constant(runtime::NDArray data, Span span = Span(), MetaData mdata = MetaData());TVM_DEFINE_OBJECT_REF_METHODS(Constant, RelayExpr, ConstantNode);
};
1. 核心功能
-
构造函数
explicit Constant(runtime::NDArray data, Span span = Span(), MetaData mdata = MetaData());- 接收
NDArray数据,构造一个常量表达式。 - 示例:
# Python 前端等价代码 data = np.array([1, 2, 3], dtype="float32") const_expr = relay.Constant(tvm.nd.array(data))
- 接收
-
智能指针方法
TVM_DEFINE_OBJECT_REF_METHODS(Constant, RelayExpr, ConstantNode);展开后提供:
operator->():直接访问ConstantNode成员(如const_expr->data)。get():获取底层ConstantNode指针。- 自动内存管理(通过
ObjectRef的引用计数)。
二. 核心内容概述
在TVM源码中,include/tvm/relay/expr.h 是 Relay IR(中间表示)的核心头文件,定义了所有Relay表达式的基础数据结构和类型系统。它是实现TVM高层计算图表示的关键组成部分。以下是该文件的详细解析:
相关重要文件
| 文件路径 | 关联内容 |
|---|---|
include/tvm/relay/type.h | 类型系统(TensorType等) |
include/tvm/relay/op.h | 运算符定义 |
include/tvm/relay/adt.h | 代数数据类型支持 |
src/relay/ir/expr.cc | 表达式方法的实现 |
include/tvm/relay/expr.h文件主要包含:
- (1) Relay表达式基类(
RelayExpr/RelayExprNode) - (2) 所有具体表达式类型的声明(如变量、常量、函数调用等)
- (3) 表达式类型的遍历和转换接口
- (4) 类型系统和属性访问的支持
(1) Relay表达式基类
class RelayExprNode : public BaseExprNode { /*...*/ };
class RelayExpr : public BaseExpr { /*...*/ };
- 角色:所有Relay表达式的公共基类
- 功能:
- 提供类型系统支持(通过
checked_type_字段) - 实现属性访问(
VisitAttrs) - 支持结构化相等比较(
SEqualReduce)
- 提供类型系统支持(通过
1. RelayExprNode 和 RelayExpr 的区别与用法
RelayExprNode 是 Relay 表达式的实际实现类,是一个 C++ 类,包含了表达式的所有数据和功能实现。它是所有 Relay 表达式类型的基类。
RelayExpr 是一个智能指针(relay::Expr),它指向 RelayExprNode 或其子类的实例。它提供了对 RelayExprNode 的安全访问和管理。
2. 主要区别
| 特性 | RelayExprNode | RelayExpr |
|---|---|---|
| 类型 | C++ 类 | 智能指针(std::shared_ptr 的封装) |
| 生命周期管理 | 需要手动管理 | 自动管理 |
| 使用方式 | 通常不直接使用,作为实现细节 | 用户主要交互的接口 |
| 继承关系 | 作为基类定义表达式结构 | 作为访问接口 |
3. 使用模式
在 TVM 中,通常的模式是:
- 定义一个继承自
RelayExprNode的具体表达式节点类 - 使用
RelayExpr作为这些节点的引用
例子1:常量表达式
// 创建一个常量表达式
auto const_node = relay::ConstantNode::make(tvm::runtime::NDArray::Zeros(...));
RelayExpr const_expr = const_node;// 通常更简洁的写法
RelayExpr const_expr = relay::Constant(tvm::runtime::NDArray::Zeros(...));
例子2:变量表达式
// 创建一个变量表达式
auto var_node = relay::VarNode::make("x", relay::Type());
RelayExpr var_expr = var_node;// 或者更简洁地
RelayExpr var_expr = relay::Var("x", relay::Type());
例子3:函数应用
// 创建函数应用表达式
RelayExpr func = ...; // 某个函数
RelayExpr arg = ...; // 某个参数
auto call_node = relay::CallNode::make(func, {arg});
RelayExpr call_expr = call_node;// 或者
RelayExpr call_expr = relay::Call(func, {arg});
4. 实际使用建议
-
用户代码:在大多数情况下,你应该使用
RelayExpr而不是直接操作RelayExprNode。 -
扩展 Relay:如果你想定义新的表达式类型,需要继承
RelayExprNode并实现相应接口。 -
类型转换:可以使用
as<T>方法将RelayExpr向下转换为特定类型的节点指针:
RelayExpr expr = ...;
if (const auto* call = expr.as<CallNode>()) {// 现在可以访问 CallNode 的特定成员call->op;call->args;
}
- 创建新表达式:TVM 提供了辅助函数来创建表达式,通常以节点类型名去掉 “Node” 命名(如
relay::Var()创建VarNode的RelayExpr)。
这种分离设计使得 Relay IR 既灵活又安全,同时保持了良好的性能特性
(2) 具体表达式类型
| 表达式类型 | 说明 | 关键成员/方法 |
|---|---|---|
VarNode | 变量(输入/中间结果) | String name_hint, Type type_annotation, Id vid |
ConstantNode | 常量张量(如模型权重) | runtime::NDArray data, tensor_type(), is_scalar() |
CallNode | 函数/运算符调用 | Expr op, Array<Expr> args, Attrs attrs, Array<Type> type_args |
LetNode | Let绑定(实现变量作用域) | Var var, Expr value, Expr body |
TupleNode | 元组结构(多返回值) | Array<Expr> fields |
TupleGetItemNode | 从元组中获取元素 | Expr tuple, int index |
IfNode | 条件表达式 | Expr cond, Expr true_branch, Expr false_branch |
OpNode | 基本运算符(如add/concat) | 通过Op::Get("op_name")获取 |
FunctionNode | 函数定义(在function.h中声明,但属于表达式) | Array<Var> params, Expr body, Type ret_type, Array<TypeVar> type_params |
RefCreateNode | 创建可变引用(用于状态更新) | Expr value |
RefReadNode | 读取引用值 | Expr ref |
RefWriteNode | 更新引用值 | Expr ref, Expr value |
ConstructorNode | 代数数据类型(ADT)的构造器(在adt.h中声明) | String tag, Array<Type> inputs |
MatchNode | 模式匹配(ADT处理) | Expr data, Array<Clause> clauses |
TempExprNode | 临时表达式(用于优化过程中的中间表示) | 通常作为优化Pass的中间载体 |
GlobalVarNode | 全局函数引用(跨模块调用) | String name_hint |
SeqExprNode | 顺序执行多个表达式(类似语句块) | Array<Binding> bindings, Expr body |
1. 表达式类型 VarNode举例子
include/tvm/relay/expr.h
class Var;
/*! \brief Container for Var */
class VarNode : public ExprNode {public:/*!* \brief The unique identifier of the Var.** vid will be preserved for the same Var during type inference* and other rewritings, while the VarNode might be recreated* to attach additional information.* This property can be used to keep track of parameter Var* information across passes.*/Id vid;/*!* \brief type annotaion of the variable.* This field records user provided type annotation of the Var.* This field is optional and can be None.*/Type type_annotation;/*! \return The name hint of the variable */const String& name_hint() const { return vid->name_hint; }void VisitAttrs(tvm::AttrVisitor* v) {v->Visit("vid", &vid);v->Visit("type_annotation", &type_annotation);v->Visit("span", &span);v->Visit("mdata", &mdata);v->Visit("_checked_type_", &checked_type_);}bool SEqualReduce(const VarNode* other, SEqualReducer equal) const {return equal(type_annotation, other->type_annotation) && equal.FreeVarEqualImpl(this, other);}void SHashReduce(SHashReducer hash_reduce) const {hash_reduce(type_annotation);hash_reduce.FreeVarHashImpl(this);}static constexpr const char* _type_key = "relay.Var";TVM_DECLARE_FINAL_OBJECT_INFO(VarNode, ExprNode);
};class Var : public Expr {public:/*!* \brief The constructor* \param name_hint The name hint of a variable.* \param type_annotation The type annotation of a variable.* \param span The source span of the expression.*/TVM_DLL Var(String name_hint, Type type_annotation, Span span = Span(), MetaData mdata = MetaData()): Var(Id(name_hint), type_annotation, span, mdata) {}/*!* \brief The constructor* \param vid The unique id of a variable.* \param type_annotation The type annotation of a variable.* \param span The source span of the expression.*/TVM_DLL Var(Id vid, Type type_annotation, Span span = Span(), MetaData mdata = MetaData());TVM_DEFINE_OBJECT_REF_METHODS(Var, RelayExpr, VarNode);
};
1. 核心设计理念
VarNode 和 Var 共同实现了 Relay IR 的变量系统,采用 TVM 标准的 Object-ObjectRef 设计模式:
VarNode:存储实际数据的节点类(继承自ExprNode)Var:管理VarNode的智能指针包装类(继承自Expr)
2. 关键成员解析
(1) 核心字段
| 成员 | 类型 | 作用 |
|---|---|---|
vid | Id | 唯一标识符,跨 Pass 保持不变(即使节点被重建) |
type_annotation | Type | 用户显式指定的类型注解(可空) |
name_hint() | String | 通过 vid->name_hint 获取的可读名称(非唯一) |
span | Span | 源码位置信息(用于错误定位) |
mdata | MetaData | 扩展元数据 |
(2) 特殊方法
| 方法 | 功能 |
|---|---|
SEqualReduce | 结构化相等比较(用于优化 Pass 的重复检测) |
SHashReduce | 哈希计算(支持快速查找) |
VisitAttrs | 属性序列化/反序列化 |
3. 变量标识系统
(1) vid (Unique ID)
class IdNode : public Object {public:String name_hint;// ... 其他元数据
};
- 核心特性:
- 通过
Id(name_hint)构造,但系统会保证其唯一性 - 即使优化 Pass 重建变量节点,
vid保持不变 - 用于跨 Pass 跟踪参数变量(如梯度更新时识别同一参数)
- 通过
(2) name_hint 与 vid 的关系
x = relay.var("input", shape=(1,3)) # 实际创建:# vid = Id("input_0x7f") (自动去重)# name_hint = "input" (用户友好)
4. 类型系统整合
(1) 类型注解流程
graph TDA[用户构造] -->|relay.var(..., dtype="float32")| B(type_annotation)B --> C[类型检查]C -->|更新| D(_checked_type_)
(2) 类型推导规则
- 若
type_annotation存在:必须与实际使用类型兼容 - 若为空:从上下文推断类型
5. 内存模型与跨语言交互
(1) C++ 层构造
// 方式1:通过 name_hint
Var x("data", TensorType({1,3}, DataType::Float(32)));// 方式2:直接指定 Id
Var x(Id("data_0x7f"), TensorType({1,3}, DataType::Float(32)));
(2) Python 绑定
# Python 前端接口
x = relay.var(name="input",shape=(1,3),dtype="float32",span=SourceSpan(...)
)
(3) 对象生命周期
sequenceDiagramPython->>C++: relay.var() 创建请求C++->>Heap: 分配 VarNodeC++->>Python: 返回 Var(ObjectRef)Python->>C++: 析构时触发引用计数-1
6. 关键应用场景
(1) 函数参数定义
def build_linear():x = relay.var("x", shape=(1,3))w = relay.var("w", shape=(3,2))b = relay.var("b", shape=(2,))y = relay.add(relay.matmul(x, w), b)return relay.Function([x, w, b], y)
(2) 优化 Pass 中的变量处理
// 在 ConstantFolding 中识别变量引用
if (const VarNode* var = expr.as<VarNode>()) {if (var_map.count(var->vid)) {// 替换为已知常量}
}
(3) 类型检查
// 检查变量类型是否匹配
bool CheckType(const VarNode* var, const Type& expected) {return var->checked_type().as<TensorType>()->dtype == expected;
}
7. 设计亮点总结
- 稳定性:
vid保证变量在优化过程中的持久标识 - 灵活性:
type_annotation支持显式/隐式类型指定 - 安全性:
TVM_DECLARE_FINAL_OBJECT_INFO防止错误继承 - 可调试性:
span和name_hint增强错误可读性 - 性能:
SEqualReduce/SHashReduce优化图操作效率
8. 典型问题分析
Q: 为什么需要同时存在 vid 和 name_hint?
A: 分工不同:
name_hint:面向用户,提供可读性(允许重复)vid:面向系统,保证唯一性和跨Pass一致性
Q: 何时会重建 VarNode?
A: 典型场景:
- 类型推断后附加
_checked_type_ - 优化 Pass 中克隆表达式时保留原
vid但新建节点
(3) TVM_DECLARE_BASE_OBJECT_INFO 宏详解
这个宏是 TVM 类型系统的核心,用于在 C++ 中动态注册和管理对象的类型信息。它的核心作用是: 为每个类自动生成类型注册代码,使其能被 TVM 运行时识别和操作。
1. 宏的参数
#define TVM_DECLARE_BASE_OBJECT_INFO(TypeName, ParentType)
TypeName:当前类名(如ConstantNode)ParentType:父类名(如ExprNode)
2. 静态断言检查(防止非法继承)
static_assert(!ParentType::_type_final, "ParentObj marked as final");
- 作用:如果父类被标记为
final(通过_type_final),则禁止子类继承。
2. 运行时类型索引(RuntimeTypeIndex)
static uint32_t RuntimeTypeIndex() {// 检查子类槽位配置是否合法static_assert(TypeName::_type_child_slots == 0 || ParentType::_type_child_slots == 0 ||TypeName::_type_child_slots < ParentType::_type_child_slots,"子类槽位数不能超过父类限制");// 如果已预分配类型ID,直接返回if (TypeName::_type_index != ::tvm::runtime::TypeIndex::kDynamic) {return TypeName::_type_index;}// 否则动态分配return _GetOrAllocRuntimeTypeIndex();
}
- 功能:返回类的唯一类型 ID(
uint32_t)。 - 优化:优先使用预分配的
_type_index(性能更高),否则动态分配。
3. 动态分配类型索引(_GetOrAllocRuntimeTypeIndex)
static uint32_t _GetOrAllocRuntimeTypeIndex() {static uint32_t tidx = Object::GetOrAllocRuntimeTypeIndex(TypeName::_type_key, // 类型名称字符串(如 "relay.Constant")TypeName::_type_index, // 预分配的类型IDParentType::RuntimeTypeIndex(), // 父类类型IDTypeName::_type_child_slots, // 为子类预留的槽位数TypeName::_type_child_slots_can_overflow // 是否允许超额);return tidx;
}
- 作用:向 TVM 运行时注册类型,并分配唯一 ID。
- 关键参数:
_type_child_slots:限制子类数量(防止类型爆炸)。_type_child_slots_can_overflow:为true时允许突破限制。
通俗版解释:TVM的类型身份证系统
你可以把TVM的类型系统想象成一个学校的学生管理系统,而TVM_DECLARE_BASE_OBJECT_INFO就是给学生(类)办身份证的机器:
1. 为什么要办身份证?
- 每个学生(类)需要唯一学号(类型ID)
- 需要知道他的班主任是谁(父类)
- 防止有人冒充转校生(非法继承)
2. 办证过程(宏的作用)
// 给"小明同学"办证,班主任是"李老师"
TVM_DECLARE_BASE_OBJECT_INFO(小明, 李老师)
这个宏会自动做三件事:
-
检查家世清白
static_assert(!李老师::是final班, "班主任明确不收新学生!");- 如果班主任声明"我们班不接收转学生",就报错
-
分配学号
- 优先用预留的VIP学号(
_type_index) - 没有就现场摇号(
_GetOrAllocRuntimeTypeIndex)
- 优先用预留的VIP学号(
-
登记亲属关系
学号 = 教务处.登记(姓名:"小明",班主任:李老师.学号,可带小弟人数:3 // _type_child_slots );
3. 特殊班级(FINAL版)
TVM_DECLARE_FINAL_OBJECT_INFO(学霸班, 实验班)
- 相当于在班级门口挂**“禁止转入”**牌子
- 其他班同学想转学过来会直接报错
4. 实际有什么用?
- 查身份证快:
obj->IsInstance<小明>()比查户口本快 - 安全转班:
obj.as<小明>()能安全转换类型 - 防止冒名顶替:禁止随便认爹(错误继承)
举个栗子🌰
# Python前端定义一个"汉堡店"类
@register_relay_node("food.HamburgerShop")
class HamburgerShopNode(ExprNode):_type_key = "food.HamburgerShop"_type_child_slots = 2 # 允许开2家分店
C++层通过这个宏:
- 给汉堡店分配类型ID(比如9527)
- 记录它的父类是ExprNode
- 允许最多2个子类(比如
CheeseBurgerShop、ChickenBurgerShop)
一句话总结
这个宏就是TVM给类发身份证+建家族档案的工具,让系统能:
- ✅ 快速识别"你是谁"(类型检查)
- ✅ 知道"你爸是谁"(继承关系)
- ❌ 防止"乱认亲戚"(非法继承)
(4) 遍历接口
void VisitAttrs(tvm::AttrVisitor* v) {v->Visit("data", &data);v->Visit("span", &span);v->Visit("mdata", &mdata);v->Visit("_checked_type_", &checked_type_);}
VisitAttrs 是 TVM 中用于统一序列化、反序列化和属性访问的核心接口。以下是 ConstantNode 使用该函数的具体示例,涵盖 C++ 和 Python 场景:
1. C++ 场景示例
(1) 模型序列化(保存为JSON)
// 创建常量节点
runtime::NDArray arr = runtime::NDArray::Empty({2, 2}, DLDataType{kDLFloat, 32, 1}, DLContext{kDLCPU, 0});
ConstantNode* const_node = new ConstantNode();
const_node->data = arr;// 序列化为JSON
JSONAttrVisitor visitor;
const_node->VisitAttrs(&visitor); // 触发以下调用:// visitor.Visit("data", &data)// visitor.Visit("span", &span)...
std::string json = visitor.GetJSON();
输出JSON片段:
{"type_key": "relay.Constant","data": {"b64": "AABAA...", "dtype": "float32", "shape": [2, 2]},"span": null,"_checked_type_": "TensorType([2,2], float32)"
}
(2) 优化Pass中的常量修改
class ConstantMutator : public AttrMutator {public:void VisitAttrs(AttrVisitor* v) override {if (v->IsMutator()) { // 检查是否为修改模式runtime::NDArray new_data = ...; // 生成新数据v->Visit("data", &new_data); // 修改data字段}}
};// 调用示例:
ConstantMutator mutator;
const_node->VisitAttrs(&mutator); // 修改常量数据
(3) 调试打印
class DebugPrinter : public AttrVisitor {public:void Visit(const char* key, runtime::NDArray* data) override {std::cout << key << ": shape=" << data.Shape();}
};DebugPrinter printer;
const_node->VisitAttrs(&printer); // 输出:data: shape=[2,2]
2. Python 场景示例
(1) 直接属性访问
import tvm
from tvm import relay# 创建常量
data = tvm.nd.array(np.zeros((2,2), dtype="float32"))
const = relay.Constant(data)# Python属性访问(背后调用VisitAttrs)
print(const.data) # 访问NDArray → 触发Visit("data", &data)
print(const.span) # 访问源码位置 → Visit("span", &span)
输出:
<tvm.nd.NDArray shape=(2, 2), dtype=float32>
None # 未设置span时的默认值
(2) 模型保存与加载
# 保存模型(触发序列化)
mod = tvm.IRModule.from_expr(const)
mod.save("const.json") # 内部调用VisitAttrs# 加载模型(触发反序列化)
loaded_mod = tvm.ir.load_json("const.json")
loaded_const = loaded_mod["main"].body
assert isinstance(loaded_const, relay.Constant)
(3) 自定义属性访问器
class MyVisitor(tvm.ir.AttrVisitor):def visit(self, name, value):print(f"Attribute {name} has type {type(value)}")visitor = MyVisitor()
const.visit_attrs(visitor) # 显式调用VisitAttrs
输出:
Attribute data has type <class 'tvm.runtime.ndarray.NDArray'>
Attribute span has type <class 'tvm.ir.Span'>
...
class Constant;
/*!* \brief Constant tensor type.*/
class ConstantNode : public ExprNode {public:/*! \brief The data of the tensor */runtime::NDArray data;/*! \return The corresponding tensor type of the data */TensorType tensor_type() const;/*! \return Whether it is scalar(rank-0 tensor) */bool is_scalar() const { return data->ndim == 0; }void VisitAttrs(tvm::AttrVisitor* v) {v->Visit("data", &data);v->Visit("span", &span);v->Visit("mdata", &mdata);v->Visit("_checked_type_", &checked_type_);}bool SEqualReduce(const ConstantNode* other, SEqualReducer equal) const {return equal(data, other->data);}void SHashReduce(SHashReducer hash_reduce) const { hash_reduce(data); }static constexpr const char* _type_key = "relay.Constant";TVM_DECLARE_FINAL_OBJECT_INFO(ConstantNode, ExprNode);
};class Constant : public Expr {public:/*!* \brief The constructor* \param data The data of the constant tensor.* \param span The source span of the expression.*/TVM_DLL explicit Constant(runtime::NDArray data, Span span = Span(), MetaData mdata = MetaData());TVM_DEFINE_OBJECT_REF_METHODS(Constant, RelayExpr, ConstantNode);
};
以下是关于 ConstantNode 和 Constant 类的详细解释与概括,结合它们在 TVM Relay IR 中的作用和实现设计:
相关文章:
《Relay IR的基石:expr.h 中的表达式类型系统剖析》
TVM Relay源码深度解读 文章目录 TVM Relay源码深度解读一 、从Constant看Relay表达式的设计哲学1. 类定义概述2. ConstantNode 详解1. 核心成员2. 关键方法3. 类型系统注册 3. Constant 详解1. 核心功能 二. 核心内容概述(1) Relay表达式基类1. RelayExprNode 和 RelayExpr 的…...
《马尼拉》桌游期望计算器
《马尼拉》桌游期望计算器:做出最明智的决策 注:本项目仍在开发验证中,计算结果可能不够准确,欢迎游戏爱好者提供协助! 在线使用 | GitHub 项目简介 马尼拉期望计算器是一个基于 Vue 3 Vite 开发的网页应用ÿ…...
23种设计模式-结构型模式之适配器模式(Java版本)
Java 适配器模式(Adapter Pattern)详解 🔌 什么是适配器模式? 适配器模式用于将一个类的接口转换成客户端所期望的另一种接口,让原本接口不兼容的类可以协同工作。 📦 就像插头转换器,让不同…...
动态LOD策略细节层级控制:根据视角距离动态简化远距量子态渲染
动态LOD策略在量子计算可视化中的优化实现 1. 细节层级控制:动态简化远距量子态渲染 在量子计算的可视化中,量子态通常表现为高维数据(如布洛赫球面或多量子比特纠缠态)。动态LOD(Level of Detail)策略通过以下方式优化渲染性能: 距离驱动的几何简化: 远距离渲染:当…...
算法 | 成长优化算法(Growth Optimizer,GO)原理,公式,应用,算法改进研究综述,matlab代码
===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 ===================================================== 成长优化算法 一、算法原理二、核心公式三、应用领域四、算法改进研究五…...
线程池的介绍
目录 一、什么是线程池 二、线程池的详细内容 三、线程池的简化 一、什么是线程池 提到线程池,我们可能想到 常量池,可以先来说说常量池: 像是字符串常量,在Java程序最初构建的时候,就已经准备好了,等程…...
安恒安全渗透面试题
《网安面试指南》https://mp.weixin.qq.com/s/RIVYDmxI9g_TgGrpbdDKtA?token1860256701&langzh_CN 5000篇网安资料库https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247486065&idx2&snb30ade8200e842743339d428f414475e&chksmc0e4732df793fa3bf39…...
基于瑞芯微RK3576国产ARM八核2.2GHz A72 工业评估板——ROS2系统使用说明
前 言 本文主要介绍创龙科技TL3576-MiniEVM评估板演示基于Ubuntu的ROS系统(版本:ROS2 Foxy)使用说明,包括镜像编译、镜像替换,以及ROS系统测试的方法。适用开发环境如下。 Windows开发环境:Windows 10 64bit Linux虚拟机环境:VMware16.2.5、Ubuntu22.04.5 64bit U-B…...
Python爬虫实战:获取高考网专业数据并分析,为志愿填报做参考
一、引言 高考志愿填报是考生人生的关键节点,合理的志愿填报能为其未来发展奠定良好基础。计算机类专业作为当下热门领域,相关信息对考生填报志愿至关重要。教育在线网站虽提供丰富的计算机类专业数据,但存在反爬机制,增加了数据获取难度。本研究借助 Scrapy 爬虫技术及多…...
计算机是如何工作的(上)
对于学习JavaEE初阶为什么要知道计算机是如何工作的,是因为在未来我们写代码的时候,会出现一些bug,而在代码层面是看不出来的,所以我们需要了解一些关于计算机内部是如何工作的,从而提高代码的健壮度。 计算机的组成&…...
基础服务系列-Windows10 安装AnacondaJupyter
下载 https://www.anaconda.com/products/individual 安装 安装Jupyter 完成安装 启动Jupyter 浏览器访问 默认浏览器打开,IE不兼容,可以换个浏览器 修改密码 运行脚本...
构造微调训练数据集
借助 ChatGPT 和 GPT API我们可以实现自动化批量构造训练数据集。 下面我们以中国古典哲学数据集为例,展示了自动构造训练集的主要流程: 使用 LangChain 构造训练数据样例 o基于 ChatGPT 设计 System Role 提示词 。使用 0penAI GPT-4o-mini 生成基础数据 解析 Open…...
Kubernetes架构介绍
实验环境 安装好k8s集群 一、kubernetes组件构成 1、架构图 2、组件介绍 使用以下命令查看相关资源 kubectl get nodes 查看群集节点 kubectl get ns 查看名称空间 kubectl get pod -A …...
远程服务器的mysql连接不上,问题出在哪里
使用本地ideal测试连接报错记录 排查 检查mysql服务是否正常,输入命令systemctl status mysql查看 检查端口netstat -plnt | grep mysql 最后检查服务器的防火墙设置 我以为在服务器厂商的控制面板设置放行规则就行,导致一直无法排查出问题,最后才发现由…...
Java高频面试之并发编程-04
hello啊,各位观众姥爷们!!!本baby今天来报道了!哈哈哈哈哈嗝🐶 面试官:调用 start()方法时会执行 run()方法,那为什么不直接调用 run()方法? 多线程中调用 start() 方法…...
【第16届蓝桥杯软件赛】CB组第一次省赛
个人主页:Guiat 归属专栏:算法竞赛 文章目录 A. 移动距离(5分填空题)B. 客流量上限(5分填空题)C. 可分解的正整数D. 产值调整E. 画展布置F. 水质检测G. 生产车间H. 装修报价 正文 总共10道题。 A. 移动距离…...
云原生--基础篇-2--云计算概述(云计算是云原生的基础,IaaS、PaaS和SaaS服务模型)
1、云计算概念 云计算是一种通过互联网提供计算资源(包括服务器、存储、数据库、网络、软件等)和服务的技术模式。用户无需拥有和维护物理硬件,而是可以根据需要租用这些资源,并按使用量付费。 2、云计算特点 (1&am…...
uniapp云打包针对谷歌视频图片权限的解决方案
谷歌在24年底推出把图片和视频细分为两个权限,uniapp使用uni.chooseImage云打包默认图片视频为一个权限,不符合谷歌要求会被下架 解决方法,在项目根目录下新建AndroidManifest.xml移除不必要的权限 <?xml version"1.0" encoding"utf…...
vllm+vllm-ascend本地部署QwQ-32B
1 模型下载 可按照此处方法下载预热后的模型,速度较快(推荐artget方式) https://mirrors.tools.huawei.com/mirrorDetail/67b75986118b030fb5934fc7?mirrorNamehuggingface&catalogllms或者从hugging face官方下载。 2 vllm-ascend安…...
栈和队列--数据结构初阶(2)(C/C++)
文章目录 前言理论部分栈的模拟实现STL中的栈容器队列的模拟实现STL中的队列容器 作业部分 前言 这期的话会给大家讲解栈和队列的模拟实现和在STL中栈和队列怎么用的一些知识和习题部分(这部分侧重于理论知识,习题倒还是不难) 理论部分 栈的模拟实现 typedef int…...
C++常用函数合集
万能头文件:#include<bits/stdc.h> 1. 输入输出流(I/O)函数 1.1cin 用于从标准输入流读取数据。 1.2cout 用于向标准输出流写入数据。 // 输入输出流(I/O)函数 #include <iostream> using namespace…...
OpenGL shader开发实战学习笔记:第十二章 深入光照
1. 深入光照 1.1. 平行光 我们在前面的章节中,已经介绍了平行光的基本原理和实现步骤 平行光的基本原理是,所有的光都从同一个方向照射到物体上,这个方向就是平行光的方向。 1.2. 点光源 点光源的基本原理是,所有的光都从一个…...
CentOS7系统安装Docker教程
一、安装前准备 1、检查系统环境:Docker 要求系统为 64 位,且内核版本 3.10 以上。通过uname -r命令查看当前系统内核版本 。比如执行uname -r后,显示3.10.0-1160.el7.x86_64 ,说明满足内核版本要求。 2、卸载旧版本(…...
获取电脑信息(登录电脑的进程、C盘文件信息、浏览器信息、IP)
电脑的进程信息 // 获取登录电脑的进程信息String os System.getProperty("os.name").toLowerCase();String command;if (os.contains("win")) {command "tasklist";} else {command "ps -ef";}try {Process process new ProcessB…...
PCB 射频天线设计和版图创建技巧
本文要点 射频天线有多种形式,从整合在芯片中的扁平天线,到直接印制在PCB上的铜质天线。 创建带有一个或多个天线的版图时,需要确保在PCB不同电路模块之间彼此隔离。 在设计一个射频天线时,应该使用CAD工具,此类…...
uniapp-商城-29-vuex 关于系统状态的管理
按照我们前面讲的,vuex,的使用方式: 步骤如下: 1 先创建store 文件夹 2 在 store 中 创建一个 index.js 3、 在 store 中,创建一个modules文件夹 4、在store中,创建一个getters.js 5、在modules文件…...
小迪安全-112-yii反序列化链,某达oa,某商场,影响分析
yii是和tp一样的框架 入口文件 web目录下 相对tp比较简单一些,对比tp找一下他的url结构 对应的位置结构 这个contorllers文件的actionindex就是触发的方法 控制器,指向的index文件,就可以去视图模块看index文件 这就是前端展示的文件 自…...
区间选点详解
步骤 operator< 的作用在 C 中, operator< 是一个运算符重载函数,它定义了如何比较两个对象的大小。在 std::sort 函数中,它会用到这个比较函数来决定排序的顺序。 在 sort 中,默认会使用 < 运算符来比较两个对象…...
如何在白平衡标定种构建不同类型的白平衡色温坐标系
目录 一、预备知识: 二、常见的白平衡色温坐标系 三、白平衡色温坐标系的理解 1)横纵坐标轴分别代表什么含义? 2)色温坐标系中原点表示什么含义? 3)某M/某H的色温坐标为什么是长成这样呢?…...
Oracle RMAN同步数据库Active database duplicate
Active database duplicate,不需要先把目标数据库进行rman备份,只要目标数据库处于归档模式下即可直接通过网络对数据库进行copy,且copy完成后自动open数据库。这对于大数据特别是T级别的数据库来说优点非常明显,复制前不需要进行…...
