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上如何优化卷积的更多相关文章

  1. TVM 优化 ARM GPU 上的移动深度学习

    TVM 优化 ARM GPU 上的移动深度学习 随着深度学习的巨大成功,将深度神经网络部署到移动设备的需求正在迅速增长.与桌面平台上所做的类似,在移动设备中使用 GPU 既有利于推理速度,也有利于能源 ...

  2. TVM在ARM GPU上优化移动深度学习

    TVM在ARM GPU上优化移动深度学习 随着深度学习的巨大成功,将深度神经网络部署到移动设备的需求正在迅速增长.与在台式机平台上所做的类似,在移动设备中使用GPU可以提高推理速度和能源效率.但是,大 ...

  3. TensorFlow之CNN:运用Batch Norm、Dropout和早停优化卷积神经网络

    学卷积神经网络的理论的时候,我觉得自己看懂了,可是到了用代码来搭建一个卷积神经网络时,我发现自己有太多模糊的地方.这次还是基于MINIST数据集搭建一个卷积神经网络,首先给出一个基本的模型,然后再用B ...

  4. 如何使用TensorCores优化卷积

    如何使用TensorCores优化卷积 本文将演示如何在TVM中使用TensorCores编写高性能的卷积计划.假设卷积的输入有大量数据.首先介绍如何在GPU上优化卷积. TensorCore简介 每 ...

  5. 在配有英特尔® Iris™ 显卡的系统上通过优化对 Just Cause 3 进行增强

    高端 PC 继续通过高性能显卡驱动桌面游戏. 一流的"梦想机器"基于第六代智能 英特尔® 酷睿™ 处理器i7-6700K等 CPU,通常与高端独立显卡配合使用以运行要求最严苛的游戏 ...

  6. 深入剖析GPU Early Z优化

    最近在公司群里同事发了一个UE4关于Mask材质的优化,比如在场景中有大面积的草和树的时候,可以在很大程度上提高效率.这其中的原理就是利用了GPU的特性Early Z,但是它的做法跟我最开始的理解有些 ...

  7. GPU上的图像和信号处理

    GPU上的图像和信号处理 NVIDIA Performance Primitives(NPP)库提供GPU加速的图像,视频和信号处理功能,其执行速度比仅CPU实施快30倍.拥有5000多个用于图像和信 ...

  8. NVIDIA GPU上的Tensor线性代数

    NVIDIA GPU上的Tensor线性代数 cuTENSOR库是同类中第一个GPU加速的张量线性代数库,提供张量收缩,归约和逐元素运算.cuTENSOR用于加速在深度学习训练和推理,计算机视觉,量子 ...

  9. NVIDIA GPU上的直接线性求解器

    NVIDIA GPU上的直接线性求解器 NVIDIA cuSOLVER库提供了密集且稀疏的直接线性求解器和本征求解器的集合,它们为计算机视觉,CFD,计算化学和线性优化应用程序提供了显着的加速.cuS ...

随机推荐

  1. hdu4810

    题意:      给你n个数,让你输出n个数,没一次输出的是在这n个数里面取i个数异或的和(所有情况<C n中取i>). 思路:      首先把所有的数都拆成二进制,然后把他们在某一位上 ...

  2. POJ1364基本的查分约束问题

    题意:       给了由n个数组成的一个数列,然后给你各种区间的和是大于ci还是小于ci啥的,最后问你是否冲突. 思路:       差分约束水题,不过wa了两次,原因处理区间问题的细节马虎了,说下 ...

  3. 一个不错的过TP思路,转载CSDN

    也许大家也是研究腾讯游戏的爱好者,对腾讯的游戏都有过这样的体会  例如OD与CE无法进行如以下操作: 无法附加进程, 无法打开进程, 游戏进程被隐藏无法在工具中查看到,内存无法读取代码  内存修改后游 ...

  4. HTTP1.0,1.1,2.0,HTTPS

    HTTP1.0/1.1/2.0/HTTPS HTTP(超文本传输协议)是互联网上应用最为广泛的一种网络协议.所有的WWW文件都必须遵守这个标准.设计HTTP最初的目的是为了提供一种发布和接收HTML页 ...

  5. Andrew Ng机器学习算法入门(一):简介

    简介 最近在参加一个利用机器学习来解决安全问题的算法比赛,但是对机器学习的算法一直不了解,所以先了解一下机器学习相关的算法. Andrew Ng就是前段时间从百度离职的吴恩达.关于吴恩达是谁,相信程序 ...

  6. 基于RRCF(robust random cut forest)的时间序列异常检测流程

    摘要:RRCF是亚马逊提出的一个流式异常检测算法,是对孤立森林的改进,可对时序或非时序数据进行异常检测.本文是我从事AIOps研发工作时所做的基于RRCF的时序异常检测方案. 1.      数据格式 ...

  7. 北航OO(2020)第三单元博客作业

    一.JML理论基础及相关工具链 1.JML理论基础 该部分梳理本单元作业中涉及到的JML知识. 1.1注释结构 JML采用javadoc注释的方式来表示规格,且每行以@开头.通过使用//@annota ...

  8. 敏杰开题——软工团队项目选择与NABCD分析

    这是一篇软件工程课程博客 Q A 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 团队项目选择 我们在这个课程的目标是 团队协作实践敏捷开发 这个作业在哪个具 ...

  9. .Net Core——用代码写代码?

    想要用代码写代码,肯定是绕不开反射的.反射的概念相比都不陌生,只是应用多少就因人而异,今天分享一个代码生成器的思路,仅供参考,不要过分依赖哦. 思路分析 众所周知,利用反射可以在程序运行时获取到任一对 ...

  10. Spring 实现策略模式--自定义注解方式解耦if...else

    策略模式 定义 定义一簇算法类,将每个算法分别封装起来,让他们可以互相替换,策略模式可以使算法的变化独立于使用它们的客户端 场景 使用策略模式,可以避免冗长的if-else 或 switch分支判断 ...