【TVM 教程】如何使用 TensorCores 优化卷积

如何使用TensorCores优化卷积 如何使用TensorCores优化卷积 本文将演示如何在TVM中使用TensorCores编写高性能的卷积计划。假设卷积的输入有大量数据。首先介绍如何在GPU上优化卷积。 TensorCore简介 每个Tensor核心都提供一个4x4x4的矩阵处理阵列,该阵列可以运行 ,其中A,B,C和D是4x4矩阵,如图所示。矩阵乘法输入A和B是FP16矩阵,而累加矩阵C和D可以是FP16或FP32矩阵。D = A * B + C 但是,CUDA程序员只能使用扭曲级原语,在张量核上执行16x16x16半精度矩阵乘法。在调 阅读详情

Apache TVM 是一个端到端的深度学习编译框架,适用于 CPU、GPU 和各种机器学习加速芯片。更多 TVM 中文文档可访问 →Apache TVM 是一个端到端的深度学习编译框架,适用于 CPU、GPU 和各种机器学习加速芯片。 | Apache TVM 中文站

本教程演示如何在 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")

定义张量内联函数​

实际上,TensorCore 是一种特殊的硬件操作。因此,可以只用 tensorize 将一个计算单元替换为 TensorCore 指令。首先需要定义张量内联函数。

TensorCore 中有四个基本操作:fill_fragment, load_matrix, mma_sync 和 store_matrix。由于 fill_fragment 和 mma_sync 都用于矩阵乘法,所以可以只写以下三个内联函数。
def intrin_wmma_load_matrix(scope):
    n = 16
    A = te.placeholder((n, n), name="A", dtype="float16")
    BA = tvm.tir.decl_buffer(A.shape, A.dtype, scope="shared", data_alignment=32, offset_factor=256)
    C = te.compute((n, n), lambda i, j: A[i, j], name="C")
    BC = tvm.tir.decl_buffer(C.shape, C.dtype, scope=scope, data_alignment=32, offset_factor=256)

 def intrin_func(ins, outs):
        ib = tvm.tir.ir_builder.create()

        BA = ins[0]
        BC = outs[0]
        ib.emit(
            tvm.tir.call_intrin(
 "handle",
 "tir.tvm_load_matrix_sync",
                BC.data,
                n,
                n,
                n,
                BC.elem_offset // 256,
                BA.access_ptr("r"),
                n,
 "row_major",
 )
 )
 return ib.get()

 return te.decl_tensor_intrin(C.op, intrin_func, binds={A: BA, C: BC})

def intrin_wmma_gemm():
    n = 16
    A = te.placeholder((n, n), name="A", dtype="float16")
    B = te.placeholder((n, n), name="B", dtype="float16")
    k = te.reduce_axis((0, n), name="k")
    C = te.compute(
 (n, n),
 lambda ii, jj: te.sum(A[ii, k].astype("float") * B[k, jj].astype("float"), axis=k),
        name="C",
 )
    BA = tvm.tir.decl_buffer(
        A.shape, A.dtype, name="BA", scope="wmma.matrix_a", data_alignment=32, offset_factor=256
 )
    BB = tvm.tir.decl_buffer(
        B.shape, B.dtype, name="BB", scope="wmma.matrix_b", data_alignment=32, offset_factor=256
 )
    BC = tvm.tir.decl_buffer(
        C.shape, C.dtype, name="BC", scope="wmma.accumulator", data_alignment=32, offset_factor=256
 )

 def intrin_func(ins, outs):
        BA, BB = ins
 (BC,) = outs

 def init():
            ib = tvm.tir.ir_builder.create()
            ib.emit(
                tvm.tir.call_intrin(
 "handle", "tir.tvm_fill_fragment", BC.data, n, n, n, BC.elem_offset // 256, 0.0
 )
 )
 return ib.get()

 def update():
            ib = tvm.tir.ir_builder.create()
            ib.emit(
                tvm.tir.call_intrin(
 "handle",
 "tir.tvm_mma_sync",
                    BC.data,
                    BC.elem_offset // 256,
                    BA.data,
                    BA.elem_offset // 256,
                    BB.data,
                    BB.elem_offset // 256,
                    BC.data,
                    BC.elem_offset // 256,
 )
 )
 return ib.get()

 return update(), init(), update()

 return te.decl_tensor_intrin(C.op, intrin_func, binds={A: BA, B: BB, C: BC})

