如何在GPU上优化卷积
本文将演示如何在TVM中编写高性能的卷积实现。以平方大小的输入张量和滤波器为例,并假设卷积的输入量很大。使用不同的布局来存储数据,以实现更好的数据局部性。缓冲区布局为HWCN,代表高度,宽度,通道,批次。
准备和算法
将固定大小用于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",
)
存储层级
首先指定缓冲区的内存层次结构。下图显示了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),线程块负责为输出通道和批处理计算block_factor x block_factor(64 x 64)的区域。由于共享内存空间的限制,每次仅将Apad和B中的step x block_factor(8 x 64)数据加载到共享内存中的缓冲区中。

# 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
将工作负载从线程块划分到各个线程。为避免内存库冲突,使用虚拟线程将区域划分为4个部分,然后平铺为8x8网格。如下图所示,每个线程计算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 block_factor数据从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
生成CUDA内核
最后,使用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))
出:
Convolution: 53.197723 ms
https://tvm.apache.org/docs/tutorials/optimize/opt_conv_cuda.html#sphx-glr-tutorials-optimize-opt-conv-cuda-py
如何在GPU上优化卷积的更多相关文章
- TVM在ARM GPU上优化移动深度学习
TVM在ARM GPU上优化移动深度学习 随着深度学习的巨大成功,将深度神经网络部署到移动设备的需求正在迅速增长.与在台式机平台上所做的类似,在移动设备中使用GPU可以提高推理速度和能源效率.但是,大 ...
- 如何在CPU上优化GEMM(下)
如何在CPU上优化GEMM(下) Array Packing 另一个重要的技巧是数组打包.这个技巧是对数组的存储维度进行重新排序,将某个维度上的连续访问模式在平滑后转换为顺序模式. 如上图所示,在阻塞 ...
- 如何在CPU上优化GEMM(上)
如何在CPU上优化GEMM(上) (TL:DR)TVM提供了抽象接口,用户分别描述算法和算法的实现组织(所谓的调度).通常,在高性能调度中编写算法会破坏算法的可读性和模块性.尝试各种看似有希望的时间表 ...
- 如何使用TensorCores优化卷积
如何使用TensorCores优化卷积 本文将演示如何在TVM中使用TensorCores编写高性能的卷积计划.假设卷积的输入有大量数据.首先介绍如何在GPU上优化卷积. TensorCore简介 每 ...
- GPU上如何优化卷积
GPU上如何优化卷积 本文将演示如何在TVM中编写高性能卷积实现.我们以平方大小的输入张量和滤波器为例,假设卷积的输入是大批量的.在本例中,使用不同的布局来存储数据,以实现更好的数据局部性.缓冲区布局 ...
- TVM 优化 ARM GPU 上的移动深度学习
TVM 优化 ARM GPU 上的移动深度学习 随着深度学习的巨大成功,将深度神经网络部署到移动设备的需求正在迅速增长.与桌面平台上所做的类似,在移动设备中使用 GPU 既有利于推理速度,也有利于能源 ...
- TensorFlow之CNN:运用Batch Norm、Dropout和早停优化卷积神经网络
学卷积神经网络的理论的时候,我觉得自己看懂了,可是到了用代码来搭建一个卷积神经网络时,我发现自己有太多模糊的地方.这次还是基于MINIST数据集搭建一个卷积神经网络,首先给出一个基本的模型,然后再用B ...
- GPU自动调度卷积层
GPU自动调度卷积层 本文对GPU使用自动调度程序. 与依靠手动模板定义搜索空间的基于模板的autotvm不同,自动调度程序不需要任何模板.用户只需要编写计算声明,无需任何调度命令或模板.自动调度程序 ...
- 如何在TVM上集成Codegen(上)
如何在TVM上集成Codegen(上) 许多常用的深度学习内核,或者提供DNNL或TensorRT等框架和图形引擎,让用户以某种方式描述他们的模型,从而获得高性能.此外,新兴的深度学习加速器也有自己的 ...
随机推荐
- SpringCloud之配置中心(config)的使用Git+数据库实现
SpringCloud微服务实战系列教程 -------------------------目录------------------------------ 一.配置中心应用(Git) 二.配置中心的 ...
- android签名分析及漏洞修复
本篇我们来看看android的签名机制.发布出来的apk都是有META-INF文件夹,里面包含如下三个文件: 下面来一一解释这三个文件的作用(打包apk时签名过程):SignApk.main() 1. ...
- POJ3040给奶牛发工资
题意: 有n种硬币,每种硬币有mi个,然后让你给奶牛发工资,每周发至少c元(就是不找零钱的意思)然后问你能发几周?(硬币之间都是倍数关系) 思路: 这个题目做了两天,丢脸,看完 ...
- Win64 驱动内核编程-30.枚举与删除线程回调
枚举与删除线程回调 进程回调可以监视进程的创建和退出,这个在前面的章节已经总结过了.某些游戏保护的驱动喜欢用这个函数来监视有没有黑名单中的程序运行,如果运行则阻止运行或者把游戏退出.而线程回调则通常用 ...
- Android adb不是内部或外部命令 问题解决
就是没有配置环境变量, 这个只需要将android安装:例如C:\Program File\android-sdk-windows\tools加入到系统变量Path中,需要注意的是Path中会配置的有 ...
- 用最容易的方式学会单链表(Python实现)
单链表与数组 在本博客中,我们介绍单链表这种数据结构,链表结构为基于数组的序列提供了另一种选择(例如Python列表). 基于数组的序列也会有如下缺点: 一个动态数组的长度可能超过实际存储数组元素所需 ...
- Idea创建Maven Web项目的web.xml版本问题
问题描述:创建Maven Web项目时,选择MavenWebapp模板时,自动生成的web.xml文件版本为1.4,如图所示 如何才能修改为常用的4.0版本的xml文件呢? 这个文件是从Maven仓库 ...
- 【目录】Java项目开发中的知识记录
此篇文章为学习Java的目录,<a href="#"></>这种的是还没有写的文章.已经加a标签的是已经写完的.没写的文章急切需要的话可以直接留言,不是特别 ...
- 如何在centos上配置802.1Q VLAN标记,linux单网卡多vlan多网段Ip配置案例
介绍 VLAN使将大型网络分成较小且易于管理的网络成为可能.802.1Q是所有供应商都在其网络设备中实施的标准.某些交换机能够将多个VLAN分配给单个网络端口.使用此功能,您可以将多个VLAN分配给单 ...
- JavaWeb——MySQL基础
内容索引 数据库的基本概念 MySQL数据库软件 安装 卸载 配置 SQL 数据库的基本概念 1. 数据库的英文单词: DataBase 简称 : DB 2. 什么数据库? * 用于存储和管理数据的仓 ...