GPU上如何优化卷积
GPU上如何优化卷积
本文将演示如何在TVM中编写高性能卷积实现。我们以平方大小的输入张量和滤波器为例,假设卷积的输入是大批量的。在本例中,使用不同的布局来存储数据,以实现更好的数据局部性。缓冲区布局为HWCN,代表高度、宽度、通道、批次。
Preparation and Algorithm
对于256个通道和14 x 14维的输入张量,使用固定大小。批量大小是256。卷积滤波器包含512个尺寸为3 x 3的滤波器。使用步幅大小1和填充大小1进行卷积。下面的代码定义了TVM中的卷积算法。
import numpy as np
import tvm
from tvm import te
# The sizes of inputs and filters
batch = 256
in_channel = 256
out_channel = 512
in_size = 14
kernel = 3
pad = 1
stride = 1
# Algorithm
A = te.placeholder((in_size, in_size, in_channel, batch), name="A")
W = te.placeholder((kernel, kernel, in_channel, out_channel), name="W")
out_size = (in_size - kernel + 2 * pad) // stride + 1
# Pad input
Apad = te.compute(
(in_size + 2 * pad, in_size + 2 * pad, in_channel, batch),
lambda yy, xx, cc, nn: tvm.tir.if_then_else(
tvm.tir.all(yy >= pad, yy - pad < in_size, xx >= pad, xx - pad < in_size),
A[yy - pad, xx - pad, cc, nn],
tvm.tir.const(0.0, "float32"),
),
name="Apad",
)
# Create reduction variables
rc = te.reduce_axis((0, in_channel), name="rc")
ry = te.reduce_axis((0, kernel), name="ry")
rx = te.reduce_axis((0, kernel), name="rx")
# Compute the convolution
B = te.compute(
(out_size, out_size, out_channel, batch),
lambda yy, xx, ff, nn: te.sum(
Apad[yy * stride + ry, xx * stride + rx, rc, nn] * W[ry, rx, rc, ff], axis=[ry, rx, rc]
),
name="B",
)
Memory Hierarchy
首先指定缓冲区的内存层次结构。下图显示了GPU内存层次结构。与CPU内存层次结构的一个重要区别是GPU提供了一个称为共享内存的缓存缓冲区,由程序员管理。因此,如何最大限度地利用共享内存中的数据是实现GPU内核高性能的关键。

在本例中,将Apad和W加载到缓冲区AA和WW中,存储在共享内存中。这些缓冲区将由同一线程块内的所有线程共享,以计算卷积。然后每个线程将自己的部分从共享缓冲区加载到本地寄存器AL和WL中。BL是输出B的本地缓存,它也存储在线程本地寄存器中。
# Designate the memory hierarchy
s = te.create_schedule(B.op)
s[Apad].compute_inline() # compute Apad inline
AA = s.cache_read(Apad, "shared", [B])
WW = s.cache_read(W, "shared", [B])
AL = s.cache_read(AA, "local", [B])
WL = s.cache_read(WW, "local", [B])
BL = s.cache_write(B, "local")
Blocking
下面的代码将工作负载分成线程块和单个线程。我们遵循矩阵乘法中的分块方案。如下图所示,给定一个像素坐标(y,x),线程块负责计算输出通道和批处理的块系数x块系数(64x64)的区域。由于共享内存空间的限制,我们每次只从Apad和B加载stepx块系数(8x64)数据到共享内存中的缓冲区。

# tile consts
tile = 8
num_thread = 8
block_factor = tile * num_thread
step = 8
vthread = 2
# Get the GPU thread indices
block_x = te.thread_axis("blockIdx.x")
block_y = te.thread_axis("blockIdx.y")
block_z = te.thread_axis("blockIdx.z")
thread_x = te.thread_axis((0, num_thread), "threadIdx.x")
thread_y = te.thread_axis((0, num_thread), "threadIdx.y")
thread_xz = te.thread_axis((0, vthread), "vthread", name="vx")
thread_yz = te.thread_axis((0, vthread), "vthread", name="vy")
# Split the workloads
hi, wi, fi, ni = s[B].op.axis
bz = s[B].fuse(hi, wi)
by, fi = s[B].split(fi, factor=block_factor)
bx, ni = s[B].split(ni, factor=block_factor)
# Bind the iteration variables to GPU thread indices
s[B].bind(bz, block_z)
s[B].bind(by, block_y)
s[B].bind(bx, block_x)
Virtual Thread Split
进一步将工作负载从一个线程块分割到各个线程。为了避免冲突,将8个线程分成4个部分,然后使用8个线程分成4个部分。因此,如下图所示,每个线程计算4个跨距网格,其中每个网格的大小为4 x 4。