def intrin_wmma_store_matrix():
    n = 16
    A = te.placeholder((n, n), name="A", dtype="float32")
    BA = tvm.tir.decl_buffer(
        A.shape, A.dtype, scope="wmma.accumulator", data_alignment=32, offset_factor=256
 )
    C = te.compute((n, n), lambda i, j: A[i, j], name="C")
    BC = tvm.tir.decl_buffer(C.shape, C.dtype, scope="global", data_alignment=32, offset_factor=256)

 def intrin_func(ins, outs):
        ib = tvm.tir.ir_builder.create()
        BA = ins[0]
        BC = outs[0]
        ib.emit(
            tvm.tir.call_intrin(
 "handle",
 "tir.tvm_store_matrix_sync",
                BA.data,
                n,
                n,
                n,
                BA.elem_offset // 256,
                BC.access_ptr("w"),
                n,
 "row_major",
 )
 )
 return ib.get()

 return te.decl_tensor_intrin(C.op, intrin_func, binds={A: BA, C: BC})

调度计算​

要在 TVM 中使用 TensorCores,必须将计算调度到特定的结构中,从而匹配张量内联函数。和传统的 GPU 程序一样,也可以用共享内存来提升速度。如果对分块和共享内存有任何疑问,请参阅 如何在 GPU 上优化卷积。

在这个例子中,每个块包含 2x4 个 warp,每个 warp 调用 4x2 TensorCore 指令。因此,每个 warp 的输出 shape 为 64x32,每个块输出 128x128 个 titles。由于共享内存空间的限制,一次只加载 2 个块(2x128x128 tiles)。

备注
Warp 级操作
所有 TensorCore 指令都是 warp 级指令,这意味着一个 warp 中的所有 32 个线程应该同时执行此指令。
threadIdx.x extent=32 是解决此问题的最简单方法之一。除了那些直接或间接包含 TensorCore 内联函数的循环,可以将 threadIdx.x 绑定到任何循环。
这并不是唯一的解决方案,唯一应该做的就是确保一个 warp 中的所有线程都可以同时调用 TensorCore。

# 定义 tile 大小
block_row_warps = 4
block_col_warps = 2
warp_row_tiles = 2
warp_col_tiles = 4
warp_size = 32
chunk = 2

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("threadIdx.x")
thread_y = te.thread_axis("threadIdx.y")
thread_z = te.thread_axis("threadIdx.z")

nc, hc, wc, oc, nnc, ooc = Conv.op.axis
block_k = s[Conv].fuse(hc, wc)
s[Conv].bind(block_k, block_z)
nc, nci = s[Conv].split(nc, factor=warp_row_tiles)
block_i, nc = s[Conv].split(nc, factor=block_row_warps)
oc, oci = s[Conv].split(oc, factor=warp_col_tiles)
block_j, oc = s[Conv].split(oc, factor=block_col_warps)
s[Conv].reorder(block_k, block_i, block_j, nc, oc, nci, oci, nnc, ooc)
s[Conv].bind(block_i, block_x)
s[Conv].bind(block_j, block_y)
s[Conv].bind(nc, thread_y)
s[Conv].bind(oc, thread_z)

# 调度本地计算
s[ConvF].compute_at(s[Conv], oc)
n, h, w, o, nnf, oof = ConvF.op.axis
ko, ki = s[ConvF].split(ic, factor=chunk)
s[ConvF].reorder(ko, kh, ki, kw, n, o, nnf, oof, ii)

# 将中间计算移动到每个输出计算块中
s[AF].compute_at(s[ConvF], kw)
s[WF].compute_at(s[ConvF], kw)

# A 的共享内存调度
s[AS].compute_at(s[ConvF], kh)
n, h, w, i, nn, ii = AS.op.axis
tx, xo = s[AS].split(n, nparts=block_row_warps)
ty, yo = s[AS].split(xo, nparts=block_col_warps)
t = s[AS].fuse(nn, ii)
to, ti = s[AS].split(t, factor=warp_size)
s[AS].bind(tx, thread_y)
s[AS].bind(ty, thread_z)
s[AS].bind(ti, thread_x)

# W 的共享内存调度
s[WS].compute_at(s[ConvF], kh)
kh, kw, ic, o, ii, oo = WS.op.axis
tx, xo = s[WS].split(o, nparts=block_row_warps)
ty, yo = s[WS].split(xo, nparts=block_col_warps)
t = s[WS].fuse(ii, oo)
to, ti = s[WS].split(t, nparts=warp_size)
s[WS].bind(tx, thread_y)
s[WS].bind(ty, thread_z)
s[WS].bind(to, thread_x)
s[WS].vectorize(ti)
print(tvm.lower(s, [A, W, Conv], simple_mode=True))

输出结果:

