如何使用 TensorCores 优化卷积
单击 此处 下载完整的示例代码
作者:Siyuan Feng
本教程演示如何在 TVM 中使用 TensorCores 编写高性能卷积调度。在这个例子中,会假设卷积输入的 batch 较大。强烈建议前置讲解 如何在 GPU 上优化卷积。
TensorCore 介绍
每个 Tensor Core 都提供了一个 4x4x4 矩阵处理数组,它使得 D = A * B + C
,其中 A、B、C 和 D 是 4x4 矩阵,如图所示。矩阵乘法输入 A 和 B 是 FP16 矩阵,而累加 矩阵 C 和 D 可以是 FP16 或 FP32 矩阵。
但是,CUDA 开发者只能使用 warp 级原语 wmma::mma_sync(acc_frag, a_frag, b_frag, acc_frag)
在张量核上执行 16x16x16 半精度矩阵乘法。调用矩阵乘法之前,开发者必须使用原始 wmma::load_matrix_sync
显式地将数据从内存加载到寄存器中。NVCC 编译器将该原语转换为多个内存加载指令。在运行时,每个线程从矩阵 A 加载 16 个元素,从 B 加载 16 个元素。
准备和算法
对具有 256 个通道和 14 x 14 维度的输入张量使用固定大小。batch size 为 256,卷积过滤器包含 512 个大小为 3 x 3 的过滤器,使用 stride size 为 1 和 padding size 为 1 进行卷积。在示例中,使用 NHWCnc 内存布局。以下代码定义了 TVM 中的卷积算法。
import tvm
from tvm import te
import numpy as np
from tvm.contrib import nvcc
# 输入和过滤器的大小
batch_size = 256
height = 14
width = 14
in_channels = 256
out_channels = 512
kernel_h = 3
kernel_w = 3
pad_h = 1
pad_w = 1
stride_h = 1
stride_w = 1
# TensorCore shape
block_size = 16
assert batch_size % block_size == 0
assert in_channels % block_size == 0
assert out_channels % block_size == 0
# 输入特征图:(N,H,W,IC,n,ic)
data_shape = (
batch_size // block_size,
height,
width,
in_channels // block_size,
block_size,
block_size,
)
# Kernel: (H, W, IC, OC, ic, oc)
kernel_shape = (
kernel_h,
kernel_w,
in_channels // block_size,
out_channels // block_size,
block_size,
block_size,
)
# 输出特征图:(N, H, W, OC, n, oc)
output_shape = (
batch_size // block_size,
height,
width,
out_channels // block_size,
block_size,
block_size,
)
# Reduction axes
kh = te.reduce_axis((0, kernel_h), name="kh")
kw = te.reduce_axis((0, kernel_w), name="kw")
ic = te.reduce_axis((0, in_channels // block_size), name="ic")
ii = te.reduce_axis((0, block_size), name="ii")
# 算法
A = te.placeholder(data_shape, name="A", dtype="float16")
W = te.placeholder(kernel_shape, name="W", dtype="float16")
Apad = te.compute(
(
batch_size // block_size,
height + 2 * pad_h,
width + 2 * pad_w,
in_channels // block_size,
block_size,
block_size,
),
lambda n, h, w, i, nn, ii: tvm.tir.if_then_else(
tvm.tir.all(h >= pad_h, h - pad_h < height, w >= pad_w, w - pad_w < width),
A[n, h - pad_h, w - pad_w, i, nn, ii],
tvm.tir.const(0.0, "float16"),
),
name="Apad",
)
Conv = te.compute(
output_shape,
lambda n, h, w, o, nn, oo: te.sum(
Apad[n, h * stride_h + kh, w * stride_w + kw, ic, nn, ii].astype("float32")
* W[kh, kw, ic, o, ii, oo].astype("float32"),
axis=[ic, kh, kw, ii],
),
name="Conv",
)
s = te.create_schedule(Conv.op)
s[Apad].compute_inline()
内存范围
传统的 GPU 调度有全局、共享和本地内存范围。为了支持 TensorCores,添加另外三个特殊的内存范围:wmma.matrix_a
, wmma.matrix_b
和 wmma.accumulator
。在硬件上,所有片段范围都存储在芯片上寄存器级别,与本地内存相同。
# 指定内存层次结构
AS = s.cache_read(Apad, "shared", [Conv])
WS = s.cache_read(W, "shared", [Conv])
AF = s.cache_read(AS, "wmma.matrix_a", [Conv])
WF = s.cache_read(WS, "wmma.matrix_b", [Conv])
ConvF = s.cache_write(Conv, "wmma.accumulator")