技术标签: split 调度Schedule tile TVM中文教程 TVM深度学习编译器
TVM是用于高效内核代码构建的版本领域专用语言(Domain-Specialed-Language,DSL) 。
这篇教程,我们将展示通过TVM提供的各种原语怎么去调度计算。
from __future__ import absolute_import, print_function
import tvm
import numpy as np
通常存在几种计算相同结果的方法,但是,不同的方法将导致不同的局部性和性能,所以TVM要求用户提供怎么去调用Schedule描述计算是如何执行的。
Schedule是计算的变换的集合,它通过变换程序中的计算循环Loop,实现不同性能。
#定义一些变量
n = tvm.var('n')
m = tvm.var('m')
调度(Schedule)能通过一系列计算ops来定义,默认情况下,调度计算张量以航为顺序。
#定义矩阵元素element-wise乘法
A = tvm.placeholder((m,n), name='A')
B = tvm.placeholder((m,n), name='B')
C = tvm.compute((m,n),lambda i,j: A[i,j] * B[i,j], name ='C')
#创建调度
s = tvm.create_schedule([C.op])
#lower会将计算从定义转换为真正的可调用函数。 使用参数`simple_mode = True`,它将返回一个可读的C伪代码,我们在这里使用它来打印计划结果。
print(tvm.lower(s, [A, B, C], simple_mode=True))
输出:
produce C {
for (i, 0, m) {
for (j, 0, n) {
C[((i*n) + j)] = (A[((i*n) + j)]*B[((i*n) + j)])
}
}
}
一个调度过程由多个阶段组成,一个阶段表示操作的一个调度。我们提供各种方法来调度每个阶段。
split
通过factor
分裂指定轴为两个轴。
A = tvm.placeholder((m,), name='A')
B = tvm.compute((m,), lambda i: A[i]*2, name='B')
s = tvm.create_schedule(B.op)
#分裂0轴为两个轴,先计算内循环再计算外循环,xo为外循环,xi为内循环
xo, xi = s[B].split(B.op.axis[0], factor=32)
print(tvm.lower(s, [A, B], simple_mode=True))
输出:
produce B {
for (i.outer, 0, ((m + 31)/32)) {
for (i.inner, 0, 32) {
if (likely(((i.outer*32) < (m - i.inner)))) {
B[((i.outer*32) + i.inner)] = (A[((i.outer*32) + i.inner)]*2.000000f)
}
}
}
}
使用nparts
与factor
作用相反,nparts指定外循环次数,factor指定内循环次数。
A = tvm.placeholder((m,), name='A')
B = tvm.compute((m,), lambda i: A[i], name='B')
s = tvm.create_schedule(B.op)
bx, tx = s[B].split(B.op.axis[0], nparts=32)
print(tvm.lower(s, [A, B], simple_mode=True))
输出:
produce B {
for (i.outer, 0, 32) {
for (i.inner, 0, ((m + 31)/32)) {
if (likely((i.inner < (m - (i.outer*((m + 31)/32)))))) {
if (likely(((0 - (i.outer*((m + 31)/32))) <= i.inner))) {
B[(i.inner + (i.outer*((m + 31)/32)))] = A[(i.inner + (i.outer*((m + 31)/32)))]
}
}
}
}
}
tile
通过平铺两个轴执行计算图块
A = tvm.placeholder((m, n), name='A')
B = tvm.compute((m, n), lambda i, j: A[i, j], name='B')
s = tvm.create_schedule(B.op)
xo, yo, xi, yi = s[B].tile(B.op.axis[0], B.op.axis[1], x_factor=10, y_factor=5)
print(tvm.lower(s, [A, B], simple_mode=True))
输出:
produce B {
for (i.outer, 0, ((m + 9)/10)) {
for (j.outer, 0, ((n + 4)/5)) {
//先执行10x5的图块,滑动下一个图块
for (i.inner, 0, 10) {
for (j.inner, 0, 5) {
if (likely(((i.outer*10) < (m - i.inner)))) {
if (likely(((j.outer*5) < (n - j.inner)))) {
B[(((j.outer*5) + (((i.outer*10) + i.inner)*n)) + j.inner)] = A[(((j.outer*5) + (((i.outer*10) + i.inner)*n)) + j.inner)]
}
}
}
}
}
}
}
fuse
能融合一个计算的两个轴
A = tvm.placeholder((m,n),name='A')
B = tvm.compute((m,n), lambda i,j: A[i,j], name='B')
s = tvm.create_schedule(B.op)
#首先平铺成4轴(i.outer,j.outer,i.inner,j.inner)
xo,yo,xi,yi = s[B].tile(B.op.axis[0],B.op.axis[1], x_factor=10, y_factor=5)
#然后融合(i.inner,j.inner)进一个轴:(i.inner.j.inner.fused)
fused = s[B].fuse(xi,yj)
print(tvm.lower(s, [A, B], simple_mode=True))
输出:
produce B {
for (i.outer, 0, ((m + 9)/10)) {
for (j.outer, 0, ((n + 4)/5)) {
for (i.inner.j.inner.fused, 0, 50) {
if (likely(((i.outer*10) < (m - (i.inner.j.inner.fused/5))))) {
if (likely(((j.outer*5) < (n - (i.inner.j.inner.fused % 5))))) {
B[(((j.outer*5) + (i.inner.j.inner.fused % 5)) + (((i.outer*10) + (i.inner.j.inner.fused/5))*n))] = A[(((j.outer*5) + (i.inner.j.inner.fused % 5)) + (((i.outer*10) + (i.inner.j.inner.fused/5))*n))]
}
}
}
}
}
}
reorder
能按照指定顺序重新排列轴(类似于permute)。
A = tvm.placeholder((m, n), name='A')
B = tvm.compute((m, n), lambda i, j: A[i, j], name='B')
s = tvm.create_schedule(B.op)
#首先平铺成4轴(i.outer,j.outer,i.inner,j.inner)
xo, yo, xi, yi = s[B].tile(B.op.axis[0], B.op.axis[1], x_factor=10, y_factor=5)
s[B].reorder(xi,yo,xo,yi)
print(tvm.lower(s, [A, B], simple_mode=True))
输出:
produce B {
for (i.inner, 0, 10) {
for (j.outer, 0, ((n + 4)/5)) {
for (i.outer, 0, ((m + 9)/10)) {
for (j.inner, 0, 5) {
if (likely(((i.outer*10) < (m - i.inner)))) {
if (likely(((j.outer*5) < (n - j.inner)))) {
B[(((j.outer*5) + (((i.outer*10) + i.inner)*n)) + j.inner)] = A[(((j.outer*5) + (((i.outer*10) + i.inner)*n)) + j.inner)]
}
}
}
}
}
}
}
bind
可以使用线程轴绑定指定的轴,通常在GPU编程中使用。
A = tvm.placeholder((n,), name='A')
B = tvm.compute(A.shape, lambda i: A[i] * 2, name='B')
s = tvm.create_schedule(B.op)
bx, tx = s[B].split(B.op.axis[0], factor=64)
s[B].bind(bx, tvm.thread_axis("blockIdx.x"))
s[B].bind(tx, tvm.thread_axis("threadIdx.x"))
print(tvm.lower(s, [A, B], simple_mode=True))
输出:
produce B {
// attr [iter_var(blockIdx.x, , blockIdx.x)] thread_extent = ((n + 63)/64)
// attr [iter_var(threadIdx.x, , threadIdx.x)] thread_extent = 64
if (likely(((blockIdx.x*64) < (n - threadIdx.x)))) {
B[((blockIdx.x*64) + threadIdx.x)] = (A[((blockIdx.x*64) + threadIdx.x)]*2.000000f)
}
}
对于包含多个算子的调度,TVM默认从root开始遍历计算张量。
A = tvm.placeholder((m,), name='A')
B = tvm.compute((m,), lambda i: A[i]+1, name='B')
C = tvm.compute((m,), lambda i: B[i]*2, name='C')
s = tvm.create_schedule(C.op)
print(tvm.lower(s, [A, B, C], simple_mode=True))
输出:
produce B {
for (i, 0, m) {
B[i] = (A[i] + 1.000000f)
}
}
produce C {
for (i, 0, m) {
C[i] = (B[i]*2.000000f)
}
}
compute_at
可以将B
的计算移动到C
的第一个计算轴。
A = tvm.placeholder((m,), name='A')
B = tvm.compute((m,), lambda i: A[i]+1, name='B')
C = tvm.compute((m,), lambda i: B[i]*2, name='C')
s = tvm.create_schedule(C.op)
# 移动B循环到C循环的第一个轴
s[B].compute_at(S[C], C.op.axis[0])
print(tvm.lower(s, [A, B, C], simple_mode=True))
输出:
produce C {
for (i, 0, m) {
produce B {
B[i] = (A[i] + 1.000000f)
}
C[i] = (B[i]*2.000000f)
}
}
compute_inline
可以将一个计算阶段标记为内联,然后将计算体扩展并插入需要张量的地址处。(和C中的内联函数一个意思)
A = tvm.placeholder((m,), name='A')
B = tvm.compute((m,), lambda i: A[i]+1, name='B')
C = tvm.compute((m,), lambda i: B[i]*2, name='C')
s = tvm.create_schedule(C.op)
s[B].compute_inline()
print(tvm.lower(s, [A, B, C], simple_mode=True))
输出:
produce C {
for (i, 0, m) {
//类似内联函数,直接合成一个循环
C[i] = ((A[i]*2.000000f) + 2.000000f)
}
}
compute_root
可以将一个计算阶段的计算移动到root。(compute_at
的逆过程)
A = tvm.placeholder((m,), name='A')
B = tvm.compute((m,), lambda i: A[i]+1, name='B')
C = tvm.compute((m,), lambda i: B[i]*2, name='C')
s = tvm.create_schedule(C.op)
# B移动到C的0轴
s[B].compute_at(s[C], C.op.axis[0])
# B重新移动回root
s[B].compute_root()
print(tvm.lower(s, [A, B, C], simple_mode=True))
输出:
produce B {
for (i, 0, m) {
B[i] = (A[i] + 1.000000f)
}
}
produce C {
for (i, 0, m) {
C[i] = (B[i]*2.000000f)
}
}
本教程介绍了tvm中的调度原语,它允许用户轻松灵活地调度计算。
为了获得良好性能的内核实现,一般工作流程通常是:
文章浏览阅读2.5w次,点赞6次,收藏50次。官方解释是,docker 容器是机器上的沙盒进程,它与主机上的所有其他进程隔离。所以容器只是操作系统中被隔离开来的一个进程,所谓的容器化,其实也只是对操作系统进行欺骗的一种语法糖。_docker菜鸟教程
文章浏览阅读5.7k次,点赞3次,收藏14次。该如何避免的,今天小编给大家推荐两个下载Windows系统官方软件的资源网站,可以杜绝软件捆绑等行为。该站提供了丰富的Windows官方技术资源,比较重要的有MSDN技术资源文档库、官方工具和资源、应用程序、开发人员工具(Visual Studio 、SQLServer等等)、系统镜像、设计人员工具等。总的来说,这两个都是非常优秀的Windows系统镜像资源站,提供了丰富的Windows系统镜像资源,并且保证了资源的纯净和安全性,有需要的朋友可以去了解一下。这个非常实用的资源网站的创建者是国内的一个网友。_msdn我告诉你
文章浏览阅读1.2k次。vue2封装对话框el-dialog组件_
文章浏览阅读4.7k次,点赞5次,收藏6次。MFC 文本框换行 标签: it mfc 文本框1.将Multiline属性设置为True2.换行是使用"\r\n" (宽字符串为L"\r\n")3.如果需要编辑并且按Enter键换行,还要将 Want Return 设置为 True4.如果需要垂直滚动条的话将Vertical Scroll属性设置为True,需要水平滚动条的话将Horizontal Scroll属性设_c++ mfc同一框内输入二行怎么换行
文章浏览阅读832次。检查Linux是否是否开启所需端口,默认为6379,若未打开,将其开启:以root用户执行iptables -I INPUT -p tcp --dport 6379 -j ACCEPT如果还是未能解决,修改redis.conf,修改主机地址:bind 192.168.85.**;然后使用该配置文件,重新启动Redis服务./redis-server redis.conf..._redis-server doesn't support auth command or ismisconfigured. try
文章浏览阅读4.9k次。济大数电实验报告_数据选择器及其应用
文章浏览阅读236次。1研究内容消费在生产中占据十分重要的地位,是生产的最终目的和动力,是保持省内经济稳定快速发展的核心要素。预测河南省社会消费品零售总额,是进行宏观经济调控和消费体制改变创新的基础,是河南省内人民对美好的全面和谐社会的追求的要求,保持河南省经济稳定和可持续发展具有重要意义。本文建立灰色预测模型,利用MATLAB软件,预测出2019年~2023年河南省社会消费品零售总额预测值分别为21881...._灰色预测模型用什么软件
文章浏览阅读1.2k次。12.4-在Qt中使用Log4Qt输出Log文件,看这一篇就足够了一、为啥要使用第三方Log库,而不用平台自带的Log库二、Log4j系列库的功能介绍与基本概念三、Log4Qt库的基本介绍四、将Log4qt组装成为一个单独模块五、使用配置文件的方式配置Log4Qt六、使用代码的方式配置Log4Qt七、在Qt工程中引入Log4Qt库模块的方法八、获取示例中的源代码一、为啥要使用第三方Log库,而不用平台自带的Log库首先要说明的是,在平时开发和调试中开发平台自带的“打印输出”已经足够了。但_log4qt
文章浏览阅读786次。全局观思维模型,一个教我们由点到线,由线到面,再由面到体,不断的放大格局去思考问题的思维模型。_计算机中对于全局观的
文章浏览阅读330次。一、CountDownLatch介绍CountDownLatch采用减法计算;是一个同步辅助工具类和CyclicBarrier类功能类似,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。二、CountDownLatch俩种应用场景: 场景一:所有线程在等待开始信号(startSignal.await()),主流程发出开始信号通知,既执行startSignal.countDown()方法后;所有线程才开始执行;每个线程执行完发出做完信号,既执行do..._countdownluach于cyclicbarrier的用法
文章浏览阅读508次。Prometheus 算是一个全能型选手,原生支持容器监控,当然监控传统应用也不是吃干饭的,所以就是容器和非容器他都支持,所有的监控系统都具备这个流程,_-自动化监控系统prometheus&grafana实战
文章浏览阅读4.7k次。输入关键字,可以通过键盘的搜索按钮完成搜索功能。_react search