@main = primfn(A_1: handle, W_1: handle, Conv_1: handle) -> ()
  attr = {"from_legacy_te_schedule": True, "global_symbol": "main", "tir.noalias": True}
  buffers = {A: Buffer(A_2: Pointer(float16), float16, [12845056], []),
             W: Buffer(W_2: Pointer(float16), float16, [1179648], []),
             Conv: Buffer(Conv_2: Pointer(float32), float32, [25690112], [])}
  buffer_map = {A_1: A, W_1: W, Conv_1: Conv}
  preflattened_buffer_map = {A_1: A_3: Buffer(A_2, float16, [16, 14, 14, 16, 16, 16], []), W_1: W_3: Buffer(W_2, float16, [3, 3, 16, 32, 16, 16], []), Conv_1: Conv_3: Buffer(Conv_2, float32, [16, 14, 14, 32, 16, 16], [])} {
  attr [IterVar(blockIdx.z: int32, (nullptr), "ThreadIndex", "blockIdx.z")] "thread_extent" = 196;
  allocate(Conv.wmma.accumulator: Pointer(wmma.accumulator float32), float32, [2048]), storage_scope = wmma.accumulator;
  allocate(Apad.shared: Pointer(shared float16), float16, [12288]), storage_scope = shared;
  allocate(W.shared: Pointer(shared float16), float16, [12288]), storage_scope = shared;
  allocate(Apad.shared.wmma.matrix_a: Pointer(wmma.matrix_a float16), float16, [512]), storage_scope = wmma.matrix_a;
  allocate(W.shared.wmma.matrix_b: Pointer(wmma.matrix_b float16), float16, [1024]), storage_scope = wmma.matrix_b;
  attr [IterVar(blockIdx.x: int32, (nullptr), "ThreadIndex", "blockIdx.x")] "thread_extent" = 2;
  attr [IterVar(blockIdx.y: int32, (nullptr), "ThreadIndex", "blockIdx.y")] "thread_extent" = 4;
  attr [IterVar(threadIdx.y: int32, (nullptr), "ThreadIndex", "threadIdx.y")] "thread_extent" = 4;
  attr [IterVar(threadIdx.z: int32, (nullptr), "ThreadIndex", "threadIdx.z")] "thread_extent" = 2 {
 for (n.c.init: int32, 0, 2) {
 for (o.c.init: int32, 0, 4) {
 for (nn.c.init: int32, 0, 16) {
 for (oo.c.init: int32, 0, 16) {
            Conv.wmma.accumulator_1: Buffer(Conv.wmma.accumulator, float32, [2048], [], scope="wmma.accumulator")[((((n.c.init*1024) + (o.c.init*256)) + (nn.c.init*16)) + oo.c.init)] = 0f32
 }
 }
 }
 }
 for (ic.outer: int32, 0, 8) {
 for (kh: int32, 0, 3) {
 for (ax2: int32, 0, 3) {
 for (ax3: int32, 0, 2) {
 for (ax4.ax5.fused.outer: int32, 0, 8) {
 let cse_var_2: int32 = (ax3*256)
 let cse_var_1: int32 = (ax4.ax5.fused.outer*32)
              attr [IterVar(threadIdx.x: int32, (nullptr), "ThreadIndex", "threadIdx.x")] "thread_extent" = 32;
              Apad.shared_1: Buffer(Apad.shared, float16, [12288], [], scope="shared")[((((((threadIdx.y*3072) + (threadIdx.z*1536)) + (ax2*512)) + cse_var_2) + cse_var_1) + threadIdx.x)] = @tir.if_then_else(((((1 <= (floordiv(blockIdx.z, 14) + kh)) && ((floordiv(blockIdx.z, 14) + kh) < 15)) && (1 <= (ax2 + floormod(blockIdx.z, 14)))) && ((ax2 + floormod(blockIdx.z, 14)) < 15)), A[(((((((((((blockIdx.x*6422528) + (threadIdx.y*1605632)) + (threadIdx.z*802816)) + (kh*57344)) + (blockIdx.z*4096)) + (ax2*4096)) + (ic.outer*512)) + cse_var_2) + cse_var_1) + threadIdx.x) - 61440)], 0f16, dtype=float16)
 }
 }
 }
 for (ax1: int32, 0, 3) {
 for (ax2_1: int32, 0, 2) {
            attr [IterVar(threadIdx.x, (nullptr), "ThreadIndex", "threadIdx.x")] "thread_extent" = 32;
            W.shared_1: Buffer(W.shared, float16, [12288], [], scope="shared")[ramp((((((ax1*4096) + (ax2_1*2048)) + (threadIdx.y*512)) + (threadIdx.z*256)) + (threadIdx.x*8)), 1, 8)] = W[ramp(((((((((kh*393216) + (ax1*131072)) + (ic.outer*16384)) + (ax2_1*8192)) + (blockIdx.y*2048)) + (threadIdx.y*512)) + (threadIdx.z*256)) + (threadIdx.x*8)), 1, 8)]
 }
 }
 for (ic.inner: int32, 0, 2) {
 for (kw: int32, 0, 3) {
 for (ax0: int32, 0, 2) {
 for (ax4: int32, 0, 16) {
 for (ax5: int32, 0, 16) {
 let cse_var_3: int32 = (ax4*16)
                  Apad.shared.wmma.matrix_a_1: Buffer(Apad.shared.wmma.matrix_a, float16, [512], [], scope="wmma.matrix_a")[(((ax0*256) + cse_var_3) + ax5)] = Apad.shared_1[((((((threadIdx.y*3072) + (ax0*1536)) + (kw*512)) + (ic.inner*256)) + cse_var_3) + ax5)]
 }
 }
 }
 for (ax3_1: int32, 0, 4) {
 for (ax4_1: int32, 0, 16) {
 for (ax5_1: int32, 0, 16) {
 let cse_var_5: int32 = (ax3_1*256)
 let cse_var_4: int32 = (ax4_1*16)
                  W.shared.wmma.matrix_b_1: Buffer(W.shared.wmma.matrix_b, float16, [1024], [], scope="wmma.matrix_b")[((cse_var_5 + cse_var_4) + ax5_1)] = W.shared_1[((((((kw*4096) + (ic.inner*2048)) + (threadIdx.z*1024)) + cse_var_5) + cse_var_4) + ax5_1)]
 }
 }
 }
 for (n.c: int32, 0, 2) {
 for (o.c: int32, 0, 4) {
 for (nn.c: int32, 0, 16) {
 for (oo.c: int32, 0, 16) {
 for (ii: int32, 0, 16) {
 let cse_var_8: int32 = (o.c*256)
 let cse_var_7: int32 = (nn.c*16)
 let cse_var_6: int32 = ((((n.c*1024) + cse_var_8) + cse_var_7) + oo.c)
                      Conv.wmma.accumulator_1[cse_var_6] = (Conv.wmma.accumulator_1[cse_var_6] + (cast(float32, Apad.shared.wmma.matrix_a_1[(((n.c*256) + cse_var_7) + ii)])*cast(float32, W.shared.wmma.matrix_b_1[((cse_var_8 + (ii*16)) + oo.c)])))
 }
 }
 }
 }
 }
 }
 }
 }
 }
 for (n.inner: int32, 0, 2) {
 for (o.inner: int32, 0, 4) {
 for (nn: int32, 0, 16) {
 for (oo: int32, 0, 16) {
 let cse_var_10: int32 = (o.inner*256)
 let cse_var_9: int32 = (nn*16)
            Conv[(((((((((blockIdx.x*12845056) + (threadIdx.y*3211264)) + (n.inner*1605632)) + (blockIdx.z*8192)) + (blockIdx.y*2048)) + (threadIdx.z*1024)) + cse_var_10) + cse_var_9) + oo)] = Conv.wmma.accumulator_1[((((n.inner*1024) + cse_var_10) + cse_var_9) + oo)]
 }
 }
 }
 }
 }
}

