visitor模式,因为它在编译器的框架中应用的广泛,在TVM中也是无处不在。

visitor模式介绍

Visitor(访问者)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式

GoF 设计模式这本书中提出Visitor模式的时候就是以编译器作为例子的。在编译器中,一般会用抽象语法树来构建中间表示。然后会有一些优化pass是基于抽象语法树来做的,例如类型检查,常量折叠,代码优化等等

有一种实现方法是如下图,对每个数据类型都实现Pass相关的功能。这就导致了一个问题,每增加一个优化Pass,都要重新修改现有的数据结构从而增加新的方法。

如果对象结构中数据元素的类型相对稳定的情况下(不经常增加新的数据类型),可以考虑使用下图中的方法。这样在新增加一个优化Pass的时候,就只要新增一个Visitor类继承自Visitor类就可以,然后添加相应的Visit方法,而不需要修改原有的对象结构中数据元素

visitor模式在TVM中的应用

上图是TVM中Vistor的几个基础的类。这儿只列举了CallNode和FunctionNode这两个数据类型,其它的还有IfNode,LetNode等,大家可以查看源码。

ExprFunctor::VisitExpr(const Expr& n, Args... args) 会根据Expr& n的类型进行分发,从而调用相应的VisitExpr_接口,例如n的类型是Function,那就会调用VisitExpr_(const FunctionNode* op, Args... args)接口

ExprVisitor 类不会改变数据,ExprMutator会改变数据。

下面解读下ExprVisitor的源码。

void ExprVisitor::VisitExpr(const Expr& expr) {
auto it = visit_counter_.find(expr.get());
if (it != visit_counter_.end()) {
++it->second;
} else {
using TParent = ExprFunctor<void(const Expr&)>;
TParent::VisitExpr(expr);
visit_counter_.insert({expr.get(), 1});
}
}

ExprVisitor 重写了ExprFunctor的VisitExpr函数,成员变量visit_counter用来记录已经访问过的expr,防止再次访问。最终还是会调用ExprFunctor的VisitExpr函数,用来做分发功能。如果这时候的expr类型是Call类型,那么紧接着就会调用ExprVisitor::VisitExpr_(const CallNode* op) 函数。

void ExprVisitor::VisitExpr_(const CallNode* op) {
this->VisitSpan(op->span);
this->VisitExpr(op->op); for (auto ty_arg : op->type_args) {
this->VisitType(ty_arg);
} for (auto arg : op->args) {
this->VisitExpr(arg);
}
}

可以看到它会对args调用VisitExpr函数,又会trigger ExprFunctor的分发功能,判断args的具体类型,然后去调用相应的VisitExpr_方法。

添加Pass打印包含的Relay op

添加新的Pass,如果想要遍历expr,只要添加一个新类继承自ExprVisitor 或者ExprMutator就可以。

下面是我添加的一个打印Call op的pass,在调用CallNode的时候打印下op的类型,所以只需要重写VisitExpr_(const CallNode* call)函数就可以。文件添加在tvm/src/relay/transform 目录下

#include <tvm/relay/analysis.h>
#include <tvm/relay/attrs/transform.h>
#include <tvm/relay/expr_functor.h>
#include <tvm/relay/interpreter.h>
#include <tvm/relay/op.h>
#include <tvm/relay/op_attr_types.h>
#include <tvm/relay/transform.h>
#include <tvm/runtime/container.h>
#include <tvm/runtime/ndarray.h>
#include <tvm/runtime/object.h> namespace tvm {
namespace relay { class TestCallExpr: public ExprVisitor {
public: private:
std::unordered_map<Expr, bool, ObjectPtrHash, ObjectPtrEqual> memo_; void VisitExpr_(const CallNode* call) final {
ExprVisitor::VisitExpr_(call);
std::cout << "call :" << std::endl;
std::cout << AsText(call->op, false, nullptr) << std::endl;
}
}; Expr TestCallParse(const Expr& expr, const IRModule& mod) {
TestCallExpr().VisitExpr(expr);
return expr;
} namespace transform {
Pass TestCallParse() {
runtime::TypedPackedFunc<Function(Function, IRModule, PassContext)> pass_func =
[=](Function f, IRModule m, PassContext pc) {
return Downcast<Function>(TestCallParse(f, m));
};
return CreateFunctionPass(pass_func, 2, "TestCallParse", {});
} TVM_REGISTER_GLOBAL("relay._transform.TestCallParse").set_body_typed(TestCallParse);
}
}
}

在python/tvm/relay/transform/transform.py中添加如下代码

def TestCallParse():
return _ffi_api.TestCallParse()

添加测试代码

