如何在 GPU 上优化卷积
备注
单击 此处 下载完整的示例代码
作者:Haichen Shen
本教程演示了如何在 TVM 中编写高性能卷积实现。以正方形大小的输入张量和滤波器为例,假设卷积输入的 batch 较大。在此示 例中,使用不同的布局来存储数据,以实现更好的数据局部性。缓冲区布局是 HWCN,分别代表高度、宽度、通道、batch。
准备和算法
对具有 256 个通道和 14 x 14 维度的输入张量使用固定大小。batch size 为 256,卷积过滤器包含 512 个大小为 3 x 3 的过滤器,用步长为 1 和 padding size 为 1 进行卷积。以下代码定义了 TVM 中的卷积算法。
import numpy as np
import tvm
from tvm import te
# 输入和过滤器的大小
batch = 256
in_channel = 256
out_channel = 512
in_size = 14
kernel = 3
pad = 1
stride = 1
# 算法
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 输入
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",
)
# 创建归约变量
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")
# 计算卷积
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 的本地缓存,也存储在线程本地寄存器中。
# 指定内存层次结构
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")