将计算降级为内联函数​

最后一个阶段将计算循环降级到 TensorCore 硬件内联函数,这是通过将 2D 卷积映射到张量内联函数实现的。

s[AF].tensorize(AF.op.axis[-2], intrin_wmma_load_matrix("wmma.matrix_a"))
s[WF].tensorize(WF.op.axis[-2], intrin_wmma_load_matrix("wmma.matrix_b"))
s[Conv].tensorize(nnc, intrin_wmma_store_matrix())
s[ConvF].tensorize(nnf, intrin_wmma_gemm())
print(tvm.lower(s, [A, W, Conv], simple_mode=True))

输出结果:

@main = primfn(A_1: handle, W_1: handle, Conv_1: handle) -> ()
  attr = {"from_legacy_te_schedule": True, "global_symbol": "main", "tir.noalias": True}
  buffers = {A: Buffer(A_2: Pointer(float16), float16, [12845056], []),
             W: Buffer(W_2: Pointer(float16), float16, [1179648], []),
             Conv: Buffer(Conv_2: Pointer(float32), float32, [25690112], [])}
  buffer_map = {A_1: A, W_1: W, Conv_1: Conv}
  preflattened_buffer_map = {A_1: A_3: Buffer(A_2, float16, [16, 14, 14, 16, 16, 16], []), W_1: W_3: Buffer(W_2, float16, [3, 3, 16, 32, 16, 16], []), Conv_1: Conv_3: Buffer(Conv_2, float32, [16, 14, 14, 32, 16, 16], [])} {
  attr [IterVar(blockIdx.z: int32, (nullptr), "ThreadIndex", "blockIdx.z")] "thread_extent" = 196;
  allocate(Conv.wmma.accumulator: Pointer(wmma.accumulator float32), float32, [2048]), storage_scope = wmma.accumulator;
  allocate(Apad.shared: Pointer(shared float16), float16, [12288]), storage_scope = shared;
  allocate(W.shared: Pointer(shared float16), float16, [12288]), storage_scope = shared;
  allocate(Apad.shared.wmma.matrix_a: Pointer(wmma.matrix_a float16), float16, [512]), storage_scope = wmma.matrix_a;
  allocate(W.shared.wmma.matrix_b: Pointer(wmma.matrix_b float16), float16, [1024]), storage_scope = wmma.matrix_b;
  attr [IterVar(blockIdx.x: int32, (nullptr), "ThreadIndex", "blockIdx.x")] "thread_extent" = 2;
  attr [IterVar(blockIdx.y: int32, (nullptr), "ThreadIndex", "blockIdx.y")] "thread_extent" = 4;
  attr [IterVar(threadIdx.y: int32, (nullptr), "ThreadIndex", "threadIdx.y")] "thread_extent" = 4;
  attr [IterVar(threadIdx.z: int32, (nullptr), "ThreadIndex", "threadIdx.z")] "thread_extent" = 2 {
 for (n.c.init: int32, 0, 2) {
 for (o.c.init: int32, 0, 4) {
        @tir.tvm_fill_fragment(Conv.wmma.accumulator, 16, 16, 16, ((n.c.init*4) + o.c.init), 0f32, dtype=handle)
      }
    }
    for (ic.outer: int32, 0, 8) {
      for (kh: int32, 0, 3) {
        for (ax2: int32, 0, 3) {
          for (ax3: int32, 0, 2) {
            for (ax4.ax5.fused.outer: int32, 0, 8) {
              let cse_var_2: int32 = (ax3*256)
              let cse_var_1: int32 = (ax4.ax5.fused.outer*32)
              attr [IterVar(threadIdx.x: int32, (nullptr), "ThreadIndex", "threadIdx.x")] "thread_extent" = 32;
              Apad.shared_1: Buffer(Apad.shared, float16, [12288], [], scope="shared")[((((((threadIdx.y*3072) + (threadIdx.z*1536)) + (ax2*512)) + cse_var_2) + cse_var_1) + threadIdx.x)] = @tir.if_then_else(((((1 <= (floordiv(blockIdx.z, 14) + kh)) && ((floordiv(blockIdx.z, 14) + kh) < 15)) && (1 <= (ax2 + floormod(blockIdx.z, 14)))) && ((ax2 + floormod(blockIdx.z, 14)) < 15)), A[(((((((((((blockIdx.x*6422528) + (threadIdx.y*1605632)) + (threadIdx.z*802816)) + (kh*57344)) + (blockIdx.z*4096)) + (ax2*4096)) + (ic.outer*512)) + cse_var_2) + cse_var_1) + threadIdx.x) - 61440)], 0f16, dtype=float16)
 }
 }
 }
 for (ax1: int32, 0, 3) {
 for (ax2_1: int32, 0, 2) {
            attr [IterVar(threadIdx.x, (nullptr), "ThreadIndex", "threadIdx.x")] "thread_extent" = 32;
            W.shared_1: Buffer(W.shared, float16, [12288], [], scope="shared")[ramp((((((ax1*4096) + (ax2_1*2048)) + (threadIdx.y*512)) + (threadIdx.z*256)) + (threadIdx.x*8)), 1, 8)] = W[ramp(((((((((kh*393216) + (ax1*131072)) + (ic.outer*16384)) + (ax2_1*8192)) + (blockIdx.y*2048)) + (threadIdx.y*512)) + (threadIdx.z*256)) + (threadIdx.x*8)), 1, 8)]
 }
 }
 for (ic.inner: int32, 0, 2) {
 for (kw: int32, 0, 3) {
 for (ax0: int32, 0, 2) {
              @tir.tvm_load_matrix_sync(Apad.shared.wmma.matrix_a, 16, 16, 16, ax0, @tir.tvm_access_ptr(@tir.type_annotation(, dtype=float16), Apad.shared, ((((threadIdx.y*3072) + (ax0*1536)) + (kw*512)) + (ic.inner*256)), 256, 1, dtype=handle), 16, "row_major", dtype=handle)
 }
 for (ax3_1: int32, 0, 4) {
              @tir.tvm_load_matrix_sync(W.shared.wmma.matrix_b, 16, 16, 16, ax3_1, @tir.tvm_access_ptr(@tir.type_annotation(, dtype=float16), W.shared, ((((kw*4096) + (ic.inner*2048)) + (threadIdx.z*1024)) + (ax3_1*256)), 256, 1, dtype=handle), 16, "row_major", dtype=handle)
 }
 for (n.c: int32, 0, 2) {
 for (o.c: int32, 0, 4) {
 let cse_var_3: int32 = ((n.c*4) + o.c)
                @tir.tvm_mma_sync(Conv.wmma.accumulator, cse_var_3, Apad.shared.wmma.matrix_a, n.c, W.shared.wmma.matrix_b, o.c, Conv.wmma.accumulator, cse_var_3, dtype=handle)
              }
            }
          }
        }
      }
    }
    for (n.inner: int32, 0, 2) {
      for (o.inner: int32, 0, 4) {
        @tir.tvm_store_matrix_sync(Conv.wmma.accumulator, 16, 16, 16, ((n.inner*4) + o.inner), @tir.tvm_access_ptr(@tir.type_annotation(, dtype=float32), Conv_2, (((((((blockIdx.x*12845056) + (threadIdx.y*3211264)) + (n.inner*1605632)) + (blockIdx.z*8192)) + (blockIdx.y*2048)) + (threadIdx.z*1024)) + (o.inner*256)), 256, 2, dtype=handle), 16, "row_major", dtype=handle)
 }
 }
 }
}

