%%shell
# Installs the latest dev build of TVM from PyPI. If you wish to build
# from source, see https://tvm.apache.org/docs/install/from_source.html
pip install apache-tvm --pre
TensorIR 构建#
在本节中,我们将介绍在Apache TVM Unity中编写TensorIR函数的方法。本教程假设您已经熟悉TensorIR的基本概念。
Note
本教程专注于构建 **独立的** TensorIR 函数。这里介绍的技术对于最终用户编译Relax模型来说不是必需的。
使用 TVMScript 构建 TensorIR#
通过 TVMScript 创建 TensorIR 函数的最简单方法。TVMScript 是 TVM Python 方言,代表 TVM 中的 TensorIR。
标准格式#
以下是ir_module的完整格式,以及在TVMScript中:
%cd ..
import set_env
/media/pc/data/lxw/ai/tvm-book/doc/tutorials
import numpy as np
import tvm
from tvm.script import ir as I
from tvm.script import tir as T
@I.ir_module
class MyModule:
@T.prim_func
def mm_relu(
A: T.Buffer((128, 128), "float32"),
B: T.Buffer((128, 128), "float32"),
C: T.Buffer((128, 128), "float32"),
):
Y = T.alloc_buffer((128, 128), dtype="float32")
for i in range(128):
for j in range(128):
for k in range(128):
with T.block("Y"):
vi = T.axis.spatial(128, i)
vj = T.axis.spatial(128, j)
vk = T.axis.reduce(128, k)
T.reads(A[vi, vk], B[vk, vj])
T.writes(Y[vi, vj])
with T.init():
Y[vi, vj] = T.float32(0)
Y[vi, vj] = Y[vi, vj] + A[vi, vk] * B[vk, vj]
for i in range(128):
for j in range(128):
with T.block("C"):
vi = T.axis.spatial(128, i)
vj = T.axis.spatial(128, j)
T.reads(Y[vi, vj])
T.writes(C[vi, vj])
C[vi, vj] = T.max(Y[vi, vj], T.float32(0))
使用语法糖的简洁写法#
为了便于编写,我们可以采用以下语法糖来简化代码:
利用
T.grid
来压缩嵌套循环;使用
T.axis.remap
来简化块迭代器注释;排除
T.reads
和T.writes
对于可以从块体中推断出内容的块;
@I.ir_module
class ConciseModule:
@T.prim_func
def mm_relu(
A: T.Buffer((128, 128), "float32"),
B: T.Buffer((128, 128), "float32"),
C: T.Buffer((128, 128), "float32"),
):
Y = T.alloc_buffer((128, 128), dtype="float32")
for i, j, k in T.grid(128, 128, 128):
with T.block("Y"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
Y[vi, vj] = T.float32(0)
Y[vi, vj] = Y[vi, vj] + A[vi, vk] * B[vk, vj]
for i, j in T.grid(128, 128):
with T.block("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = T.max(Y[vi, vj], T.float32(0))
我们可以使用以下代码来验证这两个模块是等价的:
print(tvm.ir.structural_equal(MyModule, ConciseModule))
True
与Python变量交互#
尽管TVMScript不由Python解释器执行,但与Python的有限交互是可行的。例如,可以使用Python变量来确定TensorIR的形状和数据类型。
# Python variables
M = N = K = 128
dtype = "float32"
# IRModule in TVMScript
@I.ir_module
class ConciseModuleFromPython:
@T.prim_func
def mm_relu(
A: T.Buffer((M, K), dtype),
B: T.Buffer((K, N), dtype),
C: T.Buffer((M, N), dtype),
):
Y = T.alloc_buffer((M, N), dtype)
for i, j, k in T.grid(M, N, K):
with T.block("Y"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
Y[vi, vj] = T.cast(T.float32(0), dtype)
Y[vi, vj] = Y[vi, vj] + A[vi, vk] * B[vk, vj]
for i, j in T.grid(M, N):
with T.block("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = T.max(Y[vi, vj], T.cast(T.float32(0), dtype))
检查等价性:
print(tvm.ir.structural_equal(ConciseModule, ConciseModuleFromPython))
True
具有动态形状的TensorIR函数#
尽管TVMScript不由Python解释器执行,但与Python的有限交互是可行的。例如,可以使用Python变量来确定TensorIR的形状和数据类型。
@I.ir_module
class DynamicShapeModule:
@T.prim_func
def mm_relu(a: T.handle, b: T.handle, c: T.handle):
# Dynamic shape definition
M, N, K = T.int32(), T.int32(), T.int32()
# Bind the input buffers with the dynamic shapes
A = T.match_buffer(a, [M, K], dtype)
B = T.match_buffer(b, [K, N], dtype)
C = T.match_buffer(c, [M, N], dtype)
Y = T.alloc_buffer((M, N), dtype)
for i, j, k in T.grid(M, N, K):
with T.block("Y"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
Y[vi, vj] = T.cast(T.float32(0), dtype)
Y[vi, vj] = Y[vi, vj] + A[vi, vk] * B[vk, vj]
for i, j in T.grid(M, N):
with T.block("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = T.max(Y[vi, vj], T.cast(T.float32(0), dtype))
现在让我们检查运行时动态形状推断:
def evaluate_dynamic_shape(lib: tvm.runtime.Module, m: int, n: int, k: int):
A = tvm.nd.array(np.random.uniform(size=(m, k)).astype("float32"))
B = tvm.nd.array(np.random.uniform(size=(k, n)).astype("float32"))
C = tvm.nd.array(np.zeros((m, n), dtype="float32"))
lib(A, B, C)
return C.numpy()
# Compile lib only once
dyn_shape_lib = tvm.build(DynamicShapeModule, target="llvm")
# Able to handle different shapes
print(evaluate_dynamic_shape(dyn_shape_lib, m=4, n=4, k=4))
print(evaluate_dynamic_shape(dyn_shape_lib, m=64, n=64, k=128))
[[0.16451547 0.50090414 0.8583847 0.32155716]
[0.20271252 0.4771819 0.80689687 0.61640453]
[0.33459634 0.8734329 1.1705399 0.66972595]
[0.12425445 0.6644838 0.78279924 0.6951682 ]]
[[27.651514 27.577396 28.004745 ... 26.727785 25.57019 27.509298]
[33.626328 34.74948 35.911846 ... 31.718143 31.96671 33.94121 ]
[34.660847 33.629997 35.93563 ... 32.55892 30.147036 32.397274]
...
[34.610607 34.85859 36.330494 ... 32.244164 30.1908 33.078796]
[29.228226 30.63518 30.514061 ... 28.481937 27.415495 32.225956]
[33.07741 28.754986 35.55928 ... 27.838202 28.615347 30.62801 ]]
使用张量表达式创建TensorIR#
通常,为了更简洁地表达计算,会忽略TensorIR的具体内容,从而导致了TensorIR的实际生成。这就是张量表达式(TE)的相关之处。
张量表达式(TE)是一种特定领域的语言,通过类似表达式的API描述一系列计算。
备注
张量表达式在TVM堆栈中包含两个组件:表达式和调度。表达式是体现计算模式的特定领域语言,正是我们在本节中讨论的内容。相反,TE调度是传统的调度方法,已被TVM Unity堆栈中的TensorIR调度所取代。
创建静态形状函数#
我们使用上一小节中的mm_relu
示例来演示TE创建方法。
from tvm import te
A = te.placeholder((128, 128), "float32", name="A")
B = te.placeholder((128, 128), "float32", name="B")
k = te.reduce_axis((0, 128), "k")
Y = te.compute((128, 128), lambda i, j: te.sum(A[i, k] * B[k, j], axis=k), name="Y")
C = te.compute((128, 128), lambda i, j: te.max(Y[i, j], 0), name="C")
这里te.compute
采用的签名是te.compute(output_shape, fcompute)
。
fcompute函数描述了我们如何计算给定索引的每个元素Y[i, j]
的值:
上述lambda表达式封装了计算:\(Y_{i, j} = \sum_k A_{i, k} \times B_{k, j}\)。定义了计算后,我们可以通过纳入相关的感兴趣参数来构建TensorIR函数。 在这个特定实例中,我们的目标是构建一个具有两个输入参数A, B和一个输出参数C的函数。
te_func = te.create_prim_func([A, B, C]).with_attr({"global_symbol": "mm_relu"})
TEModule = tvm.IRModule({"mm_relu": te_func})
TEModule.show()
# from tvm.script import ir as I
# from tvm.script import tir as T
@I.ir_module
class Module:
@T.prim_func
def mm_relu(A: T.Buffer((128, 128), "float32"), B: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")):
T.func_attr({"tir.noalias": T.bool(True)})
# with T.block("root"):
Y = T.alloc_buffer((128, 128))
for i, j, k in T.grid(128, 128, 128):
with T.block("Y"):
v_i, v_j, v_k = T.axis.remap("SSR", [i, j, k])
T.reads(A[v_i, v_k], B[v_k, v_j])
T.writes(Y[v_i, v_j])
with T.init():
Y[v_i, v_j] = T.float32(0.0)
Y[v_i, v_j] = Y[v_i, v_j] + A[v_i, v_k] * B[v_k, v_j]
for i, j in T.grid(128, 128):
with T.block("C"):
v_i, v_j = T.axis.remap("SS", [i, j])
T.reads(Y[v_i, v_j])
T.writes(C[v_i, v_j])
C[v_i, v_j] = T.max(Y[v_i, v_j], T.float32(0.0))
创建动态形状函数#
我们还可以使用张量表达式创建动态形状函数。唯一的区别是我们需要将输入张量的形状指定为符号变量。
# Declare symbolic variables
M, N, K = te.var("m"), te.var("n"), te.var("k")
A = te.placeholder((M, N), "float32", name="A")
B = te.placeholder((K, N), "float32", name="B")
k = te.reduce_axis((0, K), "k")
Y = te.compute((M, N), lambda i, j: te.sum(A[i, k] * B[k, j], axis=k), name="Y")
C = te.compute((M, N), lambda i, j: te.max(Y[i, j], 0), name="C")
dyn_te_func = te.create_prim_func([A, B, C]).with_attr({"global_symbol": "mm_relu"})
DynamicTEModule = tvm.IRModule({"mm_relu": dyn_te_func})
DynamicTEModule.show()
# from tvm.script import ir as I
# from tvm.script import tir as T
@I.ir_module
class Module:
@T.prim_func
def mm_relu(var_A: T.handle, var_B: T.handle, var_C: T.handle):
T.func_attr({"tir.noalias": T.bool(True)})
m, n = T.int32(), T.int32()
A = T.match_buffer(var_A, (m, n))
k = T.int32()
B = T.match_buffer(var_B, (k, n))
C = T.match_buffer(var_C, (m, n))
# with T.block("root"):
Y = T.alloc_buffer((m, n))
for i, j, k_1 in T.grid(m, n, k):
with T.block("Y"):
v_i, v_j, v_k = T.axis.remap("SSR", [i, j, k_1])
T.reads(A[v_i, v_k], B[v_k, v_j])
T.writes(Y[v_i, v_j])
with T.init():
Y[v_i, v_j] = T.float32(0.0)
Y[v_i, v_j] = Y[v_i, v_j] + A[v_i, v_k] * B[v_k, v_j]
for i, j in T.grid(m, n):
with T.block("C"):
v_i, v_j = T.axis.remap("SS", [i, j])
T.reads(Y[v_i, v_j])
T.writes(C[v_i, v_j])
C[v_i, v_j] = T.max(Y[v_i, v_j], T.float32(0.0))