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. js提示Cannot read property ‘replace‘ of undefined

    JS提示Cannot read property 'replace' of undefined 出现这个错误的原因一般是传的参数为null 在传参之前加个是否为null的判断可以解决异常.

  2. APEX实战第1篇:本地部署拥有AI能力的APEX

    学会部署APEX是为了更好构建企业级AI应用打基础,比如企业级的知识平台.智能报表等. 先前在<手把手教你喂养 DeepSeek 本地模型>,使用AnythingLLM方式,虽然操作上已经 ...

  3. QT5笔记: 22. 自定义代理

    代理作用:在界面发生编辑时可以指定编辑所用的组件,可以沟通Model和View 自定义代理需要继承的基类和需要实现的方法 使用步骤: 继承QStyledItemDelegate,实现上面的四个方法 在 ...

  4. 【C#语法糖 Lambda】2 - 表达式树与LINQ

    一.声明 曾经遇到一个这样的场景: GetEntities(a => a.OrderKindCodeId == 16 && a.IsDeleted, this.DefaultCo ...

  5. Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南

    引言:AI技术新纪元的破局者 2025年3月6日凌晨,武汉Monica团队正式发布全球首款通用AI代理系统Manus,该工具在GitHub开源社区引发热议,单日Star数突破5万.与传统对话式AI不同 ...

  6. Windows 提权-弱服务_1

    本文通过 Google 翻译 Weak Service File Permissions – Windows Privilege Escalation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭 ...

  7. Jupyter Notebook的所有文件ipynb保存下来

    前言 如果你想要保存整个 Jupyter Notebook 工作目录,包括所有笔记本和其他相关文件,最直接的方法是将整个文件夹压缩为一个 ZIP 或 TAR 文件. 下载单个文件 压缩文件夹下载 在 ...

  8. nginx 配置go服务反向代理

    nginx 配置 详细请看Nginx 极简教程 server { listen 80; server_name localhost; #charset koi8-r; # nginx访问活动日志 ac ...

  9. wps时间戳转换成日期

    第一步 打开WPS表格,选择空表格 第二步 右击选择"设置单元格格式" 第三步 选择"日期",然后选择需要的日期类型 第四步 然后在表格里,输入公式 =(D2/ ...

  10. git和github的入门操作

    之前因为工作中用的都是SVN版本控制工具,没接触过git和github,现在开始深入自学Django框架技术后,看到官网推荐使用git,然后这两天网上查阅了很多文章教程,学到入门操作需要学习的点,太多 ...