生成 CUDA 内核​

最后使用 TVM 生成和编译 CUDA 内核,并评估卷积的延迟。由于 TensorCores 仅支持 Compute Capability 7.0 或更高版本的 NVIDIA GPU,因此可能无法在构建服务器上运行。

dev = tvm.cuda(0)
if nvcc.have_tensorcore(dev.compute_version):
    with tvm.transform.PassContext(config={"tir.UnrollLoop": {"auto_max_step": 16}}):
        func = tvm.build(s, [A, W, Conv], "cuda")
    a_np = np.random.uniform(size=data_shape).astype(A.dtype)
    w_np = np.random.uniform(size=kernel_shape).astype(W.dtype)
    a = tvm.nd.array(a_np, dev)
    w = tvm.nd.array(w_np, dev)
    c = tvm.nd.array(np.zeros(output_shape, dtype=Conv.dtype), dev)
    evaluator = func.time_evaluator(func.entry_name, dev, number=10)
    print("conv2d with tensor core: %f ms" % (evaluator(a, w, c).mean * 1e3))

输出结果:

conv2d with tensor core: 6.835711 ms  

总结​

本教程演示如何用 TVM 调度原语在特定 GPU 上调用 TensorCore。

下载 Python 源代码:opt_conv_tensorcore.py

