TVM Reduction降低算力
TVM Reduction降低算力
这是有关如何降低算力TVM的介绍材料。像sum / max / min这样的关联约简运算符是线性代数运算的典型构造块。
本文将演示如何降低TVM算力。
from __future__ import absolute_import, print_function
import tvm
import tvm.testing
from tvm import te
import numpy as np
描述行数
假设要计算行总数作为示例。用numpy语义可以写成B = numpy.sum(A, axis=1)
以下几行描述了行求和算子。创建归约公式,使用 te.reduce_axis来声明归约轴。te.reduce_axis降低算力的范围。 te.sum接受要降低算力的表达式以及降低算力轴,并计算声明范围内所有k的值之和。
等效的C代码如下:
for (int i = 0; i < n; ++i) {
B[i] = 0;
for (int k = 0; k < m; ++k) {
B[i] = B[i] + A[i][k];
}
}
n = te.var("n")
m = te.var("m")
A = te.placeholder((n, m), name="A")
k = te.reduce_axis((0, m), "k")
B = te.compute((n,), lambda i: te.sum(A[i, k], axis=k), name="B")
调度降低算力
有几种调度降低算力的方法。在执行任何操作之前,打印出默认调度的IR代码。
s = te.create_schedule(B.op)
print(tvm.lower(s, [A, B], simple_mode=True))
输出:
primfn(A_1: handle, B_1: handle) -> ()
attr = {"global_symbol": "main", "tir.noalias": True}
buffers = {B: Buffer(B_2: Pointer(float32), float32, [n: int32], [stride: int32], type="auto"),
A: Buffer(A_2: Pointer(float32), float32, [n, m: int32], [stride_1: int32, stride_2: int32], type="auto")}
buffer_map = {A_1: A, B_1: B} {
for (i: int32, 0, n) {
B_2[(i*stride)] = 0f32
for (k: int32, 0, m) {
B_2[(i*stride)] = ((float32*)B_2[(i*stride)] + (float32*)A_2[((i*stride_1) + (k*stride_2))])
}
}
}
会发现IR代码与C代码非常相似。减速轴类似于法线轴,可以拆分。
在下面的代码中,将B的行轴和轴拆分为不同的因子。结果是嵌套归约。
ko, ki = s[B].split(B.op.reduce_axis[0], factor=16)
xo, xi = s[B].split(B.op.axis[0], factor=32)
print(tvm.lower(s, [A, B], simple_mode=True))
输出:
primfn(A_1: handle, B_1: handle) -> ()
attr = {"global_symbol": "main", "tir.noalias": True}
buffers = {B: Buffer(B_2: Pointer(float32), float32, [n: int32], [stride: int32], type="auto"),
A: Buffer(A_2: Pointer(float32), float32, [n, m: int32], [stride_1: int32, stride_2: int32], type="auto")}
buffer_map = {A_1: A, B_1: B} {
for (i.outer: int32, 0, floordiv((n + 31), 32)) {
for (i.inner: int32, 0, 32) {
if @tir.likely((((i.outer*32) + i.inner) < n), dtype=bool) {
B_2[(((i.outer*32) + i.inner)*stride)] = 0f32
}
if @tir.likely((((i.outer*32) + i.inner) < n), dtype=bool) {
for (k.outer: int32, 0, floordiv((m + 15), 16)) {
for (k.inner: int32, 0, 16) {
if @tir.likely((((k.outer*16) + k.inner) < m), dtype=bool) {
B_2[(((i.outer*32) + i.inner)*stride)] = ((float32*)B_2[(((i.outer*32) + i.inner)*stride)] + (float32*)A_2[((((i.outer*32) + i.inner)*stride_1) + (((k.outer*16) + k.inner)*stride_2))])
}
}
}
}
}
}
}
要构建GPU内核,可以将B的行绑定到GPU线程。
s[B].bind(xo, te.thread_axis("blockIdx.x"))
s[B].bind(xi, te.thread_axis("threadIdx.x"))
print(tvm.lower(s, [A, B], simple_mode=True))
输出:
primfn(A_1: handle, B_1: handle) -> ()
attr = {"global_symbol": "main", "tir.noalias": True}
buffers = {B: Buffer(B_2: Pointer(float32), float32, [n: int32], [stride: int32], type="auto"),
A: Buffer(A_2: Pointer(float32), float32, [n, m: int32], [stride_1: int32, stride_2: int32], type="auto")}
buffer_map = {A_1: A, B_1: B} {
attr [IterVar(blockIdx.x: int32, (nullptr), "ThreadIndex", "blockIdx.x")] "thread_extent" = floordiv((n + 31), 32);
attr [IterVar(threadIdx.x: int32, (nullptr), "ThreadIndex", "threadIdx.x")] "thread_extent" = 32 {
if @tir.likely((((blockIdx.x*32) + threadIdx.x) < n), dtype=bool) {
B_2[(((blockIdx.x*32) + threadIdx.x)*stride)] = 0f32
}
for (k.outer: int32, 0, floordiv((m + 15), 16)) {
for (k.inner: int32, 0, 16) {
if @tir.likely((((blockIdx.x*32) + threadIdx.x) < n), dtype=bool) {
if @tir.likely((((k.outer*16) + k.inner) < m), dtype=bool) {
B_2[(((blockIdx.x*32) + threadIdx.x)*stride)] = ((float32*)B_2[(((blockIdx.x*32) + threadIdx.x)*stride)] + (float32*)A_2[((((blockIdx.x*32) + threadIdx.x)*stride_1) + (((k.outer*16) + k.inner)*stride_2))])
}
}
}
}
}
}
归约分解和并行化
建立归约的一个问题是,不能简单地在归约轴上并行化。需要对约简的算子进行划分,在对临时数组进行约简之前,将局部约简结果存储在临时数组中。
rfactor原语会重写计算。在下面的调度中,将B的结果写入临时结果B.rf。分解后的尺寸成为B.rf的第一尺寸。
s = te.create_schedule(B.op)
ko, ki = s[B].split(B.op.reduce_axis[0], factor=16)
BF = s.rfactor(B, ki)
print(tvm.lower(s, [A, B], simple_mode=True))
输出:
primfn(A_1: handle, B_1: handle) -> ()
attr = {"global_symbol": "main", "tir.noalias": True}
buffers = {B: Buffer(B_2: Pointer(float32), float32, [n: int32], [stride: int32], type="auto"),
A: Buffer(A_2: Pointer(float32), float32, [n, m: int32], [stride_1: int32, stride_2: int32], type="auto")}
buffer_map = {A_1: A, B_1: B} {
attr [B.rf: Pointer(float32)] "storage_scope" = "global";
allocate(B.rf, float32, [(n*16)]) {
for (k.inner: int32, 0, 16) {
for (i: int32, 0, n) {
B.rf[((k.inner*n) + i)] = 0f32
for (k.outer: int32, 0, floordiv((m + 15), 16)) {
if @tir.likely((((k.outer*16) + k.inner) < m), dtype=bool) {
B.rf[((k.inner*n) + i)] = ((float32*)B.rf[((k.inner*n) + i)] + (float32*)A_2[((i*stride_1) + (((k.outer*16) + k.inner)*stride_2))])
}
}
}
}
for (ax0: int32, 0, n) {
B_2[(ax0*stride)] = 0f32
for (k.inner.v: int32, 0, 16) {
B_2[(ax0*stride)] = ((float32*)B_2[(ax0*stride)] + (float32*)B.rf[((k.inner.v*n) + ax0)])
}
}
}
}
B的调度算子也将被重写为Bf缩减结果的第一轴上的和
print(s[B].op.body)
输出:
[reduce(combiner=comm_reducer(result=[(x + y)], lhs=[x], rhs=[y], identity_element=[0f]), source=[B.rf[k.inner.v, ax0]], init=[], axis=[iter_var(k.inner.v, range(min=0, ext=16))], where=(bool)1, value_index=0)]
降低算力跨线
现在,我们可以在分解后的轴上进行并行化处理。在此,B的复位轴标记为螺纹。TVM将算力减少轴标记为线程,如果它是唯一的算力降低,则可以在设备中进行交叉线程。
分解后的情况确实如此。也可以直接在还原轴上计算BF。最终生成的内核将按blockIdx.x划分行,按threadIdx.x划分threadIdx.y列,最后对threadIdx.x进行跨线程缩减
xo, xi = s[B].split(s[B].op.axis[0], factor=32)
s[B].bind(xo, te.thread_axis("blockIdx.x"))
s[B].bind(xi, te.thread_axis("threadIdx.y"))
tx = te.thread_axis("threadIdx.x")
s[B].bind(s[B].op.reduce_axis[0], tx)
s[BF].compute_at(s[B], s[B].op.reduce_axis[0])
s[B].set_store_predicate(tx.var.equal(0))
fcuda = tvm.build(s, [A, B], "cuda")
print(fcuda.imported_modules[0].get_source())
输出:
extern "C" __global__ void default_function_kernel0(float* __restrict__ A, float* __restrict__ B, int m, int n, int stride, int stride1, int stride2) {
float B_rf[1];
__shared__ float red_buf0[512];
B_rf[(0)] = 0.000000e+00f;
for (int k_outer = 0; k_outer < (m >> 4); ++k_outer) {
if (((((int)blockIdx.x) * 32) + ((int)threadIdx.y)) < n) {
B_rf[(0)] = (B_rf[(0)] + A[(((((((int)blockIdx.x) * 32) + ((int)threadIdx.y)) * stride) + (((k_outer * 16) + ((int)threadIdx.x)) * stride1)))]);
}
}
for (int k_outer1 = 0; k_outer1 < (((m & 15) + 15) >> 4); ++k_outer1) {
if (((((int)blockIdx.x) * 32) + ((int)threadIdx.y)) < n) {
if (((((m >> 4) * 16) + (k_outer1 * 16)) + ((int)threadIdx.x)) < m) {
B_rf[(0)] = (B_rf[(0)] + A[(((((((int)blockIdx.x) * 32) + ((int)threadIdx.y)) * stride) + (((((m >> 4) * 16) + (k_outer1 * 16)) + ((int)threadIdx.x)) * stride1)))]);
}
}
}
__syncthreads();
((volatile float*)red_buf0)[(((((int)threadIdx.y) * 16) + ((int)threadIdx.x)))] = B_rf[(0)];
__syncthreads();
if (((int)threadIdx.x) < 8) {
((volatile float*)red_buf0)[(((((int)threadIdx.y) * 16) + ((int)threadIdx.x)))] = (((volatile float*)red_buf0)[(((((int)threadIdx.y) * 16) + ((int)threadIdx.x)))] + ((volatile float*)red_buf0)[((((((int)threadIdx.y) * 16) + ((int)threadIdx.x)) + 8))]);
((volatile float*)red_buf0)[(((((int)threadIdx.y) * 16) + ((int)threadIdx.x)))] = (((volatile float*)red_buf0)[(((((int)threadIdx.y) * 16) + ((int)threadIdx.x)))] + ((volatile float*)red_buf0)[((((((int)threadIdx.y) * 16) + ((int)threadIdx.x)) + 4))]);
((volatile float*)red_buf0)[(((((int)threadIdx.y) * 16) + ((int)threadIdx.x)))] = (((volatile float*)red_buf0)[(((((int)threadIdx.y) * 16) + ((int)threadIdx.x)))] + ((volatile float*)red_buf0)[((((((int)threadIdx.y) * 16) + ((int)threadIdx.x)) + 2))]);
((volatile float*)red_buf0)[(((((int)threadIdx.y) * 16) + ((int)threadIdx.x)))] = (((volatile float*)red_buf0)[(((((int)threadIdx.y) * 16) + ((int)threadIdx.x)))] + ((volatile float*)red_buf0)[((((((int)threadIdx.y) * 16) + ((int)threadIdx.x)) + 1))]);
}
__syncthreads();
if (((int)threadIdx.x) == 0) {
B[((((((int)blockIdx.x) * 32) + ((int)threadIdx.y)) * stride2))] = ((volatile float*)red_buf0)[((((int)threadIdx.y) * 16))];
}
}
将结果内核与numpy进行比较,验证结果内核的正确性。
nn = 128
ctx = tvm.gpu(0)
a = tvm.nd.array(np.random.uniform(size=(nn, nn)).astype(A.dtype), ctx)
b = tvm.nd.array(np.zeros(nn, dtype=B.dtype), ctx)
fcuda(a, b)
tvm.testing.assert_allclose(b.asnumpy(), np.sum(a.asnumpy(), axis=1), rtol=1e-4)
通过2D简化描述卷积
在TVM中,可以通过2D约简来描述卷积。这是2D卷积的示例,滤波器大小= [3,3],步幅= [1,1]。
n = te.var("n")
Input = te.placeholder((n, n), name="Input")
Filter = te.placeholder((3, 3), name="Filter")
di = te.reduce_axis((0, 3), name="di")
dj = te.reduce_axis((0, 3), name="dj")
Output = te.compute(
(n - 2, n - 2),
lambda i, j: te.sum(Input[i + di, j + dj] * Filter[di, dj], axis=[di, dj]),
name="Output",
)
s = te.create_schedule(Output.op)
print(tvm.lower(s, [Input, Filter, Output], simple_mode=True))
出:
primfn(Input_1: handle, Filter_1: handle, Output_1: handle) -> ()
attr = {"global_symbol": "main", "tir.noalias": True}
buffers = {Output: Buffer(Output_2: Pointer(float32), float32, [(n: int32 - 2), (n - 2)], []),
Filter: Buffer(Filter_2: Pointer(float32), float32, [3, 3], []),
Input: Buffer(Input_2: Pointer(float32), float32, [n, n], [stride: int32, stride_1: int32], type="auto")}
buffer_map = {Input_1: Input, Filter_1: Filter, Output_1: Output} {
for (i: int32, 0, (n - 2)) {
for (j: int32, 0, (n - 2)) {
Output_2[((i*(n - 2)) + j)] = 0f32
for (di: int32, 0, 3) {
for (dj: int32, 0, 3) {
Output_2[((i*(n - 2)) + j)] = ((float32*)Output_2[((i*(n - 2)) + j)] + ((float32*)Input_2[(((i + di)*stride) + ((j + dj)*stride_1))]*(float32*)Filter_2[((di*3) + dj)]))
}
}
}
}
}
定义通用换向归约运算
除了内置的如降低算力操作te.sum, tvm.te.min和tvm.te.max,还可以通过定义交换降低算力操作te.comm_reducer。
n = te.var("n")
m = te.var("m")
product = te.comm_reducer(lambda x, y: x * y, lambda t: tvm.tir.const(1, dtype=t), name="product")
A = te.placeholder((n, m), name="A")
k = te.reduce_axis((0, m), name="k")
B = te.compute((n,), lambda i: product(A[i, k], axis=k), name="B")
注意
执行涉及多个值的归约argmax,可以通过元组输入来完成。有关更多详细信息,请参见使用协作输入来描述缩减。
总结
本文提供了降低算力调度的演练。
- 用reduce_axis描述归约。
- 如果需要并行性,请使用rfactor分解轴。
- 定义新的归约运算 te.comm_reducer
TVM Reduction降低算力的更多相关文章
- 端到端TVM编译器(上)
端到端TVM编译器(上) 摘要 将机器学习引入到各种各样的硬件设备中.AI框架依赖于特定于供应商的算子库,针对窄范围的服务器级gpu进行优化.将工作负载部署到新平台,例如手机.嵌入式设备和加速器(例如 ...
- CNN更新换代!性能提升算力减半,还即插即用
传统的卷积运算,要成为过去时了. Facebook和新加坡国立大学联手提出了新一代替代品:OctConv(Octave Convolution),效果惊艳,用起来还非常方便. OctConv就如同卷积 ...
- 创新全球算力生态价值,SPC算力生态强势来袭!
当前,区块链技术已经到了一个新的时代,即3.0时代.在区块链3.0时代,区块链技术迎来了数字经济革命,各行各业也在积极寻找与区块链能够融合的切入点.而随着区块链的愈加成熟,区块链技术也愈加被更多的人应 ...
- 如何使用TensorCores优化卷积
如何使用TensorCores优化卷积 本文将演示如何在TVM中使用TensorCores编写高性能的卷积计划.假设卷积的输入有大量数据.首先介绍如何在GPU上优化卷积. TensorCore简介 每 ...
- Inception V1、V2、V3和V4
Inception模块分为V1.V2.V3和V4. V1(GoogLeNet)的介绍 论文:Going deeper with convolutions 论文链接:https://arxiv.org/ ...
- 《区块链100问》第82集:应用类项目Golem
Golem是第一个基于以太坊区块链打造的计算资源交易平台.通过区块链,Golem能链接全球的算力资源,从而实现计算能力的全球共享.应用所有者和个体用户(算力“请求方”)可以点对点地从其他用户处租用算力 ...
- [转帖]中国AI芯“觉醒”的五年
中国AI芯“觉醒”的五年 https://www.cnbeta.com/articles/tech/857863.htm 原来 海思的营收已经超过了按摩店(AMD) 没想到.. 十多款芯片问世,多起并 ...
- 论文阅读笔记(二十三)【ECCV2018】:Robust Anchor Embedding for Unsupervised Video Person Re-Identification in the Wild
Introduction 当前主要的非监督方法都采用相同的训练数据集,这些数据集在不同摄像头中是对称的,即不存在单个行人的错误项,这些方法将在实际场景中效果下降.在本方法中,作者引入了非对称数据,如下 ...
- 自定义pass编写
自定义pass编写 TVM是一个框架,抽象了机器学习加速器的异质性.有时,用户可能需要自定义一些分析和IR转换,使TVM适应自己的专用硬件.本文可帮助用户在TVM中编写自定义pass. 先决条件 ...
随机推荐
- C/C++ 进程代码注入与提权/降权
如果将shellcode注入到具有特定权限的进程中,我们就可以获得与该进程相同的权限,此方法可以用于提权与降权操作,注入有多种方式,最简单的是直接将metasploit生成的有效载荷直接注入到目标进程 ...
- hdu3449 有依赖的背包问题
题意: 给你一些物品,每个物品有自己的价值和花费,每个物品都对应一个箱子,每个箱子有价钱,买这个物品必须买相应的箱子,给你一个价钱,问最多可以获得多少价值 <提示:多个物品可能同时对 ...
- 码农飞升记-03-OpenJDK是什么?
目录 1.OpenJDK 概述 2.OpenJDK 的发展史 3.OpenJDK Community 1.角色定义 Participant(参与者) Contributor(贡献者) OpenJDK ...
- Yii2表头强制不换行
在Yii2中使用GridView生成的表格有一个小问题,那就是表头的列宽是根据列的值自适应的,但有时值的长度比较小,表头就会出现下面这种情况(表头不在同一行,非常的不利于数据的查看) 设置强制不换行可 ...
- Spring MVC工作原理及源码解析(二)DispatcherServlet实现原理及源码解析
1.DispatcherServlet 处理流程 从上一篇文章中Spring MVC原理图中我们可以看出:DispatcherServlet 在 Spring MVC框架 中处于核心位置,它负责协调和 ...
- 老J的技术分享之总结
老J做IT这块有二十多个年头了,算是中国IT的见证者与参与者.那个时候刚开始接触和了解时,对于他的一些建议,我不是很乐于去接受,因为我觉得他的那一套技术体系不是很适合如今的情况,当时间久了后发现,他对 ...
- TLS是如何保障数据传输安全(中间人攻击)
前言 前段时间和同事讨论HTTPS的工作原理,当时对这块知识原理掌握还是靠以前看了一些博客介绍,深度不够,正好我这位同事是密码学专业毕业的,结合他密码学角度对tls加解密这阐述,让我对这块原理有了更进 ...
- 【Web前端HTML5&CSS3】05-样式继承与其他概念
笔记来源:尚硅谷Web前端HTML5&CSS3初学者零基础入门全套完整版 目录 样式继承与其他概念 1. 继承 2. 选择器的权重 3. 长度单位 像素 屏幕分辨率 图像分辨率 百分比 em ...
- C++ primer plus读书笔记——第3章 处理数据
第3章 处理数据 1. C++对于变量名称的长度没有限制,ANSI C只保证名称中的前63个字符有意义(前63个字符相同的名称被认为是相同的,即使第64个字符不同). 2. 对类型名(int)使用si ...
- .NET平台系列10 .NET统一平台愿景
系列目录 [已更新最新开发文章,点击查看详细] 2019年,微软分享了[统一的.NET堆栈和生态系统的愿景].给开发者带来的价值是,将能够使用一组API,语言和工具来针对广泛的应用程序类型,包 ...