定义

TVM从Halide继承了计算调度分离的思想,并在其内部重用了部分Halide的调度原语,也引入了一些新的调度原语,用于优化GPU和专用加速器性能。

先举个例子吧:

import tvm
from tvm import te n = 1024
dtype = "float32"
A = te.placeholder((n, n), dtype=dtype, name='A')
K = te.reduce_axis((0, n), name='k')
B = te.compute((n,), lambda i: te.sum(A[i, k], axis=k), name='B')
S = te.create_schedule(B.op)
  • Compute(计算):在tvm中,te.compute() 接口的作用是通过指定的计算规则,在指定的矩阵形状上构造一个新的张量,它并不会触发实际的计算,而是采用基于lambda表达式的张量表达式进行张量计算,即仅声明了进行何种计算。

  • Schedule(调度):用于指定计算操作的顺序和方式,如通过Schedule对象,对计算操作进行调度,包括并行化、优化内存访问模式、设备特定优化等。Schedule提供了丰富的接口和方法,可以帮助优化和控制计算的执行方式,以提高性能和效率。TVM中通过te.create_schedule()创建调度,该接口返回的是tvm.te.schedule.Schedule类对象,其中包含了若干个阶段,每个阶段对应一个描述调度方式的Compute(操作)。

简单说,Compute 主要用于定义计算图中计算操作之间的关系,而 Schedule 则用于调度和优化这些计算操作的执行方式

作用

这种分离的设计是为了提高计算图的灵活性、可扩展性和性能

  • 模块化设计:将computeSchedule分开可以使得用户更容易理解和管理计算图中的操作,降低复杂度。compute主要关注计算单元的定义,而Schedule则负责对计算进行调度和优化,这种模块化设计简化了代码结构,提高了可维护性。

  • 优化灵活性:通过将计算和调度分隔开来,用户可以根据不同的需求针对计算和调度分别进行优化。这种灵活性使得用户可以更轻松地测试不同的优化策略,找到最佳的性能表现。

  • 性能提升:分开compute和Schedule也有助于实现更高效的编译和优化过程。通过将计算和调度分别处理,TVM可以更精确地对计算进行优化,从而达到更好的性能表现

怎么做的

本节主要来看下TVM中Compute是如何实现的

代码实现见:python/tvm/te/operation.py

def compute(shape, fcompute, name="compute", tag="", attrs=None, varargs_names=None):
if _tag.TagScope.get_current() is not None:
if tag != "":
raise ValueError("nested tag is not allowed for now")
tag = _tag.TagScope.get_current().tag
shape = (shape,) if isinstance(shape, tvm.tir.PrimExpr) else shape
# for python3
shape = tuple([int(s) if isinstance(s, float) else s for s in shape])
out_ndim = len(shape) argspec = inspect.getfullargspec(fcompute)
if len(argspec.args) == 0 and argspec.varargs is None:
arg_names = [f"i{i}" for i in range(out_ndim)]
elif argspec.varargs is not None:
# if there is a varargs, it takes the remaining dimensions of out_ndim
num_remaining_args = out_ndim - len(argspec.args)
if varargs_names is not None:
if len(varargs_names) != num_remaining_args:
raise RuntimeError(
f"Number of varargs ({num_remaining_args}) does not match number"
f"of varargs_names ({len(varargs_names)})"
)
arg_names = argspec.args + varargs_names
else:
arg_names = argspec.args + [f"i{i}" for i in range(out_ndim - len(argspec.args))]
else:
arg_names = argspec.args
# if there are fewer args than out dimensions, the remaining dimensions
# are implicitly broadcast
out_ndim = len(arg_names)
assert argspec.varkw is None, "Variable keyword arguments not supported in fcompute"
assert argspec.defaults is None, "Default arguments not supported in fcompute"
assert len(argspec.kwonlyargs) == 0, "Keyword arguments are not supported in fcompute" if out_ndim != len(arg_names):
raise ValueError(
"Number of args to fcompute does not match dimension, "
f"args={len(arg_names)}, dimension={out_ndim}"
) dim_var = [tvm.tir.IterVar((0, s), x, 0) for x, s in zip(arg_names, shape[:out_ndim])]
body = fcompute(*[v.var for v in dim_var]) if isinstance(body, _tensor.TensorIntrinCall):
for i, s in enumerate(shape[out_ndim:]):
var_name = "ax" + str(i)
dim_var.append(tvm.tir.IterVar((0, s), var_name, 4))
op_node = _ffi_api.TensorComputeOp(
name,
tag,
dim_var,
body.reduce_axis,
out_ndim,
body.intrin,
body.tensors,
body.regions,
body.scalar_inputs,
)
else:
if not isinstance(body, (list, tuple)):
body = [body]
body = convert(body)
op_node = _ffi_api.ComputeOp(name, tag, attrs, dim_var, body) num = op_node.num_outputs
outputs = tuple(op_node.output(i) for i in range(num))
return outputs[0] if num == 1 else outputs