tyz, fi = s[B].split(fi, nparts=vthread) # virtual thread split
txz, ni = s[B].split(ni, nparts=vthread) # virtual thread split
ty, fi = s[B].split(fi, nparts=num_thread)
tx, ni = s[B].split(ni, nparts=num_thread)
s[B].reorder(bz, by, bx, tyz, txz, ty, tx, fi, ni)
s[B].bind(tyz, thread_yz)
s[B].bind(txz, thread_xz)
s[B].bind(ty, thread_y)
s[B].bind(tx, thread_x)
Cooperative Fetching
如前所述,每个时间步都需要将步骤x块因子数据从GPU全局内存传输到共享内存。为了减少每个线程的内存传输,下面的代码允许同一线程块中的线程协同从全局内存中获取相关数据。
# Schedule BL local write
s[BL].compute_at(s[B], tx)
yi, xi, fi, ni = s[BL].op.axis
ry, rx, rc = s[BL].op.reduce_axis
rco, rci = s[BL].split(rc, factor=step)
s[BL].reorder(rco, ry, rx, rci, fi, ni)
# Attach computation to iteration variables
s[AA].compute_at(s[BL], rx)
s[WW].compute_at(s[BL], rx)
s[AL].compute_at(s[BL], rci)
s[WL].compute_at(s[BL], rci)
# Schedule for A's shared memory load
yi, xi, ci, ni = s[AA].op.axis
ty, ci = s[AA].split(ci, nparts=num_thread)
tx, ni = s[AA].split(ni, nparts=num_thread)
_, ni = s[AA].split(ni, factor=4)
s[AA].reorder(ty, tx, yi, xi, ci, ni)
s[AA].bind(ty, thread_y)
s[AA].bind(tx, thread_x)
s[AA].vectorize(ni) # vectorize memory load
# Schedule for W's shared memory load
yi, xi, ci, fi = s[WW].op.axis
ty, ci = s[WW].split(ci, nparts=num_thread)
tx, fi = s[WW].split(fi, nparts=num_thread)
_, fi = s[WW].split(fi, factor=4)
s[WW].reorder(ty, tx, yi, xi, ci, fi)
s[WW].bind(ty, thread_y)
s[WW].bind(tx, thread_x)
s[WW].vectorize(fi) # vectorize memory load
Generate CUDA Kernel
最后利用TVM生成并编译了CUDA内核,并对卷积延迟进行了评估。
func = tvm.build(s, [A, W, B], "cuda")
ctx = tvm.gpu(0)
a_np = np.random.uniform(size=(in_size, in_size, in_channel, batch)).astype(A.dtype)
w_np = np.random.uniform(size=(kernel, kernel, in_channel, out_channel)).astype(W.dtype)
a = tvm.nd.array(a_np, ctx)
w = tvm.nd.array(w_np, ctx)
b = tvm.nd.array(np.zeros((out_size, out_size, out_channel, batch), dtype=B.dtype), ctx)
func(a, w, b)
evaluator = func.time_evaluator(func.entry_name, ctx, number=1)
print("Convolution: %f ms" % (evaluator(a, w, b).mean * 1e3))
Out:
Convolution: 53.197723 ms
https://tvm.apache.org/docs/tutorials/optimize/opt_conv_cuda.html
GPU上如何优化卷积的更多相关文章
- TVM 优化 ARM GPU 上的移动深度学习
TVM 优化 ARM GPU 上的移动深度学习 随着深度学习的巨大成功,将深度神经网络部署到移动设备的需求正在迅速增长.与桌面平台上所做的类似,在移动设备中使用 GPU 既有利于推理速度,也有利于能源 ...
- TVM在ARM GPU上优化移动深度学习
TVM在ARM GPU上优化移动深度学习 随着深度学习的巨大成功,将深度神经网络部署到移动设备的需求正在迅速增长.与在台式机平台上所做的类似,在移动设备中使用GPU可以提高推理速度和能源效率.但是,大 ...
- TensorFlow之CNN:运用Batch Norm、Dropout和早停优化卷积神经网络
学卷积神经网络的理论的时候,我觉得自己看懂了,可是到了用代码来搭建一个卷积神经网络时,我发现自己有太多模糊的地方.这次还是基于MINIST数据集搭建一个卷积神经网络,首先给出一个基本的模型,然后再用B ...
- 如何使用TensorCores优化卷积
如何使用TensorCores优化卷积 本文将演示如何在TVM中使用TensorCores编写高性能的卷积计划.假设卷积的输入有大量数据.首先介绍如何在GPU上优化卷积. TensorCore简介 每 ...
- 在配有英特尔® Iris™ 显卡的系统上通过优化对 Just Cause 3 进行增强
高端 PC 继续通过高性能显卡驱动桌面游戏. 一流的"梦想机器"基于第六代智能 英特尔® 酷睿™ 处理器i7-6700K等 CPU,通常与高端独立显卡配合使用以运行要求最严苛的游戏 ...
- 深入剖析GPU Early Z优化
最近在公司群里同事发了一个UE4关于Mask材质的优化,比如在场景中有大面积的草和树的时候,可以在很大程度上提高效率.这其中的原理就是利用了GPU的特性Early Z,但是它的做法跟我最开始的理解有些 ...
- GPU上的图像和信号处理
GPU上的图像和信号处理 NVIDIA Performance Primitives(NPP)库提供GPU加速的图像,视频和信号处理功能,其执行速度比仅CPU实施快30倍.拥有5000多个用于图像和信 ...
- NVIDIA GPU上的Tensor线性代数
NVIDIA GPU上的Tensor线性代数 cuTENSOR库是同类中第一个GPU加速的张量线性代数库,提供张量收缩,归约和逐元素运算.cuTENSOR用于加速在深度学习训练和推理,计算机视觉,量子 ...
- NVIDIA GPU上的直接线性求解器
NVIDIA GPU上的直接线性求解器 NVIDIA cuSOLVER库提供了密集且稀疏的直接线性求解器和本征求解器的集合,它们为计算机视觉,CFD,计算化学和线性优化应用程序提供了显着的加速.cuS ...
随机推荐
- 在Android的App中动态的加载Java类
原文的地址:http://yenliangl.blogspot.com/2009/11/dynamic-loading-of-classes-in-your.html 我正在编写一个应用程序能够加载别 ...
- POJ2406简单KMP
题意: 给一个字符串,求最大的前缀循环周期,就是最小的循环节对应的最大的那个周期. 思路: KMP的简单应用,求完next数组后有这样的应用:next[i] :是最大循环节的第几位 ...
- Windows各版本以及漏洞
目录 MS-Dos Win 9X Win NT Windows Server .NET FrameWork PowerShell IIS6.0漏洞 解析漏洞 IIS7.0/7.5漏洞 畸形解析漏洞 M ...
- Word 通过添加Package 实现word藏毒
这个思路要结合近期在一些安全网站上公布的姿势来实现,先科普几个地方. (1)通过cmd本身就可以直接下载: Bitsadmin /transfer AA /download /priority nor ...
- Windows核心编程 第七章 线程的调度、优先级和亲缘性(下)
7.6 运用结构环境 现在应该懂得环境结构在线程调度中所起的重要作用了.环境结构使得系统能够记住线程的状态,这样,当下次线程拥有可以运行的C P U时,它就能够找到它上次中断运行的地方. 知道这样低层 ...
- thinkphp中常用到的sql操作
1.清空某表数据: $sql = 'truncate table table_name'; Db::execute($sql );
- Java前后端分离的认识
1.原由 在网上查了关于前后端分离的资料,有所粗浅认识.记录下来,方便以后使用.以下均是个人看法,仅做参考.如有错误请指教,共同进步. 2.为什么前后端分离? ①.一个后台,可以让多种前台系统使用.后 ...
- 问渠那得清如许?为有源头活水来——对【近取Key】产品进行的深度测评与解析
在 Build To Show 的场景中,大家各显身手,用各种办法展现技术,的确很难在单一的维度上确定谁赢谁输.但是,在 Build To Win 的场景中,往往市场就是那么一块, 竞争对手占了 70 ...
- leetcode 1081
开始的思路是遍历存储每个字符的所有位置,再进行扫描处理,但是实际操作并没有很熟练,于是在讨论区学习后,有了下面的解法! 首先需要知道不同的字符在字符串中的最后的位置(理论上的最优位置) 然后扫描字符串 ...
- ES 6 中的箭头函数及用法
ES6标准新增了一种新的函数:Arrow Function(箭头函数). 主要的几种写法如下: 组成: 参数 => 语句, 参数不是1个: (参数,参数2)=>语句 语句不止一条: 参数 ...