下载 Jupyter Notebook:opt_conv_tensorcore.ipynb

使用Auto TensorCore CodeGen优化Matmul 使用Auto TensorCore CodeGen优化Matmul 本文将演示如何使用TVM Auto TensorCore CodeGen在Volta / Turing GPU上编写高性能matmul调度。这是一个生成tensorcore内核的解决方案,其中大多数转换都是通过ir传递完成的。用户还可以编写带有张量的调度,生成TensorCore代码。两种解决方案都使用相同的tensorcore内部函数。有关更多详细信息,请参阅如何使用TensorCores优化卷积。 准备和算法 支持两种输入数据类型:fl 阅读详情

相关推荐

TVM学习教程

目录Step 1:TVM 视频介绍Step 2:研读 TVM 论文Step 3:了解 TVM 官网中可以学习的资料Step 4:上手 TVM CodeStep 5:TVM 其他相关资料 Step 1:TVM 视频介绍 https://www.bilibili.com/video/av29521815/ Step 2:研读 TVM 论文 https://www.usenix.org/system/files/osdi18-chen.pdf Step 3:了解 TVM 官网中可以学习的资料 https://tv

qq_33287871的博客 3077

【tvm官网教程01】设计和架构-1

【tvm官网教程01】设计和架构-1目的1. 编译流1.1 关键数据结构1.2 Transformations1.2.1 变换的分类1.2.2 relay/transform1.2.2.1 子函数1.2.2.2 overall问题1.2.3 tir/transform1.3 搜索空间和基于学习的transformation1.4 Target Translation1.5 Runtime Execution1.6 小结2. 逻辑组件2.1 tvm/support2.2 tvm/runtime2.3 tvm

B10030607的博客 1994

《AFSim 2.9中文参考手册》

本栏目将对《AFSim 2.9中文参考手册》进行持续更新,欢迎关注交流!对本文或者AFSIM相关内容感兴趣的朋友,可私心联系作者。《AFSim 2.9中文参考手册》内容概览。

henggesim的博客 1万+

【从零开始学深度学习编译器】一,深度学习编译器及TVM 介绍

0x0. 介绍 大家好呀,在过去的半年到一年时间里,我分享了一些算法解读,算法优化,模型转换相关的一些文章。这篇文章是自己开启学习深度学习编译器的第一篇文章,后续也会努力更新这个系列。这篇文章是开篇,所以我不会具体讲解TVM的知识,更多的是介绍一下我自己眼中的深度学习编译器是什么?以及为什么我要选择学习TVM,最后我也会给出一个让读者快速体验TVM效果的一个开发环境搭建的简要教程。 0x1. 为什么需要深度学习编译器? 深度学习编译器这个词语,我们可以先拆成两个部分来看。 首先谈谈深度学习领域。从训练框架角