fcompute(*[v.var for v in dim_var])会用dim_var替换compute.op的lambda表达式中的数值。

TVM Compute操作有ComputeOp()TensorComputeOp()

ComputeOp():每次操作一个标量的计算操作

TensorComputeOp():每次操作一个张量切片的计算操作

通过FFI接口调用C++端实现,可分别返回ComputeOp对象TensorComputeOp对象

ComputeOp()实现如下:

ComputeOp::ComputeOp(std::string name, std::string tag, Map<String, ObjectRef> attrs,
Array<IterVar> axis, Array<PrimExpr> body) {
if (!attrs.defined()) {
attrs = Map<String, ObjectRef>();
}
auto n = make_object<ComputeOpNode>();
n->name = std::move(name);
n->tag = std::move(tag);
n->attrs = std::move(attrs);
n->axis = std::move(axis);
n->body = std::move(body);
if (n->body[0]->IsInstance<tir::ReduceNode>()) {
const tir::ReduceNode* reduce = n->body[0].as<tir::ReduceNode>();
n->reduce_axis = reduce->axis;
}
VerifyComputeOp(n.get());
data_ = std::move(n);
}
// 注册
TVM_REGISTER_GLOBAL("te.ComputeOp")
.set_body_typed([](std::string name, std::string tag, Map<String, ObjectRef> attrs,
Array<IterVar> axis,
Array<PrimExpr> body) { return ComputeOp(name, tag, attrs, axis, body); });

data_为成员变量,定义如下:

ObjectPtr<Object> data_;

此处,对ComputeOp做继续分析,TensorComputeOp类似

ComputeOp对象调用 num_outputs 时,实现如下:

int ComputeOpNode::num_outputs() const { return body.size(); }

另:类ComputeOp继承自类Operation

当在调用对象的 output()方法 时,调用的是父类Operation方法,实现如下(src/te/tensor.cc):

Tensor Operation::output(size_t i) const {
auto node = make_object<TensorNode>();
node->op = *this;
node->value_index = i;
node->dtype = (*this)->output_dtype(i);
node->shape = (*this)->output_shape(i);
return Tensor(node);
}

通过上述代码,可将te.compute可以总结为如下几步:

  1. 根据传入的fcompute,翻译成对应的表达式传入
  2. 生成ComputeOpNode,记录计算节点.
  3. 根据计算节点,返回计算节点输出对应的Tensor

Respect~