import tvm
from tvm import te
from tvm import relay
from tvm.relay import transform
from tvm.relay.testing import run_opt_pass
import tvm.testing
import numpy as np dshape = (1, 16)
x = relay.var("x", shape=dshape)
y = relay.add(x, relay.const(1, "float32"))
z = relay.multiply(y, relay.const(1, "float32"))
print(z)
zz = run_opt_pass(z, transform.TestCallParse()

它对应的语法树为:

打印的结果为:

free_var %x: Tensor[(1, 16), float32];
%0 = add(%x, 1f);
multiply(%0, 1f) call :
#[version = "0.0.5"]
add
call :
#[version = "0.0.5"]
multiply

可以看出,先打印的是add,因为在遍历multiply(%0, 1f)的时候,需要遍历%0,因为%0是multiply(%0, 1f)的一个arg。遍历%0的时候 就会打印出op类型add。遍历完 multiply(%0, 1f)之后才会打印 multiply。

参考:

TVM之设计模式解读(一)--visitor模式

TVM:visitor设计模式的更多相关文章

  1. Visitor设计模式

    我猜想许多人都知道访问者设计模式,这种模式在“四人帮”的那本可复用面向对象软件基础的书被描述过.这个模式自身其实一点也不复杂(和以往的其他设计模式一样).  如上图所示: 我知道这个模式很久了,但是我 ...

  2. visitor设计模式记录

    数据类型通过枚举来区分是一种简单实用的做法. 缺点是使用的时候需要通过if .switch 去判断什么类型执行什么分支操作,说是缺点其实也要看具体场景.不过如果if代码多会导致代码很长是肯定的. 复杂 ...

  3. TypeScript Visitor设计模式

    以下翻译脑袋的VBF项目,试试看TypeScript能否重写. class RegExpr {     Accept<T>(convert: Converter<T>) {   ...

  4. 浅谈设计模式-visitor访问者模式

    先看一个和visitor无关的案例.假设你现在有一个书架,这个书架有两种操作,1添加书籍2阅读每一本书籍的简介. //书架public class Bookcase { List<Book> ...

  5. Java设计模式学习资源汇总

    本文记录了Java设计模式学习书籍.教程资源.此分享会持续更新: 1. 设计模式书籍 在豆瓣上搜索了一把,发现设计模式贯穿了人类生活的方方面面.还是回到Java与程序设计来吧. 打算先归类,再浏览,从 ...

  6. java设计模式---访问者模式

      Java深入到一定程度,就不可避免的碰到设计模式这一概念,了解设计模式,将使自 己对java中的接口或抽象类应用有更深的理解.设计模式在java的中型系统中应用广 泛,遵循一定的编程模式,才能使自 ...

  7. AOP 的利器:ASM 3.0 介绍

    引言 什么是 ASM ? ASM 是一个 Java 字节码操控框架.它能被用来动态生成类或者增强既有类的功能.ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态 ...

  8. 【java】字节码操作技术

    asm.javassist.cglib. 1.asm 比较底层,使用的visitor设计模式. 官网:https://asm.ow2.io/ 2.javassist 官网:http://www.jav ...

  9. Java ASM 技术简介

    什么是ASM ASM 是一个 Java 字节码操控框架.它能被用来动态生成类或者增强既有类的功能.ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为. ...

  10. [转]Spring中property-placeholder的使用与解析

    我们在基于spring开发应用的时候,一般都会将数据库的配置放置在properties文件中. 代码分析的时候,涉及的知识点概要: NamespaceHandler 解析xml配置文件中的自定义命名空 ...

随机推荐

  1. N-gram基本原理

    N-gram模型是一种语言模型(Language Model,LM),语言模型是一个基于概率的判别模型,它的输入是一句话(单词的顺序序列),输出是这句话的概率,即这些单词的联合概率(joint pro ...

  2. P11620 [Ynoi Easy Round 2025] TEST_34

    由子序列和最值异或可以想到线性基 发现其实线性基满足结合律 考虑线段树进行维护 那么显然的一个想法就是把1操作直接上tag 但是发现上tag其实会丢失线性基的性质 于是差分 将区间修改变为单点修改 考 ...

  3. 重生之数据结构与算法----队列&栈

    简介 上文说到,数据结构只有两种.其它的数据结构都是它的整花活. 栈 栈只能在表的一端(称为栈顶)进行插入和删除操作,遵循 "后进先出"(Last In First Out,LIF ...

  4. Ollama+DeepSeek+SlackBot

    技术背景 想必最近有在部署DeepSeek大模型的人,看标题就知道这篇文章在做什么事情了.由于Ollama对于IP的监听缺乏安全防护,并且内网部署的Ollama模型对于外网来说也是不可见的,而如果使用 ...

  5. HIVE带中括号的列名取数

    某次取数,某表中有奇怪的字段名:pointchange_ygz_[yyyy],带了个中插号,用简单查询出错 select pointchange_ygz_[yyyy] as p from t 出错信息 ...

  6. spring的三级缓存

    spring的三级缓存: Spring 容器的"三级缓存" Spring 容器的整个生命周期中,单例Bean对象是唯一的.即可以使用缓存来加速访问 Spring 源码中使用了大量的 ...

  7. 【Python】import模块和包

    模块和包 一. 模块 Python 模块(Module),是一个 Python 文件,以 .py 结尾,包含了 Python 对象定义和Python语句. 模块能定义函数,类和变量,模块里也能包含可执 ...

  8. 移除任务栏右端"显示桌面"按钮-AutoIt

    核心代码 $hwnd = WinGetHandle("[CLASS:Shell_TrayWnd]", "") ControlHide($hwnd, " ...

  9. jmeter性能测试案例:电商系统并发订单测试

    场景描述:本案例主要实现多用户同时提交订单,以检测系统对瞬时压力的响应情况.具体流程包括用户登录-添加商品-提交订单.涉及多个接口联动和参数处理,步骤如下: 第一步,登录用户 1.新建"下订 ...

  10. FastAPI数据库集成与事务管理

    title: FastAPI数据库集成与事务管理 date: 2025/04/18 00:15:34 updated: 2025/04/18 00:15:34 author: cmdragon exc ...