I good vegetable a! 4091

开关电源EMC整改实录:用WSX系列共模电感搞定30MHz辐射超标

本文详细介绍了开关电源EMC整改实战,通过WSX系列共模电感成功解决30MHz频段辐射超标问题。文章分析了共模干扰的产生机制,对比了不同磁芯材料的性能差异,并提供了电路布局优化与安装技巧,最终通过实测数据验证了整改方案的有效性。

weixin_29305313的博客 415

源码编译部署篇(二)源码编译milvus成功后如何启动standalone并调试成功!

由于Milvus官方文档只提及如何使用docker安装部署Milvus,关于源码安装方式篇幅较少,因此,我整理撰写了如何通过源码编译方式成功安装Milvus,详见此篇。之所以选择这种复杂的安装方式,可以调试源码进而熟悉Milvus如何搭建整个框架支持AI上层应用功能。根据上篇步骤部署虽然可以成功,然而,我却不能成功启动Milvus服务,这个问题困扰了一两天,现在终于得以解决并能成功调试Milvus源码。接下来,分为三个部分介绍如何启动和成功调试。

qq_43893755的博客 3368

TVM安装教程(踩坑记录)

TVM安装教程以及一些踩坑记录

m0_49339981的博客 2279

告别手动连接!一键搞定Mumu模拟器与Android Studio的ADB配置

本文详细介绍了如何通过编写自动化脚本,一键完成Mumu模拟器与Android Studio的ADB连接配置。告别繁琐的手动命令输入,该脚本能智能检查环境、判断连接状态并自动处理端口转发,显著提升Android开发者的调试与测试效率。

yog99的博客 498

TVM安装编译指南

使用brew安装的llvm地址: /usr/local/Cellar/llvm@9/9.0.1_4/bin/bin/llvm-config 开启LLVM #--------------------------------------------- # Contrib libraries #--------------------------------------------- # Whether to build with BYODT software emulated posit custom d

性感的小君君 2394

BCIC IV 2a数据集:从下载到实战应用全解析

本文全面解析了BCI Competition IV 2a(BCIC IV 2a)数据集的应用流程。从官方渠道下载数据、解析其结构,到在MATLAB和Python环境中进行数据导入、预处理(包括处理特殊受试者A04T),并详细演示了从特征提取(如CSP)到分类(如LDA、SVM)的完整实战步骤,为脑机接口研究者提供了从入门到实践的清晰指南。

weixin_28235513的博客 975

【数字孪生与仿真技术】14:数据驱动+机理模型:工业级混合建模实战(附MATLAB完整代码+案例解析)

摘要 本文针对工业建模中机理模型精度不足和数据驱动模型缺乏物理约束的痛点,提出混合建模技术作为解决方案。文章系统阐述了混合建模的3种主流架构(参数校正型、残差补偿型、组件替换型),结合清华大学催化裂化装置的实际案例,分析混合建模的工业应用价值。同时以连续搅拌反应釜(CSTR)为例,提供MATLAB环境下残差补偿型混合模型的完整实现代码,详细展示从机理模型构建到数据驱动补偿的全流程,并给出工程实践中的应对策略,为工业建模提供可落地的技术方案。 关键词 混合建模、机理模型、数据驱动、工业建模、MATLAB

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 622

linux安装tvm教程(包含基于tvm进行onnx模型部署python代码)

linux下安装tvm并进行onnx模型部署推理

wenwen_2020的博客 3251

商品标签识别系统:基于YOLO系列的深度学习实现

随着零售行业的快速发展和自动化需求的增长,商品标签识别技术变得越来越重要。本文详细介绍了一个完整的商品标签识别系统,该系统基于YOLO系列目标检测算法(包括YOLOv5、YOLOv8和YOLOv10),采用PySide6构建用户界面,并提供完整的训练代码和数据集处理方案。该系统能够准确识别商品标签中的关键信息,如价格、条形码、生产日期等,为零售自动化、库存管理和智能结算提供技术支持。

YOLO 349

【TVM 教程】使用 TVM 部署框架预量化模型 - 第 3 部分(TFLite)

更多 TVM 中文文档可访问 →此教程介绍如何量化 TFLite 计算图,并通过 TVM 编译和执行。有关使用 TFLite 量化模型的更多详细信息,参阅。TFLite 模型下载。开始前,先安装 TensorFlow 和 TFLite 包。执行命令检查 TFLite 包是否安装成功。

HyperAI超神经 1580

真随机数与伪随机数