TVM中的Compute操作的更多相关文章

  1. TVM中的调度原语

    TVM中的调度原语 TVM是一种用于高效内核构造的领域专用语言. 本文将展示如何通过TVM提供的各种原语来调度计算. from __future__ import absolute_import, p ...

  2. 线程安全使用(四) [.NET] 简单接入微信公众号开发:实现自动回复 [C#]C#中字符串的操作 自行实现比dotcore/dotnet更方便更高性能的对象二进制序列化 自已动手做高性能消息队列 自行实现高性能MVC WebAPI 面试题随笔 字符串反转

    线程安全使用(四)   这是时隔多年第四篇,主要是因为身在东软受内网限制,好多文章就只好发到东软内部网站,懒的发到外面,现在一点点把在东软写的文章给转移出来. 这里主要讲解下CancellationT ...

  3. 自主数据类型:在TVM中启用自定义数据类型探索

    自主数据类型:在TVM中启用自定义数据类型探索 介绍 在设计加速器时,一个重要的决定是如何在硬件中近似地表示实数.这个问题有一个长期的行业标准解决方案:IEEE 754浮点标准.1.然而,当试图通过构 ...

  4. 关于JavaScript中的delete操作

    关于JavaScript中的delete操作 看到一道题,是这样的: (function(x){ delete x; return x; })(1); 1 null undefined Error 我 ...

  5. C#中DataTable中的Compute方法使用收集

    原文: C#中DataTable中的Compute方法使用收集 Compute函数的参数就两个:Expression,和Filter. Expresstion是计算表达式,关于Expression的详 ...

  6. 理解CSV文件以及ABAP中的相关操作

    在很多ABAP开发中,我们使用CSV文件,有时候,关于CSV文件本身的一些问题使人迷惑.它仅仅是一种被逗号分割的文本文档吗? 让我们先来看看接下来可能要处理的几个相关组件的词汇的语义. Separat ...

  7. 第32课 Qt中的文件操作

    1. Qt的中IO操作 (1)Qt中IO操作的处理方式 ①Qt通过统一的接口简化了文件和外部设备的操作方式 ②Qt中的文件被看作一种特殊的外部设备 ③Qt中的文件操作与外部设备的操作相同 (2)IO操 ...

  8. 【Java EE 学习 33 上】【JQuery样式操作】【JQuery中的Ajax操作】【JQuery中的XML操作】

    一.JQuery中样式的操作 1.给id=mover的div采用属性增加样式.one $("#b1").click(function(){ $("#mover" ...

  9. 【Java EE 学习 32 下】【JQuery】【JQuey中的DOM操作】

    一.JQuery中的DOM操作. 什么是DOM:DOM是一中和浏览器.平台.语言无关的接口,使用该接口可以轻松访问页面中所有的标准组件.DOM简称文档对象模型,是Document Oject Mode ...

  10. git工作中的常用操作

    上班开始,打开电脑,git pull:拉取git上最新的代码: 编辑代码,准备提交时,git stash:将自己编辑的代码暂存起来,防止git pull时与库中的代码起冲突,否则自己的代码就白敲了: ...

随机推荐

  1. 在Unity中玩转表达式树:解锁游戏逻辑的动态魔法

    html { overflow-x: initial !important } :root { --bg-color: #ffffff; --text-color: #333333; --select ...

  2. 高数小技巧:和 e^x 有关的积分该怎么算?

    高数解题也需要日积月累,下面是和 \(e^{x}\) 相关的一些常用解题思路,记得收藏+关注哦,还有更多考研数学实战笔记等着你呢( ̄︶ ̄) 当前高数笔记的最新内容,可以查看: https://zhao ...

  3. 摸鱼日历,新闻简报等一些工作摸鱼日历API接口合集分享

    摸鱼人日历API接口 请求示例(图片输出): https://moyu.qqsuu.cn 请求示例(JSON输出):[推荐] https://moyu.qqsuu.cn/?type=json 调用示例 ...

  4. Zookeeper - Zookeeper启动失败,日志报错 Missing election port for server: 2

    Missing election port for server: 2 [整理日期]2023年6月1日 [基础环境]JDK 1.8.0_372.Zookeeper 3.4.5 [问题描述]进行部署分布 ...

  5. 开源一款DDS信号发生扩展板-FreakStudio多米诺系列

    原文链接: FreakStudio的博客 摘要 信号发生扩展板通过SPI接口生成可调频率和幅度的正弦波.方波和三角波,频率小于1MHz.支持幅度调节,提供原始和6倍放大输出接口.配备5阶低通滤波器.噪 ...

  6. Top-N推荐算法 Top-N recommendation Algorithms

    引言 推荐算法是计算机专业中的一种算法,通过一些计算,能够推测用户喜欢的东西,在互联网环境中应用比较广泛.Top-N算法在生活中非常常见,比如学术论文推荐论文.音乐软件推荐歌曲等. 今天看到一篇名叫& ...

  7. Ai 文本生成式大模型 基础知识

    提示工程-RAG-微调 工程当中也是这个次序 提示词工程 RAG 微调 先问好问题 再补充知识 最后微调模型 RAG相关技术细节 选择合适的 Chunk 大小对 RAG 流程至关重要. Chunk 过 ...

  8. 腾讯云锐驰型轻量服务器搭建开源远程桌面软件RustDesk中继服务器小记

    RustDesk是一个基于Rust编写的全平台开源远程桌面软件,其最大的特点为开箱即用,且数据完全自主掌控,甚至可以依托此项目定制化开发自己专属的远程桌面软件. 一.前言 由于我个人经常性出差,对远程 ...

  9. Escalate_Linux靶机提权学习

    靶机下载 https://www.vulnhub.com/entry/escalate_linux-1,323/ 用VMware打开 扫描端口 nmap -sS -sV -n -T4 -p- 192. ...

  10. 【Git】在 Idea 中使用 Git

    在 Idea 中使用 Git 1 安装 Git 核心程序 根据自己的电脑操作系统从 Git 官网 https://git-scm.com/ 下载对应的 Git 核心程序. 以 git-2.21.0 为 ...