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. 植物大战僵尸杂交版,最新安装包(PC+手机+苹果)+ 修改器+高清工具

    植物大战僵尸杂交版:全新游戏体验与创意碰撞 游戏简介 <植物大战僵尸杂交版>是由B站知名UP主潜艇伟伟迷基于经典游戏<植物大战僵尸>进行的一次大胆且富有创意的二次创作.这款游戏 ...

  2. MySQL - [11] InnoDB存储引擎

    Page 页.是真正理解InnoDB存储引擎的入口. 一.Page -- 页 1.1.InnoDB 数据页及其结构 为了避免一条一条读取磁盘数据,InnoDB采取页的方式,作为磁盘和内存之间交互的基本 ...

  3. Shell - shell中的数组

    Shell 数组用括号来表示,元素用 "空格" 符号分隔开,语法格式如下: arrName = (value1 value2 value3)(这种方式带值) 往数组里添加值,数组的 ...

  4. 【攻防世界】wzsc_文件上传

    wzsc_文件上传 题目来源 攻防世界 NO.GFSJ0997 题目描述 经典上传页面 用御剑扫出upload文件夹 /upload路径下是上传的文件 题解 新建几个空文件,发现后缀为txt的文件可以 ...

  5. 大数据之路Week10_day04 (Hbase的二级索引,二级索引的本质就是建立各列值与行键之间的映射关系)

    二级索引的本质就是建立各列值与行键之间的映射关系 HBASE是在hadoop之上构建非关系型,面向列存储的开源分布式结构化数据存储系统. Hbase的局限性: HBase本身只提供基于行键和全表扫描的 ...

  6. Selenium KPI接口 屏幕截图

    屏幕截图功能常用的有两种: savescreenshot()及 getscreenshotasfile(). 使用格式 self.driver.save_screenshot('baidu.png') ...

  7. antd vue 嵌套表格之实现每次展开一行

    在项目中遇到一个需求,就是使用嵌套子表格时,每次只展示一行,且展开一行另一行收起,直接上代码吧,顺便记录一下 这里需要注意,我们要在外层table组件添加如图三个属性,缺一不可,咳咳,不用杠我那个&l ...

  8. 往EXCEL粘贴超长整数字段

    写一个表格的HTML <table border="1"> <tr> <td>1</td> <td>1234567890 ...

  9. verilog实现32位有符号流水乘法器

    verilog实现32位有符号流水乘法器 1.4bit乘法流程 1.无符号X无符号二进制乘法器 以下为4bit乘法器流程(2X6) 0 0 0 0 0 0 1 0 (2) X 0 0 0 0 0 1 ...

  10. 【Java】基本语法

    一.语言概述 整体语言概述 (一)Java语言概述 1.基础常识 软件:即一系列按照特定顺序组织的计算机数据和指令的集合.分为:系统软件 和 应用软件 系统软件:windows , mac os , ...