前面我们学习了LFSR,这个玩意落地到具体的应用是什么呢?没错是寄存器,这里在网络找到了几个回答,这里做个记录,下面一起来学习一下吧。

叫好与叫座虽然不是对立面,但想在同一个作品中达到双重效果很难。 1062

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

TVM 从入门到精通 | 安装 TVM (Part 1)

手把手教学之从源码安装 TVM

HyperAI超神经 3203

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

TVM安装教程详细版本(亲测可用,踩坑整理,建议软件版本尽量使用最高的、最新的,这样踩坑的概率会低)

TVM安装,深度学习编译器

weixin_42189664的博客 8277

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

【TVM 教程】为 NVIDIA GPU 自动调优卷积网络

​首先要在 Relay 前端 API 中定义网络,可以从加载一些预定义的网络。也可以从 MXNet、ONNX 和 TensorFlow 加载模型。

HyperAI超神经 1282

TVM Compiler中文教程:TVM使用内联和数学函数

文章目录TVM使用内联和数学函数直接声明外部数学函数调用统一内联函数调用内部Lowering规则总结 TVM使用内联和数学函数 TVM支持基础算术运算操作,但是在很多情况下我们需要更复杂的內建函数。例如exp指数函数。 这些內建函数取决于目标系统,在不同的平台可能有不同的名字。这个教程中,我们将学习调用目标特定的內建函数,和怎么能够通过TVM内联API统一接口。 from __future__ i...

Mars_WH的专栏 2267

【TVM 教程】编写自定义 Pass

Apache TVM 是一个深度的深度学习编译框架,适用于 CPU、GPU 和各种机器学习加速芯片。更多 TVM 中文文档可访问 →https://tvm.hyper.ai/

HyperAI超神经 925

【TVM教程】microTVM TFLite 指南

​接下来为 Relay 创建一个构建配置,关闭两个选项,然后调用 relay.build,为选定的 TARGET 生成一个 C 源文件。当在与主机( Python 脚本执行的位置)相同架构的模拟 target 上运行时,为 TARGET 选择下面的「crt」,选择 C Runtime 作为 RUNTIME ,并选择适当的单板/虚拟机来运行它(Zephyr 将创建基于 BOARD 的正确 QEMU 虚拟机)。# 运行于物理硬件时,选择描述对应硬件的 TARGET 和 BOARD。

HyperAI超神经 699

【TVM教程】为 ARM CPU 自动调优卷积网络

首先要在relay前端API中定义网络,可以从relay.testing加载一些预定义的网络,还可以从MXNet、ONNX和TensorFlow加载模型。

HyperAI超神经 1067

【TVM 教程】使用 Relay Visualizer 可视化 Relay

​构建一个包含多个GlobalVar的 Relay IR 模块示例。定义一个add函数,并在 main 函数中调用。​有时想要强调感兴趣的信息,或者针对特定用途对事物进行不同的解析,需要遵守接口来定制解析器,下面演示如何自定义relay.var的解析器,需要实现抽象接口。self,# 没有引入边缘。所以返回一个空列表# 将其他类型委托给其他解析器。将解析器和感兴趣的渲染器传递给 visualizer,这里只用终端渲染器。

HyperAI超神经 626

TVM Compiler中文教程:TVM如何生成优化GPU卷积

文章目录TVM如何优化GPU卷积准备和算法内存层次结构分块虚拟线程分裂并发数据获取生成CUDA内核 TVM如何优化GPU卷积 本教程,我们将演示如何在TVM中编写高性能卷积实现。我们使用方形尺寸的输入张量和滤波器作为示例,并假设卷积的输入具有大批量。在此示例中,我们使用不同的布局来存储数据,以实现更好的数据局部性。缓冲区布局为HWCN,代表高度,宽度,通道,批次。 准备和算法 我们使用固定尺寸14...

Mars_WH的专栏 4085

ubuntu20.04 tvm 安装教程

ubuntu20.04 tvm 安装教程

极乐净土 839

【芯片原理】NPU矩阵乘法加速详解

概述 深度学习模型中费时的算子是往往是卷积操作,而卷积的本质是矩阵的乘加计算,所以通过硬件加速矩阵的乘加运算就能达到加速深度学习训练与推理的目标;本文旨在形象化的解释Google TPU与Nvidia TensorCore对矩阵乘加的加速过程 Nvidia TensorCore NV TensorCore 加速矩阵乘加运算示意图 Nvidia官方给出的TensorCore 加速矩阵...

HaoBBNuanMM的博客 1万+
上一篇: 分享NeurIPS 2024投稿经验!浙大团队用DePLM模型助力蛋白质优化,论文一作在线展示demo
下一篇: DeepMind与Google Research齐发力,多技术路线打造AI天气预报的「六边形战士」
HyperAI超神经
博客等级 码龄7年 8873粉丝 1753原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值