chore: import upstream snapshot with attribution
Lint / lint (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Windows (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:36:25 +08:00
commit 26446540fa
3151 changed files with 974126 additions and 0 deletions
@@ -0,0 +1,459 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-docstring
# ruff: noqa: F401, F841
import pytest
import tvm
import tvm.testing
from tvm.s_tir import Schedule
from tvm.s_tir.meta_schedule.testing import te_workload
from tvm.s_tir.schedule.analysis import (
TensorizeInfo,
get_auto_tensorize_mapping_info,
get_tensorize_loop_mapping,
is_output_block,
suggest_index_map,
)
from tvm.s_tir.tensor_intrin.cuda import (
WMMA_SYNC_16x16x16_f16f16f16_INTRIN,
WMMA_SYNC_16x16x16_f16f16f32_INTRIN,
)
from tvm.s_tir.tensor_intrin.x86 import dot_product_16x4_u8i8i32_desc
from tvm.script import tirx as T
from tvm.te import create_prim_func
from tvm.tirx import (
Evaluate,
For,
ForKind,
IndexMap,
Var,
decl_buffer,
floordiv,
floormod,
)
from tvm.tirx.analysis import expr_deep_equal
from tvm.tirx.function import TensorIntrin
from tvm.tirx.stmt_functor import pre_order_visit
def _make_vars(*args: str) -> list[Var]:
return [Var(arg, dtype="int32") for arg in args]
def _make_loops(loop_vars: list[Var], extents: list[int]) -> list[For]:
assert len(loop_vars) == len(extents)
return [
For(
loop_var=loop_var,
min=0,
extent=extent,
kind=ForKind.SERIAL,
body=Evaluate(0),
)
for loop_var, extent in zip(loop_vars, extents)
]
def test_suggest_index_map_simple():
i, j = _make_vars("i", "j")
index_map = suggest_index_map(
buffer=decl_buffer(shape=[8, 256]),
indices=[
floordiv(i, 16) * 4 + floordiv(j, 16),
floormod(i, 16) * 16 + floormod(j, 16),
],
loops=_make_loops(
loop_vars=[i, j],
extents=[32, 64],
),
predicate=True,
)
expected_index_map = IndexMap.from_func(
lambda x, y: [
floordiv(x, 4),
floordiv(y, 16),
floormod(x, 4),
floormod(y, 16),
],
)
assert index_map.is_equivalent_to(expected_index_map)
def test_suggest_index_map_bijective():
i, j = _make_vars("i", "j")
index_map = suggest_index_map(
buffer=decl_buffer(shape=[8]),
indices=[floormod(j, 4) * 2 + i],
loops=_make_loops(
loop_vars=[i, j],
extents=[2, 32],
),
predicate=True,
)
expected_index_map = IndexMap.from_func(
lambda x: [
floormod(x, 2),
floordiv(x, 2),
],
)
assert index_map.is_equivalent_to(expected_index_map)
def test_suggest_index_map_winograd():
"""use case in winograd conv where the indices are complicated"""
fused_outer, i3_3_fused, i4_0, i4_1 = _make_vars("fused_outer", "i3_3_fused", "i4_0", "i4_1")
eps = floordiv(fused_outer, 336) * 2 + floordiv(floormod(fused_outer, 16), 8)
nu = floordiv(floormod(fused_outer, 336), 112) * 2 + floordiv(floormod(fused_outer, 8), 4)
co = floormod(fused_outer, 4) * 32 + i3_3_fused
ci = (i4_0 * 32) + i4_1
buffer = decl_buffer(shape=[6, 6, 128, 128])
index_map = suggest_index_map(
buffer=buffer,
indices=[eps, nu, co, ci],
loops=_make_loops(
loop_vars=[fused_outer, i3_3_fused, i4_0, i4_1],
extents=[1008, 32, 4, 32],
),
predicate=True,
)
expected_index_map = IndexMap.from_func(
lambda i0, i1, i2, i3: (
floordiv(i0, 2),
floordiv(i1, 2),
floormod(i0, 2),
floormod(i1, 2) * 4 + floordiv(i2, 32),
floormod(i2, 32),
floordiv(i3, 32),
floormod(i3, 32),
)
)
assert index_map.is_equivalent_to(expected_index_map)
inverse_index_map = index_map.inverse(buffer.shape)
expected_inverse_index_map = IndexMap.from_func(
lambda i0, i1, i2, i3, i4, i5, i6: (
((i0 * 2) + i2),
i1 * 2 + floordiv(i3, 4),
floormod(i3, 4) * 32 + i4,
((i5 * 32) + i6),
)
)
assert inverse_index_map.is_equivalent_to(expected_inverse_index_map)
@tvm.script.ir_module
class DenseTIRModule:
@T.prim_func(s_tir=True)
def main(
placeholder: T.Buffer((1024, 1024), "uint8"),
placeholder_1: T.Buffer((64, 256, 16, 4), "int8"),
compute: T.Buffer((1024, 1024), "int32"),
) -> None:
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
with T.sblock("root"):
T.reads()
T.writes()
for i0, i1, i2 in T.grid(1024, 1024, 1024):
with T.sblock("compute"):
i, j, k = T.axis.remap("SSR", [i0, i1, i2])
T.reads(placeholder[i, k], placeholder_1[j // 16, k // 4, j % 16, k % 4])
T.writes(compute[i, j])
with T.init():
compute[i, j] = 0
compute[i, j] = compute[i, j] + T.cast(placeholder[i, k], "int32") * T.cast(
placeholder_1[j // 16, k // 4, j % 16, k % 4], "int32"
)
@tvm.script.ir_module
class Conv2dNCHWcTIRModule:
@T.prim_func(s_tir=True)
def main(
placeholder: T.Buffer((1, 4, 56, 56, 16), "uint8"),
placeholder_1: T.Buffer((16, 4, 1, 1, 4, 16, 4), "int8"),
conv2d_NCHWc_int8: T.Buffer((1, 16, 56, 56, 16), "int32"),
) -> None:
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
for i0, i1, i2, i3, i4, i5, i6, i7, i8, i9 in T.grid(1, 16, 56, 56, 16, 1, 1, 4, 4, 4):
with T.sblock("conv2d_NCHWc_int8"):
(
n,
oc_chunk,
oh,
ow,
oc_block,
kh,
kw,
ic_outer,
ic_f_inner,
ic_s_inner,
) = T.axis.remap("SSSSSRRRRR", [i0, i1, i2, i3, i4, i5, i6, i7, i8, i9])
T.reads(
placeholder[n, ic_outer, oh + kh, ow + kw, ic_f_inner * 4 + ic_s_inner],
placeholder_1[oc_chunk, ic_outer, kh, kw, ic_f_inner, oc_block, ic_s_inner],
)
T.writes(conv2d_NCHWc_int8[n, oc_chunk, oh, ow, oc_block])
with T.init():
conv2d_NCHWc_int8[n, oc_chunk, oh, ow, oc_block] = 0
conv2d_NCHWc_int8[n, oc_chunk, oh, ow, oc_block] = conv2d_NCHWc_int8[
n, oc_chunk, oh, ow, oc_block
] + T.cast(
placeholder[n, ic_outer, oh + kh, ow + kw, ic_f_inner * 4 + ic_s_inner],
"int32",
) * T.cast(
placeholder_1[oc_chunk, ic_outer, kh, kw, ic_f_inner, oc_block, ic_s_inner],
"int32",
)
def collect_loops(prim_func):
loops = []
def callback(node):
if isinstance(node, tvm.tirx.For):
loops.append(node)
return True
pre_order_visit(prim_func.body, callback)
return loops
def test_get_tensorize_loop_mapping_dense_16x4():
s = Schedule(DenseTIRModule)
block = s.get_sblock("compute")
info = get_tensorize_loop_mapping(s, block, dot_product_16x4_u8i8i32_desc)
assert isinstance(info, TensorizeInfo)
desc_loop_to_sref = dict((v, k) for k, v in info.loop_map.items())
desc_loops = collect_loops(dot_product_16x4_u8i8i32_desc)
_, loop_j, loop_k = s.get_loops(block)
assert desc_loops[0] in desc_loop_to_sref and desc_loops[1] in desc_loop_to_sref
assert s.get(desc_loop_to_sref[desc_loops[0]]) == s.get(loop_j)
assert s.get(desc_loop_to_sref[desc_loops[1]]) == s.get(loop_k)
def test_get_tensorize_loop_mapping_conv2d_nchwc_16x4():
s = Schedule(Conv2dNCHWcTIRModule)
block = s.get_sblock("conv2d_NCHWc_int8")
info = get_tensorize_loop_mapping(s, block, dot_product_16x4_u8i8i32_desc)
desc_loop_to_sref = dict((v, k) for k, v in info.loop_map.items())
desc_loops = collect_loops(dot_product_16x4_u8i8i32_desc)
# i4 corresonds to the inner output channel axis of the NCHWc output tensor
# for i0, i1, i2, i3, i4, i5, i6, i7, i8, i9 in T.grid(1, 16, 56, 56, 16, 1, 1, 4, 4, 4):
_, _, _, _, i4, _, _, _, _, i9 = s.get_loops(block)
assert desc_loops[0] in desc_loop_to_sref and desc_loops[1] in desc_loop_to_sref
assert s.get(desc_loop_to_sref[desc_loops[0]]) == s.get(i4)
assert s.get(desc_loop_to_sref[desc_loops[1]]) == s.get(i9)
def test_get_tensorize_loop_mapping_matmul_mma():
@T.prim_func(s_tir=True)
def matmul_16x16x16xf16f16f16_desc(
A: T.Buffer((16, 16), "float16", align=64, offset_factor=1),
B: T.Buffer((16, 16), "float16", align=64, offset_factor=1),
C: T.Buffer((16, 16), "float16", align=64, offset_factor=1),
) -> None:
with T.sblock("root"):
T.reads(C[0:16, 0:16], A[0:16, 0:16], B[0:16, 0:16])
T.writes(C[0:16, 0:16])
for i, j, k in T.grid(16, 16, 16):
with T.sblock("update"):
vii, vjj, vkk = T.axis.remap("SSR", [i, j, k])
C[vii, vjj] = C[vii, vjj] + A[vii, vkk] * B[vjj, vkk]
matmul = create_prim_func(
te_workload.matmul_relu(
n=512,
m=512,
k=512,
)
)
s = Schedule(matmul)
block = s.get_sblock("C")
i0, i1, i2 = s.get_loops(block)
desc_loops = collect_loops(matmul_16x16x16xf16f16f16_desc)
for do_reorder in [False, True]:
# Mapping should be invariant to the loop permutation
if do_reorder:
s.reorder(i2, i0, i1)
info = get_tensorize_loop_mapping(s, block, matmul_16x16x16xf16f16f16_desc)
assert info is not None
desc_loop_to_sref = dict((v, k) for k, v in info.loop_map.items())
for i in range(3):
assert desc_loops[i] in desc_loop_to_sref
assert s.get(desc_loop_to_sref[desc_loops[0]]) == s.get(i0)
assert s.get(desc_loop_to_sref[desc_loops[1]]) == s.get(i1)
assert s.get(desc_loop_to_sref[desc_loops[2]]) == s.get(i2)
def test_get_tensorize_loop_mapping_padding_matmul():
matmul = create_prim_func(
te_workload.matmul_relu(
n=127,
m=256,
k=65,
in_dtype="float16",
out_dtype="float16",
)
)
s = Schedule(matmul)
block = s.get_sblock("C")
desc = TensorIntrin.get(WMMA_SYNC_16x16x16_f16f16f16_INTRIN).desc
info = get_tensorize_loop_mapping(s, block, desc, allow_padding=True)
assert info is not None
expected_padding = [16, 1, 16]
actual_padding = info.block_iter_paddings
assert actual_padding is not None
assert len(actual_padding) == len(expected_padding)
for actual, expected in zip(actual_padding, expected_padding):
assert actual == expected
def check_index_map(workload, block_name, intrin_name, expected_index_map):
s = Schedule(workload)
block = s.get_sblock(block_name)
desc_func = TensorIntrin.get(intrin_name).desc
info = get_auto_tensorize_mapping_info(s, block, desc_func)
if expected_index_map is None:
assert info is None
return
assert len(info.mappings) == 1
assert IndexMap.from_func(expected_index_map).is_equivalent_to(info.mappings[0])
def test_get_auto_tensorize_mapping_info_conv2d():
conv2d = create_prim_func(
te_workload.conv2d_nhwc(4, 16, 16, 64, 64, 3, 1, 1, in_dtype="float16", out_dtype="float32")
)
check_index_map(
conv2d,
"conv2d_nhwc",
WMMA_SYNC_16x16x16_f16f16f32_INTRIN,
lambda n, h, w, c, rh, rw, rc: (n * 256 + h * 16 + w, c, rh * 192 + rw * 64 + rc),
)
def test_get_auto_tensorize_mapping_info_conv2d_unit_batch():
conv2d = create_prim_func(
te_workload.conv2d_nhwc(1, 16, 16, 64, 64, 3, 1, 1, in_dtype="float16", out_dtype="float32")
)
check_index_map(
conv2d,
"conv2d_nhwc",
WMMA_SYNC_16x16x16_f16f16f32_INTRIN,
lambda n, h, w, c, rh, rw, rc: (n * 256 + h * 16 + w, c, rh * 192 + rw * 64 + rc),
)
@pytest.mark.parametrize("b,m,n,k", [(1, 512, 512, 512), (16, 32, 32, 32)])
def test_get_auto_tensorize_mapping_info_batch_matmul(b, m, n, k):
matmul = create_prim_func(
te_workload.batch_matmul_nkkm(b, m, n, k, in_dtype="float16", out_dtype="float32")
)
check_index_map(
matmul, "Z", WMMA_SYNC_16x16x16_f16f16f32_INTRIN, lambda b, m, n, k: (b, m, n, k)
)
@pytest.mark.parametrize(
"n,m,k,expected",
[
(
512,
512,
512,
lambda n, m, k: (
n,
m,
k,
),
),
(1, 32, 32, lambda n, m, k: (n, m, k)),
],
)
def test_get_auto_tensorize_mapping_info_matmul(n, m, k, expected):
matmul = create_prim_func(te_workload.matmul(n, m, k, in_dtype="float16", out_dtype="float32"))
check_index_map(matmul, "C", WMMA_SYNC_16x16x16_f16f16f32_INTRIN, expected)
def test_is_output_block():
@T.prim_func(s_tir=True)
def two_elementwise(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128), "float32")
B = T.sblock_alloc_buffer((128, 128), "float32")
C = T.match_buffer(c, (128, 128), "float32")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + 1.0
sch = tvm.s_tir.Schedule(two_elementwise)
block_rv = sch.get_sblock("C")
assert is_output_block(sch, block_rv)
def test_empty_grid():
@T.prim_func(s_tir=True)
def foo(out: T.Buffer((T.int64(1), T.int64(8), T.int64(8)), "int32")):
act = T.sblock_alloc_buffer((1, 8, 8), "int32")
for z2, y2, x2 in T.grid(1, 8, 8):
with T.sblock("b0"):
az, ay, ax = T.axis.remap("SSS", [z2, y2, x2])
T.writes(act[az, ay, ax])
act[az, ay, az] = T.int32(0)
# Empty grid:
for z1, y1, x1 in T.grid(0, 8, 8):
with T.sblock("b1"):
az, ay, ax = T.axis.remap("SSS", [z1, y1, x1])
T.reads(act[az + 1, ay, ax])
T.writes(out[az, ay, ax])
out[az, ay, ax] = act[az + 1, ay, ax]
# The block below is not needed to show the bug, but the 'out'
# buffer would be undefined without it.
for z2, y2, x2 in T.grid(1, 8, 8):
with T.sblock("b2"):
az, ay, ax = T.axis.remap("SSS", [z2, y2, x2])
T.writes(out[az, ay, ax])
out[az, ay, az] = T.int32(0)
# This caused a crash before.
sch = tvm.s_tir.Schedule(foo)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,333 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: E501, F401
import tvm
import tvm.testing
from tvm import tirx
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.script import tirx as T
def test_annotate_read_buffer_access():
@T.prim_func(s_tir=True)
def before(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")):
B = T.sblock_alloc_buffer((128, 128), "float32")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def expected(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")):
B = T.sblock_alloc_buffer((128, 128), "float32")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(A[vi - 1 : vi - 1 + 2, vj - 1 : vj - 1 + 2])
T.writes(B[vi, vj])
T.sblock_attr({"explicit_read_region": [0]})
B[vi, vj] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + 1.0
sch = tvm.s_tir.Schedule(before, debug_mask="all")
block = sch.get_sblock("B")
sch.annotate_buffer_access(
block, 0, "read", lambda vi, vj: ((vi - 1, vi + 1), (vj - 1, vj + 1))
)
assert_structural_equal_ignore_global_symbol(sch.mod["main"], expected)
verify_trace_roundtrip(sch=sch, mod=before)
def test_annotate_write_buffer_access():
@T.prim_func(s_tir=True)
def before(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")):
B = T.sblock_alloc_buffer((128, 128), "float32")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def expected(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")):
B = T.sblock_alloc_buffer((128, 128), "float32")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(A[vi, vj])
T.writes(B[vi : vi + 2, vj : vj + 2])
T.sblock_attr({"explicit_write_region": [0]})
B[vi, vj] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + 1.0
sch = tvm.s_tir.Schedule(before, debug_mask="all")
block = sch.get_sblock("B")
sch.annotate_buffer_access(block, 0, "write", lambda vi, vj: ((vi, vi + 2), (vj, vj + 2)))
assert_structural_equal_ignore_global_symbol(sch.mod["main"], expected)
verify_trace_roundtrip(sch=sch, mod=before)
def test_annotate_buffer_access_for_resize():
# fmt: off
@T.prim_func(s_tir=True)
def resize_before(x: T.Buffer((1, 1, 32, 32), "float16"), resize: T.Buffer((1, 1, 16, 16), "float16")):
for i0, i1, i2, i3 in T.grid(1, 1, 16, 16):
with T.sblock("resize"):
v_i0, v_i1, v_i2, v_i3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
T.reads(x[v_i0, v_i1, 0:32, 0:32])
T.writes(resize[v_i0, v_i1, v_i2, v_i3])
resize[v_i0, v_i1, v_i2, v_i3] = T.Cast("float16", T.Cast("float32", x[v_i0, v_i1, T.max(T.min(T.Cast("int32", T.floor((T.Cast("float32", v_i2) + T.float32(0.5)) * T.float32(2) - T.float32(0.5) + T.float32(1.0000000000000001e-05))), 31), 0), T.max(T.min(T.Cast("int32", T.floor((T.Cast("float32", v_i3) + T.float32(0.5)) * T.float32(2) - T.float32(0.5) + T.float32(1.0000000000000001e-05))), 31), 0)]))
@T.prim_func(s_tir=True)
def resize_expected(x: T.Buffer((1, 1, 32, 32), "float16"), resize: T.Buffer((1, 1, 16, 16), "float16")):
for i0, i1, i2, i3 in T.grid(1, 1, 16, 16):
with T.sblock("resize"):
v_i0, v_i1, v_i2, v_i3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
T.reads(x[v_i0, v_i1, v_i2 * 2 - 3:v_i2 * 2 + 3, v_i3 * 2 - 3:v_i3 * 2 + 3])
T.writes(resize[v_i0, v_i1, v_i2, v_i3])
T.sblock_attr({"explicit_read_region": [0]})
resize[v_i0, v_i1, v_i2, v_i3] = T.Cast("float16", T.Cast("float32", x[v_i0, v_i1, T.max(T.min(T.Cast("int32", T.floor((T.Cast("float32", v_i2) + T.float32(0.5)) * T.float32(2) - T.float32(0.5) + T.float32(1.0000000000000001e-05))), 31), 0), T.max(T.min(T.Cast("int32", T.floor((T.Cast("float32", v_i3) + T.float32(0.5)) * T.float32(2) - T.float32(0.5) + T.float32(1.0000000000000001e-05))), 31), 0)]))
# fmt: on
sch = tvm.s_tir.Schedule(resize_before, debug_mask="all")
block = sch.get_sblock("resize")
sch.annotate_buffer_access(
block,
0,
"read",
gen_new_ranges=lambda v_i0, v_i1, v_i2, v_i3: [
v_i0,
v_i1,
(v_i2 * 2 - 3, v_i2 * 2 + 3),
(v_i3 * 2 - 3, v_i3 * 2 + 3),
],
)
assert_structural_equal_ignore_global_symbol(sch.mod["main"], resize_expected)
verify_trace_roundtrip(sch=sch, mod=resize_before)
def test_annotate_buffer_access_read_and_write():
@T.prim_func(s_tir=True)
def before(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")):
B = T.sblock_alloc_buffer((128, 128), "float32")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(A[vi, vj])
T.writes(B[vi, vj])
B[vi, vj] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(B[vi, vj])
T.writes(C[vi, vj])
C[vi, vj] = B[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def expected(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")):
B = T.sblock_alloc_buffer((128, 128), "float32")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(A[vi - 1 : vi + 2, vj - 1 : vj + 2])
T.writes(B[vi : vi + 2, vj : vj + 2])
T.sblock_attr({"explicit_read_region": [0], "explicit_write_region": [0]})
B[vi, vj] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(B[vi, vj])
T.writes(C[vi, vj])
C[vi, vj] = B[vi, vj] + 1.0
sch = tvm.s_tir.Schedule(before, debug_mask="all")
block = sch.get_sblock("B")
sch.annotate_buffer_access(
block, 0, "read", lambda vi, vj: ((vi - 1, vi + 2), (vj - 1, vj + 2))
)
sch.annotate_buffer_access(block, 0, "write", lambda vi, vj: ((vi, vi + 2), (vj, vj + 2)))
assert_structural_equal_ignore_global_symbol(sch.mod["main"], expected)
verify_trace_roundtrip(sch=sch, mod=before)
def test_double_annotate_buffer_access_read():
@T.prim_func(s_tir=True)
def before(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")):
B = T.sblock_alloc_buffer((128, 128), "float32")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(A[vi, vj])
T.writes(B[vi, vj])
B[vi, vj] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(B[vi, vj])
T.writes(C[vi, vj])
C[vi, vj] = B[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def expected(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")):
B = T.sblock_alloc_buffer((128, 128), "float32")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(A[vi - 2 : vi + 3, vj - 2 : vj + 3])
T.writes(B[vi, vj])
T.sblock_attr({"explicit_read_region": [0]})
B[vi, vj] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(B[vi, vj])
T.writes(C[vi, vj])
C[vi, vj] = B[vi, vj] + 1.0
sch = tvm.s_tir.Schedule(before, debug_mask="all")
block = sch.get_sblock("B")
sch.annotate_buffer_access(
block, 0, "read", lambda vi, vj: ((vi - 1, vi + 2), (vj - 1, vj + 2))
)
sch.annotate_buffer_access(
block, 0, "read", lambda vi, vj: ((vi - 2, vi + 3), (vj - 2, vj + 3))
)
assert_structural_equal_ignore_global_symbol(sch.mod["main"], expected)
verify_trace_roundtrip(sch=sch, mod=before)
def test_annotate_buffer_access_with_compute_at_for_resize():
# fmt: off
@T.prim_func(s_tir=True)
def before(x: T.Buffer((1, 3, 200, 200), "float32"), y: T.Buffer((1, 3, 100, 100), "float32")):
x_global = T.sblock_alloc_buffer([1, 3, 200, 200], dtype="float32")
for ax0, ax1, ax2, ax3 in T.grid(1, 3, 200, 200):
with T.sblock("cache"):
v0, v1, v2, v3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
x_global[v0, v1, v2, v3] = x[v0, v1, v2, v3]
for i0, i1, i2, i3 in T.grid(1, 3, 100, 100):
with T.sblock("resize"):
v_i0, v_i1, v_i2, v_i3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
y[v_i0, v_i1, v_i2, v_i3] = x_global[v_i0, v_i1, T.Cast("int32", T.floor(v_i2 * 2 + 0.5)), T.Cast("int32", T.floor(v_i3 * 2 + 0.5))]
@T.prim_func(s_tir=True)
def after(x: T.Buffer((1, 3, 200, 200), "float32"), y: T.Buffer((1, 3, 100, 100), "float32")):
x_global = T.sblock_alloc_buffer((1, 3, 200, 200))
for i0, i1, i2_0, i3_0 in T.grid(1, 3, 10, 10):
for ax0, ax1 in T.grid(24, 24):
with T.sblock("cache"):
v0 = T.axis.spatial(1, 0)
v1 = T.axis.spatial(3, i1)
v2 = T.axis.spatial(200, i2_0 * 20 - 3 + ax0)
v3 = T.axis.spatial(200, i3_0 * 20 - 3 + ax1)
T.where(3 <= i2_0 * 20 + ax0 and i2_0 * 20 + ax0 < 203 and 3 <= i3_0 * 20 + ax1 and i3_0 * 20 + ax1 < 203)
T.reads(x[v0, v1, v2, v3])
T.writes(x_global[v0, v1, v2, v3])
x_global[v0, v1, v2, v3] = x[v0, v1, v2, v3]
for i2_1, i3_1 in T.grid(10, 10):
with T.sblock("resize"):
v_i0, v_i1 = T.axis.remap("SS", [i0, i1])
v_i2 = T.axis.spatial(100, i2_0 * 10 + i2_1)
v_i3 = T.axis.spatial(100, i3_0 * 10 + i3_1)
T.reads(x_global[v_i0, v_i1, v_i2 * 2 - 3:v_i2 * 2 - 3 + 6, v_i3 * 2 - 3:v_i3 * 2 - 3 + 6])
T.writes(y[v_i0, v_i1, v_i2, v_i3])
T.sblock_attr({"explicit_read_region": [0]})
y[v_i0, v_i1, v_i2, v_i3] = x_global[v_i0, v_i1, T.Cast("int32", T.floor(T.Cast("float32", v_i2 * 2) + T.float32(0.5))), T.Cast("int32", T.floor(T.Cast("float32", v_i3 * 2) + T.float32(0.5)))]
@T.prim_func(s_tir=True)
def after_without_annotate_buffer_access(x: T.Buffer((1, 3, 200, 200), "float32"), y: T.Buffer((1, 3, 100, 100), "float32")):
x_global = T.sblock_alloc_buffer((1, 3, 200, 200))
for i0, i1, i2_0, i3_0 in T.grid(1, 3, 10, 10):
for ax0, ax1 in T.grid(200, 200):
with T.sblock("cache"):
v0 = T.axis.spatial(1, 0)
v1, v2, v3 = T.axis.remap("SSS", [i1, ax0, ax1])
T.reads(x[v0, v1, v2, v3])
T.writes(x_global[v0, v1, v2, v3])
x_global[v0, v1, v2, v3] = x[v0, v1, v2, v3]
for i2_1, i3_1 in T.grid(10, 10):
with T.sblock("resize"):
v_i0, v_i1 = T.axis.remap("SS", [i0, i1])
v_i2 = T.axis.spatial(100, i2_0 * 10 + i2_1)
v_i3 = T.axis.spatial(100, i3_0 * 10 + i3_1)
T.reads(x_global[v_i0, v_i1, 0:200, 0:200])
T.writes(y[v_i0, v_i1, v_i2, v_i3])
y[v_i0, v_i1, v_i2, v_i3] = x_global[v_i0, v_i1, T.Cast("int32", T.floor(T.Cast("float32", v_i2 * 2) + T.float32(0.5))), T.Cast("int32", T.floor(T.Cast("float32", v_i3 * 2) + T.float32(0.5)))]
# fmt: on
# Schedule with annotate_buffer_access
sch = tvm.s_tir.Schedule(before, debug_mask="all")
block = sch.get_sblock("resize")
cache_block = sch.get_sblock("cache")
# Annotate buffer access
sch.annotate_buffer_access(
block,
0,
"read",
lambda vn, vc, vh, vw: (vn, vc, (vh * 2 - 3, vh * 2 + 3), (vw * 2 - 3, vw * 2 + 3)),
)
h, w = sch.get_loops(block)[-2:]
ho, hi = sch.split(h, factors=[10, 10])
wo, wi = sch.split(w, factors=[10, 10])
sch.reorder(ho, wo, hi, wi)
sch.compute_at(cache_block, wo)
assert_structural_equal_ignore_global_symbol(sch.mod["main"], after)
verify_trace_roundtrip(sch=sch, mod=before)
# Schedule without annotate_buffer_access
sch_without_annotate = tvm.s_tir.Schedule(before, debug_mask="all")
block_without_annotate = sch_without_annotate.get_sblock("resize")
cache_block_without_annotate = sch_without_annotate.get_sblock("cache")
h, w = sch_without_annotate.get_loops(block_without_annotate)[-2:]
ho, hi = sch_without_annotate.split(h, factors=[10, 10])
wo, wi = sch_without_annotate.split(w, factors=[10, 10])
sch_without_annotate.reorder(ho, wo, hi, wi)
sch_without_annotate.compute_at(cache_block_without_annotate, wo)
assert_structural_equal_ignore_global_symbol(
sch_without_annotate.mod["main"], after_without_annotate_buffer_access
)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,157 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: F401
import sys
import pytest
import tvm
import tvm.testing
from tvm import s_tir, tirx
from tvm.s_tir.schedule import DepKind
from tvm.script import tirx as T
from tvm.tirx.stmt_functor import post_order_visit
# pylint: disable=no-member,invalid-name,unused-variable
@T.prim_func(s_tir=True)
def elementwise(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128), "float32")
C = T.match_buffer(c, (128, 128), "float32")
B = T.sblock_alloc_buffer((128, 128), "float32")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def matmul(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
B = T.match_buffer(b, [128, 128])
C = T.match_buffer(c, [128, 128])
for i, j in T.grid(128, 128):
with T.sblock("init"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = T.float32(0)
for k in range(0, 128):
with T.sblock("update"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
@T.prim_func(s_tir=True)
def war_dependency(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
B = T.match_buffer(b, (128, 128))
C = T.match_buffer(c, (128, 128))
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + 1.0
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
# pylint: enable=no-member,invalid-name,unused-variable
# pylint: disable=invalid-name
def _get_sblock(s: s_tir.ScheduleState, name_hint: str) -> s_tir.StmtSRef:
result = None
def f_visit(node):
nonlocal result
if isinstance(node, tvm.tirx.SBlock) and node.name_hint == name_hint:
result = node
func = s.mod["main"]
post_order_visit(func.body, f_visit)
assert result is not None and isinstance(result, tvm.tirx.SBlock)
return s.get_sref(result)
def test_elementwise_dependency():
s = tvm.s_tir.ScheduleState(elementwise, debug_mask="all")
root = _get_sblock(s, "root")
block_b = _get_sblock(s, "B")
block_c = _get_sblock(s, "C")
# Check get_deps_by_src
(dep,) = s.get_sblock_scope(root).get_deps_by_src(block_b)
assert dep.src.same_as(block_b)
assert dep.dst.same_as(block_c)
assert dep.kind == DepKind.RAW
# Check get_deps_by_dst
(dep,) = s.get_sblock_scope(root).get_deps_by_dst(block_c)
assert dep.src.same_as(block_b)
assert dep.dst.same_as(block_c)
assert dep.kind == DepKind.RAW
def test_matmul_dependency():
s = tvm.s_tir.ScheduleState(matmul, debug_mask="all")
root = _get_sblock(s, "root")
init = _get_sblock(s, "init")
update = _get_sblock(s, "update")
# Check get_deps_by_src
p0, p1 = s.get_sblock_scope(root).get_deps_by_src(init)
assert p0.src.same_as(init)
assert p0.dst.same_as(update)
assert p1.src.same_as(init)
assert p1.dst.same_as(update)
assert (p0.kind == DepKind.RAW and p1.kind == DepKind.WAW) or (
p0.kind == DepKind.WAW and p1.kind == DepKind.RAW
)
# Check get_deps_by_dst
p0, p1 = s.get_sblock_scope(root).get_deps_by_dst(update)
assert p0.src.same_as(init)
assert p0.dst.same_as(update)
assert p1.src.same_as(init)
assert p1.dst.same_as(update)
assert (p0.kind == DepKind.RAW and p1.kind == DepKind.WAW) or (
p0.kind == DepKind.WAW and p1.kind == DepKind.RAW
)
def test_war_dependency():
s = tvm.s_tir.ScheduleState(war_dependency, debug_mask="all")
root = _get_sblock(s, "root")
block_c = _get_sblock(s, "C")
block_b = _get_sblock(s, "B")
# Check get_deps_by_src
(dep,) = s.get_sblock_scope(root).get_deps_by_src(block_c)
assert dep.src.same_as(block_c)
assert dep.dst.same_as(block_b)
assert dep.kind == DepKind.WAR
# Check get_deps_by_dst
(dep,) = s.get_sblock_scope(root).get_deps_by_dst(block_b)
assert dep.src.same_as(block_c)
assert dep.dst.same_as(block_b)
assert dep.kind == DepKind.WAR
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,375 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: F401, F841
import pytest
import tvm
import tvm.testing
from tvm import tirx
from tvm.s_tir.schedule.testing import verify_trace_roundtrip
from tvm.script import tirx as T
# fmt: off
# pylint: disable=no-member,invalid-name,unused-variable,line-too-long,redefined-outer-name,unexpected-keyword-arg,too-many-nested-blocks
@T.prim_func(s_tir=True)
def single_elementwise(A: T.Buffer((128, 128), "float32"), B: T.Buffer((128, 128), "float32")):
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
# fmt: on
# pylint: disable=no-member,invalid-name,unused-variable,line-too-long,redefined-outer-name,unexpected-keyword-arg,too-many-nested-blocks
def test_blockize_outer():
@T.prim_func(s_tir=True)
def after_blockize_outer(
A: T.Buffer((128, 128), "float32"),
B: T.Buffer((128, 128), "float32"),
) -> None:
with T.sblock("blockized_B"):
vio = T.axis.spatial(1, 0)
vjo = T.axis.spatial(1, 0)
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
func = single_elementwise
s = tvm.s_tir.Schedule(func, debug_mask="all")
x, _ = s.get_loops(s.get_sblock("B"))
s.blockize(x)
tvm.ir.assert_structural_equal(
s.mod["main"], after_blockize_outer.with_attr("global_symbol", "single_elementwise")
)
verify_trace_roundtrip(sch=s, mod=func)
def test_blockize_inner():
@T.prim_func(s_tir=True)
def after_blockize_inner(
A: T.Buffer((128, 128), "float32"),
B: T.Buffer((128, 128), "float32"),
) -> None:
for i in T.serial(128):
with T.sblock("blockized_B"):
vi = T.axis.spatial(128, i)
vjo = T.axis.spatial(1, 0)
for j in T.serial(128):
with T.sblock("B"):
vj = T.axis.remap("S", [j])
B[vi, vj] = A[vi, vj] * 2.0
func = single_elementwise
s = tvm.s_tir.Schedule(func, debug_mask="all")
_, y = s.get_loops(s.get_sblock("B"))
s.blockize(y)
tvm.ir.assert_structural_equal(
s.mod["main"], after_blockize_inner.with_attr("global_symbol", "single_elementwise")
)
verify_trace_roundtrip(sch=s, mod=func)
def test_two_elementwise_blockize_reverse_compute_at():
@T.prim_func(s_tir=True)
def before_blockize_rca(
A: T.Buffer((128, 128), "float32"),
C: T.Buffer((128, 128), "float32"),
) -> None:
B = T.sblock_alloc_buffer([128, 128], dtype="float32")
for i, j in T.grid(8, 8):
with T.sblock("B_o"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(A[vi * 16 : vi * 16 + 16, vj * 16 : vj * 16 + 16])
T.writes(B[vi * 16 : vi * 16 + 16, vj * 16 : vj * 16 + 16])
for i_1, j_1 in T.grid(16, 16):
with T.sblock("B"):
vi_i, vj_i = T.axis.remap("SS", [i_1, j_1])
T.reads(A[vi * 16 + vi_i, vj * 16 + vj_i])
T.writes(B[vi * 16 + vi_i, vj * 16 + vj_i])
B[vi * 16 + vi_i, vj * 16 + vj_i] = A[vi * 16 + vi_i, vj * 16 + vj_i] * 2.0
for ax0, ax1 in T.grid(16, 16):
with T.sblock("C"):
vi = T.axis.spatial(128, i * 16 + ax0)
vj = T.axis.spatial(128, j * 16 + ax1)
T.reads(B[vi, vj])
T.writes(C[vi, vj])
C[vi, vj] = B[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def after_blockize_rca(
A: T.Buffer((128, 128), "float32"),
C: T.Buffer((128, 128), "float32"),
) -> None:
B = T.sblock_alloc_buffer([128, 128], dtype="float32")
for i, j in T.grid(8, 8):
with T.sblock("B_o"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(A[vi * 16 : vi * 16 + 16, vj * 16 : vj * 16 + 16])
T.writes(B[vi * 16 : vi * 16 + 16, vj * 16 : vj * 16 + 16])
for i_1, j_1 in T.grid(16, 16):
with T.sblock("B"):
vi_i, vj_i = T.axis.remap("SS", [i_1, j_1])
T.reads(A[vi * 16 + vi_i, vj * 16 + vj_i])
T.writes(B[vi * 16 + vi_i, vj * 16 + vj_i])
B[vi * 16 + vi_i, vj * 16 + vj_i] = A[vi * 16 + vi_i, vj * 16 + vj_i] * 2.0
with T.sblock("C_o"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(B[vi * 16 : vi * 16 + 16, vj * 16 : vj * 16 + 16])
T.writes(C[vi * 16 : vi * 16 + 16, vj * 16 : vj * 16 + 16])
for ax0, ax1 in T.grid(16, 16):
with T.sblock("C"):
vi_i, vj_i = T.axis.remap("SS", [ax0, ax1])
T.reads(B[vi * 16 + vi_i, vj * 16 + vj_i])
T.writes(C[vi * 16 + vi_i, vj * 16 + vj_i])
C[vi * 16 + vi_i, vj * 16 + vj_i] = B[vi * 16 + vi_i, vj * 16 + vj_i] + 1.0
func = before_blockize_rca
s = tvm.s_tir.Schedule(func, debug_mask="all")
_, _, x, _ = s.get_loops(s.get_sblock("C"))
s.blockize(x)
tvm.ir.assert_structural_equal(
s.mod["main"], after_blockize_rca.with_attr("global_symbol", "before_blockize_rca")
)
verify_trace_roundtrip(sch=s, mod=func)
def test_two_elementwise_blockize_compute_at():
@T.prim_func(s_tir=True)
def before_blockize_compute_at(
A: T.Buffer((128, 128), "float32"),
C: T.Buffer((128, 128), "float32"),
) -> None:
# body
# with T.sblock("root")
B = T.sblock_alloc_buffer([128, 128], dtype="float32")
for i_0, j_0 in T.grid(8, 8):
for ax0, ax1 in T.grid(16, 16):
with T.sblock("B"):
vi = T.axis.spatial(128, i_0 * 16 + ax0)
vj = T.axis.spatial(128, j_0 * 16 + ax1)
T.reads(A[vi, vj])
T.writes(B[vi, vj])
B[vi, vj] = A[vi, vj] * 2.0
with T.sblock("C_o"):
vi_o, vj_o = T.axis.remap("SS", [i_0, j_0])
T.reads(B[vi_o * 16 : vi_o * 16 + 16, vj_o * 16 : vj_o * 16 + 16])
T.writes(C[vi_o * 16 : vi_o * 16 + 16, vj_o * 16 : vj_o * 16 + 16])
for i_1, j_1 in T.grid(16, 16):
with T.sblock("C"):
vi_i, vj_i = T.axis.remap("SS", [i_1, j_1])
T.reads(B[vi_o * 16 + vi_i, vj_o * 16 + vj_i])
T.writes(C[vi_o * 16 + vi_i, vj_o * 16 + vj_i])
C[vi_o * 16 + vi_i, vj_o * 16 + vj_i] = (
B[vi_o * 16 + vi_i, vj_o * 16 + vj_i] + 1.0
)
@T.prim_func(s_tir=True)
def after_blockize_compute_at(
A: T.Buffer((128, 128), "float32"),
C: T.Buffer((128, 128), "float32"),
) -> None:
B = T.sblock_alloc_buffer([128, 128], dtype="float32")
for i_0, j_0 in T.grid(8, 8):
with T.sblock("B_o"):
vi_o, vj_o = T.axis.remap("SS", [i_0, j_0])
T.reads(A[vi_o * 16 : vi_o * 16 + 16, vj_o * 16 : vj_o * 16 + 16])
T.writes(B[vi_o * 16 : vi_o * 16 + 16, vj_o * 16 : vj_o * 16 + 16])
for ax0, ax1 in T.grid(16, 16):
with T.sblock("B"):
vi_i, vj_i = T.axis.remap("SS", [ax0, ax1])
T.reads(A[vi_o * 16 + vi_i, vj_o * 16 + vj_i])
T.writes(B[vi_o * 16 + vi_i, vj_o * 16 + vj_i])
B[vi_o * 16 + vi_i, vj_o * 16 + vj_i] = (
A[vi_o * 16 + vi_i, vj_o * 16 + vj_i] * 2.0
)
with T.sblock("C_o"):
vi_o, vj_o = T.axis.remap("SS", [i_0, j_0])
T.reads(B[vi_o * 16 : vi_o * 16 + 16, vj_o * 16 : vj_o * 16 + 16])
T.writes(C[vi_o * 16 : vi_o * 16 + 16, vj_o * 16 : vj_o * 16 + 16])
for i_1, j_1 in T.grid(16, 16):
with T.sblock("C"):
vi_i, vj_i = T.axis.remap("SS", [i_1, j_1])
T.reads(B[vi_o * 16 + vi_i, vj_o * 16 + vj_i])
T.writes(C[vi_o * 16 + vi_i, vj_o * 16 + vj_i])
C[vi_o * 16 + vi_i, vj_o * 16 + vj_i] = (
B[vi_o * 16 + vi_i, vj_o * 16 + vj_i] + 1.0
)
func = before_blockize_compute_at
s = tvm.s_tir.Schedule(func, debug_mask="all")
_, _, x, _ = s.get_loops(s.get_sblock("B"))
s.blockize(x)
tvm.ir.assert_structural_equal(
s.mod["main"],
after_blockize_compute_at.with_attr("global_symbol", "before_blockize_compute_at"),
)
verify_trace_roundtrip(sch=s, mod=func)
def test_blockize_init_loops():
@T.prim_func(s_tir=True)
def rowsum(A: T.Buffer((128, 128), "float32"), B: T.Buffer((128,), "float32")) -> None:
for k, i in T.grid(128, 128):
with T.sblock("B"):
vk, vi = T.axis.remap("RS", [k, i])
with T.init():
B[vi] = 0.0
B[vi] = B[vi] + A[vi, vk]
@T.prim_func(s_tir=True)
def after_rowsum_blockize(
A: T.Buffer((128, 128), "float32"),
B: T.Buffer((128,), "float32"),
) -> None:
with T.sblock("blockized_B"):
vko = T.axis.R(1, 0)
vio = T.axis.S(1, 0)
with T.init():
for i1 in T.serial(0, 128):
with T.sblock("B_init"):
vi_init = T.axis.S(128, i1)
B[vi_init] = T.float32(0)
for i0, i1_1 in T.grid(128, 128):
with T.sblock("B"):
vk, vi = T.axis.remap("RS", [i0, i1_1])
B[vi] = B[vi] + A[vi, vk]
s = tvm.s_tir.Schedule(rowsum, debug_mask="all")
k, _ = s.get_loops(s.get_sblock("B"))
s.blockize(k)
tvm.ir.assert_structural_equal(
s.mod["main"], after_rowsum_blockize.with_attr("global_symbol", "rowsum")
)
verify_trace_roundtrip(sch=s, mod=rowsum)
@pytest.mark.parametrize("preserve_unit_iters", [True, False])
def test_blockize_outer_int64_shape(preserve_unit_iters):
@T.prim_func(s_tir=True)
def single_elementwise_int64(
A: T.Buffer((T.int64(16), T.int64(128)), "float32"),
B: T.Buffer((T.int64(16), T.int64(128)), "float32"),
) -> None:
for i0, j0, i1, j1 in T.grid(T.int64(1), T.int64(8), T.int64(16), T.int64(16)):
with T.sblock("B"):
vi = T.axis.S(T.int64(16), i0 * T.int64(16) + i1)
vj = T.axis.S(T.int64(128), j0 * T.int64(16) + j1)
B[vi, vj] = A[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def after_single_elementwise_int64_blockize(
A: T.Buffer((T.int64(16), T.int64(128)), "float32"),
B: T.Buffer((T.int64(16), T.int64(128)), "float32"),
) -> None:
for i0, j0 in T.grid(T.int64(1), T.int64(8)):
with T.sblock("B_o"):
vi_o = T.axis.spatial(T.int64(1), T.int64(0))
vj_o = T.axis.spatial(T.int64(8), j0)
for i1, j1 in T.grid(T.int64(16), T.int64(16)):
with T.sblock("B"):
vi_i, vj_i = T.axis.remap("SS", [i1, j1])
B[vi_i, vj_o * T.int64(16) + vj_i] = A[
vi_i, vj_o * T.int64(16) + vj_i
] + T.float32(1)
@T.prim_func(s_tir=True)
def after_single_elementwise_int64_blockize_preserve_unit_iters(
A: T.Buffer((T.int64(16), T.int64(128)), "float32"),
B: T.Buffer((T.int64(16), T.int64(128)), "float32"),
) -> None:
for i0, j0 in T.grid(T.int64(1), T.int64(8)):
with T.sblock("B_o"):
vi_o = T.axis.spatial(T.int64(1), i0)
vj_o = T.axis.spatial(T.int64(8), j0)
for i1, j1 in T.grid(T.int64(16), T.int64(16)):
with T.sblock("B"):
vi_i, vj_i = T.axis.remap("SS", [i1, j1])
B[vi_i, vj_o * T.int64(16) + vj_i] = A[
vi_i, vj_o * T.int64(16) + vj_i
] + T.float32(1)
s = tvm.s_tir.Schedule(single_elementwise_int64, debug_mask="all")
_, _, i1, _ = s.get_loops(s.get_sblock("B"))
s.blockize(i1, preserve_unit_iters=preserve_unit_iters)
expected = (
after_single_elementwise_int64_blockize_preserve_unit_iters
if preserve_unit_iters
else after_single_elementwise_int64_blockize
)
tvm.ir.assert_structural_equal(
s.mod["main"], expected.with_attr("global_symbol", "single_elementwise_int64")
)
verify_trace_roundtrip(sch=s, mod=single_elementwise_int64)
def test_blockize_blocks():
@T.prim_func(s_tir=True)
def blocks_func(A: T.Buffer((128, 128), "float32"), B: T.Buffer((128, 128), "float32")) -> None:
for m in T.serial(6):
for i, j in T.grid(3, 1):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(A[vi, vj])
T.writes(B[vi, vj])
B[vi, vj] = A[vi, vj] * 2.0
for i, j in T.grid(128, 64):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(A[vi, vj + 64])
T.writes(B[vi, vj + 64])
B[vi, vj + 64] = A[vi, vj + 64] * 3.0
@T.prim_func(s_tir=True)
def after_blocks_blockize(
A: T.Buffer((128, 128), "float32"), B: T.Buffer((128, 128), "float32")
) -> None:
for m in range(6):
with T.sblock("outer_B_C_"):
vi_o = T.axis.spatial(1, 0)
vj_o = T.axis.spatial(1, 0)
T.reads(A[0:128, 0:128])
T.writes(B[0:128, 0:128])
for i, j in T.grid(3, 1):
with T.sblock("B"):
vi_i = T.axis.spatial(3, i)
T.reads(A[vi_i, 0])
T.writes(B[vi_i, 0])
B[vi_i, 0] = A[vi_i, 0] * T.float32(2)
for i, j in T.grid(128, 64):
with T.sblock("C"):
vi_i, vj_i = T.axis.remap("SS", [i, j])
T.reads(A[vi_i, vj_i + 64])
T.writes(B[vi_i, vj_i + 64])
B[vi_i, vj_i + 64] = A[vi_i, vj_i + 64] * T.float32(3)
s = tvm.s_tir.Schedule(blocks_func, debug_mask="all")
blocks = [s.get_sblock("B"), s.get_sblock("C")]
s.blockize(blocks, preserve_unit_iters=False)
expected = after_blocks_blockize
tvm.ir.assert_structural_equal(
s.mod["main"], expected.with_attr("global_symbol", "blocks_func")
)
verify_trace_roundtrip(sch=s, mod=blocks_func)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,478 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: F401
import sys
import pytest
import tvm
import tvm.testing
from tvm import tirx
from tvm.s_tir.schedule.testing import verify_trace_roundtrip
from tvm.script import tirx as T
# pylint: disable=no-member,invalid-name,unused-variable
########## Function before schedule ##########
@T.prim_func(s_tir=True)
def resize(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (1, 3, 40, 40))
B = T.match_buffer(b, (1, 3, 80, 80))
for i0, i1, i2, i3 in T.grid(1, 3, 80, 80):
with T.sblock("A"):
n, c, vi, vj = T.axis.remap("SSSS", [i0, i1, i2, i3])
B[n, c, vi, vj] = A[n, c, vi // 4 + vj // 4, vj // 2]
@T.prim_func(s_tir=True)
def resize_cache_index(
A: T.Buffer((1, 3, 40, 40), "float32"), B: T.Buffer((1, 3, 80, 80), "float32")
) -> None:
index_var_0 = T.sblock_alloc_buffer([80, 80], dtype="int32", strides=[1])
index_var_1 = T.sblock_alloc_buffer([80], dtype="int32", strides=[1])
for ax0, ax1 in T.grid(80, 80):
with T.sblock("index_0"):
v0, v1 = T.axis.remap("SS", [ax0, ax1])
T.reads()
T.writes(index_var_0[v0, v1])
index_var_0[v0, v1] = v0 // 4 + v1 // 4
for ax0 in T.serial(80):
with T.sblock("index_1"):
v0 = T.axis.spatial(80, ax0)
T.reads()
T.writes(index_var_1[v0])
index_var_1[v0] = v0 // 2
for i0, i1, i2, i3 in T.grid(1, 3, 80, 80):
with T.sblock("A"):
n, c, vi, vj = T.axis.remap("SSSS", [i0, i1, i2, i3])
T.reads(A[n, c, vi // 4 + vj // 4, vj // 2])
T.writes(B[n, c, vi, vj])
B[n, c, vi, vj] = A[n, c, index_var_0[vi, vj], index_var_1[vj]]
@T.prim_func(s_tir=True)
def bilinear_resize(
x: T.Buffer((1, 3, 40, 40), "float16"), resize: T.Buffer((1, 3, 80, 80), "float16")
):
for i0, i1, i2, i3 in T.grid(1, 3, 80, 80):
with T.sblock("resize"):
i0_1, i1_1, i2_1, i3_1 = T.axis.remap("SSSS", [i0, i1, i2, i3])
T.reads(x[i0_1, i1_1, 0:40, 0:40])
T.writes(resize[i0_1, i1_1, i2_1, i3_1])
resize[i0_1, i1_1, i2_1, i3_1] = T.Cast(
"float16",
(
T.Cast(
"float32",
x[
i0_1,
i1_1,
T.max(
T.min(
T.Cast(
"int32",
T.floor(
(T.Cast("float32", i2_1) + T.float32(0.5))
* T.float32(0.5)
- T.float32(0.5),
dtype="float32",
),
),
39,
),
0,
),
T.max(
T.min(
T.Cast(
"int32",
T.floor(
(T.Cast("float32", i3_1) + T.float32(0.5))
* T.float32(0.5)
- T.float32(0.5),
dtype="float32",
),
),
39,
),
0,
),
],
)
* (
T.float32(1)
- (
(T.Cast("float32", i3_1) + T.float32(0.5)) * T.float32(0.5)
- T.float32(0.5)
- T.Cast(
"float32",
T.Cast(
"int32",
T.floor(
(T.Cast("float32", i3_1) + T.float32(0.5)) * T.float32(0.5)
- T.float32(0.5),
dtype="float32",
),
),
)
)
)
+ T.Cast(
"float32",
x[
i0_1,
i1_1,
T.max(
T.min(
T.Cast(
"int32",
T.floor(
(T.Cast("float32", i2_1) + T.float32(0.5))
* T.float32(0.5)
- T.float32(0.5),
dtype="float32",
),
),
39,
),
0,
),
T.max(
T.min(
T.Cast(
"int32",
T.floor(
(T.Cast("float32", i3_1) + T.float32(0.5))
* T.float32(0.5)
- T.float32(0.5),
dtype="float32",
),
)
+ 1,
39,
),
0,
),
],
)
* (
(T.Cast("float32", i3_1) + T.float32(0.5)) * T.float32(0.5)
- T.float32(0.5)
- T.Cast(
"float32",
T.Cast(
"int32",
T.floor(
(T.Cast("float32", i3_1) + T.float32(0.5)) * T.float32(0.5)
- T.float32(0.5),
dtype="float32",
),
),
)
)
)
* (
T.float32(1)
- (
(T.Cast("float32", i2_1) + T.float32(0.5)) * T.float32(0.5)
- T.float32(0.5)
- T.Cast(
"float32",
T.Cast(
"int32",
T.floor(
(T.Cast("float32", i2_1) + T.float32(0.5)) * T.float32(0.5)
- T.float32(0.5),
dtype="float32",
),
),
)
)
)
+ (
T.Cast(
"float32",
x[
i0_1,
i1_1,
T.max(
T.min(
T.Cast(
"int32",
T.floor(
(T.Cast("float32", i2_1) + T.float32(0.5))
* T.float32(0.5)
- T.float32(0.5),
dtype="float32",
),
)
+ 1,
39,
),
0,
),
T.max(
T.min(
T.Cast(
"int32",
T.floor(
(T.Cast("float32", i3_1) + T.float32(0.5))
* T.float32(0.5)
- T.float32(0.5),
dtype="float32",
),
),
39,
),
0,
),
],
)
* (
T.float32(1)
- (
(T.Cast("float32", i3_1) + T.float32(0.5)) * T.float32(0.5)
- T.float32(0.5)
- T.Cast(
"float32",
T.Cast(
"int32",
T.floor(
(T.Cast("float32", i3_1) + T.float32(0.5)) * T.float32(0.5)
- T.float32(0.5),
dtype="float32",
),
),
)
)
)
+ T.Cast(
"float32",
x[
i0_1,
i1_1,
T.max(
T.min(
T.Cast(
"int32",
T.floor(
(T.Cast("float32", i2_1) + T.float32(0.5))
* T.float32(0.5)
- T.float32(0.5),
dtype="float32",
),
)
+ 1,
39,
),
0,
),
T.max(
T.min(
T.Cast(
"int32",
T.floor(
(T.Cast("float32", i3_1) + T.float32(0.5))
* T.float32(0.5)
- T.float32(0.5),
dtype="float32",
),
)
+ 1,
39,
),
0,
),
],
)
* (
(T.Cast("float32", i3_1) + T.float32(0.5)) * T.float32(0.5)
- T.float32(0.5)
- T.Cast(
"float32",
T.Cast(
"int32",
T.floor(
(T.Cast("float32", i3_1) + T.float32(0.5)) * T.float32(0.5)
- T.float32(0.5),
dtype="float32",
),
),
)
)
)
* (
(T.Cast("float32", i2_1) + T.float32(0.5)) * T.float32(0.5)
- T.float32(0.5)
- T.Cast(
"float32",
T.Cast(
"int32",
T.floor(
(T.Cast("float32", i2_1) + T.float32(0.5)) * T.float32(0.5)
- T.float32(0.5),
dtype="float32",
),
),
)
),
)
@T.prim_func(s_tir=True)
def cached_bilinear_resize(
x: T.Buffer((1, 3, 40, 40), "float16"), resize: T.Buffer((1, 3, 80, 80), "float16")
):
index_var_0 = T.sblock_alloc_buffer([80], dtype="float32", strides=[1])
index_var_1 = T.sblock_alloc_buffer([80], dtype="int32", strides=[1])
index_var_2 = T.sblock_alloc_buffer([80], dtype="int32", strides=[1])
for ax0 in T.serial(80):
with T.sblock("index_0"):
v0 = T.axis.spatial(80, ax0)
T.reads()
T.writes(index_var_0[v0])
index_var_0[v0] = (
(T.Cast("float32", v0) + T.float32(0.5)) * T.float32(0.5)
- T.float32(0.5)
- T.Cast(
"float32",
T.Cast(
"int32",
T.floor(
(T.Cast("float32", v0) + T.float32(0.5)) * T.float32(0.5)
- T.float32(0.5),
dtype="float32",
),
),
)
)
for ax0 in T.serial(80):
with T.sblock("index_1"):
v0 = T.axis.spatial(80, ax0)
T.reads()
T.writes(index_var_1[v0])
index_var_1[v0] = T.Cast(
"int32",
T.floor(
(T.Cast("float32", v0) + T.float32(0.5)) * T.float32(0.5) - T.float32(0.5),
dtype="float32",
),
)
for ax0 in T.serial(80):
with T.sblock("index_2"):
v0 = T.axis.spatial(80, ax0)
T.reads()
T.writes(index_var_2[v0])
index_var_2[v0] = T.Cast(
"int32",
T.floor(
(T.Cast("float32", v0) + T.float32(0.5)) * T.float32(0.5) - T.float32(0.5),
dtype="float32",
),
)
for i0, i1, i2, i3 in T.grid(1, 3, 80, 80):
with T.sblock("resize"):
i0_1, i1_1, i2_1, i3_1 = T.axis.remap("SSSS", [i0, i1, i2, i3])
T.reads(x[i0_1, i1_1, 0:40, 0:40])
T.writes(resize[i0_1, i1_1, i2_1, i3_1])
resize[i0_1, i1_1, i2_1, i3_1] = T.Cast(
"float16",
(
T.Cast(
"float32",
x[
i0_1,
i1_1,
T.max(T.min(index_var_1[i2_1], 39), 0),
T.max(T.min(index_var_2[i3_1], 39), 0),
],
)
* (T.float32(1) - index_var_0[i3_1])
+ T.Cast(
"float32",
x[
i0_1,
i1_1,
T.max(T.min(index_var_1[i2_1], 39), 0),
T.max(T.min(index_var_2[i3_1] + 1, 39), 0),
],
)
* index_var_0[i3_1]
)
* (
T.float32(1)
- (
(T.Cast("float32", i2_1) + T.float32(0.5)) * T.float32(0.5)
- T.float32(0.5)
- T.Cast("float32", index_var_1[i2_1])
)
)
+ (
T.Cast(
"float32",
x[
i0_1,
i1_1,
T.max(T.min(index_var_1[i2_1] + 1, 39), 0),
T.max(T.min(index_var_2[i3_1], 39), 0),
],
)
* (T.float32(1) - index_var_0[i3_1])
+ T.Cast(
"float32",
x[
i0_1,
i1_1,
T.max(T.min(index_var_1[i2_1] + 1, 39), 0),
T.max(T.min(index_var_2[i3_1] + 1, 39), 0),
],
)
* index_var_0[i3_1]
)
* (
(T.Cast("float32", i2_1) + T.float32(0.5)) * T.float32(0.5)
- T.float32(0.5)
- T.Cast("float32", index_var_1[i2_1])
),
)
def test_basic_cache_index():
sch = tvm.s_tir.Schedule(resize, debug_mask="all")
block = sch.get_sblock("A")
sch.cache_index(block, "global")
tvm.ir.assert_structural_equal(
resize_cache_index, sch.mod["main"].with_attr("global_symbol", "resize_cache_index")
)
verify_trace_roundtrip(sch=sch, mod=resize)
def test_resize_bilinear_cache_index():
sch = tvm.s_tir.Schedule(bilinear_resize, debug_mask="all")
block = sch.get_sblock("resize")
sch.cache_index(block, "global", 4)
tvm.ir.assert_structural_equal(
sch.mod["main"], cached_bilinear_resize.with_attr("global_symbol", "bilinear_resize")
)
verify_trace_roundtrip(sch=sch, mod=bilinear_resize)
if __name__ == "__main__":
tvm.testing.main()
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,424 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: F401
import numpy as np
import pytest
import tvm
import tvm.testing
from tvm import tirx
from tvm.s_tir.schedule.testing import assert_structural_equal_ignore_global_symbol
from tvm.script import tirx as T
# pylint: disable=no-member,invalid-name,unused-variable,unexpected-keyword-arg
def check_decompose_padding(origin, scheduled, expected, check_run=False):
assert_structural_equal_ignore_global_symbol(scheduled, expected)
if check_run:
in_buffer = origin.buffer_map[origin.params[0]]
out_buffer = origin.buffer_map[origin.params[1]]
in_shape = [int(_) for _ in in_buffer.shape]
out_shape = [int(_) for _ in out_buffer.shape]
x = tvm.runtime.tensor(np.random.uniform(0, 64, in_shape).astype(in_buffer.dtype))
y0 = tvm.runtime.tensor(np.zeros(out_shape).astype(out_buffer.dtype))
y1 = tvm.runtime.tensor(np.zeros(out_shape).astype(out_buffer.dtype))
f_origin = tvm.compile(origin)
f_scheduled = tvm.compile(scheduled)
f_origin(x, y0)
f_scheduled(x, y1)
tvm.testing.assert_allclose(y0.numpy(), y1.numpy())
def test_int64_indices_batch_decompose_padding():
@T.prim_func(s_tir=True)
def before_decompose(
x: T.Buffer((T.int64(1), T.int64(128), T.int64(128)), "int32"),
y: T.Buffer((T.int64(1), T.int64(140), T.int64(128)), "int32"),
):
for b, i, j in T.grid(T.int64(1), T.int64(140), T.int64(128)):
with T.sblock("block"):
vb, vi, vj = T.axis.remap("SSS", [b, i, j])
y[vb, vi, vj] = T.if_then_else(vi < T.int64(128), x[vb, vi, vj], 0)
@T.prim_func(s_tir=True)
def after_decompose(
x: T.Buffer((T.int64(1), T.int64(128), T.int64(128)), "int32"),
y: T.Buffer((T.int64(1), T.int64(140), T.int64(128)), "int32"),
):
# with T.sblock("root"):
for b, i in T.grid(T.int64(1), T.int64(140)):
# Use T.serial(T.int64(0), T.int64(128)) so iter_var dom.min is int64
# (matches schedule output; `range(T.int64(...))` would emit an int32 min).
for j in T.serial(T.int64(0), T.int64(128)):
with T.sblock("block_pad_const"):
vb = T.axis.spatial(T.int64(1), T.int64(0))
vi, vj = T.axis.remap("SS", [i, j])
T.reads()
T.writes(y[vb, vi, vj])
y[vb, vi, vj] = 0
for j in T.serial(T.int64(0), T.int64(128)):
with T.sblock("block"):
vb = T.axis.spatial(T.int64(1), T.int64(0))
vi = T.axis.spatial(T.int64(128), i)
vj = T.axis.spatial(T.int64(128), j)
T.where(i < T.int64(128))
T.reads(x[vb, vi, vj])
T.writes(y[vb, vi, vj])
y[vb, vi, vj] = x[vb, vi, vj]
sch = tvm.s_tir.Schedule(before_decompose, debug_mask="all")
block = sch.get_sblock("block")
sch.decompose_padding(block, sch.get_loops(block)[2])
check_decompose_padding(before_decompose, sch.mod["main"], after_decompose, check_run=False)
def test_1d_decompose_padding():
@T.prim_func(s_tir=True)
def before_decompose(x: T.Buffer(128, "int32"), y: T.Buffer(140, "int32")):
for i in range(140):
with T.sblock("block"):
vi = T.axis.remap("S", [i])
y[vi] = T.if_then_else(vi >= 6 and vi < 134, x[vi - 6], 0, dtype="int32")
@T.prim_func(s_tir=True)
def after_decompose(x: T.Buffer(128, "int32"), y: T.Buffer(140, "int32")):
for i in T.serial(140):
with T.sblock("block_pad_const"):
vi = T.axis.spatial(140, i)
T.reads()
T.writes(y[vi])
y[vi] = 0
for i in T.serial(128):
with T.sblock("block"):
vi = T.axis.spatial(128, i)
T.reads(x[vi])
T.writes(y[vi + 6])
y[vi + 6] = x[vi]
sch = tvm.s_tir.Schedule(before_decompose, debug_mask="all")
block = sch.get_sblock("block")
sch.decompose_padding(block, sch.get_loops(block)[0])
check_decompose_padding(before_decompose, sch.mod["main"], after_decompose, check_run=False)
@T.prim_func(s_tir=True)
def sum_pool_2d(
x: T.Buffer((1, 16, 225, 225), "int8"), tensor: T.Buffer((1, 16, 225, 225), "int8")
):
pad_temp = T.sblock_alloc_buffer([1, 16, 231, 231], dtype="int8")
for i0, i1, i2, i3 in T.grid(1, 16, 231, 231):
with T.sblock("pad_temp"):
ax0, ax1, ax2, ax3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
pad_temp[ax0, ax1, ax2, ax3] = T.if_then_else(
3 <= ax2 and ax2 < 228 and 3 <= ax3 and ax3 < 228,
x[ax0, ax1, ax2 - 3, ax3 - 3],
T.int8(0),
dtype="int8",
)
for i0, i1, i2, i3, i4, i5 in T.grid(1, 16, 225, 225, 7, 7):
with T.sblock("tensor"):
ax0, ax1, ax2, ax3, rv0, rv1 = T.axis.remap("SSSSRR", [i0, i1, i2, i3, i4, i5])
with T.init():
tensor[ax0, ax1, ax2, ax3] = T.int8(0)
tensor[ax0, ax1, ax2, ax3] = (
tensor[ax0, ax1, ax2, ax3] + pad_temp[ax0, ax1, ax2 + rv0, ax3 + rv1]
)
def test_decompose_hw_padding_direct():
"""Case 0. direct decompose"""
@T.prim_func(s_tir=True)
def pooling_decompose_0(
x: T.Buffer((1, 16, 225, 225), "int8"), tensor: T.Buffer((1, 16, 225, 225), "int8")
):
pad_temp = T.sblock_alloc_buffer([1, 16, 231, 231], dtype="int8")
for i0, i1, i2, i3 in T.grid(1, 16, 231, 231):
with T.sblock("pad_temp_pad_const"):
ax0, ax1, ax2, ax3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
pad_temp[ax0, ax1, ax2, ax3] = T.int8(0)
for i0, i1, i2, i3 in T.grid(1, 16, 225, 225):
with T.sblock("pad_temp"):
ax0, ax1, ax2, ax3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
pad_temp[ax0, ax1, ax2 + 3, ax3 + 3] = x[ax0, ax1, ax2, ax3]
for i0, i1, i2, i3, i4, i5 in T.grid(1, 16, 225, 225, 7, 7):
with T.sblock("tensor"):
ax0, ax1, ax2, ax3, rv0, rv1 = T.axis.remap("SSSSRR", [i0, i1, i2, i3, i4, i5])
with T.init():
tensor[ax0, ax1, ax2, ax3] = T.int8(0)
tensor[ax0, ax1, ax2, ax3] = (
tensor[ax0, ax1, ax2, ax3] + pad_temp[ax0, ax1, ax2 + rv0, ax3 + rv1]
)
sch = tvm.s_tir.Schedule(sum_pool_2d, debug_mask="all")
pad = sch.get_sblock("pad_temp")
sch.decompose_padding(pad, sch.get_loops(pad)[0])
check_decompose_padding(sum_pool_2d, sch.mod["main"], pooling_decompose_0, check_run=True)
def test_decompose_hw_padding_tiled():
"""Case 1. tiling and then decompose"""
@T.prim_func(s_tir=True)
def pooling_decompose_1(
x: T.Buffer((1, 16, 225, 225), "int8"), tensor: T.Buffer((1, 16, 225, 225), "int8")
) -> None:
pad_temp = T.sblock_alloc_buffer([1, 16, 231, 231], dtype="int8")
for i0, i2_0, i3_0 in T.grid(1, 3, 3):
for ax0, ax1, ax2 in T.grid(16, 81, 81):
with T.sblock("pad_temp_pad_const"):
ax0_1 = T.axis.spatial(1, 0)
ax1_1 = T.axis.spatial(16, ax0)
ax2_1 = T.axis.spatial(231, i2_0 * 75 + ax1)
ax3 = T.axis.spatial(231, i3_0 * 75 + ax2)
T.reads()
T.writes(pad_temp[ax0_1, ax1_1, ax2_1, ax3])
pad_temp[ax0_1, ax1_1, ax2_1, ax3] = T.int8(0)
for ax0, ax1, ax2 in T.grid(16, 81, 81):
with T.sblock("pad_temp"):
ax0_2 = T.axis.spatial(1, 0)
ax1_2 = T.axis.spatial(16, ax0)
ax2_2 = T.axis.spatial(225, i2_0 * 75 + ax1 - 3)
ax3 = T.axis.spatial(225, i3_0 * 75 + ax2 - 3)
T.where(
3 <= i2_0 * 75 + ax1
and i2_0 * 75 + ax1 < 228
and 3 <= i3_0 * 75 + ax2
and i3_0 * 75 + ax2 < 228
)
T.reads(x[ax0_2, ax1_2, ax2_2, ax3])
T.writes(pad_temp[ax0_2, ax1_2, ax2_2 + 3, ax3 + 3])
pad_temp[ax0_2, ax1_2, ax2_2 + 3, ax3 + 3] = x[ax0_2, ax1_2, ax2_2, ax3]
for i1, i2_1, i3_1, i4, i5 in T.grid(16, 75, 75, 7, 7):
with T.sblock("tensor"):
ax0_3, ax1_3 = T.axis.remap("SS", [i0, i1])
ax2_3 = T.axis.spatial(225, i2_0 * 75 + i2_1)
ax3 = T.axis.spatial(225, i3_0 * 75 + i3_1)
rv0, rv1 = T.axis.remap("RR", [i4, i5])
T.reads(pad_temp[ax0_3, ax1_3, ax2_3 + rv0, ax3 + rv1])
T.writes(tensor[ax0_3, ax1_3, ax2_3, ax3])
with T.init():
tensor[ax0_3, ax1_3, ax2_3, ax3] = T.int8(0)
tensor[ax0_3, ax1_3, ax2_3, ax3] = (
tensor[ax0_3, ax1_3, ax2_3, ax3]
+ pad_temp[ax0_3, ax1_3, ax2_3 + rv0, ax3 + rv1]
)
sch = tvm.s_tir.Schedule(sum_pool_2d, debug_mask="all")
block = sch.get_sblock("tensor")
pad = sch.get_sblock("pad_temp")
n, c, h, w, kh, kw = sch.get_loops(block)
ho, hi = sch.split(h, [3, 75])
wo, wi = sch.split(w, [3, 75])
sch.reorder(n, ho, wo, c, hi, wi, kh, kw)
sch.compute_at(sch.get_sblock("pad_temp"), wo)
sch.decompose_padding(pad, sch.get_loops(pad)[3])
check_decompose_padding(sum_pool_2d, sch.mod["main"], pooling_decompose_1, check_run=True)
def test_decompose_hw_padding_tiled_and_lift_pad():
"""Case 2. tiling and then decompose, lift const pad values to outer loop"""
@T.prim_func(s_tir=True)
def pooling_decompose_2(
x: T.Buffer((1, 16, 225, 225), "int8"), tensor: T.Buffer((1, 16, 225, 225), "int8")
) -> None:
pad_temp = T.sblock_alloc_buffer([1, 16, 231, 231], dtype="int8")
for i0, i2_0, i3_0, ax0, ax1, ax2 in T.grid(1, 3, 3, 16, 81, 81):
with T.sblock("pad_temp_pad_const"):
ax0_1 = T.axis.spatial(1, 0)
ax1_1 = T.axis.spatial(16, ax0)
ax2_1 = T.axis.spatial(231, i2_0 * 75 + ax1)
ax3 = T.axis.spatial(231, i3_0 * 75 + ax2)
T.reads()
T.writes(pad_temp[ax0_1, ax1_1, ax2_1, ax3])
pad_temp[ax0_1, ax1_1, ax2_1, ax3] = T.int8(0)
for i0, i2_0, i3_0 in T.grid(1, 3, 3):
for ax0, ax1, ax2 in T.grid(16, 81, 81):
with T.sblock("pad_temp"):
ax0_2 = T.axis.spatial(1, 0)
ax1_2 = T.axis.spatial(16, ax0)
ax2_2 = T.axis.spatial(225, i2_0 * 75 + ax1 - 3)
ax3 = T.axis.spatial(225, i3_0 * 75 + ax2 - 3)
T.where(
3 <= i2_0 * 75 + ax1
and i2_0 * 75 + ax1 < 228
and 3 <= i3_0 * 75 + ax2
and i3_0 * 75 + ax2 < 228
)
T.reads(x[ax0_2, ax1_2, ax2_2, ax3])
T.writes(pad_temp[ax0_2, ax1_2, ax2_2 + 3, ax3 + 3])
pad_temp[ax0_2, ax1_2, ax2_2 + 3, ax3 + 3] = x[ax0_2, ax1_2, ax2_2, ax3]
for i1, i2_1, i3_1, i4, i5 in T.grid(16, 75, 75, 7, 7):
with T.sblock("tensor"):
ax0_3, ax1_3 = T.axis.remap("SS", [i0, i1])
ax2_3 = T.axis.spatial(225, i2_0 * 75 + i2_1)
ax3 = T.axis.spatial(225, i3_0 * 75 + i3_1)
rv0, rv1 = T.axis.remap("RR", [i4, i5])
T.reads(pad_temp[ax0_3, ax1_3, ax2_3 + rv0, ax3 + rv1])
T.writes(tensor[ax0_3, ax1_3, ax2_3, ax3])
with T.init():
tensor[ax0_3, ax1_3, ax2_3, ax3] = T.int8(0)
tensor[ax0_3, ax1_3, ax2_3, ax3] = (
tensor[ax0_3, ax1_3, ax2_3, ax3]
+ pad_temp[ax0_3, ax1_3, ax2_3 + rv0, ax3 + rv1]
)
sch = tvm.s_tir.Schedule(sum_pool_2d, debug_mask="all")
block = sch.get_sblock("tensor")
pad = sch.get_sblock("pad_temp")
n, c, h, w, kh, kw = sch.get_loops(block)
ho, hi = sch.split(h, [3, 75])
wo, wi = sch.split(w, [3, 75])
sch.reorder(n, ho, wo, c, hi, wi, kh, kw)
sch.compute_at(sch.get_sblock("pad_temp"), wo)
sch.decompose_padding(pad, sch.get_loops(pad)[0])
check_decompose_padding(sum_pool_2d, sch.mod["main"], pooling_decompose_2, check_run=True)
def test_decompose_hw_padding_non_perfect_tiled():
"""Case 3. non-perfect tiling and then decompose"""
@T.prim_func(s_tir=True)
def pooling_decompose_3(
x: T.Buffer((1, 16, 225, 225), "int8"), tensor: T.Buffer((1, 16, 225, 225), "int8")
) -> None:
pad_temp = T.sblock_alloc_buffer([1, 16, 231, 231], dtype="int8")
for i0, i2_0, i3_0 in T.grid(1, 3, 3):
for ax0, ax1, ax2 in T.grid(16, 86, 86):
with T.sblock("pad_temp_pad_const"):
ax0_1 = T.axis.spatial(1, 0)
ax1_1 = T.axis.spatial(16, ax0)
ax2_1 = T.axis.spatial(231, i2_0 * 80 + ax1)
ax3 = T.axis.spatial(231, i3_0 * 80 + ax2)
T.where(i2_0 * 80 + ax1 < 231 and i3_0 * 80 + ax2 < 231)
T.reads()
T.writes(pad_temp[ax0_1, ax1_1, ax2_1, ax3])
pad_temp[ax0_1, ax1_1, ax2_1, ax3] = T.int8(0)
for ax0, ax1, ax2 in T.grid(16, 86, 86):
with T.sblock("pad_temp"):
ax0_2 = T.axis.spatial(1, 0)
ax1_2 = T.axis.spatial(16, ax0)
ax2_2 = T.axis.spatial(225, i2_0 * 80 + ax1 - 3)
ax3 = T.axis.spatial(225, i3_0 * 80 + ax2 - 3)
T.where(
3 <= i2_0 * 80 + ax1
and i2_0 * 80 + ax1 < 228
and 3 <= i3_0 * 80 + ax2
and i3_0 * 80 + ax2 < 228
and i2_0 * 80 + ax1 < 231
and i3_0 * 80 + ax2 < 231
)
T.reads(x[ax0_2, ax1_2, ax2_2, ax3])
T.writes(pad_temp[ax0_2, ax1_2, ax2_2 + 3, ax3 + 3])
pad_temp[ax0_2, ax1_2, ax2_2 + 3, ax3 + 3] = x[ax0_2, ax1_2, ax2_2, ax3]
for i1, i2_1, i3_1, i4, i5 in T.grid(16, 80, 80, 7, 7):
with T.sblock("tensor"):
ax0_3, ax1_3 = T.axis.remap("SS", [i0, i1])
ax2_3 = T.axis.spatial(225, i2_0 * 80 + i2_1)
ax3 = T.axis.spatial(225, i3_0 * 80 + i3_1)
rv0, rv1 = T.axis.remap("RR", [i4, i5])
T.where(i2_0 * 80 + i2_1 < 225 and i3_0 * 80 + i3_1 < 225)
T.reads(pad_temp[ax0_3, ax1_3, ax2_3 + rv0, ax3 + rv1])
T.writes(tensor[ax0_3, ax1_3, ax2_3, ax3])
with T.init():
tensor[ax0_3, ax1_3, ax2_3, ax3] = T.int8(0)
tensor[ax0_3, ax1_3, ax2_3, ax3] = (
tensor[ax0_3, ax1_3, ax2_3, ax3]
+ pad_temp[ax0_3, ax1_3, ax2_3 + rv0, ax3 + rv1]
)
sch = tvm.s_tir.Schedule(sum_pool_2d, debug_mask="all")
block = sch.get_sblock("tensor")
pad = sch.get_sblock("pad_temp")
n, c, h, w, kh, kw = sch.get_loops(block)
ho, hi = sch.split(h, [None, 80])
wo, wi = sch.split(w, [None, 80])
sch.reorder(n, ho, wo, c, hi, wi, kh, kw)
sch.compute_at(sch.get_sblock("pad_temp"), wo)
sch.decompose_padding(pad, sch.get_loops(pad)[3])
check_decompose_padding(sum_pool_2d, sch.mod["main"], pooling_decompose_3, check_run=True)
def test_decompose_wrt_single_child_subtree():
"""Test the case when the decompose position is under the single child subtree"""
@T.prim_func(s_tir=True)
def pad_op(
x: T.Buffer((1, 16, 225, 225), "int8"),
y: T.Buffer((1, 16, 231, 231), dtype="int8"),
):
for i0, i1, i2, i3 in T.grid(1, 16, 231, 231):
with T.sblock("pad_temp"):
ax0, ax1, ax2, ax3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
y[ax0, ax1, ax2, ax3] = T.if_then_else(
3 <= ax2 and ax2 < 228 and 3 <= ax3 and ax3 < 228,
x[ax0, ax1, ax2 - 3, ax3 - 3],
T.int8(0),
dtype="int8",
)
@T.prim_func(s_tir=True)
def pad_op_after(
x: T.Buffer((1, 16, 225, 225), "int8"), y: T.Buffer((1, 16, 231, 231), "int8")
):
for i0, i1 in T.grid(1, 16):
for i2, i3 in T.grid(231, 231):
with T.sblock("pad_temp_pad_const"):
ax0 = T.axis.spatial(1, 0)
ax1, ax2, ax3 = T.axis.remap("SSS", [i1, i2, i3])
y[ax0, ax1, ax2, ax3] = T.int8(0)
for i2, i3 in T.grid(225, 225):
with T.sblock("pad_temp"):
ax0 = T.axis.spatial(1, 0)
ax1, ax2, ax3 = T.axis.remap("SSS", [i1, i2, i3])
y[ax0, ax1, ax2 + 3, ax3 + 3] = x[ax0, ax1, ax2, ax3]
sch = tvm.s_tir.Schedule(pad_op, debug_mask="all")
pad = sch.get_sblock("pad_temp")
_, _, h, _ = sch.get_loops(pad)
sch.decompose_padding(pad, h)
check_decompose_padding(pad_op, sch.mod["main"], pad_op_after, check_run=True)
def test_not_to_decompose_trivial_predicate():
"""Test the case when the padding condition is trivial"""
@T.prim_func(s_tir=True)
def trivial_pad(
x: T.Buffer((1, 16, 225, 225), "int8"), y: T.Buffer([1, 16, 225, 225], dtype="int8")
):
for i0, i1, i2, i3 in T.grid(1, 16, 225, 225):
with T.sblock("pad_temp"):
ax0, ax1, ax2, ax3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
y[ax0, ax1, ax2, ax3] = T.if_then_else(
0 <= ax2 and ax2 < 225 and 0 <= ax3 and ax3 < 225,
x[ax0, ax1, ax2, ax3],
T.int8(0),
dtype="int8",
)
sch = tvm.s_tir.Schedule(trivial_pad, debug_mask="all")
pad = sch.get_sblock("pad_temp")
_, _, h, _ = sch.get_loops(pad)
assert not sch.can_decompose_padding(pad, h)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,101 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: F401
import pytest
import tvm
import tvm.testing
from tvm import s_tir, tirx
from tvm.script import tirx as T
# pylint: disable=no-member,invalid-name,unused-variable
@T.prim_func(s_tir=True)
def matmul(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
B = T.match_buffer(b, [128, 128])
C = T.match_buffer(c, [128, 128])
for i, j in T.grid(128, 128):
with T.sblock("init"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = T.float32(0)
for k in range(128):
with T.sblock("update"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
@T.prim_func(s_tir=True)
def two_kernels(var_A: T.handle, var_B: T.handle, seq_len: T.int32):
T.func_attr({"tirx.noalias": True})
A = T.match_buffer(var_A, (1, seq_len * 8), "int32")
B = T.match_buffer(var_B, (1, seq_len * 8), "int32", align=8)
with T.sblock("exclusive_scan"):
T.reads()
T.writes()
s8: T.int32 = seq_len * 8
if s8 == 0:
blockIdx_x = T.launch_thread("blockIdx.x", 1)
else:
with T.launch_thread("threadIdx.x", 1024) as threadIdx_x:
blockIdx_x = T.launch_thread("blockIdx.x", T.ceildiv(s8, 1024))
i: T.int32 = blockIdx_x * 1024 + threadIdx_x
if i < s8:
B[i // s8, i % s8] = A[i // s8, i % s8]
# pylint: enable=no-member,invalid-name,unused-variable
def test_tir_schedule_error_detail():
sch = tvm.s_tir.Schedule(matmul, debug_mask="all", error_render_level="detail")
with pytest.raises(tvm.s_tir.ScheduleError) as excinfo:
sch.get_sblock("wrong_name")
(msg,) = excinfo.value.args
assert "Cannot find a block with the name: wrong_name" in msg
def test_tir_schedule_error_fast():
sch = tvm.s_tir.Schedule(matmul, debug_mask="all", error_render_level="fast")
with pytest.raises(tvm.s_tir.ScheduleError) as excinfo:
sch.get_sblock("wrong_name")
(msg,) = excinfo.value.args
assert "Cannot find a block with the specified name" in msg
def test_tir_schedule_error_none():
sch = tvm.s_tir.Schedule(matmul, debug_mask="all", error_render_level="none")
with pytest.raises(tvm.s_tir.ScheduleError) as excinfo:
sch.get_sblock("wrong_name")
(msg,) = excinfo.value.args
assert "(not rendered)" in msg
def test_tir_schedule_attribute_error():
sch = tvm.s_tir.Schedule(matmul)
with pytest.raises(AttributeError):
sch.non_existent_field()
def test_tir_schedule_two_kernels():
s_tir.Schedule(two_kernels)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,706 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: E741, F401
import sys
import pytest
import tvm
import tvm.testing
from tvm import tirx
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.script import tirx as T
# pylint: disable=no-member,invalid-name,unused-variable
@T.prim_func(s_tir=True)
def element_wise(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
B = T.match_buffer(b, (128, 128))
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
@T.prim_func(s_tir=True)
def element_wise_parallelized(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
B = T.match_buffer(b, (128, 128))
for i0 in T.parallel(0, 128):
for i1 in T.serial(0, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i0, i1])
B[vi, vj] = A[vi, vj] * 2.0
@T.prim_func(s_tir=True)
def element_wise_i_bound(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
B = T.match_buffer(b, (128, 128))
for i0 in T.thread_binding(0, 128, thread="threadIdx.x"):
for i1 in T.serial(0, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i0, i1])
B[vi, vj] = A[vi, vj] * 2.0
@T.prim_func(s_tir=True)
def element_wise_compute_at_split(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
C = T.match_buffer(c, (128, 128))
B = T.sblock_alloc_buffer((128, 128))
for i in T.serial(0, 128):
for j0 in T.serial(0, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j0])
B[vi, vj] = A[vi, vj] * 2.0
for j1o, j1i in T.grid(32, 4):
with T.sblock("C"):
vi = T.axis.S(128, i)
vj = T.axis.S(128, j1o * 4 + j1i)
C[vi, vj] = B[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def element_wise_compute_at_split_vectorized(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
C = T.match_buffer(c, (128, 128))
B = T.sblock_alloc_buffer((128, 128))
for i in T.serial(0, 128):
for j0 in T.serial(0, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j0])
B[vi, vj] = A[vi, vj] * 2.0
for j1o in T.serial(0, 32):
for j1i in T.vectorized(0, 4):
with T.sblock("C"):
vi = T.axis.S(128, i)
vj = T.axis.S(128, j1o * 4 + j1i)
C[vi, vj] = B[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def element_wise_split_predicate(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
B = T.match_buffer(b, [128, 128])
for i, j_0, j_1 in T.grid(128, 13, 10):
with T.sblock("B"):
T.where(j_0 * 10 + j_1 < 128)
vi = T.axis.S(128, i)
vj = T.axis.S(128, j_0 * 10 + j_1)
B[vi, vj] = A[vi, vj] * 2.0
@T.prim_func(s_tir=True)
def element_wise_split_predicate_parallelized(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
B = T.match_buffer(b, [128, 128])
for i in T.serial(0, 128):
for j_0 in T.parallel(0, 13):
for j_1 in T.serial(0, 10):
with T.sblock("B"):
T.where(j_0 * 10 + j_1 < 128)
vi = T.axis.S(128, i)
vj = T.axis.S(128, j_0 * 10 + j_1)
B[vi, vj] = A[vi, vj] * 2.0
@T.prim_func(s_tir=True)
def element_wise_split_predicate_vectorized(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
B = T.match_buffer(b, [128, 128])
for i in T.vectorized(0, 128):
for j_0, j_1 in T.grid(13, 10):
with T.sblock("B"):
T.where(j_0 * 10 + j_1 < 128)
vi = T.axis.S(128, i)
vj = T.axis.S(128, j_0 * 10 + j_1)
B[vi, vj] = A[vi, vj] * 2.0
@T.prim_func(s_tir=True)
def element_wise_compute_at_split_j0_j1o_bound(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
C = T.match_buffer(c, (128, 128))
B = T.sblock_alloc_buffer((128, 128))
for i in T.serial(0, 128):
for j0 in T.thread_binding(0, 128, thread="threadIdx.x"):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j0])
B[vi, vj] = A[vi, vj] * 2.0
for j1o in T.thread_binding(0, 32, thread="threadIdx.x"):
for j1i in T.serial(0, 4):
with T.sblock("C"):
vi = T.axis.S(128, i)
vj = T.axis.S(128, j1o * 4 + j1i)
C[vi, vj] = B[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def matmul(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
B = T.match_buffer(b, (128, 128))
C = T.match_buffer(c, (128, 128))
for i, j, k in T.grid(128, 128, 128):
with T.sblock("C"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
C[vi, vj] = 0.0
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
@T.prim_func(s_tir=True)
def rowsum(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
B = T.match_buffer(b, (128,))
for i, k in T.grid(128, 128):
with T.sblock("B"):
vi, vk = T.axis.remap("SR", [i, k])
with T.init():
B[vi] = 0.0
B[vi] = B[vi] + A[vi, vk]
@T.prim_func(s_tir=True)
def rowsum_unrolled(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
B = T.match_buffer(b, (128,))
for i0 in T.unroll(0, 128):
for i1 in T.serial(0, 128):
with T.sblock("B"):
vi, vk = T.axis.remap("SR", [i0, i1])
with T.init():
B[vi] = 0.0
B[vi] = B[vi] + A[vi, vk]
@T.prim_func(s_tir=True)
def rowsum_not_quasi_affine(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
B = T.match_buffer(b, (128,))
for i, k in T.grid(128, 16):
with T.sblock("B"):
vi = T.axis.S(128, i)
vk = T.axis.R(128, T.floordiv(k * k, 2))
with T.init():
B[vi] = 0.0
B[vi] = B[vi] + A[vi, vk]
@T.prim_func(s_tir=True)
def rowsum_not_compact_data_flow(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
B = T.match_buffer(b, (128,))
for i, k in T.grid(128, 16):
with T.sblock("B"):
vi, vk = T.axis.remap("SR", [i, k])
with T.init():
B[vk] = 0.0
B[vk] = B[vk] + A[vi, vk]
@T.prim_func(s_tir=True)
def rowsum_cross_thread_reduction(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
B = T.match_buffer(b, (128,))
for i0 in T.serial(0, 128):
for i1 in T.thread_binding(0, 128, thread="threadIdx.x"):
with T.sblock("B"):
vi, vk = T.axis.remap("SR", [i0, i1])
with T.init():
B[vi] = 0.0
B[vi] = B[vi] + A[vi, vk]
@T.prim_func(s_tir=True)
def opaque_block(a: T.handle) -> None:
A = T.match_buffer(a, (16,))
for i in T.serial(0, 15):
with T.sblock("opaque"):
A[i + 1] = A[i + 1] + A[i]
@T.prim_func(s_tir=True)
def block_inside_init(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [128, 128, 128], dtype="float32")
B = T.match_buffer(b, [128, 128], dtype="float32")
for i in T.serial(0, 128):
with T.sblock("outer"):
vi = T.axis.S(128, i)
with T.init():
for j in T.serial(0, 128):
with T.sblock("init"):
vj = T.axis.S(128, j)
B[vi, vj] = 0.0
for k in T.serial(0, 128):
for j in T.serial(0, 128):
with T.sblock("inner"):
vj, vk = T.axis.remap("SR", [j, k])
B[vi, vj] = B[vi, vj] + A[vi, vj, vk]
@T.prim_func(s_tir=True)
def thread_bound_block_inside_init(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [128, 128, 128], dtype="float32")
B = T.match_buffer(b, [128, 128], dtype="float32")
for i in T.thread_binding(0, 128, thread="threadIdx.x"):
with T.sblock("outer"):
vi = T.axis.S(128, i)
with T.init():
for j in T.serial(0, 128):
with T.sblock("init"):
vj = T.axis.S(128, j)
B[vi, vj] = 0.0
for k in T.serial(0, 128):
for j in T.serial(0, 128):
with T.sblock("inner"):
vj, vk = T.axis.remap("SR", [j, k])
B[vi, vj] = B[vi, vj] + A[vi, vj, vk]
@T.prim_func(s_tir=True)
def decomposed_gemm(
A: T.Buffer((16, 16), "float32"),
B: T.Buffer((16, 16), "float32"),
C: T.Buffer((16, 16), "float32"),
):
local = T.sblock_alloc_buffer((16, 16), "float32")
for i, j in T.grid(4, 4):
for ii, jj in T.grid(4, 4):
with T.sblock("init"):
vi = T.axis.S(16, i * 4 + ii)
vj = T.axis.S(16, j * 4 + jj)
local[vi, vj] = 0
for k, ii, jj in T.grid(16, 4, 4):
with T.sblock("update"):
vi = T.axis.S(16, i * 4 + ii)
vj = T.axis.S(16, j * 4 + jj)
vk = T.axis.R(16, k)
local[vi, vj] += A[vi, vk] * B[vj, vk]
for ii, jj in T.grid(4, 4):
with T.sblock("C"):
vi = T.axis.S(16, i * 4 + ii)
vj = T.axis.S(16, j * 4 + jj)
C[vi, vj] = local[vi, vj]
@T.prim_func(s_tir=True)
def decomposed_gemm_after_vectorize(
A: T.Buffer((16, 16), "float32"),
B: T.Buffer((16, 16), "float32"),
C: T.Buffer((16, 16), "float32"),
):
local = T.sblock_alloc_buffer((16, 16), "float32")
for i, j in T.grid(4, 4):
for ii, jj in T.grid(4, 4):
with T.sblock("init"):
vi = T.axis.S(16, i * 4 + ii)
vj = T.axis.S(16, j * 4 + jj)
local[vi, vj] = 0
for k, ii, jj in T.grid(16, 4, 4):
with T.sblock("update"):
vi = T.axis.S(16, i * 4 + ii)
vj = T.axis.S(16, j * 4 + jj)
vk = T.axis.R(16, k)
local[vi, vj] += A[vi, vk] * B[vj, vk]
for ii in range(4):
for jj in T.vectorized(4):
with T.sblock("C"):
vi = T.axis.S(16, i * 4 + ii)
vj = T.axis.S(16, j * 4 + jj)
C[vi, vj] = local[vi, vj]
@T.prim_func(s_tir=True)
def nested_block_bind(
A: T.Buffer((16, 16, 16, 16), "float32"), B: T.Buffer((16, 16, 16), "float32")
):
for i, j in T.grid(16, 16):
with T.sblock("outer"):
vi, vj = T.axis.remap("SS", [i, j])
for k, l in T.grid(16, 16):
with T.sblock("inner"):
vk, vl = T.axis.remap("SR", [k, l])
with T.init():
B[vi, vj, vk] = 0.0
B[vi, vj, vk] = B[vi, vj, vk] + A[vi, vj, vk, vl]
@T.prim_func(s_tir=True)
def thread_bound_nested_block(
A: T.Buffer((16, 16, 16, 16), "float32"), B: T.Buffer((16, 16, 16), "float32")
) -> None:
for i in T.serial(16):
for j in T.thread_binding(16, thread="blockIdx.x"):
with T.sblock("outer"):
vi, vj = T.axis.remap("SS", [i, j])
for k in T.serial(16):
for l in T.thread_binding(16, thread="threadIdx.x"):
with T.sblock("inner"):
vk, vl = T.axis.remap("SR", [k, l])
with T.init():
B[vi, vj, vk] = T.float32(0)
B[vi, vj, vk] = B[vi, vj, vk] + A[vi, vj, vk, vl]
@T.prim_func(s_tir=True)
def nested_block_bind_after_cache_read(
A: T.Buffer((16, 16), "float32"), B: T.Buffer((16,), "float32")
) -> None:
for i in T.serial(16):
with T.sblock("outer"):
vi = T.axis.spatial(16, i)
A_shared = T.sblock_alloc_buffer([1, 16], dtype="float32", scope="shared")
for ax0, ax1 in T.grid(1, 16):
with T.sblock("A_shared"):
v0 = T.axis.spatial(16, vi + ax0)
v1 = T.axis.spatial(16, ax1)
A_shared[v0, v1] = A[v0, v1]
for j in T.serial(16):
with T.sblock("inner"):
vj = T.axis.reduce(16, j)
with T.init():
B[vi] = T.float32(0)
B[vi] = B[vi] + A_shared[vi, vj]
@T.prim_func(s_tir=True)
def thread_bound_nested_block_after_cache_read(
A: T.Buffer((16, 16), "float32"), B: T.Buffer((16,), "float32")
) -> None:
for i in T.thread_binding(16, thread="blockIdx.x"):
with T.sblock("outer"):
vi = T.axis.spatial(16, i)
A_shared = T.sblock_alloc_buffer([1, 16], dtype="float32", scope="shared")
for ax0, ax1 in T.grid(1, 16):
with T.sblock("A_shared"):
v0 = T.axis.spatial(16, vi + ax0)
v1 = T.axis.spatial(16, ax1)
A_shared[v0, v1] = A[v0, v1]
for j in T.thread_binding(16, thread="threadIdx.x"):
with T.sblock("inner"):
vj = T.axis.reduce(16, j)
with T.init():
B[vi] = T.float32(0)
B[vi] = B[vi] + A_shared[vi, vj]
@T.prim_func(s_tir=True)
def decomposed_gemm_parallelize_init(
A: T.Buffer((16, 16), "float32"),
B: T.Buffer((16, 16), "float32"),
C: T.Buffer((16, 16), "float32"),
) -> None:
local = T.sblock_alloc_buffer([16, 16], dtype="float32")
for i, j in T.grid(4, 4):
for ii in T.serial(4):
for jj in T.vectorized(4):
with T.sblock("init"):
vi = T.axis.spatial(16, i * 4 + ii)
vj = T.axis.spatial(16, j * 4 + jj)
T.reads()
T.writes(local[vi, vj])
local[vi, vj] = 0
for k, ii, jj in T.grid(16, 4, 4):
with T.sblock("update"):
vi = T.axis.spatial(16, i * 4 + ii)
vj = T.axis.spatial(16, j * 4 + jj)
vk = T.axis.reduce(16, k)
T.reads(local[vi, vj], A[vi, vk], B[vj, vk])
T.writes(local[vi, vj])
local[vi, vj] = local[vi, vj] + A[vi, vk] * B[vj, vk]
for ii, jj in T.grid(4, 4):
with T.sblock("C"):
vi = T.axis.spatial(16, i * 4 + ii)
vj = T.axis.spatial(16, j * 4 + jj)
T.reads(local[vi, vj])
T.writes(C[vi, vj])
C[vi, vj] = local[vi, vj]
@T.prim_func(s_tir=True)
def scatter_compute(A: T.Buffer((16,), "float32"), B: T.Buffer((16,), "float32")):
for i in T.grid(8):
with T.sblock("first_half"):
vi = T.axis.spatial(16, 8 + i)
B[vi] = A[vi - 8]
for i in T.grid(8):
with T.sblock("last_half"):
vi = T.axis.spatial(16, i)
B[vi] = A[vi + 8]
@T.prim_func(s_tir=True)
def scatter_compute_parallelize(
A: T.Buffer((16,), "float32"), B: T.Buffer((16,), "float32")
) -> None:
# body
# with T.sblock("root")
for i in T.parallel(8):
with T.sblock("first_half"):
vi = T.axis.spatial(16, 8 + i)
T.reads(A[vi - 8])
T.writes(B[vi])
B[vi] = A[vi - 8]
for i in T.parallel(8):
with T.sblock("last_half"):
vi = T.axis.spatial(16, i)
T.reads(A[vi + 8])
T.writes(B[vi])
B[vi] = A[vi + 8]
# pylint: enable=no-member,invalid-name,unused-variable
def test_parallel():
s = tvm.s_tir.Schedule(element_wise, debug_mask="all")
i, _ = s.get_loops(s.get_sblock("B"))
s.parallel(i)
assert_structural_equal_ignore_global_symbol(s.mod["main"], element_wise_parallelized)
verify_trace_roundtrip(s, mod=element_wise)
def test_parallel_predicate():
s = tvm.s_tir.Schedule(element_wise_split_predicate, debug_mask="all")
_, j, _ = s.get_loops(s.get_sblock("B"))
s.parallel(j)
assert_structural_equal_ignore_global_symbol(
s.mod["main"], element_wise_split_predicate_parallelized
)
verify_trace_roundtrip(s, mod=element_wise_split_predicate)
def test_parallel_reduction_block_iter():
s = tvm.s_tir.Schedule(matmul, debug_mask="all")
_, _, k = s.get_loops(s.get_sblock("C"))
with pytest.raises(tvm.s_tir.ScheduleError):
s.parallel(k)
def test_parallel_not_quasi_affine():
s = tvm.s_tir.Schedule(rowsum_not_quasi_affine, debug_mask="all")
i, _ = s.get_loops(s.get_sblock("B"))
with pytest.raises(tvm.s_tir.ScheduleError):
s.parallel(i)
def test_parallel_not_compact_data_flow():
s = tvm.s_tir.Schedule(rowsum_not_compact_data_flow, debug_mask="all")
i, _ = s.get_loops(s.get_sblock("B"))
with pytest.raises(tvm.s_tir.ScheduleError):
s.parallel(i)
def test_vectorize():
s = tvm.s_tir.Schedule(element_wise_compute_at_split, debug_mask="all")
_, _, j1i = s.get_loops(s.get_sblock("C"))
s.vectorize(j1i)
assert_structural_equal_ignore_global_symbol(
s.mod["main"], element_wise_compute_at_split_vectorized
)
verify_trace_roundtrip(s, mod=element_wise_compute_at_split)
def test_vectorize_predicate():
s = tvm.s_tir.Schedule(element_wise_split_predicate, debug_mask="all")
i, _, _ = s.get_loops(s.get_sblock("B"))
s.vectorize(i)
assert_structural_equal_ignore_global_symbol(
s.mod["main"], element_wise_split_predicate_vectorized
)
verify_trace_roundtrip(s, mod=element_wise_split_predicate)
def test_vectorize_opaque_block():
s = tvm.s_tir.Schedule(opaque_block, debug_mask="all")
(i,) = s.get_loops(s.get_sblock("opaque"))
with pytest.raises(tvm.s_tir.ScheduleError):
s.vectorize(i)
def test_unroll():
s = tvm.s_tir.Schedule(rowsum, debug_mask="all")
i, _ = s.get_loops(s.get_sblock("B"))
s.unroll(i)
assert_structural_equal_ignore_global_symbol(s.mod["main"], rowsum_unrolled)
verify_trace_roundtrip(s, mod=rowsum)
def test_unroll_after_bind():
s = tvm.s_tir.Schedule(rowsum, debug_mask="all")
i, _ = s.get_loops(s.get_sblock("B"))
s.bind(i, "blockIdx.x")
s.unroll(i)
assert_structural_equal_ignore_global_symbol(s.mod["main"], rowsum_unrolled)
verify_trace_roundtrip(s, mod=rowsum)
def test_bind1():
s = tvm.s_tir.Schedule(element_wise, debug_mask="all")
i, _ = s.get_loops(s.get_sblock("B"))
s.bind(i, "threadIdx.x")
assert_structural_equal_ignore_global_symbol(s.mod["main"], element_wise_i_bound)
verify_trace_roundtrip(s, mod=element_wise)
def test_bind2():
s = tvm.s_tir.Schedule(element_wise_compute_at_split, debug_mask="all")
_, j0 = s.get_loops(s.get_sblock("B"))
_, j1o, _ = s.get_loops(s.get_sblock("C"))
s.bind(j0, "threadIdx.x")
s.bind(j1o, "threadIdx.x")
assert_structural_equal_ignore_global_symbol(
s.mod["main"], element_wise_compute_at_split_j0_j1o_bound
)
verify_trace_roundtrip(s, mod=element_wise_compute_at_split)
def test_bind_cross_thread_reduction():
s = tvm.s_tir.Schedule(rowsum, debug_mask="all")
_, k = s.get_loops(s.get_sblock("B"))
s.bind(k, "threadIdx.x")
assert_structural_equal_ignore_global_symbol(s.mod["main"], rowsum_cross_thread_reduction)
verify_trace_roundtrip(s, mod=rowsum)
def test_bind_not_cross_thread_reduction():
s = tvm.s_tir.Schedule(rowsum, debug_mask="all")
_, k = s.get_loops(s.get_sblock("B"))
with pytest.raises(tvm.s_tir.ScheduleError):
s.bind(k, "blockIdx.x")
def test_bind_after_bind():
s = tvm.s_tir.Schedule(element_wise, debug_mask="all")
i, _ = s.get_loops(s.get_sblock("B"))
s.bind(i, "blockIdx.x")
s.bind(i, "threadIdx.x")
assert_structural_equal_ignore_global_symbol(s.mod["main"], element_wise_i_bound)
verify_trace_roundtrip(s, mod=element_wise)
def test_block_inside_init():
s = tvm.s_tir.Schedule(block_inside_init, debug_mask="all")
(i,) = s.get_loops(s.get_sblock("outer"))
s.bind(i, "threadIdx.x")
assert_structural_equal_ignore_global_symbol(s.mod["main"], thread_bound_block_inside_init)
verify_trace_roundtrip(s, mod=block_inside_init)
def test_vectorize_after_decompose():
s = tvm.s_tir.Schedule(decomposed_gemm, debug_mask="all")
jj = s.get_loops(s.get_sblock("C"))[-1]
s.vectorize(jj)
assert_structural_equal_ignore_global_symbol(s.mod["main"], decomposed_gemm_after_vectorize)
verify_trace_roundtrip(s, mod=decomposed_gemm)
def test_nested_block_bind():
s = tvm.s_tir.Schedule(nested_block_bind)
block_outer = s.get_sblock("outer")
block_inner = s.get_sblock("inner")
_, j = s.get_loops(block_outer)
_, l = s.get_loops(block_inner)
s.bind(l, "threadIdx.x")
s.bind(j, "blockIdx.x")
assert_structural_equal_ignore_global_symbol(s.mod["main"], thread_bound_nested_block)
verify_trace_roundtrip(s, mod=nested_block_bind)
def test_nexted_block_bind_after_cache_read():
s = tvm.s_tir.Schedule(nested_block_bind_after_cache_read)
block_outer = s.get_sblock("outer")
block_inner = s.get_sblock("inner")
(i,) = s.get_loops(block_outer)
(j,) = s.get_loops(block_inner)
s.bind(i, "blockIdx.x")
s.bind(j, "threadIdx.x")
assert_structural_equal_ignore_global_symbol(
s.mod["main"], thread_bound_nested_block_after_cache_read
)
verify_trace_roundtrip(s, mod=nested_block_bind_after_cache_read)
def test_vectorize_init():
s = tvm.s_tir.Schedule(decomposed_gemm, debug_mask="all")
init_blk = s.get_sblock("init")
upd_blk = s.get_sblock("update")
_, _, ii_0, jj_0 = s.get_loops(init_blk)
_, _, k_1, ii_1, jj_1 = s.get_loops(upd_blk)
s.vectorize(jj_0)
assert_structural_equal_ignore_global_symbol(s.mod["main"], decomposed_gemm_parallelize_init)
verify_trace_roundtrip(s, mod=decomposed_gemm)
def test_scatter_parallelize():
s = tvm.s_tir.Schedule(scatter_compute, debug_mask="all")
first = s.get_sblock("first_half")
last = s.get_sblock("last_half")
(i_0,) = s.get_loops(first)
(i_1,) = s.get_loops(last)
s.parallel(i_0)
s.parallel(i_1)
assert_structural_equal_ignore_global_symbol(s.mod["main"], scatter_compute_parallelize)
verify_trace_roundtrip(s, mod=scatter_compute)
def test_bind_thread_iter_var_dtype():
@T.prim_func(private=True, s_tir=True)
def before(
A: T.Buffer((T.int64(128), T.int64(128))),
B: T.Buffer((T.int64(128), T.int64(128))),
) -> None:
for i, j in T.grid(T.int64(128), T.int64(128)):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
@T.prim_func(private=True, s_tir=True)
def expected(
A: T.Buffer((T.int64(128), T.int64(128))),
B: T.Buffer((T.int64(128), T.int64(128))),
) -> None:
for i0 in T.thread_binding(T.int64(128), thread="threadIdx.x"):
# Use T.serial with explicit int64 min so the inner sblock iter_var dom
# is all-int64 (matches what `s.bind` emits; `range(T.int64(128))` parses
# min as int32 even when extent is int64).
for i1 in T.serial(T.int64(0), T.int64(128)):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i0, i1])
B[vi, vj] = A[vi, vj] * 2.0
s = tvm.s_tir.Schedule(before, debug_mask="all")
i, _ = s.get_loops(s.get_sblock("B"))
s.bind(i, "threadIdx.x")
assert_structural_equal_ignore_global_symbol(s.mod["main"], expected)
verify_trace_roundtrip(s, mod=before)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,279 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: F401, F841
import numpy as np
import pytest
import tvm
import tvm.testing
from tvm import tirx
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.script import tirx as T
# pylint: disable=no-member,invalid-name,unused-variable
@T.prim_func(s_tir=True)
def matmul_bias_before(
A: T.Buffer((16, 16), "int8"),
B: T.Buffer((16, 16), "int8"),
C: T.Buffer((16, 16), "int32"),
D: T.Buffer((16, 16), "int32"),
) -> None:
temp = T.sblock_alloc_buffer((16, 16), dtype="int32")
for i, j, k in T.grid(16, 16, 16):
with T.sblock("multiply"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
temp[vi, vj] = T.int32(0)
temp[vi, vj] = temp[vi, vj] + T.cast(A[vi, vk], "int32") * T.cast(B[vj, vk], "int32")
for i, j in T.grid(16, 16):
with T.sblock("add"):
vi, vj = T.axis.remap("SS", [i, j])
D[vi, vj] = temp[vi, vj] + C[vi, vj]
@T.prim_func(s_tir=True)
def matmul_bias_expected(
A: T.Buffer((16, 16), "int8"),
B: T.Buffer((16, 16), "int8"),
C: T.Buffer((16, 16), "int32"),
D: T.Buffer((16, 16), "int32"),
) -> None:
temp = T.sblock_alloc_buffer((16, 16), dtype="int32")
for i, j, k in T.grid(16, 16, 16):
with T.sblock("multiply"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
T.reads(C[vi, vj], A[vi, vk], B[vj, vk])
T.writes(D[vi, vj])
with T.init():
D[vi, vj] = C[vi, vj]
D[vi, vj] = D[vi, vj] + T.cast(A[vi, vk], "int32") * T.cast(B[vj, vk], "int32")
@T.prim_func(s_tir=True)
def matmul_bias_fp32_before(
A: T.Buffer((32, 32), "float32"),
B: T.Buffer((32, 32), "float32"),
C: T.Buffer((32, 32), "float32"),
D: T.Buffer((32, 32), "float32"),
) -> None:
temp = T.sblock_alloc_buffer((32, 32), dtype="float32")
for i, j, k in T.grid(32, 32, 32):
with T.sblock("multiply"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
temp[vi, vj] = T.float32(0)
temp[vi, vj] = temp[vi, vj] + A[vi, vk] * B[vj, vk]
for i, j in T.grid(32, 32):
with T.sblock("add"):
vi, vj = T.axis.remap("SS", [i, j])
D[vi, vj] = temp[vi, vj] + C[vi, vj]
@T.prim_func(s_tir=True)
def matmul_bias_fp32_expected(
A: T.Buffer((32, 32), "float32"),
B: T.Buffer((32, 32), "float32"),
C: T.Buffer((32, 32), "float32"),
D: T.Buffer((32, 32), "float32"),
) -> None:
temp = T.sblock_alloc_buffer((32, 32), dtype="float32")
for i, j, k in T.grid(32, 32, 32):
with T.sblock("multiply"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
T.reads(C[vi, vj], A[vi, vk], B[vj, vk])
T.writes(D[vi, vj])
with T.init():
D[vi, vj] = C[vi, vj]
D[vi, vj] = D[vi, vj] + A[vi, vk] * B[vj, vk]
@T.prim_func(s_tir=True)
def matmul_bias_multiple_epilogue_before(
A: T.Buffer((16, 16), "int8"),
B: T.Buffer((16, 16), "int8"),
C: T.Buffer((16, 16), "int32"),
D: T.Buffer((16, 16), "int32"),
E: T.Buffer((16, 16), "int32"),
) -> None:
temp = T.sblock_alloc_buffer((16, 16), dtype="int32")
for i, j, k in T.grid(16, 16, 16):
with T.sblock("multiply"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
temp[vi, vj] = T.int32(0)
temp[vi, vj] = temp[vi, vj] + T.cast(A[vi, vk], "int32") * T.cast(B[vj, vk], "int32")
for i, j in T.grid(16, 16):
with T.sblock("add"):
vi, vj = T.axis.remap("SS", [i, j])
D[vi, vj] = temp[vi, vj] + C[vi, vj]
for i, j in T.grid(16, 16):
with T.sblock("add2"):
vi, vj = T.axis.remap("SS", [i, j])
E[vi, vj] = temp[vi, vj] + C[vi, vj]
@T.prim_func(s_tir=True)
def matmul_bias_multiple_epilogue_expected(
A: T.Buffer((16, 16), "int8"),
B: T.Buffer((16, 16), "int8"),
C: T.Buffer((16, 16), "int32"),
D: T.Buffer((16, 16), "int32"),
E: T.Buffer((16, 16), "int32"),
) -> None:
temp = T.sblock_alloc_buffer((16, 16), dtype="int32")
for i, j, k in T.grid(16, 16, 16):
with T.sblock("multiply"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
T.reads(C[vi, vj], A[vi, vk], B[vj, vk])
T.writes(D[vi, vj])
with T.init():
D[vi, vj] = C[vi, vj]
D[vi, vj] = D[vi, vj] + T.cast(A[vi, vk], "int32") * T.cast(B[vj, vk], "int32")
for i, j in T.grid(16, 16):
with T.sblock("add2"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(temp[vi, vj], C[vi, vj])
T.writes(E[vi, vj])
E[vi, vj] = temp[vi, vj] + C[vi, vj]
def test_fuse_reduction_epilogue_basic():
sch = tvm.s_tir.Schedule(matmul_bias_before, debug_mask="all")
sch.fuse_reduction_epilogue("multiply", "add")
assert_structural_equal_ignore_global_symbol(sch.mod["main"], matmul_bias_expected)
verify_trace_roundtrip(sch=sch, mod=matmul_bias_before)
def test_fuse_reduction_epilogue_fp32():
sch = tvm.s_tir.Schedule(matmul_bias_fp32_before, debug_mask="all")
sch.fuse_reduction_epilogue("multiply", "add")
assert_structural_equal_ignore_global_symbol(sch.mod["main"], matmul_bias_fp32_expected)
verify_trace_roundtrip(sch=sch, mod=matmul_bias_fp32_before)
def test_fuse_reduction_epilogue_numerical_correctness():
sch_original = tvm.s_tir.Schedule(matmul_bias_before, debug_mask="all")
mod_original = tvm.compile(sch_original.mod["main"], target="llvm")
sch_fused = tvm.s_tir.Schedule(matmul_bias_before, debug_mask="all")
sch_fused.fuse_reduction_epilogue("multiply", "add")
mod_fused = tvm.compile(sch_fused.mod["main"], target="llvm")
A_np = np.random.randint(-128, 127, size=(16, 16), dtype="int8")
B_np = np.random.randint(-128, 127, size=(16, 16), dtype="int8")
C_np = np.random.randint(-1000, 1000, size=(16, 16), dtype="int32")
expected = (A_np.astype("int32") @ B_np.T.astype("int32")) + C_np
D_original_tvm = tvm.runtime.tensor(np.zeros((16, 16), dtype="int32"))
D_fused_tvm = tvm.runtime.tensor(np.zeros((16, 16), dtype="int32"))
mod_original(
tvm.runtime.tensor(A_np), tvm.runtime.tensor(B_np), tvm.runtime.tensor(C_np), D_original_tvm
)
mod_fused(
tvm.runtime.tensor(A_np), tvm.runtime.tensor(B_np), tvm.runtime.tensor(C_np), D_fused_tvm
)
D_original = D_original_tvm.numpy()
D_fused = D_fused_tvm.numpy()
tvm.testing.assert_allclose(D_original, expected, rtol=1e-5)
tvm.testing.assert_allclose(D_fused, expected, rtol=1e-5)
tvm.testing.assert_allclose(D_fused, D_original, rtol=1e-5)
def test_fuse_reduction_epilogue_multiple_epilogue():
sch = tvm.s_tir.Schedule(matmul_bias_multiple_epilogue_before, debug_mask="all")
sch.fuse_reduction_epilogue("multiply", "add")
assert_structural_equal_ignore_global_symbol(
sch.mod["main"], matmul_bias_multiple_epilogue_expected
)
verify_trace_roundtrip(sch=sch, mod=matmul_bias_multiple_epilogue_before)
mod = tvm.compile(sch.mod["main"], target="llvm")
assert mod is not None
@T.prim_func(s_tir=True)
def matmul_bias_invalid_multiple_use_before(
A: T.Buffer((16, 16), "int8"),
B: T.Buffer((16, 16), "int8"),
C1: T.Buffer((16, 16), "int32"),
C2: T.Buffer((16, 16), "int32"),
D: T.Buffer((16, 16), "int32"),
) -> None:
"""Epilogue uses the reduction result twice; fusion must be rejected."""
temp = T.sblock_alloc_buffer((16, 16), dtype="int32")
for i, j, k in T.grid(16, 16, 16):
with T.sblock("multiply"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
temp[vi, vj] = T.int32(0)
temp[vi, vj] = temp[vi, vj] + T.cast(A[vi, vk], "int32") * T.cast(B[vj, vk], "int32")
for i, j in T.grid(16, 16):
with T.sblock("bad_epilogue"):
vi, vj = T.axis.remap("SS", [i, j])
# temp[vi, vj] is used twice in the epilogue expression
D[vi, vj] = (temp[vi, vj] + C1[vi, vj]) * (temp[vi, vj] + C2[vi, vj])
def test_fuse_reduction_epilogue_reject_multiple_use():
"""fusion should be rejected when the reduction result appears more than once."""
sch = tvm.s_tir.Schedule(matmul_bias_invalid_multiple_use_before, debug_mask="all")
with pytest.raises(tvm.s_tir.ScheduleError):
sch.fuse_reduction_epilogue("multiply", "bad_epilogue")
@T.prim_func(s_tir=True)
def matmul_bias_invalid_scaling_before(
A: T.Buffer((16, 16), "int8"),
B: T.Buffer((16, 16), "int8"),
C: T.Buffer((16, 16), "int32"),
D: T.Buffer((16, 16), "int32"),
) -> None:
"""Epilogue scales the reduction result; fusion must be rejected."""
temp = T.sblock_alloc_buffer((16, 16), dtype="int32")
for i, j, k in T.grid(16, 16, 16):
with T.sblock("multiply"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
temp[vi, vj] = T.int32(0)
temp[vi, vj] = temp[vi, vj] + T.cast(A[vi, vk], "int32") * T.cast(B[vj, vk], "int32")
for i, j in T.grid(16, 16):
with T.sblock("scaled_epilogue"):
vi, vj = T.axis.remap("SS", [i, j])
# temp[vi, vj] is scaled by 2 before adding bias; this must not be fused.
D[vi, vj] = temp[vi, vj] * T.int32(2) + C[vi, vj]
def test_fuse_reduction_epilogue_reject_scaling():
"""fusion should be rejected when the reduction result is scaled by Mul/Div/Mod."""
sch = tvm.s_tir.Schedule(matmul_bias_invalid_scaling_before, debug_mask="all")
with pytest.raises(tvm.s_tir.ScheduleError):
sch.fuse_reduction_epilogue("multiply", "scaled_epilogue")
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,273 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: E501, F401, F841
import numpy as np
import pytest
import tvm
import tvm.testing
from tvm import tirx
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.script import tirx as T
# pylint: disable=no-member,invalid-name,unused-variable
@T.prim_func(s_tir=True)
def matmul_clipping_before(
A: T.Buffer((16, 16), "float32"),
B: T.Buffer((16, 16), "float32"),
D: T.Buffer((16, 16), "float32"),
lower: T.float32,
upper: T.float32,
) -> None:
"""Original function with separate reduction and clipping epilogue blocks."""
temp = T.sblock_alloc_buffer((16, 16), dtype="float32")
for i, j, k in T.grid(16, 16, 16):
with T.sblock("matmul"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
temp[vi, vj] = T.float32(0)
temp[vi, vj] = temp[vi, vj] + A[vi, vk] * B[vj, vk]
for i, j in T.grid(16, 16):
with T.sblock("clipping"):
vi, vj = T.axis.remap("SS", [i, j])
D[vi, vj] = T.min(T.max(temp[vi, vj], lower), upper)
@T.prim_func(s_tir=True)
def matmul_clipping_expected(
A: T.Buffer((16, 16), "float32"),
B: T.Buffer((16, 16), "float32"),
D: T.Buffer((16, 16), "float32"),
lower: T.float32,
upper: T.float32,
) -> None:
"""Expected function after fusion (Clipping)."""
temp = T.sblock_alloc_buffer((16, 16), dtype="float32")
for i, j, k in T.grid(16, 16, 16):
with T.sblock("matmul"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
T.reads(A[vi, vk], B[vj, vk])
T.writes(D[vi, vj])
with T.init():
D[vi, vj] = T.min(T.max(T.float32(0), lower), upper)
D[vi, vj] = T.min(T.max(D[vi, vj] + A[vi, vk] * B[vj, vk], lower), upper)
def test_matmul_clipping():
"""Test fusion of matmul with clipping epilogue."""
sch = tvm.s_tir.Schedule(matmul_clipping_before, debug_mask="all")
sch.fuse_reduction_epilogue("matmul", "clipping")
assert_structural_equal_ignore_global_symbol(sch.mod["main"], matmul_clipping_expected)
verify_trace_roundtrip(sch=sch, mod=matmul_clipping_before)
@T.prim_func(s_tir=True)
def matmul_clipping_before_per_iteration(
A: T.Buffer((16, 16), "float32"),
B: T.Buffer((16, 16), "float32"),
D: T.Buffer((16, 16), "float32"),
) -> None:
"""Original function with per-iteration clipping (same semantics as fused)."""
temp = T.sblock_alloc_buffer((16, 16), dtype="float32")
lower = T.float32(-5.0)
upper = T.float32(5.0)
for i, j in T.grid(16, 16):
with T.sblock("init"):
vi, vj = T.axis.remap("SS", [i, j])
temp[vi, vj] = T.min(T.max(T.float32(0), lower), upper) # Clip init
for i, j, k in T.grid(16, 16, 16):
with T.sblock("matmul"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
# Per-iteration clipping
temp[vi, vj] = T.min(T.max(temp[vi, vj] + A[vi, vk] * B[vj, vk], lower), upper)
for i, j in T.grid(16, 16):
with T.sblock("copy"):
vi, vj = T.axis.remap("SS", [i, j])
D[vi, vj] = temp[vi, vj]
def test_matmul_clipping_correctness_unified():
"""Test that original and fused produce identical results with per-iteration clipping."""
A_np = np.random.randn(16, 16).astype("float32")
B_np = np.random.randn(16, 16).astype("float32")
lower = -5.0
upper = 5.0
# NumPy reference for per-iteration clipping
D_ref = np.clip(0.0, lower, upper) # init with clipping
for k in range(16):
D_ref = np.clip(D_ref + np.outer(A_np[:, k], B_np[:, k]), lower, upper)
# TVM execution (original with per-iteration clipping)
mod_original = tvm.compile(matmul_clipping_before_per_iteration, target="llvm")
D_original_tvm = tvm.runtime.tensor(np.zeros((16, 16), dtype="float32"))
mod_original(
tvm.runtime.tensor(A_np),
tvm.runtime.tensor(B_np),
D_original_tvm,
)
# TVM execution (fused)
sch = tvm.s_tir.Schedule(matmul_clipping_before)
sch.fuse_reduction_epilogue("matmul", "clipping")
mod_fused = tvm.compile(sch.mod["main"], target="llvm")
D_fused_tvm = tvm.runtime.tensor(np.zeros((16, 16), dtype="float32"))
# Pass scalar values directly as Python floats
mod_fused(
tvm.runtime.tensor(A_np),
tvm.runtime.tensor(B_np),
D_fused_tvm,
lower,
upper,
)
D_original = D_original_tvm.numpy()
D_fused = D_fused_tvm.numpy()
# Now both should match exactly
np.testing.assert_allclose(D_original, D_ref, rtol=1e-5, atol=1e-6)
np.testing.assert_allclose(D_fused, D_ref, rtol=1e-5, atol=1e-6)
np.testing.assert_allclose(D_original, D_fused, rtol=1e-5, atol=1e-6)
@T.prim_func(s_tir=True)
def matmul_clipping_multiple_epilogue_before(
A: T.Buffer((16, 16), "float32"),
B: T.Buffer((16, 16), "float32"),
D: T.Buffer((16, 16), "float32"),
E: T.Buffer((16, 16), "float32"),
lower: T.float32,
upper: T.float32,
) -> None:
"""Original function with separate reduction and multiple epilogue blocks (one with clipping, one without)."""
temp = T.sblock_alloc_buffer((16, 16), dtype="float32")
for i, j, k in T.grid(16, 16, 16):
with T.sblock("matmul"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
temp[vi, vj] = T.float32(0)
temp[vi, vj] = temp[vi, vj] + A[vi, vk] * B[vj, vk]
for i, j in T.grid(16, 16):
with T.sblock("clipping"):
vi, vj = T.axis.remap("SS", [i, j])
D[vi, vj] = T.min(T.max(temp[vi, vj], lower), upper)
for i, j in T.grid(16, 16):
with T.sblock("copy"):
vi, vj = T.axis.remap("SS", [i, j])
E[vi, vj] = temp[vi, vj]
@T.prim_func(s_tir=True)
def matmul_clipping_multiple_epilogue_expected(
A: T.Buffer((16, 16), "float32"),
B: T.Buffer((16, 16), "float32"),
D: T.Buffer((16, 16), "float32"),
E: T.Buffer((16, 16), "float32"),
lower: T.float32,
upper: T.float32,
) -> None:
"""Expected function after fusion (Clipping) with multiple epilogue blocks."""
temp = T.sblock_alloc_buffer((16, 16), dtype="float32")
for i, j, k in T.grid(16, 16, 16):
with T.sblock("matmul"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
T.reads(A[vi, vk], B[vj, vk])
T.writes(D[vi, vj])
with T.init():
D[vi, vj] = T.min(T.max(T.float32(0), lower), upper)
D[vi, vj] = T.min(T.max(D[vi, vj] + A[vi, vk] * B[vj, vk], lower), upper)
for i, j in T.grid(16, 16):
with T.sblock("copy"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(temp[vi, vj])
T.writes(E[vi, vj])
E[vi, vj] = temp[vi, vj]
def test_matmul_clipping_multiple_epilogue():
"""Test fusion with multiple epilogue blocks - one with clipping, one without.
Following the same pattern as test_fuse_reduction_epilogue_multiple_epilogue,
this test verifies that fusion works correctly when there are multiple
epilogue blocks. The temp buffer is kept because the second epilogue block
still needs it.
"""
sch = tvm.s_tir.Schedule(matmul_clipping_multiple_epilogue_before, debug_mask="all")
sch.fuse_reduction_epilogue("matmul", "clipping")
assert_structural_equal_ignore_global_symbol(
sch.mod["main"], matmul_clipping_multiple_epilogue_expected
)
verify_trace_roundtrip(sch=sch, mod=matmul_clipping_multiple_epilogue_before)
mod = tvm.compile(sch.mod["main"], target="llvm")
assert mod is not None
# Test commutative variants of clipping patterns
@pytest.mark.parametrize(
"pattern_func",
[
lambda temp, lower, upper: T.min(T.max(temp, lower), upper), # min(max(temp, lower), upper)
lambda temp, lower, upper: T.min(upper, T.max(temp, lower)), # min(upper, max(temp, lower))
lambda temp, lower, upper: T.min(T.max(lower, temp), upper), # min(max(lower, temp), upper)
lambda temp, lower, upper: T.max(T.min(temp, upper), lower), # max(min(temp, upper), lower)
lambda temp, lower, upper: T.max(lower, T.min(temp, upper)), # max(lower, min(temp, upper))
],
)
def test_matmul_clipping_commutative_variants(pattern_func):
"""Test that all commutative variants of clipping patterns are recognized."""
lower = -5.0
upper = 5.0
@T.prim_func(s_tir=True)
def test_func(
A: T.Buffer((8, 8), "float32"),
B: T.Buffer((8, 8), "float32"),
D: T.Buffer((8, 8), "float32"),
) -> None:
temp = T.sblock_alloc_buffer((8, 8), dtype="float32")
for i, j, k in T.grid(8, 8, 8):
with T.sblock("matmul"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
temp[vi, vj] = T.float32(0)
temp[vi, vj] = temp[vi, vj] + A[vi, vk] * B[vj, vk]
for i, j in T.grid(8, 8):
with T.sblock("clipping"):
vi, vj = T.axis.remap("SS", [i, j])
D[vi, vj] = pattern_func(temp[vi, vj], T.float32(lower), T.float32(upper))
sch = tvm.s_tir.Schedule(test_func, debug_mask="all")
# Should not raise an error - all variants should be recognized
sch.fuse_reduction_epilogue("matmul", "clipping")
verify_trace_roundtrip(sch=sch, mod=test_func)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,231 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: E501, F401, F841
import numpy as np
import pytest
import tvm
import tvm.testing
from tvm import tirx
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.script import tirx as T
# pylint: disable=no-member,invalid-name,unused-variable
@T.prim_func(s_tir=True)
def matmul_bias_relu_before(
A: T.Buffer((16, 16), "float32"),
B: T.Buffer((16, 16), "float32"),
C: T.Buffer((16, 16), "float32"),
D: T.Buffer((16, 16), "float32"),
) -> None:
"""Original function with separate reduction and epilogue blocks (Bias + ReLU)."""
temp = T.sblock_alloc_buffer((16, 16), dtype="float32")
for i, j, k in T.grid(16, 16, 16):
with T.sblock("matmul"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
temp[vi, vj] = T.float32(0)
temp[vi, vj] = temp[vi, vj] + A[vi, vk] * B[vj, vk]
for i, j in T.grid(16, 16):
with T.sblock("bias_relu"):
vi, vj = T.axis.remap("SS", [i, j])
D[vi, vj] = T.max(temp[vi, vj] + C[vi, vj], T.float32(0))
@T.prim_func(s_tir=True)
def matmul_bias_relu_before_per_iteration(
A: T.Buffer((16, 16), "float32"),
B: T.Buffer((16, 16), "float32"),
C: T.Buffer((16, 16), "float32"),
D: T.Buffer((16, 16), "float32"),
) -> None:
"""Original function with per-iteration ReLU (same semantics as fused)."""
temp = T.sblock_alloc_buffer((16, 16), dtype="float32")
for i, j in T.grid(16, 16):
with T.sblock("init"):
vi, vj = T.axis.remap("SS", [i, j])
temp[vi, vj] = T.max(C[vi, vj], T.float32(0)) # ReLU on bias
for i, j, k in T.grid(16, 16, 16):
with T.sblock("matmul"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
# Per-iteration ReLU
temp[vi, vj] = T.max(temp[vi, vj] + A[vi, vk] * B[vj, vk], T.float32(0))
for i, j in T.grid(16, 16):
with T.sblock("copy"):
vi, vj = T.axis.remap("SS", [i, j])
D[vi, vj] = temp[vi, vj]
@T.prim_func(s_tir=True)
def matmul_bias_relu_expected(
A: T.Buffer((16, 16), "float32"),
B: T.Buffer((16, 16), "float32"),
C: T.Buffer((16, 16), "float32"),
D: T.Buffer((16, 16), "float32"),
) -> None:
"""Expected function after fusion (Bias + ReLU)."""
temp = T.sblock_alloc_buffer((16, 16), dtype="float32")
for i, j, k in T.grid(16, 16, 16):
with T.sblock("matmul"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
T.reads(C[vi, vj], A[vi, vk], B[vj, vk])
T.writes(D[vi, vj])
with T.init():
D[vi, vj] = T.max(C[vi, vj], T.float32(0))
D[vi, vj] = T.max(D[vi, vj] + A[vi, vk] * B[vj, vk], T.float32(0))
def test_matmul_bias_relu():
"""Test fusion of matmul with bias + ReLU epilogue."""
sch = tvm.s_tir.Schedule(matmul_bias_relu_before, debug_mask="all")
sch.fuse_reduction_epilogue("matmul", "bias_relu")
assert_structural_equal_ignore_global_symbol(sch.mod["main"], matmul_bias_relu_expected)
verify_trace_roundtrip(sch=sch, mod=matmul_bias_relu_before)
def test_matmul_bias_relu_correctness_unified():
"""Test that original and fused produce identical results with per-iteration ReLU."""
A_np = np.random.randn(16, 16).astype("float32")
B_np = np.random.randn(16, 16).astype("float32")
C_np = np.random.randn(16, 16).astype("float32")
# NumPy reference for per-iteration ReLU
# Simulate per-iteration ReLU behavior
# Original code computes A[vi, vk] * B[vj, vk] which is A[i, k] * B[j, k]
# For each k: add outer product of A[:, k] and B[:, k]
D_ref = np.maximum(C_np, 0) # init with ReLU on bias
for k in range(16):
# A[:, k] is shape (16,), B[:, k] is shape (16,)
# Outer product: A[:, k] * B[:, k] for all i, j = A[i, k] * B[j, k]
# Using broadcasting: A[:, k:k+1] * B[:, k:k+1].T gives (16, 1) * (1, 16) = (16, 16)
D_ref = np.maximum(D_ref + np.outer(A_np[:, k], B_np[:, k]), 0)
# TVM execution (original with per-iteration ReLU)
mod_original = tvm.compile(matmul_bias_relu_before_per_iteration, target="llvm")
D_original_tvm = tvm.runtime.tensor(np.zeros((16, 16), dtype="float32"))
mod_original(
tvm.runtime.tensor(A_np),
tvm.runtime.tensor(B_np),
tvm.runtime.tensor(C_np),
D_original_tvm,
)
# TVM execution (fused)
sch = tvm.s_tir.Schedule(matmul_bias_relu_before)
sch.fuse_reduction_epilogue("matmul", "bias_relu")
mod_fused = tvm.compile(sch.mod["main"], target="llvm")
D_fused_tvm = tvm.runtime.tensor(np.zeros((16, 16), dtype="float32"))
mod_fused(
tvm.runtime.tensor(A_np),
tvm.runtime.tensor(B_np),
tvm.runtime.tensor(C_np),
D_fused_tvm,
)
D_original = D_original_tvm.numpy()
D_fused = D_fused_tvm.numpy()
# Now both should match exactly
np.testing.assert_allclose(D_original, D_ref, rtol=1e-5, atol=1e-6)
np.testing.assert_allclose(D_fused, D_ref, rtol=1e-5, atol=1e-6)
np.testing.assert_allclose(D_original, D_fused, rtol=1e-5, atol=1e-6)
@T.prim_func(s_tir=True)
def matmul_bias_relu_multiple_epilogue_before(
A: T.Buffer((16, 16), "float32"),
B: T.Buffer((16, 16), "float32"),
C: T.Buffer((16, 16), "float32"),
D: T.Buffer((16, 16), "float32"),
E: T.Buffer((16, 16), "float32"),
) -> None:
"""Original function with separate reduction and multiple epilogue blocks (one with ReLU, one without)."""
temp = T.sblock_alloc_buffer((16, 16), dtype="float32")
for i, j, k in T.grid(16, 16, 16):
with T.sblock("matmul"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
temp[vi, vj] = T.float32(0)
temp[vi, vj] = temp[vi, vj] + A[vi, vk] * B[vj, vk]
for i, j in T.grid(16, 16):
with T.sblock("bias_relu"):
vi, vj = T.axis.remap("SS", [i, j])
D[vi, vj] = T.max(temp[vi, vj] + C[vi, vj], T.float32(0))
for i, j in T.grid(16, 16):
with T.sblock("bias"):
vi, vj = T.axis.remap("SS", [i, j])
E[vi, vj] = temp[vi, vj] + C[vi, vj]
@T.prim_func(s_tir=True)
def matmul_bias_relu_multiple_epilogue_expected(
A: T.Buffer((16, 16), "float32"),
B: T.Buffer((16, 16), "float32"),
C: T.Buffer((16, 16), "float32"),
D: T.Buffer((16, 16), "float32"),
E: T.Buffer((16, 16), "float32"),
) -> None:
"""Expected function after fusion (Bias + ReLU) with multiple epilogue blocks."""
temp = T.sblock_alloc_buffer((16, 16), dtype="float32")
for i, j, k in T.grid(16, 16, 16):
with T.sblock("matmul"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
T.reads(C[vi, vj], A[vi, vk], B[vj, vk])
T.writes(D[vi, vj])
with T.init():
D[vi, vj] = T.max(C[vi, vj], T.float32(0))
D[vi, vj] = T.max(D[vi, vj] + A[vi, vk] * B[vj, vk], T.float32(0))
for i, j in T.grid(16, 16):
with T.sblock("bias"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(temp[vi, vj], C[vi, vj])
T.writes(E[vi, vj])
E[vi, vj] = temp[vi, vj] + C[vi, vj]
def test_matmul_bias_relu_multiple_epilogue():
"""Test fusion with multiple epilogue blocks - one with ReLU, one without.
Following the same pattern as test_fuse_reduction_epilogue_multiple_epilogue,
this test verifies that fusion works correctly when there are multiple
epilogue blocks. The temp buffer is kept because the second epilogue block
still needs it.
"""
sch = tvm.s_tir.Schedule(matmul_bias_relu_multiple_epilogue_before, debug_mask="all")
sch.fuse_reduction_epilogue("matmul", "bias_relu")
assert_structural_equal_ignore_global_symbol(
sch.mod["main"], matmul_bias_relu_multiple_epilogue_expected
)
verify_trace_roundtrip(sch=sch, mod=matmul_bias_relu_multiple_epilogue_before)
mod = tvm.compile(sch.mod["main"], target="llvm")
assert mod is not None
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,71 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# mypy: ignore-errors
# ruff: noqa: F401
import sys
import pytest
import tvm.testing
from tvm.s_tir.schedule import Instruction, InstructionKind, LoopRV, SBlockRV
def test_inst_kind_get():
kind = InstructionKind.get("EnterPostproc")
assert not kind.is_pure
assert kind.name == "EnterPostproc"
def test_inst_construct_1():
block = SBlockRV()
loop0 = LoopRV()
loop1 = LoopRV()
inst = Instruction(
kind=InstructionKind.get("GetLoops"),
inputs=[block],
attrs=[],
outputs=[loop0, loop1],
)
assert str(inst) == "_, _ = sch.get_loops(block=_)"
assert len(inst.inputs) == 1
assert len(inst.attrs) == 0
assert len(inst.outputs) == 2
assert inst.kind.same_as(InstructionKind.get("GetLoops"))
assert inst.inputs[0].same_as(block)
assert inst.outputs[0].same_as(loop0)
assert inst.outputs[1].same_as(loop1)
def test_inst_construct_2():
block = SBlockRV()
inst = Instruction(
kind=InstructionKind.get("ComputeInline"),
inputs=[block],
attrs=[],
outputs=[],
)
assert str(inst) == "sch.compute_inline(block=_)"
assert len(inst.inputs) == 1
assert len(inst.attrs) == 0
assert len(inst.outputs) == 0
assert inst.kind.same_as(InstructionKind.get("ComputeInline"))
assert inst.inputs[0].same_as(block)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,278 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: F401
import pytest
import tvm
import tvm.testing
from tvm import tirx
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.script import tirx as T
# pylint: disable=no-member,invalid-name,unused-variable
@T.prim_func(s_tir=True)
def elementwise(a: T.handle, c: T.handle, d: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
C = T.match_buffer(c, (128, 128))
D = T.match_buffer(d, (64, 64))
B = T.sblock_alloc_buffer((128, 128))
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(A[vi, vj])
T.writes(B[vi, vj])
B[vi, vj] = A[vi, vj] * T.float32(2)
for i_0, j_0, i_1, j_1 in T.grid(8, 8, 16, 16):
with T.sblock("C"):
vi = T.axis.spatial(128, i_0 * 16 + i_1)
vj = T.axis.spatial(128, j_0 * 16 + j_1)
T.reads(B[vi, vj])
T.writes(C[vi, vj])
C[vi, vj] = B[vi, vj] + T.float32(1)
for i_0, j_0, i_1, j_1 in T.grid(8, 8, 8, 8):
with T.sblock("D"):
vi = T.axis.spatial(64, i_0 * 8 + i_1)
vj = T.axis.spatial(64, j_0 * 8 + j_1)
T.reads(B[vi, vj])
T.writes(D[vi, vj])
D[vi, vj] = B[vi, vj] + T.float32(2)
@T.prim_func(s_tir=True)
def elementwise_merged(a: T.handle, c: T.handle, d: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
C = T.match_buffer(c, (128, 128))
D = T.match_buffer(d, (64, 64))
B = T.sblock_alloc_buffer((128, 128))
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(A[vi, vj])
T.writes(B[vi, vj])
B[vi, vj] = A[vi, vj] * T.float32(2)
for i_0_m in range(8):
for j_0, i_1, j_1 in T.grid(8, 16, 16):
with T.sblock("C"):
vi = T.axis.spatial(128, i_0_m * 16 + i_1)
vj = T.axis.spatial(128, j_0 * 16 + j_1)
T.reads(B[vi, vj])
T.writes(C[vi, vj])
C[vi, vj] = B[vi, vj] + T.float32(1)
for j_0, i_1, j_1 in T.grid(8, 8, 8):
with T.sblock("D"):
vi = T.axis.spatial(64, i_0_m * 8 + i_1)
vj = T.axis.spatial(64, j_0 * 8 + j_1)
T.reads(B[vi, vj])
T.writes(D[vi, vj])
D[vi, vj] = B[vi, vj] + T.float32(2)
@T.prim_func(s_tir=True)
def elementwise_merged2(a: T.handle, c: T.handle, d: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
C = T.match_buffer(c, (128, 128))
D = T.match_buffer(d, (64, 64))
B = T.sblock_alloc_buffer((128, 128))
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(A[vi, vj])
T.writes(B[vi, vj])
B[vi, vj] = A[vi, vj] * T.float32(2)
for i_0_m, j_0_m in T.grid(8, 8):
for i_1, j_1 in T.grid(16, 16):
with T.sblock("C"):
vi = T.axis.spatial(128, i_0_m * 16 + i_1)
vj = T.axis.spatial(128, j_0_m * 16 + j_1)
T.reads(B[vi, vj])
T.writes(C[vi, vj])
C[vi, vj] = B[vi, vj] + T.float32(1)
for i_1, j_1 in T.grid(8, 8):
with T.sblock("D"):
vi = T.axis.spatial(64, i_0_m * 8 + i_1)
vj = T.axis.spatial(64, j_0_m * 8 + j_1)
T.reads(B[vi, vj])
T.writes(D[vi, vj])
D[vi, vj] = B[vi, vj] + T.float32(2)
def test_merge():
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
block_c = sch.get_sblock("C")
block_d = sch.get_sblock("D")
i = sch.get_loops(block_c)[0]
j = sch.get_loops(block_d)[0]
sch.merge(i, j)
assert_structural_equal_ignore_global_symbol(elementwise_merged, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=elementwise)
def test_merge2():
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
block_c = sch.get_sblock("C")
block_d = sch.get_sblock("D")
i = sch.get_loops(block_c)[1]
j = sch.get_loops(block_d)[1]
sch.merge(i, j)
assert_structural_equal_ignore_global_symbol(elementwise_merged2, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=elementwise)
def test_merge_fail_not_only_child():
@T.prim_func(s_tir=True)
def elementwise_with_seq(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
C = T.match_buffer(c, (128, 128, 128))
B = T.sblock_alloc_buffer((128, 128, 128))
D = T.sblock_alloc_buffer((128, 128, 128))
for i, j in T.grid(128, 128):
for k in T.serial(0, 128):
with T.sblock("D"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
D[vi, vj, vk] = A[vi, vj, vk] * 2.0
for k in T.serial(0, 128):
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
for i, j in T.grid(128, 128):
for k in T.serial(0, 128):
with T.sblock("C"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
C[vi, vj, vk] = B[vi, vj, vk] * 2.0
sch = tvm.s_tir.Schedule(elementwise_with_seq, debug_mask="all")
block_b = sch.get_sblock("B")
_, _, b = sch.get_loops(block_b)
block_c = sch.get_sblock("C")
_, _, c = sch.get_loops(block_c)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.merge(b, c)
def test_merge_fail_not_start_with_zero():
@T.prim_func(s_tir=True)
def elementwise_loops_not_start_with_zero(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
C = T.match_buffer(c, (128, 128, 128))
B = T.sblock_alloc_buffer((128, 128, 128))
for i, j in T.grid(128, 128):
for k in T.serial(1, 128):
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
for i, j in T.grid(128, 128):
for k in T.serial(0, 128):
with T.sblock("C"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
C[vi, vj, vk] = A[vi, vj, vk] * 2.0
sch = tvm.s_tir.Schedule(elementwise_loops_not_start_with_zero, debug_mask="all")
block_b = sch.get_sblock("B")
_, _, b = sch.get_loops(block_b)
block_c = sch.get_sblock("C")
_, _, c = sch.get_loops(block_c)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.merge(b, c)
def test_merge_fail_not_same_extent():
@T.prim_func(s_tir=True)
def elementwise_loops_not_same_extent(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
C = T.match_buffer(c, (128, 128, 128))
B = T.sblock_alloc_buffer((64, 128, 128))
for i, j in T.grid(64, 128):
for k in T.serial(0, 128):
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
for i, j in T.grid(128, 128):
for k in T.serial(0, 128):
with T.sblock("C"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
C[vi, vj, vk] = A[vi, vj, vk] * 2.0
sch = tvm.s_tir.Schedule(elementwise_loops_not_same_extent, debug_mask="all")
block_b = sch.get_sblock("B")
_, _, b = sch.get_loops(block_b)
block_c = sch.get_sblock("C")
_, _, c = sch.get_loops(block_c)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.merge(b, c)
def test_merge_fail_not_same_level():
@T.prim_func(s_tir=True)
def elementwise_not_same_level(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
C = T.match_buffer(c, (128, 128, 128))
B = T.sblock_alloc_buffer((128, 128, 128))
for i, j in T.grid(128, 128):
for k in T.serial(0, 128):
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
for i, j in T.grid(128, 128):
for k in T.serial(0, 128):
with T.sblock("C"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
C[vi, vj, vk] = A[vi, vj, vk] * 2.0
sch = tvm.s_tir.Schedule(elementwise_not_same_level, debug_mask="all")
block_b = sch.get_sblock("B")
_, b, _ = sch.get_loops(block_b)
block_c = sch.get_sblock("C")
_, _, c = sch.get_loops(block_c)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.merge(b, c)
def test_merge_fail_with_different_scope():
@T.prim_func(s_tir=True)
def elementwise_with_different_scope(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
C = T.match_buffer(c, (128, 128, 128))
B = T.sblock_alloc_buffer((128, 128, 128))
with T.sblock("A"):
for i, j in T.grid(128, 128):
for k in T.serial(0, 128):
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
for i, j in T.grid(128, 128):
for k in T.serial(0, 128):
with T.sblock("C"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
C[vi, vj, vk] = A[vi, vj, vk] * 2.0
sch = tvm.s_tir.Schedule(elementwise_with_different_scope, debug_mask="all")
block_b = sch.get_sblock("B")
_, _, b = sch.get_loops(block_b)
block_c = sch.get_sblock("C")
_, _, c = sch.get_loops(block_c)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.merge(b, c)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,307 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: F401
import pytest
import tvm
import tvm.testing
from tvm import tirx
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.script import tirx as T
# pylint: disable=no-member,invalid-name,unused-variable,unexpected-keyword-arg
@T.prim_func(s_tir=True)
def matmul_before(
A: T.Buffer((128, 127), "float32"),
B: T.Buffer((127, 127), "float32"),
C: T.Buffer((128, 127), "float32"),
) -> None:
A_shared = T.sblock_alloc_buffer((128, 127), "float32", scope="shared")
B_shared = T.sblock_alloc_buffer((127, 127), "float32", scope="shared")
C_shared = T.sblock_alloc_buffer((128, 127), "float32", scope="shared")
for i0, i1 in T.grid(128, 127):
with T.sblock("A"):
i, j = T.axis.remap("SS", [i0, i1])
A_shared[i, j] = A[i, j]
for i0, i1 in T.grid(127, 127):
with T.sblock("B"):
i, j = T.axis.remap("SS", [i0, i1])
B_shared[i, j] = B[i, j]
for i0, i1, i2 in T.grid(128, 127, 127):
with T.sblock("C_shared"):
i, j, k = T.axis.remap("SSR", [i0, i1, i2])
with T.init():
C_shared[i, j] = T.float32(0)
C_shared[i, j] = C_shared[i, j] + A_shared[i, k] * B_shared[k, j]
for i0, i1 in T.grid(128, 127):
with T.sblock("C"):
i, j = T.axis.remap("SS", [i0, i1])
C[i, j] = C_shared[i, j]
@T.prim_func(s_tir=True)
def matmul_expected(
A: T.Buffer((128, 127), "float32"),
B: T.Buffer((127, 127), "float32"),
C: T.Buffer((128, 127), "float32"),
) -> None:
A_shared_padded = T.sblock_alloc_buffer([128, 128], dtype="float32", scope="shared")
B_shared_padded = T.sblock_alloc_buffer([128, 128], dtype="float32", scope="shared")
C_shared_padded = T.sblock_alloc_buffer([128, 128], dtype="float32", scope="shared")
for i0, i1 in T.grid(128, 128):
with T.sblock("A"):
i, j = T.axis.remap("SS", [i0, i1])
T.reads(A[i, j])
T.writes(A_shared_padded[i, j])
A_shared_padded[i, j] = T.if_then_else(j < 127, A[i, j], T.float32(0), dtype="float32")
for i0, i1 in T.grid(128, 128):
with T.sblock("B"):
i, j = T.axis.remap("SS", [i0, i1])
T.reads(B[i, j])
T.writes(B_shared_padded[i, j])
B_shared_padded[i, j] = T.if_then_else(
i < 127 and j < 127, B[i, j], T.float32(0), dtype="float32"
)
for i0, i1, i2 in T.grid(128, 128, 128):
with T.sblock("C_shared"):
i, j, k = T.axis.remap("SSR", [i0, i1, i2])
T.reads(A_shared_padded[i, k], B_shared_padded[k, j])
T.writes(C_shared_padded[i, j])
with T.init():
C_shared_padded[i, j] = T.float32(0)
C_shared_padded[i, j] = (
C_shared_padded[i, j] + A_shared_padded[i, k] * B_shared_padded[k, j]
)
for i0, i1 in T.grid(128, 127):
with T.sblock("C"):
i, j = T.axis.remap("SS", [i0, i1])
T.reads(C_shared_padded[i, j])
T.writes(C[i, j])
C[i, j] = C_shared_padded[i, j]
# pylint: enable=no-member,invalid-name,unused-variable,unexpected-keyword-arg
def test_pad_matmul():
# pylint: disable=no-member,invalid-name,unused-variable,unexpected-keyword-arg
@T.prim_func(s_tir=True)
def matmul_before(
a: T.handle,
b: T.handle,
c: T.handle,
) -> None:
n = T.int32()
A = T.match_buffer(a, (128, 128), "float32")
B = T.match_buffer(b, (n, 128), "float32")
C = T.match_buffer(c, (128, n), "float32")
for i0, i1, i2 in T.grid(128, n, 128):
with T.sblock("C"):
i, j, k = T.axis.remap("SSR", [i0, i1, i2])
with T.init():
C[i, j] = T.float32(0)
C[i, j] = C[i, j] + A[i, k] * B[j, k]
@T.prim_func(s_tir=True)
def matmul_after(
a: T.handle,
b: T.handle,
c: T.handle,
):
n = T.int32()
A = T.match_buffer(a, (128, 128), "float32")
B = T.match_buffer(b, (n, 128), "float32")
C = T.match_buffer(c, (128, n), "float32")
B_pad = T.sblock_alloc_buffer(((n + 31) // 32 * 32, 128))
C_pad = T.sblock_alloc_buffer((128, (n + 31) // 32 * 32))
for i0, i1 in T.grid((n + 31) // 32 * 32, 128):
with T.sblock("B_pad"):
v0, v1 = T.axis.remap("SS", [i0, i1])
B_pad[v0, v1] = T.if_then_else(v0 < n, B[v0, v1], T.float32(0))
for i0, i1, i2 in T.grid(128, (n + 31) // 32 * 32, 128):
with T.sblock("C"):
i, j, k = T.axis.remap("SSR", [i0, i1, i2])
T.reads(A[i, k], B_pad[j, k])
T.writes(C_pad[i, j])
with T.init():
C_pad[i, j] = T.float32(0)
C_pad[i, j] = C_pad[i, j] + A[i, k] * B_pad[j, k]
for i0, i1 in T.grid(128, n):
with T.sblock("C_pad"):
v0, v1 = T.axis.remap("SS", [i0, i1])
C[v0, v1] = C_pad[v0, v1]
sch = tvm.s_tir.Schedule(matmul_before, debug_mask="all")
C = sch.get_sblock("C")
sch.pad_einsum(C, [32, 32, 32])
assert_structural_equal_ignore_global_symbol(matmul_after, sch.mod["main"])
verify_trace_roundtrip(sch, mod=matmul_before)
def test_pad_matmul_2():
@T.prim_func(s_tir=True)
def before(
a: T.handle,
b: T.handle,
m: T.handle,
d: T.handle,
):
T.func_attr({"tirx.noalias": True})
n = T.int32()
A = T.match_buffer(a, (1, n, 4096))
B = T.match_buffer(b, (11008, 4096))
M = T.match_buffer(m, (1, n, 11008))
D = T.match_buffer(d, (1, n, 11008))
C = T.sblock_alloc_buffer((1, n, 11008))
for i0, i1, i2, k in T.grid(1, n, 11008, 4096):
with T.sblock("C"):
v_i0, v_i1, v_i2, v_k = T.axis.remap("SSSR", [i0, i1, i2, k])
T.reads(A[v_i0, v_i1, v_k], B[v_i2, v_k])
T.writes(C[v_i0, v_i1, v_i2])
with T.init():
C[v_i0, v_i1, v_i2] = T.float32(0)
C[v_i0, v_i1, v_i2] = C[v_i0, v_i1, v_i2] + A[v_i0, v_i1, v_k] * B[v_i2, v_k]
for ax0, ax1, ax2 in T.grid(1, n, 11008):
with T.sblock("D"):
v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
D[v_ax0, v_ax1, v_ax2] = M[v_ax0, v_ax1, v_ax2] * C[v_ax0, v_ax1, v_ax2]
@T.prim_func(s_tir=True)
def after(a: T.handle, b: T.handle, m: T.handle, d: T.handle):
T.func_attr({"tirx.noalias": True})
n = T.int32()
A = T.match_buffer(a, (1, n, 4096))
B = T.match_buffer(b, (11008, 4096))
M = T.match_buffer(m, (1, n, 11008))
D = T.match_buffer(d, (1, n, 11008))
# with T.sblock("root"):
C = T.sblock_alloc_buffer((1, n, 11008))
A_pad = T.sblock_alloc_buffer((1, (n + 31) // 32 * 32, 4096))
C_pad = T.sblock_alloc_buffer((1, (n + 31) // 32 * 32, 11008))
for i0, i1, i2 in T.grid(1, (n + 31) // 32 * 32, 4096):
with T.sblock("A_pad"):
v0, v1, v2 = T.axis.remap("SSS", [i0, i1, i2])
A_pad[v0, v1, v2] = T.if_then_else(v1 < n, A[v0, v1, v2], T.float32(0))
for i0, i1, i2, k in T.grid(1, (n + 31) // 32 * 32, 11008, 4096):
with T.sblock("C"):
v_i0, v_i1, v_i2, v_k = T.axis.remap("SSSR", [i0, i1, i2, k])
T.reads(A_pad[v_i0, v_i1, v_k], B[v_i2, v_k])
T.writes(C_pad[v_i0, v_i1, v_i2])
with T.init():
C_pad[v_i0, v_i1, v_i2] = T.float32(0)
C_pad[v_i0, v_i1, v_i2] = (
C_pad[v_i0, v_i1, v_i2] + A_pad[v_i0, v_i1, v_k] * B[v_i2, v_k]
)
for i0, i1, i2 in T.grid(1, n, 11008):
with T.sblock("C_pad"):
v0, v1, v2 = T.axis.remap("SSS", [i0, i1, i2])
C[v0, v1, v2] = C_pad[v0, v1, v2]
for ax0, ax1, ax2 in T.grid(1, n, 11008):
with T.sblock("D"):
v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
D[v_ax0, v_ax1, v_ax2] = M[v_ax0, v_ax1, v_ax2] * C[v_ax0, v_ax1, v_ax2]
sch = tvm.s_tir.Schedule(before, debug_mask="all")
C = sch.get_sblock("C")
sch.pad_einsum(C, [1, 32, 32, 32])
assert_structural_equal_ignore_global_symbol(after, sch.mod["main"])
verify_trace_roundtrip(sch, mod=before)
def test_pad_rms():
@T.prim_func(s_tir=True)
def before(
a: T.handle,
w: T.handle,
r: T.handle,
):
T.func_attr({"tirx.noalias": True})
n = T.int32()
A = T.match_buffer(a, (1, n, 4096))
W = T.match_buffer(w, (4096,), "float32")
R = T.match_buffer(r, (1, n, 4096), "float32")
S = T.sblock_alloc_buffer((1, n), "float32")
for bsz, i, k in T.grid(1, n, 4096):
with T.sblock("S"):
v_bsz, v_i, v_k = T.axis.remap("SSR", [bsz, i, k])
T.reads(A[v_bsz, v_i, v_k])
T.writes(S[v_bsz, v_i])
with T.init():
S[v_bsz, v_i] = T.float32(0)
S[v_bsz, v_i] = S[v_bsz, v_i] + A[v_bsz, v_i, v_k] * A[v_bsz, v_i, v_k]
for bsz, i, k in T.grid(1, n, 4096):
with T.sblock("R"):
v_bsz, v_i, v_k = T.axis.remap("SSS", [bsz, i, k])
R[v_bsz, v_i, v_k] = W[v_k] * (
A[v_bsz, v_i, v_k]
/ T.sqrt(S[v_bsz, v_i] * T.float32(0.000244140625) + T.float32(1e-6))
)
@T.prim_func(s_tir=True)
def after(a: T.handle, w: T.handle, r: T.handle):
T.func_attr({"tirx.noalias": True})
n = T.int32()
A = T.match_buffer(a, (1, n, 4096))
W = T.match_buffer(w, (4096,), "float32")
R = T.match_buffer(r, (1, n, 4096))
S = T.sblock_alloc_buffer((1, n))
A_pad = T.sblock_alloc_buffer((1, (n + 31) // 32 * 32, 4096))
S_pad = T.sblock_alloc_buffer((1, (n + 31) // 32 * 32))
for i0, i1, i2 in T.grid(1, (n + 31) // 32 * 32, 4096):
with T.sblock("A_pad"):
v0, v1, v2 = T.axis.remap("SSS", [i0, i1, i2])
A_pad[v0, v1, v2] = T.if_then_else(v1 < n, A[v0, v1, v2], T.float32(0))
for bsz, i, k in T.grid(1, (n + 31) // 32 * 32, 4096):
with T.sblock("S"):
v_bsz, v_i, v_k = T.axis.remap("SSR", [bsz, i, k])
T.reads(A_pad[v_bsz, v_i, v_k])
T.writes(S_pad[v_bsz, v_i])
with T.init():
S_pad[v_bsz, v_i] = T.float32(0)
S_pad[v_bsz, v_i] = (
S_pad[v_bsz, v_i] + A_pad[v_bsz, v_i, v_k] * A_pad[v_bsz, v_i, v_k]
)
for i0, i1 in T.grid(1, n):
with T.sblock("S_pad"):
v0, v1 = T.axis.remap("SS", [i0, i1])
S[v0, v1] = S_pad[v0, v1]
for bsz, i, k in T.grid(1, n, 4096):
with T.sblock("R"):
v_bsz, v_i, v_k = T.axis.remap("SSS", [bsz, i, k])
R[v_bsz, v_i, v_k] = W[v_k] * (
A[v_bsz, v_i, v_k]
/ T.sqrt(S[v_bsz, v_i] * T.float32(0.000244140625) + T.float32(1e-6))
)
sch = tvm.s_tir.Schedule(before, debug_mask="all")
C = sch.get_sblock("S")
sch.pad_einsum(C, [1, 32, 1])
assert_structural_equal_ignore_global_symbol(after, sch.mod["main"])
verify_trace_roundtrip(sch, mod=before)
if __name__ == "__main__":
test_pad_matmul()
test_pad_matmul_2()
test_pad_rms()
@@ -0,0 +1,462 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: F401, F841
import pytest
import tvm
import tvm.testing
from tvm import te, tirx
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.script import tirx as T
from tvm.tirx.expr import IntImm
# pylint: disable=no-member,invalid-name,unused-variable
@T.prim_func(s_tir=True)
def elementwise(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
B = T.match_buffer(b, (128, 128, 128))
for i, j, k in T.grid(128, 128, 128):
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_symbolic(a: T.handle, b: T.handle, n: T.int32) -> None:
A = T.match_buffer(a, (128, 128, n))
B = T.match_buffer(b, (128, 128, n))
for i, j, k in T.grid(128, 128, n):
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_with_anno(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
B = T.match_buffer(b, (128, 128, 128))
for i, j in T.grid(128, 128):
for k in T.serial(0, 128, annotations={"useless_annotation": True}):
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
T.reads([A[vi, vj, vk]])
T.writes([B[vi, vj, vk]])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_with_thread_binding(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
B = T.match_buffer(b, (128, 128, 128))
for i, j in T.grid(128, 128):
for k in T.thread_binding(0, 128, thread="threadIdx.x"):
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
T.reads([A[vi, vj, vk]])
T.writes([B[vi, vj, vk]])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_with_opaque_block(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
B = T.match_buffer(b, (128, 128, 128))
for i, j, k in T.grid(128, 128, 128):
with T.sblock("opaque"):
T.reads([A[i, j, k]])
T.writes([B[i, j, k]])
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
T.reads([A[vi, vj, vk]])
T.writes([B[vi, vj, vk]])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_partition_with_opaque_block(a: T.handle, b: T.handle) -> None:
B = T.match_buffer(b, [128, 128, 128])
A = T.match_buffer(a, [128, 128, 128])
with T.sblock("root"):
T.reads()
T.writes()
with T.sblock("opaque_i_common"):
T.reads()
T.writes()
with T.sblock("opaque_i0_partition"):
T.reads()
T.writes()
for i0, j, k in T.grid(112, 128, 128):
with T.sblock("opaque_i0"):
T.reads(A[i0, j, k])
T.writes(B[i0, j, k])
with T.sblock("B_i0"):
vi, vj, vk = T.axis.remap("SSS", [i0, j, k])
T.reads(A[0:112, 0:128, 0:128])
T.writes(B[0:112, 0:128, 0:128])
B[vi, vj, vk] = A[vi, vj, vk] * T.float32(2)
with T.sblock("opaque_i1_partition"):
T.reads()
T.writes()
for i1 in range(112, 128):
for j, k in T.grid(128, 128):
with T.sblock("opaque_i1"):
T.reads(A[i1, j, k])
T.writes(B[i1, j, k])
with T.sblock("B_i1"):
vi, vj, vk = T.axis.remap("SSS", [i1, j, k])
T.reads(A[112:128, 0:128, 0:128])
T.writes(B[112:128, 0:128, 0:128])
B[vi, vj, vk] = A[vi, vj, vk] * T.float32(2)
@T.prim_func(s_tir=True)
def elementwise_loop_partition_case0(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [128, 128, 128])
B = T.match_buffer(b, [128, 128, 128])
with T.sblock("root"):
T.reads()
T.writes()
with T.sblock("B_i_common"):
T.reads()
T.writes()
with T.sblock("B_i0_partition"):
T.reads()
T.writes()
for i0 in range(2):
with T.sblock("B_i0_j_common"):
T.reads()
T.writes()
with T.sblock("B_i0_j0_partition"):
T.reads()
T.writes()
for j0, k in T.grid(4, 128):
with T.sblock("B_i0_j0"):
vi, vj, vk = T.axis.remap("SSS", [i0, j0, k])
T.reads(A[0:2, 0:4, 0:128])
T.writes(B[0:2, 0:4, 0:128])
B[vi, vj, vk] = A[vi, vj, vk] * T.float32(2)
with T.sblock("B_i0_j1_partition"):
T.reads()
T.writes()
for j1 in range(4, 36):
for k in range(128):
with T.sblock("B_i0_j1"):
vi, vj, vk = T.axis.remap("SSS", [i0, j1, k])
T.reads(A[0:2, 4:36, 0:128])
T.writes(B[0:2, 4:36, 0:128])
B[vi, vj, vk] = A[vi, vj, vk] * T.float32(2)
with T.sblock("B_i0_j2_partition"):
T.reads()
T.writes()
for j2 in range(36, 128):
for k in range(128):
with T.sblock("B_i0_j2"):
vi, vj, vk = T.axis.remap("SSS", [i0, j2, k])
T.reads(A[0:2, 36:128, 0:128])
T.writes(B[0:2, 36:128, 0:128])
B[vi, vj, vk] = A[vi, vj, vk] * T.float32(2)
with T.sblock("B_i1_partition"):
T.reads()
T.writes()
for i1 in range(2, 3):
for j, k in T.grid(128, 128):
with T.sblock("B_i1"):
vi, vj, vk = T.axis.remap("SSS", [i1, j, k])
T.reads(A[2, 0:128, 0:128])
T.writes(B[2, 0:128, 0:128])
B[vi, vj, vk] = A[vi, vj, vk] * T.float32(2)
with T.sblock("B_i2_partition"):
T.reads()
T.writes()
for i2 in range(3, 67):
for j, k in T.grid(128, 128):
with T.sblock("B_i2"):
vi, vj, vk = T.axis.remap("SSS", [i2, j, k])
T.reads(A[3:67, 0:128, 0:128])
T.writes(B[3:67, 0:128, 0:128])
B[vi, vj, vk] = A[vi, vj, vk] * T.float32(2)
with T.sblock("B_i3_partition"):
T.reads()
T.writes()
for i3 in range(67, 128):
for j, k in T.grid(128, 128):
with T.sblock("B_i3"):
vi, vj, vk = T.axis.remap("SSS", [i3, j, k])
T.reads(A[67:128, 0:128, 0:128])
T.writes(B[67:128, 0:128, 0:128])
B[vi, vj, vk] = A[vi, vj, vk] * T.float32(2)
@T.prim_func(s_tir=True)
def elementwise_loop_partition_case1(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [128, 128, 128])
B = T.match_buffer(b, [128, 128, 128])
with T.sblock("root"):
T.reads()
T.writes()
with T.sblock("B_i_common"):
T.reads()
T.writes()
with T.sblock("B_i0_partition"):
T.reads()
T.writes()
for i0, j, k in T.grid(63, 128, 128):
with T.sblock("B_i0"):
vi, vj, vk = T.axis.remap("SSS", [i0, j, k])
T.reads(A[0:63, 0:128, 0:128])
T.writes(B[0:63, 0:128, 0:128])
B[vi, vj, vk] = A[vi, vj, vk] * T.float32(2)
with T.sblock("B_i1_partition"):
T.reads()
T.writes()
for i1 in range(63, 64):
for j in range(128):
with T.sblock("B_i1_k_common"):
T.reads()
T.writes()
with T.sblock("B_i1_k0_partition"):
T.reads()
T.writes()
for k0 in range(1):
with T.sblock("B_i1_k0"):
vi, vj, vk = T.axis.remap("SSS", [i1, j, k0])
T.reads(A[63, 0:128, 0])
T.writes(B[63, 0:128, 0])
B[vi, vj, vk] = A[vi, vj, vk] * T.float32(2)
with T.sblock("B_i1_k1_partition"):
T.reads()
T.writes()
for k1 in range(1, 65):
with T.sblock("B_i1_k1"):
vi, vj, vk = T.axis.remap("SSS", [i1, j, k1])
T.reads(A[63, 0:128, 1:65])
T.writes(B[63, 0:128, 1:65])
B[vi, vj, vk] = A[vi, vj, vk] * T.float32(2)
with T.sblock("B_i1_k2_partition"):
T.reads()
T.writes()
for k2 in range(65, 128):
with T.sblock("B_i1_k2"):
vi, vj, vk = T.axis.remap("SSS", [i1, j, k2])
T.reads(A[63, 0:128, 65:128])
T.writes(B[63, 0:128, 65:128])
B[vi, vj, vk] = A[vi, vj, vk] * T.float32(2)
with T.sblock("B_i2_partition"):
T.reads()
T.writes()
for i2 in range(64, 128):
for j, k in T.grid(128, 128):
with T.sblock("B_i2"):
vi, vj, vk = T.axis.remap("SSS", [i2, j, k])
T.reads(A[64:128, 0:128, 0:128])
T.writes(B[64:128, 0:128, 0:128])
B[vi, vj, vk] = A[vi, vj, vk] * T.float32(2)
@T.prim_func(s_tir=True)
def opaque_access(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [16, 16], "float32")
B = T.match_buffer(b, [16, 16], "float32")
for i, j in T.grid(16, 16):
with T.sblock("A"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads([])
T.writes([A[0:16, 0:16]])
A[vi, vj] = 1
for i, j in T.grid(16, 16):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads([])
T.writes([B[0:16, 0:16]])
T.evaluate(T.tvm_fill_fragment(B.data, 16, 16, 16, 0, vi * 16 + vj, dtype="handle"))
@T.prim_func(s_tir=True)
def opaque_access_loop_partition(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (16, 16))
B = T.match_buffer(b, (16, 16))
for i in range(16):
with T.sblock("A_j_common"):
T.reads()
T.writes()
with T.sblock("A_j0_partition"):
T.reads()
T.writes()
for j0 in range(12):
with T.sblock("A_j0"):
vi, vj = T.axis.remap("SS", [i, j0])
T.reads()
T.writes(A[0:16, 0:12])
A[vi, vj] = T.float32(1)
with T.sblock("A_j1_partition"):
T.reads()
T.writes()
for j1 in range(12, 16):
with T.sblock("A_j1"):
vi, vj = T.axis.remap("SS", [i, j1])
T.reads()
T.writes(A[0:16, 12:16])
A[vi, vj] = T.float32(1)
for i in range(16):
with T.sblock("B_j_common"):
T.reads()
T.writes()
with T.sblock("B_j0_partition"):
T.reads()
T.writes()
for j0 in range(12):
with T.sblock("B_j0"):
vi, vj = T.axis.remap("SS", [i, j0])
T.reads()
T.writes(B[0:16, 0:16])
T.tvm_fill_fragment(B.data, 16, 16, 16, 0, vi * 16 + vj)
with T.sblock("B_j1_partition"):
T.reads()
T.writes()
for j1 in range(12, 16):
with T.sblock("B_j1"):
vi, vj = T.axis.remap("SS", [i, j1])
T.reads()
T.writes(B[0:16, 0:16])
T.tvm_fill_fragment(B.data, 16, 16, 16, 0, vi * 16 + vj)
# pylint: enable=no-member,invalid-name,unused-variable
def test_loop_partition():
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, k = sch.get_loops(block_b)
sch.loop_partition(i, factors=[2, 1, 64])
block_b_partition = sch.get_sblock("B_i0")
i, j, k = sch.get_loops(block_b_partition)
loops = sch.loop_partition(j, factors=[4, 32])
assert_structural_equal_ignore_global_symbol(elementwise_loop_partition_case0, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=elementwise)
def test_partition_with_inferred_factor():
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, k = sch.get_loops(block_b)
sch.loop_partition(i, factors=[None, 1, 64])
block_b_partition = sch.get_sblock("B_i1")
i, j, k = sch.get_loops(block_b_partition)
sch.loop_partition(k, factors=[1, 64, None])
assert_structural_equal_ignore_global_symbol(elementwise_loop_partition_case1, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=elementwise)
def test_partition_with_opaque_block():
sch = tvm.s_tir.Schedule(elementwise_with_opaque_block, debug_mask="all")
block_opaque = sch.get_sblock("opaque")
i, _, _ = sch.get_loops(block_opaque)
sch.loop_partition(i, factors=[None, 16])
assert_structural_equal_ignore_global_symbol(
elementwise_partition_with_opaque_block, sch.mod["main"]
)
verify_trace_roundtrip(sch=sch, mod=elementwise_with_opaque_block)
def test_partition_with_opaque_access():
sch = tvm.s_tir.Schedule(opaque_access, debug_mask="all")
block_a = sch.get_sblock("A")
_, j = sch.get_loops(block_a)
sch.loop_partition(j, factors=[None, 4])
block_b = sch.get_sblock("B")
_, j = sch.get_loops(block_b)
sch.loop_partition(j, factors=[None, 4])
assert_structural_equal_ignore_global_symbol(opaque_access_loop_partition, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=opaque_access)
def test_partition_int64_extent_with_mixed_factors():
def _create_prim_func():
m = te.const(384, "int64")
A = te.placeholder((m,), name="A", dtype="float32")
B = te.compute((m,), lambda i: A[i] + 1, name="B")
return te.create_prim_func([A, B])
mod = _create_prim_func()
sch = tvm.s_tir.Schedule(mod, debug_mask="all")
(i,) = sch.get_loops(sch.get_sblock("B"))
sch.loop_partition(
i,
factors=[
te.const(1, "int64"),
te.const(51, "int32"),
],
)
def test_partition_fail_symbolic():
sch = tvm.s_tir.Schedule(elementwise_symbolic, debug_mask="all")
block_b = sch.get_sblock("B")
_, _, k = sch.get_loops(block_b)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.loop_partition(k, factors=[10, None])
def test_partition_fail_out_of_bound():
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, k = sch.get_loops(block_b)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.loop_partition(i, factors=[1000, 2, 3])
def test_partition_with_non_positive_factors():
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, k = sch.get_loops(block_b)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.loop_partition(i, factors=[-2, -64])
with pytest.raises(tvm.s_tir.ScheduleError):
sch.loop_partition(j, factors=[0, None])
with pytest.raises(tvm.s_tir.ScheduleError):
sch.loop_partition(k, factors=[None, -16])
def test_partition_fail_with_annotation():
sch = tvm.s_tir.Schedule(elementwise_with_anno, debug_mask="all")
block_b = sch.get_sblock("B")
_, j, k = sch.get_loops(block_b)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.loop_partition(k, factors=[None, 10])
def test_partition_fail_with_thread_binding():
sch = tvm.s_tir.Schedule(elementwise_with_thread_binding, debug_mask="all")
block_b = sch.get_sblock("B")
_, j, k = sch.get_loops(block_b)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.loop_partition(k, factors=[None, 10])
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,241 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ruff: noqa: E501, F401
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
import sys
import pytest
import tvm
from tvm import tirx
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.script import tirx as T
# fmt: off
# pylint: disable=no-member,invalid-name,unused-variable,line-too-long,redefined-outer-name,unexpected-keyword-arg,too-many-nested-blocks,not-callable
@T.prim_func(s_tir=True)
def cuda_matmul(a: T.handle, b: T.handle, c: T.handle) -> None: # pylint: disable=undefined-loop-variable
A = T.match_buffer(a, [2048, 2048], "float32")
B = T.match_buffer(b, [2048, 2048], "float32")
C = T.match_buffer(c, [2048, 2048], "float32")
for by in T.thread_binding(0, 32, thread = "blockIdx.y"):
for bx in T.thread_binding(0, 32, thread = "blockIdx.x"):
for vy in T.thread_binding(0, 2, thread = "vthread.y"):
for vx in T.thread_binding(0, 2, thread = "vthread.x"):
for ty in T.thread_binding(0, 8, thread = "threadIdx.y"):
for tx in T.thread_binding(0, 8, thread = "threadIdx.x"):
for k0 in T.serial(0, 256):
for k1 in T.unroll(0, 8):
for _, i, j in T.grid(1, 4, 4):
with T.sblock("C"):
vi = T.axis.S(2048, by * 64 + vy * 32 + ty * 4 + i)
vj = T.axis.S(2048, bx * 64 + vx * 32 + tx * 4 + j)
vk = T.axis.R(2048, k0 * 8 + k1)
T.reads([C[vi, vj], A[vi, vk], B[vk, vj]])
T.writes([C[vi, vj]])
with T.init():
C[vi, vj] = 0.0
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vk, vj]
@T.prim_func(s_tir=True)
def cuda_matmul_read_at_a(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [2048, 2048], dtype="float32")
B = T.match_buffer(b, [2048, 2048], dtype="float32")
C = T.match_buffer(c, [2048, 2048], dtype="float32")
A_shared = T.sblock_alloc_buffer([2048, 2048], dtype="float32", scope="shared")
for by in T.thread_binding(0, 32, thread="blockIdx.y"):
for bx in T.thread_binding(0, 32, thread="blockIdx.x"):
for vy in T.thread_binding(0, 2, thread="vthread.y"):
for vx in T.thread_binding(0, 2, thread="vthread.x"):
for ty in T.thread_binding(0, 8, thread="threadIdx.y"):
for tx in T.thread_binding(0, 8, thread="threadIdx.x"):
for k0 in T.serial(0, 256):
with T.sblock("A_shared"):
v0 = T.axis.S(32, by)
v1 = T.axis.S(256, k0)
T.reads([A[v0 * 64 : v0 * 64 + 64, v1 * 8 : v1 * 8 + 8]])
T.writes([A_shared[v0 * 64 : v0 * 64 + 64, v1 * 8 : v1 * 8 + 8]])
T.sblock_attr({"auto_copy": True})
for ax0, ax1 in T.grid(64, 8):
A_shared[v0 * 64 + ax0, v1 * 8 + ax1] = A[v0 * 64 + ax0, v1 * 8 + ax1]
for k1 in T.unroll(0, 8):
for v_, i, j in T.grid(1, 4, 4):
with T.sblock("C"):
vi = T.axis.S(2048, by * 64 + vy * 32 + ty * 4 + i)
vj = T.axis.S(2048, bx * 64 + vx * 32 + tx * 4 + j)
vk = T.axis.R(2048, k0 * 8 + k1)
T.reads([C[vi, vj], A_shared[vi, vk], B[vk, vj]])
T.writes([C[vi, vj]])
with T.init():
C[vi, vj] = T.float32(0)
C[vi, vj] = C[vi, vj] + A_shared[vi, vk] * B[vk, vj]
@T.prim_func(s_tir=True)
def cuda_matmul_read_at_ab(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [2048, 2048], dtype="float32")
B = T.match_buffer(b, [2048, 2048], dtype="float32")
C = T.match_buffer(c, [2048, 2048], dtype="float32")
A_shared = T.sblock_alloc_buffer([2048, 2048], dtype="float32", scope="shared")
B_shared = T.sblock_alloc_buffer([2048, 2048], dtype="float32", scope="shared")
for by in T.thread_binding(0, 32, thread="blockIdx.y"):
for bx in T.thread_binding(0, 32, thread="blockIdx.x"):
for vy in T.thread_binding(0, 2, thread="vthread.y"):
for vx in T.thread_binding(0, 2, thread="vthread.x"):
for ty in T.thread_binding(0, 8, thread="threadIdx.y"):
for tx in T.thread_binding(0, 8, thread="threadIdx.x"):
for k0 in T.serial(0, 256):
with T.sblock("A_shared"):
v0 = T.axis.S(32, by)
v1 = T.axis.S(256, k0)
T.reads([A[v0 * 64 : v0 * 64 + 64, v1 * 8 : v1 * 8 + 8]])
T.writes([A_shared[v0 * 64 : v0 * 64 + 64, v1 * 8 : v1 * 8 + 8]])
T.sblock_attr({"auto_copy": True})
for ax0, ax1 in T.grid(64, 8):
A_shared[v0 * 64 + ax0, v1 * 8 + ax1] = A[v0 * 64 + ax0, v1 * 8 + ax1]
with T.sblock("B_shared"):
v0 = T.axis.S(256, k0)
v1 = T.axis.S(32, bx)
T.reads([B[v0 * 8 : v0 * 8 + 8, v1 * 64 : v1 * 64 + 64]])
T.writes([B_shared[v0 * 8 : v0 * 8 + 8, v1 * 64 : v1 * 64 + 64]])
T.sblock_attr({"auto_copy": True})
for ax0, ax1 in T.grid(8, 64):
B_shared[v0 * 8 + ax0, v1 * 64 + ax1] = B[v0 * 8 + ax0, v1 * 64 + ax1]
for k1 in T.unroll(0, 8):
for v_, i, j in T.grid(1, 4, 4):
with T.sblock("C"):
vi = T.axis.S(2048, by * 64 + vy * 32 + ty * 4 + i)
vj = T.axis.S(2048, bx * 64 + vx * 32 + tx * 4 + j)
vk = T.axis.R(2048, k0 * 8 + k1)
T.reads([C[vi, vj], A_shared[vi, vk], B_shared[vk, vj]])
T.writes([C[vi, vj]])
with T.init():
C[vi, vj] = T.float32(0)
C[vi, vj] = C[vi, vj] + A_shared[vi, vk] * B_shared[vk, vj]
@T.prim_func(s_tir=True)
def cuda_matmul_write_at_c(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [2048, 2048], dtype="float32")
B = T.match_buffer(b, [2048, 2048], dtype="float32")
C = T.match_buffer(c, [2048, 2048], dtype="float32")
A_shared = T.sblock_alloc_buffer([2048, 2048], dtype="float32", scope="shared")
B_shared = T.sblock_alloc_buffer([2048, 2048], dtype="float32", scope="shared")
C_shared = T.sblock_alloc_buffer([2048, 2048], dtype="float32", scope="shared")
for by in T.thread_binding(0, 32, thread="blockIdx.y"):
for bx in T.thread_binding(0, 32, thread="blockIdx.x"):
for vy in T.thread_binding(0, 2, thread="vthread.y"):
for vx in T.thread_binding(0, 2, thread="vthread.x"):
for ty in T.thread_binding(0, 8, thread="threadIdx.y"):
for tx in T.thread_binding(0, 8, thread="threadIdx.x"):
for k0 in T.serial(0, 256):
with T.sblock("A_shared"):
v0 = T.axis.S(32, by)
v1 = T.axis.S(256, k0)
T.reads([A[v0 * 64 : v0 * 64 + 64, v1 * 8 : v1 * 8 + 8]])
T.writes([A_shared[v0 * 64 : v0 * 64 + 64, v1 * 8 : v1 * 8 + 8]])
T.sblock_attr({"auto_copy": True})
for ax0, ax1 in T.grid(64, 8):
A_shared[v0 * 64 + ax0, v1 * 8 + ax1] = A[v0 * 64 + ax0, v1 * 8 + ax1]
with T.sblock("B_shared"):
v0 = T.axis.S(256, k0)
v1 = T.axis.S(32, bx)
T.reads([B[v0 * 8 : v0 * 8 + 8, v1 * 64 : v1 * 64 + 64]])
T.writes([B_shared[v0 * 8 : v0 * 8 + 8, v1 * 64 : v1 * 64 + 64]])
T.sblock_attr({"auto_copy": True})
for ax0, ax1 in T.grid(8, 64):
B_shared[v0 * 8 + ax0, v1 * 64 + ax1] = B[v0 * 8 + ax0, v1 * 64 + ax1]
for k1 in T.unroll(0, 8):
for v_, i, j in T.grid(1, 4, 4):
with T.sblock("C"):
vi = T.axis.S(2048, by * 64 + vy * 32 + ty * 4 + i)
vj = T.axis.S(2048, bx * 64 + vx * 32 + tx * 4 + j)
vk = T.axis.R(2048, k0 * 8 + k1)
T.reads([C_shared[vi, vj], A_shared[vi, vk], B_shared[vk, vj]])
T.writes([C_shared[vi, vj]])
with T.init():
C_shared[vi, vj] = T.float32(0)
C_shared[vi, vj] = C_shared[vi, vj] + A_shared[vi, vk] * B_shared[vk, vj]
with T.sblock("C_shared"):
v0 = T.axis.S(32, by)
v1 = T.axis.S(32, bx)
T.reads([C_shared[v0 * 64 : v0 * 64 + 64, v1 * 64 : v1 * 64 + 64]])
T.writes([C[v0 * 64 : v0 * 64 + 64, v1 * 64 : v1 * 64 + 64]])
T.sblock_attr({"auto_copy": True})
for ax0, ax1 in T.grid(64, 64):
C[v0 * 64 + ax0, v1 * 64 + ax1] = C_shared[v0 * 64 + ax0, v1 * 64 + ax1]
# pylint: enable=no-member,invalid-name,unused-variable,line-too-long,redefined-outer-name,unexpected-keyword-arg,too-many-nested-blocks,not-callable
# fmt: on
def test_read_at_global_to_shared_a():
sch = tvm.s_tir.Schedule(cuda_matmul, debug_mask="all")
block = sch.get_sblock("C")
# pylint: disable=invalid-name
_by, _bx, _vy, _vx, _ty, _tx, k0, _k1, _, _i, _j = sch.get_loops(block)
# pylint: enable=invalid-name
sch.read_at(k0, block, 1, "shared")
assert_structural_equal_ignore_global_symbol(sch.mod["main"], cuda_matmul_read_at_a)
verify_trace_roundtrip(sch, cuda_matmul)
def test_read_at_global_to_shared_ab():
sch = tvm.s_tir.Schedule(cuda_matmul_read_at_a, debug_mask="all")
block = sch.get_sblock("C")
# pylint: disable=invalid-name
_by, _bx, _vy, _vx, _ty, _tx, k0, _k1, _, _i, _j = sch.get_loops(block)
# pylint: enable=invalid-name
sch.read_at(k0, block, 2, "shared")
assert_structural_equal_ignore_global_symbol(sch.mod["main"], cuda_matmul_read_at_ab)
verify_trace_roundtrip(sch, cuda_matmul_read_at_a)
def test_read_at_local_to_shared_c():
sch = tvm.s_tir.Schedule(cuda_matmul_read_at_ab, debug_mask="all")
block = sch.get_sblock("C")
# pylint: disable=invalid-name
_by, _bx, _vy, _vx, _ty, tx, _k0, _k1, _, _i, _j = sch.get_loops(block)
# pylint: enable=invalid-name
sch.write_at(tx, block, 0, "shared")
assert_structural_equal_ignore_global_symbol(sch.mod["main"], cuda_matmul_write_at_c)
verify_trace_roundtrip(sch, cuda_matmul_read_at_ab)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,395 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: F401
import sys
import pytest
import tvm_ffi
import tvm
import tvm.testing
from tvm import tirx
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.script import ir as I
from tvm.script import tirx as T
# pylint: disable=no-member,invalid-name,unused-variable,unexpected-keyword-arg
@T.prim_func(s_tir=True)
def rowsum_blockized(a: T.handle, b: T.handle) -> None:
B = T.match_buffer(b, [32, 4])
A = T.match_buffer(a, [32, 4, 128])
for i0, i2_0 in T.grid(32, 16):
with T.sblock("blockized_B"):
io, ko = T.axis.remap("SR", [i0, i2_0])
with T.init():
for i1 in T.serial(0, 4):
with T.sblock("B_init"):
ii_init = T.axis.S(4, i1)
B[io, ii_init] = 0.0
for i1_1, i2_1 in T.grid(4, 8):
with T.sblock("B"):
ii = T.axis.S(4, i1_1)
k = T.axis.R(128, ko * 8 + i2_1)
B[io, ii] = B[io, ii] + A[io, ii, k]
@T.prim_func(s_tir=True)
def matmul(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
B = T.match_buffer(b, [128, 128])
C = T.match_buffer(c, [128, 128])
for i, j, k in T.grid(128, 128, 128):
with T.sblock("update"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
C[vi, vj] = 0.0
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
@T.prim_func(s_tir=True)
def matmul_decompose0(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
B = T.match_buffer(b, [128, 128])
C = T.match_buffer(c, [128, 128])
for i, j in T.grid(128, 128):
with T.sblock("init"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = 0.0
for i, j, k in T.grid(128, 128, 128):
with T.sblock("update"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
@T.prim_func(s_tir=True)
def matmul_decompose1(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [32, 4, 128], elem_offset=0, align=64, offset_factor=1)
B = T.match_buffer(b, [32, 4], elem_offset=0, align=64, offset_factor=1)
for i0 in T.serial(0, 32):
with T.sblock("blockized_B_init"):
io = T.axis.S(32, i0)
for i1 in T.serial(0, 4):
with T.sblock("B_init"):
ii = T.axis.S(4, i1)
B[io, ii] = T.float32(0)
for i0, i2_o in T.grid(32, 16):
with T.sblock("blockized_B_update"):
io, ko = T.axis.remap("SR", [i0, i2_o])
for i1, i2_i in T.grid(4, 8):
with T.sblock("B"):
ii = T.axis.S(4, i1)
k = T.axis.R(128, ko * 8 + i2_i)
B[io, ii] = B[io, ii] + A[io, ii, k]
@T.prim_func(s_tir=True)
def matmul_decompose2(a: T.handle, b: T.handle, c: T.handle) -> None:
C = T.match_buffer(c, [128, 128], elem_offset=0, align=64, offset_factor=1)
B = T.match_buffer(b, [128, 128], elem_offset=0, align=64, offset_factor=1)
A = T.match_buffer(a, [128, 128], elem_offset=0, align=64, offset_factor=1)
for i0, i1 in T.grid(128, 128):
with T.sblock("update_init"):
vi_init, vj_init = T.axis.remap("SS", [i0, i1])
C[vi_init, vj_init] = T.float32(0)
for i2 in T.serial(0, 128):
with T.sblock("update_update"):
vi, vj, vk = T.axis.remap("SSR", [i0, i1, i2])
C[vi, vj] = C[vi, vj] + (A[vi, vk] * B[vj, vk])
@T.prim_func(s_tir=True)
def matmul_decompose_fail3(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
B = T.match_buffer(b, [128, 128])
C = T.match_buffer(c, [128, 128])
for i, k, j in T.grid(128, 128, 128):
with T.sblock("update"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
C[vi, vj] = 0.0
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
@T.prim_func(s_tir=True)
def matmul_decompose4(a: T.handle, b: T.handle, c: T.handle) -> None:
C = T.match_buffer(c, [128, 128], elem_offset=0, align=64, offset_factor=1)
B = T.match_buffer(b, [128, 128], elem_offset=0, align=64, offset_factor=1)
A = T.match_buffer(a, [128, 128], elem_offset=0, align=64, offset_factor=1)
# body
with T.sblock("root"):
T.reads([])
T.writes([])
for i0_0 in T.serial(0, 16):
for i0_1_init, i1_init in T.grid(8, 128):
with T.sblock("update_init"):
vi_init = T.axis.S(128, i0_0 * 8 + i0_1_init)
vj_init = T.axis.S(128, i1_init)
C[vi_init, vj_init] = T.float32(0)
for i0_1, i1, i2_0, i2_1 in T.grid(8, 128, 19, 7):
with T.sblock("update_update"):
T.where(((i2_0 * 7) + i2_1) < 128)
vi = T.axis.S(128, i0_0 * 8 + i0_1)
vj = T.axis.S(128, i1)
vk = T.axis.R(128, i2_0 * 7 + i2_1)
C[vi, vj] = C[vi, vj] + (A[vi, vk] * B[vj, vk])
@T.prim_func(s_tir=True)
def matmul_with_annotation(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
B = T.match_buffer(b, [128, 128])
C = T.match_buffer(c, [128, 128])
for i, j, k in T.grid(128, 128, 128):
with T.sblock("update"):
T.sblock_attr({"test_annotation": 1})
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
C[vi, vj] = 0.0
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
@T.prim_func(s_tir=True)
def matmul_decompose_with_annotation(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
B = T.match_buffer(b, [128, 128])
C = T.match_buffer(c, [128, 128])
for i, j in T.grid(128, 128):
with T.sblock("init"):
T.sblock_attr({"test_annotation": 1})
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = 0.0
for i, j, k in T.grid(128, 128, 128):
with T.sblock("update"):
T.sblock_attr({"test_annotation": 1})
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
@T.prim_func(s_tir=True)
def colsum_with_vectorization(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [128, 32], dtype="float32")
B = T.match_buffer(b, [32], dtype="float32")
for k in T.serial(0, 128):
for i in T.vectorized(0, 32):
with T.sblock("B"):
vk, vi = T.axis.remap("RS", [k, i])
with T.init():
B[vi] = T.float32(0)
B[vi] = B[vi] + A[vk, vi]
@T.prim_func(s_tir=True)
def colsum_decompose_with_vectorization(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [128, 32], dtype="float32")
B = T.match_buffer(b, [32], dtype="float32")
for i in T.vectorized(0, 32):
with T.sblock("B_init"):
vi = T.axis.S(32, i)
B[vi] = T.float32(0)
for k in T.serial(0, 128):
for i in T.vectorized(0, 32):
with T.sblock("B"):
vk, vi = T.axis.remap("RS", [k, i])
B[vi] = B[vi] + A[vk, vi]
# pylint: enable=no-member,invalid-name,unused-variable,unexpected-keyword-arg
use_block_name = tvm.testing.parameter(by_dict={"block_obj": False, "block_name": True})
def test_reduction_decompose0(use_block_name):
s = tvm.s_tir.Schedule(matmul, debug_mask="all")
C = "update" if use_block_name else s.get_sblock("update")
i, j, k = s.get_loops(C)
s.decompose_reduction(C, i)
assert_structural_equal_ignore_global_symbol(matmul_decompose0, s.mod["main"])
verify_trace_roundtrip(s, mod=matmul)
def test_reduction_decompose1(use_block_name):
s = tvm.s_tir.Schedule(rowsum_blockized, debug_mask="all")
blockized_B = "blockized_B" if use_block_name else s.get_sblock("blockized_B")
io, ko = s.get_loops(blockized_B)
s.decompose_reduction(blockized_B, io)
assert_structural_equal_ignore_global_symbol(matmul_decompose1, s.mod["main"])
verify_trace_roundtrip(s, mod=rowsum_blockized)
def test_reduction_decompose2():
s = tvm.s_tir.Schedule(matmul, debug_mask="all")
C = s.get_sblock("update")
i, j, k = s.get_loops(C)
s.decompose_reduction(C, k)
assert_structural_equal_ignore_global_symbol(matmul_decompose2, s.mod["main"])
verify_trace_roundtrip(s, mod=matmul)
def test_reduction_decompose3():
s = tvm.s_tir.Schedule(matmul_decompose_fail3, debug_mask="all")
C = s.get_sblock("update")
i, j, k = s.get_loops(C)
with pytest.raises(tvm.s_tir.ScheduleError):
s.decompose_reduction(C, k)
def test_reduction_decompose4():
s = tvm.s_tir.Schedule(matmul, debug_mask="all")
C = s.get_sblock("update")
i, j, k = s.get_loops(C)
io, ii = s.split(i, factors=[16, 8])
ko, ki = s.split(k, factors=[19, 7])
s.decompose_reduction(C, ii)
assert_structural_equal_ignore_global_symbol(matmul_decompose4, s.mod["main"])
verify_trace_roundtrip(s, mod=matmul)
def test_reduction_decompose_with_annotation():
s = tvm.s_tir.Schedule(matmul_with_annotation, debug_mask="all")
C = s.get_sblock("update")
i, j, k = s.get_loops(C)
s.decompose_reduction(C, i)
assert_structural_equal_ignore_global_symbol(matmul_decompose_with_annotation, s.mod["main"])
verify_trace_roundtrip(s, mod=matmul_with_annotation)
def test_reduction_decompose_with_different_for_kind():
s = tvm.s_tir.Schedule(colsum_with_vectorization, debug_mask="all")
B = s.get_sblock("B")
k, _ = s.get_loops(B)
B_init = s.decompose_reduction(B, k)
assert_structural_equal_ignore_global_symbol(s.mod["main"], colsum_decompose_with_vectorization)
assert s.get(B).same_as(s.get(s.get_sblock("B_update")))
assert s.get(B_init).same_as(s.get(s.get_sblock("B_init")))
verify_trace_roundtrip(s, mod=colsum_with_vectorization)
def test_decompose_reduction_ref_hash_check():
mod = tvm.IRModule.from_expr(matmul.with_attr("global_symbol", "main"))
mod_bak = mod
hash_before = tvm_ffi.structural_hash(mod_bak)
s = tvm.s_tir.Schedule(mod["main"], debug_mask="all")
C = s.get_sblock("update")
i, j, k = s.get_loops(C)
s.decompose_reduction(C, k)
hash_after = tvm_ffi.structural_hash(mod_bak)
assert hash_before == hash_after
def test_decompose_reduction_nested_block():
@T.prim_func(s_tir=True)
def nested_block(A: T.Buffer((1, 64), "float32"), B: T.Buffer((1,), "float32")):
for i, ko in T.grid(1, 2):
with T.sblock("outer"):
vi, vko = T.axis.remap("SR", [i, ko])
C = T.sblock_alloc_buffer((32,), dtype="float32")
with T.init():
B[vi] = T.float32(0)
for ki in T.serial(32):
with T.sblock("inner_1"):
vki = T.axis.remap("S", [ki])
C[vki] = A[vi, vko * 32 + vki]
for ki in T.serial(32):
with T.sblock("inner_2"):
vki = T.axis.remap("R", [ki])
B[vi] += C[vki]
@T.prim_func(s_tir=True)
def decomposed_nested_block(A: T.Buffer((1, 64), "float32"), B: T.Buffer((1,), "float32")):
for i in range(1):
with T.sblock("outer_init"):
vi = T.axis.spatial(1, i)
T.reads()
T.writes(B[vi])
B[vi] = T.float32(0)
for ko in range(2):
with T.sblock("outer_update"):
vi, vko = T.axis.remap("SR", [i, ko])
T.reads(B[vi], A[vi, vko * 32 : vko * 32 + 32])
T.writes(B[vi])
C = T.sblock_alloc_buffer((32,))
for ki in range(32):
with T.sblock("inner_1"):
vki = T.axis.spatial(32, ki)
T.reads(A[vi, vko * 32 + vki])
T.writes(C[vki])
C[vki] = A[vi, vko * 32 + vki]
for ki in range(32):
with T.sblock("inner_2"):
vki = T.axis.reduce(32, ki)
T.reads(B[vi], C[vki])
T.writes(B[vi])
B[vi] = B[vi] + C[vki]
sch = tvm.s_tir.Schedule(nested_block, debug_mask="all")
outer = sch.get_sblock("outer")
i, ko = sch.get_loops(outer)
sch.decompose_reduction(outer, ko)
assert_structural_equal_ignore_global_symbol(decomposed_nested_block, sch.mod["main"])
verify_trace_roundtrip(sch, mod=nested_block)
def test_decompose_reduction_with_thread_binding():
@I.ir_module(s_tir=True)
class Before:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((32, 16), "float32"), B: T.Buffer((32,), "float32")):
for t in T.thread_binding(0, 32, thread="threadIdx.x"):
for r in T.serial(16):
with T.sblock("B"):
vi, vr = T.axis.remap("SR", [t, r])
with T.init():
B[vi] = T.float32(0)
B[vi] += A[vi, vr]
@I.ir_module(s_tir=True)
class Expected:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((32, 16), "float32"), B: T.Buffer((32,), "float32")):
for t_init in T.thread_binding(0, 32, thread="threadIdx.x"):
with T.sblock("B_init"):
vi = T.axis.remap("S", [t_init])
B[vi] = T.float32(0)
for t in T.thread_binding(0, 32, thread="threadIdx.x"):
for r in T.serial(16):
with T.sblock("B"):
vi, vr = T.axis.remap("SR", [t, r])
B[vi] += A[vi, vr]
sch = tvm.s_tir.Schedule(Before)
t, _ = sch.get_loops("B")
sch.decompose_reduction("B", t)
After = sch.mod
tvm.ir.assert_structural_equal(After, Expected)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,355 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: F401
import pytest
import tvm
import tvm.testing
from tvm import tirx
from tvm.s_tir.schedule.schedule import ScheduleError
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.script import tirx as T
@T.prim_func(s_tir=True)
def transpose_elementwise(
A: T.Buffer((128, 128), "float32"), B: T.Buffer((128, 128), "float32")
) -> None:
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vj, vi] * 2.0
@T.prim_func(s_tir=True)
def transpose_elementwise_reindex_read(
A: T.Buffer((128, 128), "float32"), B: T.Buffer((128, 128), "float32")
) -> None:
A_reindex = T.sblock_alloc_buffer((128, 128), "float32")
for i, j in T.grid(128, 128):
with T.sblock("A_reindex"):
vi, vj = T.axis.remap("SS", [i, j])
A_reindex[vi, vj] = A[vj, vi]
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A_reindex[vi, vj] * 2.0
@T.prim_func(s_tir=True)
def conv2d_nhwc(
Input: T.Buffer((1, 224, 224, 3), "float32"),
Weight: T.Buffer((7, 7, 3, 64), "float32"),
Conv2d_nhwc: T.Buffer((1, 112, 112, 64), "float32"),
) -> None:
PadInput = T.sblock_alloc_buffer([1, 230, 230, 3], dtype="float32")
for i0, i1, i2, i3 in T.grid(1, 230, 230, 3):
with T.sblock("PadInput"):
i0_1, i1_1, i2_1, i3_1 = T.axis.remap("SSSS", [i0, i1, i2, i3])
PadInput[i0_1, i1_1, i2_1, i3_1] = T.if_then_else(
((((i1_1 >= 3) and (i1_1 < 227)) and (i2_1 >= 3)) and (i2_1 < 227)),
Input[i0_1, (i1_1 - 3), (i2_1 - 3), i3_1],
T.float32(0),
dtype="float32",
)
for i0, i1, i2, i3, i4, i5, i6 in T.grid(1, 112, 112, 64, 7, 7, 3):
with T.sblock("conv2d_nhwc"):
n, h, w, co, rh, rw, rc = T.axis.remap("SSSSRRR", [i0, i1, i2, i3, i4, i5, i6])
with T.init():
Conv2d_nhwc[n, h, w, co] = T.float32(0)
Conv2d_nhwc[n, h, w, co] = Conv2d_nhwc[n, h, w, co] + (
PadInput[n, ((h * 2) + rh), ((w * 2) + rw), ((T.floordiv(co, 64) * 3) + rc)]
* Weight[rh, rw, rc, co]
)
@T.prim_func(s_tir=True)
def conv2d_nhwc_reindex_data(
Input: T.Buffer((1, 224, 224, 3), "float32"),
Weight: T.Buffer((7, 7, 3, 64), "float32"),
Conv2d_nhwc: T.Buffer((1, 112, 112, 64), "float32"),
) -> None:
PadInput = T.sblock_alloc_buffer([1, 230, 230, 3], dtype="float32")
ReindexInput = T.sblock_alloc_buffer([1, 112, 112, 7, 7, 3], dtype="float32")
for i0, i1, i2, i3 in T.grid(1, 230, 230, 3):
with T.sblock("PadInput"):
i0_1, i1_1, i2_1, i3_1 = T.axis.remap("SSSS", [i0, i1, i2, i3])
PadInput[i0_1, i1_1, i2_1, i3_1] = T.if_then_else(
((((i1_1 >= 3) and (i1_1 < 227)) and (i2_1 >= 3)) and (i2_1 < 227)),
Input[i0_1, (i1_1 - 3), (i2_1 - 3), i3_1],
T.float32(0),
dtype="float32",
)
for i0, i1, i2, i3, i4, i5 in T.grid(1, 112, 112, 7, 7, 3):
with T.sblock("ReindexInput"):
n, h, w, rh, rw, rc = T.axis.remap("SSSSSS", [i0, i1, i2, i3, i4, i5])
ReindexInput[n, h, w, rh, rw, rc] = PadInput[n, ((h * 2) + rh), ((w * 2) + rw), rc]
for i0, i1, i2, i3, i4, i5, i6 in T.grid(1, 112, 112, 64, 7, 7, 3):
with T.sblock("conv2d_nhwc"):
n, h, w, co, rh, rw, rc = T.axis.remap("SSSSRRR", [i0, i1, i2, i3, i4, i5, i6])
with T.init():
Conv2d_nhwc[n, h, w, co] = T.float32(0)
Conv2d_nhwc[n, h, w, co] = Conv2d_nhwc[n, h, w, co] + (
ReindexInput[n, h, w, rh, rw, rc] * Weight[rh, rw, rc, co]
)
@T.prim_func(s_tir=True)
def conv2d_nhwc_reindex_weight(
var_inputs: T.handle, var_weight: T.handle, var_conv2d_nhwc: T.handle
) -> None:
inputs = T.match_buffer(var_inputs, [1, 224, 224, 3], dtype="float32")
weight = T.match_buffer(var_weight, [7, 7, 3, 64], dtype="float32")
conv2d_nhwc = T.match_buffer(var_conv2d_nhwc, [1, 112, 112, 64], dtype="float32")
PadInput = T.sblock_alloc_buffer([1, 230, 230, 3], dtype="float32")
weight_reindex = T.sblock_alloc_buffer([64, 7, 7, 3], dtype="float32")
for i0, i1, i2, i3 in T.grid(1, 230, 230, 3):
with T.sblock("PadInput"):
i0_1, i1_1, i2_1, i3_1 = T.axis.remap("SSSS", [i0, i1, i2, i3])
T.reads(inputs[i0_1, i1_1 - 3, i2_1 - 3, i3_1])
T.writes(PadInput[i0_1, i1_1, i2_1, i3_1])
PadInput[i0_1, i1_1, i2_1, i3_1] = T.if_then_else(
i1_1 >= 3 and i1_1 < 227 and i2_1 >= 3 and i2_1 < 227,
inputs[i0_1, i1_1 - 3, i2_1 - 3, i3_1],
T.float32(0),
dtype="float32",
)
for ax3, ax4, ax5, ax6 in T.grid(64, 7, 7, 3):
with T.sblock("weight_reindex"):
v3, v4, v5, v6 = T.axis.remap("SSSS", [ax3, ax4, ax5, ax6])
T.reads(weight[v4, v5, v6, v3])
T.writes(weight_reindex[v3, v4, v5, v6])
weight_reindex[v3, v4, v5, v6] = weight[v4, v5, v6, v3]
for i0, i1, i2, i3, i4, i5, i6 in T.grid(1, 112, 112, 64, 7, 7, 3):
with T.sblock("conv2d_nhwc"):
n, h, w, co, rh, rw, rc = T.axis.remap("SSSSRRR", [i0, i1, i2, i3, i4, i5, i6])
T.reads(
PadInput[n, h * 2 + rh, w * 2 + rw, co // 64 * 3 + rc],
weight_reindex[co, rh, rw, rc],
)
T.writes(conv2d_nhwc[n, h, w, co])
with T.init():
conv2d_nhwc[n, h, w, co] = T.float32(0)
conv2d_nhwc[n, h, w, co] = (
conv2d_nhwc[n, h, w, co]
+ PadInput[n, h * 2 + rh, w * 2 + rw, co // 64 * 3 + rc]
* weight_reindex[co, rh, rw, rc]
)
@T.prim_func(s_tir=True)
def matmul(
A: T.Buffer((512, 512), "float32"),
B: T.Buffer((512, 512), "float32"),
C: T.Buffer((512, 512), "float32"),
) -> None:
for i0, i1, i2 in T.grid(512, 512, 512):
with T.sblock("matmul"):
i, j, k = T.axis.remap("SSR", [i0, i1, i2])
T.reads(C[i, j], A[i, k], B[k, j])
T.writes(C[i, j])
with T.init():
C[i, j] = T.float32(0)
C[i, j] = C[i, j] + A[i, k] * B[k, j]
@T.prim_func(s_tir=True)
def matmul_reindex_write(
A: T.Buffer((512, 512), "float32"),
B: T.Buffer((512, 512), "float32"),
C: T.Buffer((512, 512), "float32"),
) -> None:
C_reindex = T.sblock_alloc_buffer([512, 512], dtype="float32")
for i0, i1, i2 in T.grid(512, 512, 512):
with T.sblock("matmul"):
i, j, k = T.axis.remap("SSR", [i0, i1, i2])
T.reads(C_reindex[i, j], A[i, k], B[k, j])
T.writes(C_reindex[i, j])
with T.init():
C_reindex[i, j] = T.float32(0)
C_reindex[i, j] = C_reindex[i, j] + A[i, k] * B[k, j]
for i0, i1 in T.grid(512, 512):
with T.sblock("C_reindex"):
v0, v1 = T.axis.remap("SS", [i0, i1])
T.reads(C_reindex[v0, v1])
T.writes(C[v0, v1])
C[v0, v1] = C_reindex[v0, v1]
@T.prim_func(s_tir=True)
def multiple_read(A: T.Buffer((128, 128), "float32"), B: T.Buffer((128, 128), "float32")) -> None:
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vj, vi] + A[vi, vj]
@T.prim_func(s_tir=True)
def mixed_dtype(
p0: T.Buffer((T.int64(2), 1280), "float16"),
p1: T.Buffer((1280, 1280), "float16"),
T_matmul_NT: T.Buffer((T.int64(2), 1280), "float16"),
) -> None:
for i0, i1, i2 in T.grid(T.int64(2), 1280, 1280):
with T.sblock("T_matmul_NT"):
i = T.axis.spatial(T.int64(2), i0)
j, k = T.axis.remap("SR", [i1, i2])
T.reads(p0[i, k], p1[j, k])
T.writes(T_matmul_NT[i, j])
with T.init():
T_matmul_NT[i, j] = T.float16(0)
T_matmul_NT[i, j] = T_matmul_NT[i, j] + p0[i, k] * p1[j, k]
@T.prim_func(s_tir=True)
def mixed_dtype_reindex_write(
p0: T.Buffer((T.int64(2), 1280), "float16"),
p1: T.Buffer((1280, 1280), "float16"),
T_matmul_NT: T.Buffer((T.int64(2), 1280), "float16"),
) -> None:
T_matmul_NT_reindex = T.sblock_alloc_buffer([T.int64(2), 1280], dtype="float16")
for i0, i1, i2 in T.grid(T.int64(2), 1280, 1280):
with T.sblock("T_matmul_NT"):
i = T.axis.spatial(T.int64(2), i0)
j, k = T.axis.remap("SR", [i1, i2])
T.reads(p0[i, k], p1[j, k])
T.writes(T_matmul_NT_reindex[i, j])
with T.init():
T_matmul_NT_reindex[i, j] = T.float16(0)
T_matmul_NT_reindex[i, j] = T_matmul_NT_reindex[i, j] + p0[i, k] * p1[j, k]
for ax0, ax1 in T.grid(T.int64(2), 1280):
with T.sblock("T_matmul_NT_reindex"):
v0 = T.axis.spatial(T.int64(2), ax0)
v1 = T.axis.remap("S", [ax1])
T.reads(T_matmul_NT_reindex[v0, v1])
T.writes(T_matmul_NT[v0, v1])
T_matmul_NT[v0, v1] = T_matmul_NT_reindex[v0, v1]
@T.prim_func(s_tir=True)
def matmul_unit_dim(
A: T.Buffer((1, 512), "float32"),
B: T.Buffer((512, 1), "float32"),
C: T.Buffer((1, 1), "float32"),
) -> None:
for i0, i1, i2 in T.grid(1, 1, 512):
with T.sblock("matmul"):
i, j, k = T.axis.remap("SSR", [i0, i1, i2])
T.reads(C[i, j], A[i, k], B[k, j])
T.writes(C[i, j])
with T.init():
C[i, j] = T.float32(0)
C[i, j] = C[i, j] + A[i, k] * B[k, j]
@T.prim_func(s_tir=True)
def matmul_unit_dim_reindex_write(
A: T.Buffer((1, 512), "float32"),
B: T.Buffer((512, 1), "float32"),
C: T.Buffer((1, 1), "float32"),
) -> None:
C_reindex = T.sblock_alloc_buffer([1, 1], dtype="float32")
for i0, i1, i2 in T.grid(1, 1, 512):
with T.sblock("matmul"):
i, j, k = T.axis.remap("SSR", [i0, i1, i2])
T.reads(C_reindex[i, j], A[i, k], B[k, j])
T.writes(C_reindex[i, j])
with T.init():
C_reindex[i, j] = T.float32(0)
C_reindex[i, j] = C_reindex[i, j] + A[i, k] * B[k, j]
for i0, i1 in T.grid(1, 1):
with T.sblock("C_reindex"):
v0, v1 = T.axis.remap("SS", [i0, i1])
T.reads(C_reindex[v0, v1])
T.writes(C[v0, v1])
C[v0, v1] = C_reindex[v0, v1]
use_block_name = tvm.testing.parameter(by_dict={"block_obj": False, "block_name": True})
use_buffer_name = tvm.testing.parameter(by_dict={"buffer_index": False, "buffer_name": True})
def test_reindex_read_basic(use_block_name, use_buffer_name):
sch = tvm.s_tir.Schedule(transpose_elementwise)
block = "B" if use_block_name else sch.get_sblock("B")
buf = "A" if use_buffer_name else ("read", 0)
sch.reindex(block, buf)
assert_structural_equal_ignore_global_symbol(
transpose_elementwise_reindex_read, sch.mod["main"]
)
verify_trace_roundtrip(sch=sch, mod=transpose_elementwise)
def test_conv2d_reindex_weight(use_block_name, use_buffer_name):
sch = tvm.s_tir.Schedule(conv2d_nhwc)
block = "conv2d_nhwc" if use_block_name else sch.get_sblock("conv2d_nhwc")
buf = "Weight" if use_buffer_name else ("read", 1)
sch.reindex(block, buf)
assert_structural_equal_ignore_global_symbol(conv2d_nhwc_reindex_weight, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=conv2d_nhwc)
def test_conv2d_reindex_data(use_block_name, use_buffer_name):
sch = tvm.s_tir.Schedule(conv2d_nhwc)
block = "conv2d_nhwc" if use_block_name else sch.get_sblock("conv2d_nhwc")
buf = "PadInput" if use_buffer_name else ("read", 0)
sch.reindex(block, buf)
assert_structural_equal_ignore_global_symbol(conv2d_nhwc_reindex_data, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=conv2d_nhwc)
def test_matmul_reindex_write(use_block_name, use_buffer_name):
sch = tvm.s_tir.Schedule(matmul)
block = "matmul" if use_block_name else sch.get_sblock("matmul")
buf = "C" if use_buffer_name else ("write", 0)
sch.reindex(block, buf)
assert_structural_equal_ignore_global_symbol(matmul_reindex_write, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=matmul)
def test_reindex_fail_multiple_read(use_block_name, use_buffer_name):
sch = tvm.s_tir.Schedule(multiple_read)
block = "B" if use_block_name else sch.get_sblock("B")
buf = "A" if use_buffer_name else ("read", 0)
with pytest.raises(ScheduleError):
sch.reindex(block, buf)
def test_reindex_mixed_dtype(use_block_name, use_buffer_name):
sch = tvm.s_tir.Schedule(mixed_dtype)
block = "T_matmul_NT" if use_block_name else sch.get_sblock("T_matmul_NT")
buf = "T_matmul_NT" if use_buffer_name else ("write", 0)
sch.reindex(block, buf)
assert_structural_equal_ignore_global_symbol(mixed_dtype_reindex_write, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=mixed_dtype)
def test_matmul_unit_dim_reindex_write(use_block_name, use_buffer_name):
sch = tvm.s_tir.Schedule(matmul_unit_dim)
block = "matmul" if use_block_name else sch.get_sblock("matmul")
buf = "C" if use_buffer_name else ("write", 0)
sch.reindex(block, buf)
assert_structural_equal_ignore_global_symbol(matmul_unit_dim_reindex_write, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=matmul_unit_dim)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,403 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: E741, F401
import sys
import pytest
import tvm
import tvm.testing
from tvm import tirx
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.script import tirx as T
# pylint: disable=no-member,invalid-name,unused-variable
@T.prim_func(s_tir=True)
def elementwise(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128, 128))
B = T.match_buffer(b, (128, 128, 128, 128))
for i, j, k, l in T.grid(128, 128, 128, 128):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
B[vi, vj, vk, vl] = A[vi, vj, vk, vl] * 2.0
@T.prim_func(s_tir=True)
def elementwise_not_affine(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128, 128))
B = T.match_buffer(b, (128, 128, 128, 128))
for i, j, k, l in T.grid(128, 128, 128, 8):
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
vl = T.axis.S(128, l * 16)
B[vi, vj, vk, vl] = A[vi, vj, vk, vl] * 2.0
@T.prim_func(s_tir=True)
def elementwise_dependent_loop(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128, 128))
B = T.match_buffer(b, (128, 128, 128, 128))
for i in T.serial(0, 128):
for j, k, l in T.grid(128, i, 128):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
B[vi, vj, vk, vl] = A[vi, vj, vk, vl] * 2.0
@T.prim_func(s_tir=True)
def elementwise_predicate(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128, 128))
B = T.match_buffer(b, (128, 128, 128, 128))
for i, j, k, l in T.grid(128, 128, 128, 128):
with T.sblock("B"):
T.where(i * 2097152 + j * 16384 + k * 128 + l < 100)
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
B[vi, vj, vk, vl] = A[vi, vj, vk, vl] * 2.0
@T.prim_func(s_tir=True)
def elementwise_non_single_branch(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
C = T.sblock_alloc_buffer((128, 128, 128))
B = T.match_buffer(b, (128, 128, 128))
for i, j in T.grid(128, 128):
for k in T.serial(0, 128):
with T.sblock("C"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
C[vi, vj, vk] = A[vi, vj, vk] * 2.0
for k in T.serial(0, 128):
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
B[vi, vj, vk] = C[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_with_loops_not_same_scope(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
B = T.match_buffer(b, (128, 128, 128))
for i, j in T.grid(128, 128):
with T.sblock("A"):
vi, vj = T.axis.remap("SS", [i, j])
for k in T.serial(0, 128):
with T.sblock("B"):
vk = T.axis.S(128, k)
T.reads([A[vi, vj, vk]])
T.writes([B[vi, vj, vk]])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_with_wrong_block_var_type(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
B = T.match_buffer(b, (128, 128, 128))
for i, j, k in T.grid(128, 128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
vk = T.axis.scan(128, k)
T.reads([A[vi, vj, vk]])
T.writes([B[vi, vj, vk]])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_reordered(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128, 128))
B = T.match_buffer(b, (128, 128, 128, 128))
for l, j, k, i in T.grid(128, 128, 128, 128):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
B[vi, vj, vk, vl] = A[vi, vj, vk, vl] * 2.0
@T.prim_func(s_tir=True)
def elementwise_reordered2(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128, 128))
B = T.match_buffer(b, (128, 128, 128, 128))
for k, j, i, l in T.grid(128, 128, 128, 128):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
B[vi, vj, vk, vl] = A[vi, vj, vk, vl] * 2.0
@T.prim_func(s_tir=True)
def elementwise_reordered_with_predicate(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128, 128))
B = T.match_buffer(b, (128, 128, 128, 128))
for l, j, k, i in T.grid(128, 128, 128, 128):
with T.sblock("B"):
T.where(i * 2097152 + j * 16384 + k * 128 + l < 100)
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
B[vi, vj, vk, vl] = A[vi, vj, vk, vl] * 2.0
@T.prim_func(s_tir=True)
def opaque_access(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [16, 16], "float32")
B = T.match_buffer(b, [16, 16], "float32")
for i, j in T.grid(16, 16):
with T.sblock("A"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads([])
T.writes([A[0:16, 0:16]])
A[vi, vj] = 1
for i, j in T.grid(16, 16):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads([])
T.writes([B[0:16, 0:16]])
T.evaluate(T.tvm_fill_fragment(B.data, 16, 16, 16, 0, vi * 16 + vj, dtype="handle"))
@T.prim_func(s_tir=True)
def opaque_access_reorder(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [16, 16], "float32")
B = T.match_buffer(b, [16, 16], "float32")
for j, i in T.grid(16, 16):
with T.sblock("A"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads([])
T.writes([A[0:16, 0:16]])
A[vi, vj] = 1
for j, i in T.grid(16, 16):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads([])
T.writes([B[0:16, 0:16]])
T.evaluate(T.tvm_fill_fragment(B.data, 16, 16, 16, 0, vi * 16 + vj, dtype="handle"))
# pylint: enable=no-member,invalid-name,unused-variable
def test_reorder():
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, k, l = sch.get_loops(block_b)
sch.reorder(l, i)
assert_structural_equal_ignore_global_symbol(elementwise_reordered, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=elementwise)
def test_reorder2():
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, k, l = sch.get_loops(block_b)
sch.reorder(k, i, l)
assert_structural_equal_ignore_global_symbol(elementwise_reordered2, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=elementwise)
def test_reorder_with_opaque_access():
sch = tvm.s_tir.Schedule(opaque_access, debug_mask="all")
block_a = sch.get_sblock("A")
i, j = sch.get_loops(block_a)
sch.reorder(j, i)
block_b = sch.get_sblock("B")
i, j = sch.get_loops(block_b)
sch.reorder(j, i)
assert_structural_equal_ignore_global_symbol(opaque_access_reorder, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=opaque_access)
def test_reorder_overlapped_access():
@T.prim_func(s_tir=True)
def overlapped_access(A: T.Buffer((14, 4), "float32"), B: T.Buffer((14, 4), "float32")):
# example to write first axis multiple times
for v0, v1, v2 in T.grid(6, 4, 4):
with T.sblock("block"):
i = T.axis.spatial(14, v0 * 2 + v1)
j = T.axis.spatial(4, v2)
B[i, j] = A[i, j] + 1.0
@T.prim_func(s_tir=True)
def overlapped_access_reorder(A: T.Buffer((14, 4), "float32"), B: T.Buffer((14, 4), "float32")):
# example to write first axis multiple times
for v0, v2, v1 in T.grid(6, 4, 4):
with T.sblock("block"):
i = T.axis.spatial(14, v0 * 2 + v1)
j = T.axis.spatial(4, v2)
B[i, j] = A[i, j] + 1.0
sch = tvm.s_tir.Schedule(overlapped_access, debug_mask="all")
v0, v1, v2 = sch.get_loops(sch.get_sblock("block"))
sch.reorder(v0, v2, v1)
assert_structural_equal_ignore_global_symbol(overlapped_access_reorder, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=overlapped_access)
def test_reorder_with_partial_affineness():
@T.prim_func(s_tir=True)
def non_affine_func(A: T.Buffer((14, 4), "float32"), B: T.Buffer((14, 4), "float32")):
for v0, v1, v2 in T.grid(6, 4, 4):
with T.sblock("block"):
i = T.axis.spatial(14, v0 * v0 + v1)
j = T.axis.spatial(4, v2)
B[i, j] = A[i, j] + 1.0
@T.prim_func(s_tir=True)
def non_affine_func_reorder(A: T.Buffer((14, 4), "float32"), B: T.Buffer((14, 4), "float32")):
for v0, v2, v1 in T.grid(6, 4, 4):
with T.sblock("block"):
i = T.axis.spatial(14, v0 * v0 + v1)
j = T.axis.spatial(4, v2)
B[i, j] = A[i, j] + 1.0
sch = tvm.s_tir.Schedule(non_affine_func, debug_mask="all")
v0, v1, v2 = sch.get_loops(sch.get_sblock("block"))
with pytest.raises(tvm.s_tir.ScheduleError):
sch.reorder(v0, v2, v1)
sch.reorder(v2, v1)
assert_structural_equal_ignore_global_symbol(non_affine_func_reorder, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=non_affine_func)
def test_reorder_with_cascade_tiled_ops():
@T.prim_func(s_tir=True)
def cascade_pool_ops(
x: T.Buffer((1, 16, 112, 112), "float32"), y2: T.Buffer((1, 16, 108, 108), "float32")
) -> None:
y1 = T.sblock_alloc_buffer([1, 16, 110, 110], dtype="float32")
for n, c, h, w, kh, kw in T.grid(1, 16, 110, 110, 3, 3):
with T.sblock("pool_0"):
ax0, ax1, ax2, ax3, rv0, rv1 = T.axis.remap("SSSSRR", [n, c, h, w, kh, kw])
with T.init():
y1[ax0, ax1, ax2, ax3] = 0.0
y1[ax0, ax1, ax2, ax3] = y1[ax0, ax1, ax2, ax3] + x[ax0, ax1, ax2 + rv0, ax3 + rv1]
for n, c, h, w, kh, kw in T.grid(1, 16, 108, 108, 3, 3):
with T.sblock("pool_1"):
ax0, ax1, ax2, ax3, rv0, rv1 = T.axis.remap("SSSSRR", [n, c, h, w, kh, kw])
with T.init():
y2[ax0, ax1, ax2, ax3] = 0.0
y2[ax0, ax1, ax2, ax3] = y2[ax0, ax1, ax2, ax3] + y1[ax0, ax1, ax2 + rv0, ax3 + rv1]
@T.prim_func(s_tir=True)
def cascade_pool_ops_tile_reordered(
x: T.Buffer((1, 16, 112, 112), "float32"), y2: T.Buffer((1, 16, 108, 108), "float32")
) -> None:
y1 = T.sblock_alloc_buffer([1, 16, 110, 110], dtype="float32")
for n, c, h_o in T.grid(1, 16, 27):
for w, h_i, kh, kw in T.grid(110, 6, 3, 3):
with T.sblock("pool_0"):
ax0 = T.axis.spatial(1, 0)
ax1 = T.axis.spatial(16, c)
ax2 = T.axis.spatial(110, h_o * 4 + h_i)
ax3, rv0, rv1 = T.axis.remap("SRR", [w, kh, kw])
with T.init():
y1[ax0, ax1, ax2, ax3] = 0.0
y1[ax0, ax1, ax2, ax3] = (
y1[ax0, ax1, ax2, ax3] + x[ax0, ax1, ax2 + rv0, ax3 + rv1]
)
for h_i, w, kh, kw in T.grid(4, 108, 3, 3):
with T.sblock("pool_1"):
ax0 = T.axis.spatial(1, n)
ax1 = T.axis.spatial(16, c)
ax2 = T.axis.spatial(108, h_o * 4 + h_i)
ax3, rv0, rv1 = T.axis.remap("SRR", [w, kh, kw])
with T.init():
y2[ax0, ax1, ax2, ax3] = 0.0
y2[ax0, ax1, ax2, ax3] = (
y2[ax0, ax1, ax2, ax3] + y1[ax0, ax1, ax2 + rv0, ax3 + rv1]
)
sch = tvm.s_tir.schedule.Schedule(cascade_pool_ops)
pool_0 = sch.get_sblock("pool_0")
pool_1 = sch.get_sblock("pool_1")
_, _, h, w, _, _ = sch.get_loops(pool_1)
ho, _ = sch.split(h, factors=[None, 4])
sch.compute_at(pool_0, ho)
_, _, _, h_i, w, _, _ = sch.get_loops(pool_0)
sch.reorder(w, h_i)
assert_structural_equal_ignore_global_symbol(
cascade_pool_ops_tile_reordered, sch.mod["main"], True
)
verify_trace_roundtrip(sch=sch, mod=cascade_pool_ops)
def test_reorder_with_predicate():
sch = tvm.s_tir.Schedule(elementwise_predicate, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, k, l = sch.get_loops(block_b)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.reorder(l, i)
def test_reorder_fail_with_multi_appearance_loops():
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, k, l = sch.get_loops(block_b)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.reorder(k, i, i)
def test_reorder_fail_with_non_single_branch_loop():
sch = tvm.s_tir.Schedule(elementwise_non_single_branch, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, k = sch.get_loops(block_b)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.reorder(k, i)
sch = tvm.s_tir.Schedule(elementwise_non_single_branch, debug_mask="all")
block_b = sch.get_sblock("B")
block_c = sch.get_sblock("C")
i, j, k1 = sch.get_loops(block_b)
_, _, k2 = sch.get_loops(block_c)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.reorder(k1, i, k2)
def test_reorder_fail_with_loops_not_under_same_scope():
sch = tvm.s_tir.Schedule(elementwise_with_loops_not_same_scope, debug_mask="all")
block_b = sch.get_sblock("B")
block_a = sch.get_sblock("A")
i, j = sch.get_loops(block_a)
k = sch.get_loops(block_b)[0]
with pytest.raises(tvm.s_tir.ScheduleError):
sch.reorder(k, i)
def test_reorder_fail_with_wrong_block_var_type():
sch = tvm.s_tir.Schedule(elementwise_with_wrong_block_var_type, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, k = sch.get_loops(block_b)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.reorder(k, i)
def test_reorder_fail_with_dependent_loops():
sch = tvm.s_tir.Schedule(elementwise_dependent_loop, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, k, l = sch.get_loops(block_b)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.reorder(l, i)
def test_reorder_fail_not_affine_bindings():
sch = tvm.s_tir.Schedule(elementwise_not_affine, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, k, l = sch.get_loops(block_b)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.reorder(l, i)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,90 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ruff: noqa: F401
import pytest
import tvm
import tvm.testing
from tvm import tirx
from tvm.s_tir.schedule.testing import verify_trace_roundtrip
from tvm.script import tirx as T
@T.prim_func(s_tir=True)
def matmul(
A: T.Buffer((128, 128), "float32"),
B: T.Buffer((128, 128), "float32"),
C: T.Buffer((128, 128), "float32"),
) -> None:
for i, j, k in T.grid(128, 128, 128):
with T.sblock("C"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
C[vi, vj] = 0.0
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
@T.prim_func(s_tir=True)
def matmul_after_reorder_block_iter_var(
A: T.Buffer((128, 128), "float32"),
B: T.Buffer((128, 128), "float32"),
C: T.Buffer((128, 128), "float32"),
):
for i, j, k in T.grid(128, 128, 128):
with T.sblock("C"):
vk, vj, vi = T.axis.remap("RSS", [k, j, i])
T.reads(A[vi, vk], B[vj, vk])
T.writes(C[vi, vj])
with T.init():
C[vi, vj] = T.float32(0)
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
def test_reorder_block_iter_var():
sch = tvm.s_tir.Schedule(matmul, debug_mask="all")
C = sch.get_sblock("C")
sch.reorder_block_iter_var(C, [2, 1, 0])
tvm.ir.assert_structural_equal(
matmul_after_reorder_block_iter_var.with_attr("global_symbol", "matmul"), sch.mod["main"]
)
verify_trace_roundtrip(sch=sch, mod=matmul)
def test_reorder_block_iter_var_fail_not_full():
sch = tvm.s_tir.Schedule(matmul, debug_mask="all")
C = sch.get_sblock("C")
with pytest.raises(tvm.s_tir.ScheduleError):
sch.reorder_block_iter_var(C, [2, 1])
def test_reorder_block_iter_var_fail_not_within_bound():
sch = tvm.s_tir.Schedule(matmul, debug_mask="all")
C = sch.get_sblock("C")
with pytest.raises(tvm.s_tir.ScheduleError):
sch.reorder_block_iter_var(C, [-1, 3, 2])
def test_reorder_block_iter_var_fail_not_unique():
sch = tvm.s_tir.Schedule(matmul, debug_mask="all")
C = sch.get_sblock("C")
with pytest.raises(tvm.s_tir.ScheduleError):
sch.reorder_block_iter_var(C, [0, 0, 2])
if __name__ == "__main__":
tvm.testing.main()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,577 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
import numpy as np
import pytest
import tvm
import tvm.testing
from tvm import s_tir, tirx
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.script import tirx as T
def check_rolling_buffer(
sch: s_tir.Schedule, origin: tirx.PrimFunc, expected: tirx.PrimFunc, check_run=False
):
scheduled = sch.mod["main"]
assert_structural_equal_ignore_global_symbol(scheduled, expected)
verify_trace_roundtrip(sch, origin)
if check_run:
in_buffer = origin.buffer_map[origin.params[0]]
out_buffer = origin.buffer_map[origin.params[1]]
in_shape = [int(_) for _ in in_buffer.shape]
out_shape = [int(_) for _ in out_buffer.shape]
x = tvm.runtime.tensor(np.random.uniform(0, 64, in_shape).astype(in_buffer.dtype))
y0 = tvm.runtime.tensor(np.zeros(out_shape).astype(out_buffer.dtype))
y1 = tvm.runtime.tensor(np.zeros(out_shape).astype(out_buffer.dtype))
f_origin = tvm.compile(origin)
f_scheduled = tvm.compile(scheduled)
f_origin(x, y0)
f_scheduled(x, y1)
tvm.testing.assert_allclose(y0.numpy(), y1.numpy())
def _tile_nd(s, tile, block_name):
outer_indices = []
inner_indices = []
block = s.get_sblock(block_name)
loops = s.get_loops(block)
for i, size in enumerate(tile):
outer, inner = s.split(loops[i], [None, size])
outer_indices.append(outer)
inner_indices.append(inner)
s.reorder(*outer_indices, *inner_indices)
return outer_indices, inner_indices
def test_1d_rolling_buffer():
@T.prim_func(s_tir=True)
def before(A: T.Buffer((4, 12), "int32"), C: T.Buffer((4, 8), "int32")):
B = T.sblock_alloc_buffer((4, 10), "int32")
for c in T.serial(4):
for i in T.serial(0, 10):
for k in T.serial(3):
with T.sblock("B"):
cc, vi, vk = T.axis.remap("SSR", [c, i, k])
with T.init():
B[cc, vi] = 0
B[cc, vi] = B[cc, vi] + A[cc, vi + vk]
for i in T.serial(0, 8):
for k in T.serial(3):
with T.sblock("C"):
cc, vi, vk = T.axis.remap("SSR", [c, i, k])
with T.init():
C[cc, vi] = 0
C[cc, vi] = C[cc, vi] + B[cc, vi + vk]
@T.prim_func(s_tir=True)
def expected(A: T.Buffer((4, 12), "int32"), C: T.Buffer((4, 8), "int32")):
B = T.sblock_alloc_buffer([4, 6], dtype="int32")
for c, i_0 in T.grid(4, 2):
for ax0, ax1 in T.grid(6, 3):
with T.sblock("B"):
T.where(i_0 < 1 or 2 <= ax0)
cc = T.axis.spatial(4, c)
vi = T.axis.opaque(10, i_0 * 4 + ax0)
vk = T.axis.reduce(3, ax1)
T.reads(A[cc, vi + vk])
T.writes(B[cc, vi % 6])
with T.init():
B[cc, vi % 6] = 0
B[cc, vi % 6] = B[cc, vi % 6] + A[cc, vi + vk]
for i_1, k in T.grid(4, 3):
with T.sblock("C"):
cc = T.axis.spatial(4, c)
vi = T.axis.opaque(8, i_0 * 4 + i_1)
vk = T.axis.reduce(3, k)
T.reads(B[cc, (vi + vk) % 6])
T.writes(C[cc, vi])
with T.init():
C[cc, vi] = 0
C[cc, vi] = C[cc, vi] + B[cc, (vi + vk) % 6]
sch = tvm.s_tir.Schedule(before, debug_mask="all")
_, i, _ = sch.get_loops(sch.get_sblock("C"))
io, _ = sch.split(i, [2, 4])
sch.compute_at(sch.get_sblock("B"), io)
sch.rolling_buffer(sch.get_sblock("B"), 0)
check_rolling_buffer(sch, before, expected, check_run=True)
@T.prim_func(s_tir=True)
def cascade_2_max_pool2d(A: T.Buffer((1, 12, 12, 16), "int8"), C: T.Buffer((1, 8, 8, 16), "int8")):
B = T.sblock_alloc_buffer([1, 10, 10, 16], dtype="int8")
for i0, i1, i2, i3, i4, i5 in T.grid(1, 10, 10, 16, 3, 3):
with T.sblock("B"):
ax0, ax1, ax2, ax3, rv0, rv1 = T.axis.remap("SSSSRR", [i0, i1, i2, i3, i4, i5])
with T.init():
B[ax0, ax1, ax2, ax3] = T.int8(-128)
B[ax0, ax1, ax2, ax3] = T.max(B[ax0, ax1, ax2, ax3], A[ax0, ax1 + rv0, ax2 + rv1, ax3])
for i0, i1, i2, i3, i4, i5 in T.grid(1, 8, 8, 16, 3, 3):
with T.sblock("C"):
ax0, ax1, ax2, ax3, rv0, rv1 = T.axis.remap("SSSSRR", [i0, i1, i2, i3, i4, i5])
with T.init():
C[ax0, ax1, ax2, ax3] = T.int8(-128)
C[ax0, ax1, ax2, ax3] = T.max(C[ax0, ax1, ax2, ax3], B[ax0, ax1 + rv0, ax2 + rv1, ax3])
@T.prim_func(s_tir=True)
def cascade_3_max_pool2d_with_stride(
A: T.Buffer((1, 24, 24, 16), "int8"), C: T.Buffer((1, 8, 8, 16), "int8")
):
B_0 = T.sblock_alloc_buffer([1, 22, 22, 16], dtype="int8")
B_1 = T.sblock_alloc_buffer([1, 10, 10, 16], dtype="int8")
for i0, i1, i2, i3, i4, i5 in T.grid(1, 22, 22, 16, 3, 3):
with T.sblock("B_0"):
ax0, ax1, ax2, ax3, rv0, rv1 = T.axis.remap("SSSSRR", [i0, i1, i2, i3, i4, i5])
with T.init():
B_0[ax0, ax1, ax2, ax3] = T.int8(-128)
B_0[ax0, ax1, ax2, ax3] = T.max(
B_0[ax0, ax1, ax2, ax3], A[ax0, ax1 + rv0, ax2 + rv1, ax3]
)
for i0, i1, i2, i3, i4, i5 in T.grid(1, 10, 10, 16, 3, 3):
with T.sblock("B_1"):
ax0, ax1, ax2, ax3, rv0, rv1 = T.axis.remap("SSSSRR", [i0, i1, i2, i3, i4, i5])
with T.init():
B_1[ax0, ax1, ax2, ax3] = T.int8(-128)
B_1[ax0, ax1, ax2, ax3] = T.max(
B_1[ax0, ax1, ax2, ax3], B_0[ax0, ax1 * 2 + rv0, ax2 * 2 + rv1, ax3]
)
for i0, i1, i2, i3, i4, i5 in T.grid(1, 8, 8, 16, 3, 3):
with T.sblock("C"):
ax0, ax1, ax2, ax3, rv0, rv1 = T.axis.remap("SSSSRR", [i0, i1, i2, i3, i4, i5])
with T.init():
C[ax0, ax1, ax2, ax3] = T.int8(-128)
C[ax0, ax1, ax2, ax3] = T.max(
C[ax0, ax1, ax2, ax3], B_1[ax0, ax1 + rv0, ax2 + rv1, ax3]
)
def test_cascade_max_pool2d_w_tiled():
@T.prim_func(s_tir=True)
def expected(A: T.Buffer((1, 12, 12, 16), "int8"), C: T.Buffer((1, 8, 8, 16), "int8")):
B = T.sblock_alloc_buffer([1, 10, 6, 16], dtype="int8")
for i0_0, i1_0, i2_0, i3_0 in T.grid(1, 1, 2, 1):
for ax0, ax1, ax2, ax3, ax4 in T.grid(10, 6, 16, 3, 3):
with T.sblock("B"):
T.where(i2_0 < 1 or 2 <= ax1)
ax0_1 = T.axis.spatial(1, 0)
ax1_1 = T.axis.spatial(10, ax0)
ax2_1 = T.axis.opaque(10, i2_0 * 4 + ax1)
ax3_1, rv0, rv1 = T.axis.remap("SRR", [ax2, ax3, ax4])
T.reads(A[ax0_1, ax1_1 + rv0, ax2_1 + rv1, ax3_1])
T.writes(B[ax0_1, ax1_1, ax2_1 % 6, ax3_1])
with T.init():
B[ax0_1, ax1_1, ax2_1 % 6, ax3_1] = T.int8(-128)
B[ax0_1, ax1_1, ax2_1 % 6, ax3_1] = T.max(
B[ax0_1, ax1_1, ax2_1 % 6, ax3_1], A[ax0_1, ax1_1 + rv0, ax2_1 + rv1, ax3_1]
)
for i0_1, i1_1, i2_1, i3_1, i4, i5 in T.grid(1, 8, 4, 16, 3, 3):
with T.sblock("C"):
ax0 = T.axis.spatial(1, i0_0 + i0_1)
ax1 = T.axis.spatial(8, i1_0 * 8 + i1_1)
ax2 = T.axis.opaque(8, i2_0 * 4 + i2_1)
ax3 = T.axis.spatial(16, i3_0 * 16 + i3_1)
rv0, rv1 = T.axis.remap("RR", [i4, i5])
T.reads(B[ax0, ax1 + rv0, (ax2 + rv1) % 6, ax3])
T.writes(C[ax0, ax1, ax2, ax3])
with T.init():
C[ax0, ax1, ax2, ax3] = T.int8(-128)
C[ax0, ax1, ax2, ax3] = T.max(
C[ax0, ax1, ax2, ax3], B[ax0, ax1 + rv0, (ax2 + rv1) % 6, ax3]
)
sch = tvm.s_tir.Schedule(cascade_2_max_pool2d, debug_mask="all")
oi, _ = _tile_nd(sch, [1, 8, 4, 16], "C")
sch.compute_at(sch.get_sblock("B"), oi[-1])
sch.rolling_buffer(sch.get_sblock("B"), 0)
check_rolling_buffer(sch, cascade_2_max_pool2d, expected, check_run=True)
def test_cascade_max_pool2d_h_tiled():
@T.prim_func(s_tir=True)
def expected(A: T.Buffer((1, 12, 12, 16), "int8"), C: T.Buffer((1, 8, 8, 16), "int8")):
B = T.sblock_alloc_buffer([1, 6, 10, 16], dtype="int8")
for i0_0, i1_0, i2_0, i3_0 in T.grid(1, 2, 1, 1):
for ax0, ax1, ax2, ax3, ax4 in T.grid(6, 10, 16, 3, 3):
with T.sblock("B"):
T.where(i1_0 < 1 or 2 <= ax0)
ax0_1 = T.axis.spatial(1, 0)
ax1_1 = T.axis.opaque(10, i1_0 * 4 + ax0)
ax2_1 = T.axis.spatial(10, ax1)
ax3_1, rv0, rv1 = T.axis.remap("SRR", [ax2, ax3, ax4])
T.reads(A[ax0_1, ax1_1 + rv0, ax2_1 + rv1, ax3_1])
T.writes(B[ax0_1, ax1_1 % 6, ax2_1, ax3_1])
with T.init():
B[ax0_1, ax1_1 % 6, ax2_1, ax3_1] = T.int8(-128)
B[ax0_1, ax1_1 % 6, ax2_1, ax3_1] = T.max(
B[ax0_1, ax1_1 % 6, ax2_1, ax3_1], A[ax0_1, ax1_1 + rv0, ax2_1 + rv1, ax3_1]
)
for i0_1, i1_1, i2_1, i3_1, i4, i5 in T.grid(1, 4, 8, 16, 3, 3):
with T.sblock("C"):
ax0 = T.axis.spatial(1, i0_0 + i0_1)
ax1 = T.axis.opaque(8, i1_0 * 4 + i1_1)
ax2 = T.axis.spatial(8, i2_0 * 8 + i2_1)
ax3 = T.axis.spatial(16, i3_0 * 16 + i3_1)
rv0, rv1 = T.axis.remap("RR", [i4, i5])
T.reads(B[ax0, (ax1 + rv0) % 6, ax2 + rv1, ax3])
T.writes(C[ax0, ax1, ax2, ax3])
with T.init():
C[ax0, ax1, ax2, ax3] = T.int8(-128)
C[ax0, ax1, ax2, ax3] = T.max(
C[ax0, ax1, ax2, ax3], B[ax0, (ax1 + rv0) % 6, ax2 + rv1, ax3]
)
sch = tvm.s_tir.Schedule(cascade_2_max_pool2d, debug_mask="all")
io, _ = _tile_nd(sch, [1, 4, 8, 16], "C")
sch.compute_at(sch.get_sblock("B"), io[-1])
sch.rolling_buffer(sch.get_sblock("B"), 0)
check_rolling_buffer(sch, cascade_2_max_pool2d, expected, check_run=True)
def test_cascade_max_pool2d_h_w_c_tiled():
@T.prim_func(s_tir=True)
def expected(A: T.Buffer((1, 12, 12, 16), "int8"), C: T.Buffer((1, 8, 8, 16), "int8")):
B = T.sblock_alloc_buffer([1, 6, 10, 16], dtype="int8")
for i0_0, i1_0, i2_0, i3_0 in T.grid(1, 2, 2, 2):
for ax0, ax1, ax2, ax3, ax4 in T.grid(6, 6, 8, 3, 3):
with T.sblock("B"):
T.where((i1_0 < 1 or 2 <= ax0) and (i2_0 < 1 or 2 <= ax1))
ax0_1 = T.axis.spatial(1, 0)
ax1_1 = T.axis.opaque(10, i1_0 * 4 + ax0)
ax2_1 = T.axis.spatial(10, i2_0 * 4 + ax1)
ax3_1 = T.axis.spatial(16, i3_0 * 8 + ax2)
rv0, rv1 = T.axis.remap("RR", [ax3, ax4])
T.reads(A[ax0_1, ax1_1 + rv0, ax2_1 + rv1, ax3_1])
T.writes(B[ax0_1, ax1_1 % 6, ax2_1, ax3_1])
with T.init():
B[ax0_1, ax1_1 % 6, ax2_1, ax3_1] = T.int8(-128)
B[ax0_1, ax1_1 % 6, ax2_1, ax3_1] = T.max(
B[ax0_1, ax1_1 % 6, ax2_1, ax3_1], A[ax0_1, ax1_1 + rv0, ax2_1 + rv1, ax3_1]
)
for i0_1, i1_1, i2_1, i3_1, i4, i5 in T.grid(1, 4, 4, 8, 3, 3):
with T.sblock("C"):
ax0 = T.axis.spatial(1, i0_0 + i0_1)
ax1 = T.axis.opaque(8, i1_0 * 4 + i1_1)
ax2 = T.axis.spatial(8, i2_0 * 4 + i2_1)
ax3 = T.axis.spatial(16, i3_0 * 8 + i3_1)
rv0, rv1 = T.axis.remap("RR", [i4, i5])
T.reads(B[ax0, (ax1 + rv0) % 6, ax2 + rv1, ax3])
T.writes(C[ax0, ax1, ax2, ax3])
with T.init():
C[ax0, ax1, ax2, ax3] = T.int8(-128)
C[ax0, ax1, ax2, ax3] = T.max(
C[ax0, ax1, ax2, ax3], B[ax0, (ax1 + rv0) % 6, ax2 + rv1, ax3]
)
sch = tvm.s_tir.Schedule(cascade_2_max_pool2d, debug_mask="all")
io, _ = _tile_nd(sch, [1, 4, 4, 8], "C")
sch.compute_at(sch.get_sblock("B"), io[-1])
sch.rolling_buffer(sch.get_sblock("B"), 0)
check_rolling_buffer(sch, cascade_2_max_pool2d, expected, check_run=True)
def test_cascade_max_pool2d_non_perfect_tiled():
@T.prim_func(s_tir=True)
def expected(A: T.Buffer((1, 12, 12, 16), "int8"), C: T.Buffer((1, 8, 8, 16), "int8")) -> None:
B = T.sblock_alloc_buffer([1, 8, 10, 16], dtype="int8")
for i0_0, i1_0, i2_0, i3_0 in T.grid(1, 2, 2, 1):
for ax0, ax1, ax2, ax3, ax4 in T.grid(8, 8, 16, 3, 3):
with T.sblock("B"):
T.where(
i1_0 * 6 + ax0 < 10
and i2_0 * 6 + ax1 < 10
and (i1_0 < 1 or 2 <= ax0)
and (i2_0 < 1 or 2 <= ax1)
)
ax0_1 = T.axis.spatial(1, 0)
ax1_1 = T.axis.opaque(10, i1_0 * 6 + ax0)
ax2_1 = T.axis.spatial(10, i2_0 * 6 + ax1)
ax3_1, rv0, rv1 = T.axis.remap("SRR", [ax2, ax3, ax4])
T.reads(A[ax0_1, ax1_1 + rv0, ax2_1 + rv1, ax3_1])
T.writes(B[ax0_1, ax1_1 % 8, ax2_1, ax3_1])
with T.init():
B[ax0_1, ax1_1 % 8, ax2_1, ax3_1] = T.int8(-128)
B[ax0_1, ax1_1 % 8, ax2_1, ax3_1] = T.max(
B[ax0_1, ax1_1 % 8, ax2_1, ax3_1], A[ax0_1, ax1_1 + rv0, ax2_1 + rv1, ax3_1]
)
for i0_1, i1_1, i2_1, i3_1, i4, i5 in T.grid(1, 6, 6, 16, 3, 3):
with T.sblock("C"):
T.where(i1_0 * 6 + i1_1 < 8 and i2_0 * 6 + i2_1 < 8)
ax0 = T.axis.spatial(1, i0_0 + i0_1)
ax1 = T.axis.opaque(8, i1_0 * 6 + i1_1)
ax2 = T.axis.spatial(8, i2_0 * 6 + i2_1)
ax3 = T.axis.spatial(16, i3_0 * 16 + i3_1)
rv0, rv1 = T.axis.remap("RR", [i4, i5])
T.reads(B[ax0, (ax1 + rv0) % 8, ax2 + rv1, ax3])
T.writes(C[ax0, ax1, ax2, ax3])
with T.init():
C[ax0, ax1, ax2, ax3] = T.int8(-128)
C[ax0, ax1, ax2, ax3] = T.max(
C[ax0, ax1, ax2, ax3], B[ax0, (ax1 + rv0) % 8, ax2 + rv1, ax3]
)
sch = tvm.s_tir.Schedule(cascade_2_max_pool2d, debug_mask="all")
io, _ = _tile_nd(sch, [1, 6, 6, 16], "C")
sch.compute_at(sch.get_sblock("B"), io[-1])
sch.rolling_buffer(sch.get_sblock("B"), 0)
check_rolling_buffer(sch, cascade_2_max_pool2d, expected, check_run=True)
def test_cascade_3_max_pool2d_with_stride():
@T.prim_func(s_tir=True)
def expected(A: T.Buffer((1, 24, 24, 16), "int8"), C: T.Buffer((1, 8, 8, 16), "int8")) -> None:
B_0 = T.sblock_alloc_buffer([1, 13, 22, 16], dtype="int8")
B_1 = T.sblock_alloc_buffer([1, 6, 10, 16], dtype="int8")
for i0_0, i1_0, i2_0, i3_0 in T.grid(1, 2, 2, 1):
for ax0, ax1, ax2, ax3, ax4 in T.grid(13, 13, 16, 3, 3):
with T.sblock("B_0"):
T.where((i1_0 < 1 or 5 <= ax0) and (i2_0 < 1 or 5 <= ax1))
ax0_1 = T.axis.spatial(1, 0)
ax1_1 = T.axis.opaque(22, i1_0 * 8 + ax0)
ax2_1 = T.axis.spatial(22, i2_0 * 8 + ax1)
ax3_1, rv0, rv1 = T.axis.remap("SRR", [ax2, ax3, ax4])
T.reads(A[ax0_1, ax1_1 + rv0, ax2_1 + rv1, ax3_1])
T.writes(B_0[ax0_1, ax1_1 % 13, ax2_1, ax3_1])
with T.init():
B_0[ax0_1, ax1_1 % 13, ax2_1, ax3_1] = T.int8(-128)
B_0[ax0_1, ax1_1 % 13, ax2_1, ax3_1] = T.max(
B_0[ax0_1, ax1_1 % 13, ax2_1, ax3_1],
A[ax0_1, ax1_1 + rv0, ax2_1 + rv1, ax3_1],
)
for ax0, ax1, ax2, ax3, ax4 in T.grid(6, 6, 16, 3, 3):
with T.sblock("B_1"):
T.where((i1_0 < 1 or 2 <= ax0) and (i2_0 < 1 or 2 <= ax1))
ax0_2 = T.axis.spatial(1, 0)
ax1_2 = T.axis.opaque(10, i1_0 * 4 + ax0)
ax2_2 = T.axis.spatial(10, i2_0 * 4 + ax1)
ax3_2, rv0, rv1 = T.axis.remap("SRR", [ax2, ax3, ax4])
T.reads(B_0[ax0_2, (ax1_2 * 2 + rv0) % 13, ax2_2 * 2 + rv1, ax3_2])
T.writes(B_1[ax0_2, ax1_2 % 6, ax2_2, ax3_2])
with T.init():
B_1[ax0_2, ax1_2 % 6, ax2_2, ax3_2] = T.int8(-128)
B_1[ax0_2, ax1_2 % 6, ax2_2, ax3_2] = T.max(
B_1[ax0_2, ax1_2 % 6, ax2_2, ax3_2],
B_0[ax0_2, (ax1_2 * 2 + rv0) % 13, ax2_2 * 2 + rv1, ax3_2],
)
for i0_1, i1_1, i2_1, i3_1, i4, i5 in T.grid(1, 4, 4, 16, 3, 3):
with T.sblock("C"):
ax0_3 = T.axis.spatial(1, i0_0 + i0_1)
ax1_3 = T.axis.opaque(8, i1_0 * 4 + i1_1)
ax2_3 = T.axis.spatial(8, i2_0 * 4 + i2_1)
ax3_3 = T.axis.spatial(16, i3_0 * 16 + i3_1)
rv0, rv1 = T.axis.remap("RR", [i4, i5])
T.reads(B_1[ax0_3, (ax1_3 + rv0) % 6, ax2_3 + rv1, ax3_3])
T.writes(C[ax0_3, ax1_3, ax2_3, ax3_3])
with T.init():
C[ax0_3, ax1_3, ax2_3, ax3_3] = T.int8(-128)
C[ax0_3, ax1_3, ax2_3, ax3_3] = T.max(
C[ax0_3, ax1_3, ax2_3, ax3_3],
B_1[ax0_3, (ax1_3 + rv0) % 6, ax2_3 + rv1, ax3_3],
)
sch = tvm.s_tir.Schedule(cascade_3_max_pool2d_with_stride, debug_mask="all")
io, _ = _tile_nd(sch, [1, 4, 4, 16], "C")
sch.compute_at(sch.get_sblock("B_1"), io[-1])
sch.compute_at(sch.get_sblock("B_0"), io[-1])
sch.rolling_buffer(sch.get_sblock("B_0"), 0)
sch.rolling_buffer(sch.get_sblock("B_1"), 0)
check_rolling_buffer(sch, cascade_3_max_pool2d_with_stride, expected, check_run=True)
def test_upscale():
@T.prim_func(s_tir=True)
def before(A: T.Buffer((1, 16, 16, 16), "int8"), C: T.Buffer((1, 24, 24, 16), "int8")) -> None:
B = T.sblock_alloc_buffer([1, 14, 14, 16], dtype="int8")
for i0_0, i1_0, i2_0, i3_0 in T.grid(1, 5, 5, 1):
for ax0, ax1, ax2, ax3, ax4 in T.grid(5, 5, 16, 3, 3):
with T.sblock("B"):
T.where(i1_0 * 5 // 2 + ax0 < 14 and i2_0 * 5 // 2 + ax1 < 14)
ax0_1 = T.axis.spatial(1, 0)
ax1_1 = T.axis.spatial(14, i1_0 * 5 // 2 + ax0)
ax2_1 = T.axis.spatial(14, i2_0 * 5 // 2 + ax1)
ax3_1 = T.axis.spatial(16, ax2)
rv0, rv1 = T.axis.remap("RR", [ax3, ax4])
T.reads(A[ax0_1, ax1_1 + rv0, ax2_1 + rv1, ax3_1])
T.writes(B[ax0_1, ax1_1, ax2_1, ax3_1])
with T.init():
B[ax0_1, ax1_1, ax2_1, ax3_1] = T.int8(-128)
B[ax0_1, ax1_1, ax2_1, ax3_1] = T.max(
B[ax0_1, ax1_1, ax2_1, ax3_1], A[ax0_1, ax1_1 + rv0, ax2_1 + rv1, ax3_1]
)
for i0_1, i1_1, i2_1, i3_1, i4, i5 in T.grid(1, 5, 5, 16, 3, 3):
with T.sblock("C"):
T.where(i1_0 * 5 + i1_1 < 24 and i2_0 * 5 + i2_1 < 24)
ax0 = T.axis.spatial(1, i0_0 + i0_1)
ax1 = T.axis.spatial(24, i1_0 * 5 + i1_1)
ax2 = T.axis.spatial(24, i2_0 * 5 + i2_1)
ax3 = T.axis.spatial(16, i3_0 * 16 + i3_1)
rv0, rv1 = T.axis.remap("RR", [i4, i5])
T.reads(B[ax0, ax1 // 2 + rv0, ax2 // 2 + rv1, ax3])
T.writes(C[ax0, ax1, ax2, ax3])
with T.init():
C[ax0, ax1, ax2, ax3] = T.int8(-128)
C[ax0, ax1, ax2, ax3] = T.max(
C[ax0, ax1, ax2, ax3], B[ax0, ax1 // 2 + rv0, ax2 // 2 + rv1, ax3]
)
@T.prim_func(s_tir=True)
def expected(
A: T.Buffer((1, 16, 16, 16), "int8"), C: T.Buffer((1, 24, 24, 16), "int8")
) -> None:
B = T.sblock_alloc_buffer([1, 5, 14, 16], dtype="int8")
for i0_0, i1_0, i2_0, i3_0 in T.grid(1, 5, 5, 1):
for ax0, ax1, ax2, ax3, ax4 in T.grid(5, 5, 16, 3, 3):
with T.sblock("B"):
T.where(
i1_0 * 5 // 2 + ax0 < 14
and i2_0 * 5 // 2 + ax1 < 14
and (i1_0 < 1 or 2 <= ax0)
and (i2_0 < 1 or 2 <= ax1)
)
ax0_1 = T.axis.spatial(1, 0)
ax1_1 = T.axis.opaque(14, i1_0 * 5 // 2 + ax0)
ax2_1 = T.axis.spatial(14, i2_0 * 5 // 2 + ax1)
ax3_1 = T.axis.spatial(16, ax2)
rv0, rv1 = T.axis.remap("RR", [ax3, ax4])
T.reads(A[ax0_1, ax1_1 + rv0, ax2_1 + rv1, ax3_1])
T.writes(B[ax0_1, ax1_1 % 5, ax2_1, ax3_1])
with T.init():
B[ax0_1, ax1_1 % 5, ax2_1, ax3_1] = T.int8(-128)
B[ax0_1, ax1_1 % 5, ax2_1, ax3_1] = T.max(
B[ax0_1, ax1_1 % 5, ax2_1, ax3_1], A[ax0_1, ax1_1 + rv0, ax2_1 + rv1, ax3_1]
)
for i0_1, i1_1, i2_1, i3_1, i4, i5 in T.grid(1, 5, 5, 16, 3, 3):
with T.sblock("C"):
T.where(i1_0 * 5 + i1_1 < 24 and i2_0 * 5 + i2_1 < 24)
ax0 = T.axis.spatial(1, i0_0 + i0_1)
ax1 = T.axis.opaque(24, i1_0 * 5 + i1_1)
ax2 = T.axis.spatial(24, i2_0 * 5 + i2_1)
ax3 = T.axis.spatial(16, i3_0 * 16 + i3_1)
rv0, rv1 = T.axis.remap("RR", [i4, i5])
T.reads(B[ax0, (ax1 // 2 + rv0) % 5, ax2 // 2 + rv1, ax3])
T.writes(C[ax0, ax1, ax2, ax3])
with T.init():
C[ax0, ax1, ax2, ax3] = T.int8(-128)
C[ax0, ax1, ax2, ax3] = T.max(
C[ax0, ax1, ax2, ax3], B[ax0, (ax1 // 2 + rv0) % 5, ax2 // 2 + rv1, ax3]
)
sch = tvm.s_tir.Schedule(before, debug_mask="all")
sch.rolling_buffer(sch.get_sblock("B"), 0)
check_rolling_buffer(sch, before, expected, check_run=True)
def test_fail_rolling_buffer_multi_writers():
@T.prim_func(s_tir=True)
def func_multi_writers(
A: T.Buffer((1, 12, 12, 16), "int8"), C: T.Buffer((1, 12, 12, 16), "int8")
):
B = T.sblock_alloc_buffer([1, 12, 12, 16], dtype="int8")
for i0, i1, i2, i3 in T.grid(1, 3, 3, 1):
for ax0, ax1, ax2 in T.grid(6, 6, 16):
with T.sblock("B_writer_0"):
ax0_1 = T.axis.spatial(1, i0)
ax1_1 = T.axis.spatial(12, i1 * 4 + ax0)
ax2_1 = T.axis.spatial(12, i2 * 4 + ax1)
ax3_1 = T.axis.spatial(16, ax2)
with T.init():
B[ax0_1, ax1_1, ax2_1, ax3_1] = T.int8(-128)
B[ax0_1, ax1_1, ax2_1, ax3_1] = A[ax0_1, ax1_1, ax2_1, ax3_1] + T.int8(1)
for ax0, ax1, ax2 in T.grid(6, 6, 16):
with T.sblock("B_writer_1"):
ax0_2 = T.axis.spatial(1, i0)
ax1_2 = T.axis.spatial(12, i1 * 4 + ax0)
ax2_2 = T.axis.spatial(12, i2 * 4 + ax1)
ax3_2 = T.axis.spatial(16, ax2)
with T.init():
B[ax0_2, ax1_2, ax2_2, ax3_2] = T.int8(-128)
B[ax0_2, ax1_2, ax2_2, ax3_2] = B[ax0_2, ax1_2, ax2_2, ax3_2] + A[
ax0_2, ax1_2, ax2_2, ax3_2
] * T.int8(2)
for ax0, ax1, ax2, ax3, ax4, ax5 in T.grid(1, 4, 4, 16, 3, 3):
with T.sblock("C"):
ax0_3 = T.axis.spatial(1, i0 + ax0)
ax1_3 = T.axis.spatial(12, i1 * 4 + ax1)
ax2_3 = T.axis.spatial(12, i2 * 4 + ax2)
ax3_3 = T.axis.spatial(16, i3 * 16 + ax3)
rv0, rv1 = T.axis.remap("RR", [ax4, ax5])
with T.init():
C[ax0_3, ax1_3, ax2_3, ax3_3] = T.int8(-128)
C[ax0_3, ax1_3, ax2_3, ax3_3] = T.max(
C[ax0_3, ax1_3, ax2_3, ax3_3], B[ax0_3, ax1_3 + rv0, ax2_3 + rv1, ax3_3]
)
sch = tvm.s_tir.Schedule(func_multi_writers, debug_mask="all")
with pytest.raises(tvm.s_tir.ScheduleError):
sch.rolling_buffer(sch.get_sblock("B_writer_0"), 0)
def test_fail_rolling_buffer_not_match():
@T.prim_func(s_tir=True)
def func_non_overlap(
A: T.Buffer((1, 12, 12, 16), "int8"), C: T.Buffer((1, 12, 12, 16), "int8")
):
B = T.sblock_alloc_buffer([1, 12, 12, 16], dtype="int8")
for i0_0, i1_0, i2_0, i3_0 in T.grid(1, 3, 3, 1):
for ax0, ax1, ax2 in T.grid(4, 4, 16):
with T.sblock("B"):
ax0_1 = T.axis.spatial(1, 0)
ax1_1 = T.axis.spatial(12, i1_0 * 4 + ax0)
ax2_1 = T.axis.spatial(12, i2_0 * 4 + ax1)
ax3 = T.axis.spatial(16, ax2)
T.reads(A[ax0_1, ax1_1, ax2_1, ax3])
T.writes(B[ax0_1, ax1_1, ax2_1, ax3])
with T.init():
B[ax0_1, ax1_1, ax2_1, ax3] = T.int8(-128)
B[ax0_1, ax1_1, ax2_1, ax3] = A[ax0_1, ax1_1, ax2_1, ax3]
for i0_1, i1_1, i2_1, i3_1, i4, i5 in T.grid(1, 4, 4, 16, 1, 1):
with T.sblock("C"):
ax0 = T.axis.spatial(1, i0_0 + i0_1)
ax1 = T.axis.spatial(12, i1_0 * 4 + i1_1)
ax2 = T.axis.spatial(12, i2_0 * 4 + i2_1)
ax3 = T.axis.spatial(16, i3_0 * 16 + i3_1)
rv0, rv1 = T.axis.remap("RR", [i4, i5])
T.reads(B[ax0, ax1 + rv0, ax2 + rv1, ax3])
T.writes(C[ax0, ax1, ax2, ax3])
with T.init():
C[ax0, ax1, ax2, ax3] = T.int8(-128)
C[ax0, ax1, ax2, ax3] = T.max(
C[ax0, ax1, ax2, ax3], B[ax0, ax1 + rv0, ax2 + rv1, ax3]
)
sch = tvm.s_tir.Schedule(func_non_overlap, debug_mask="all")
with pytest.raises(tvm.s_tir.ScheduleError):
sch.rolling_buffer(sch.get_sblock("B"), 0)
def test_fail_rolling_buffer_injection_invalid():
sch = tvm.s_tir.Schedule(cascade_2_max_pool2d, debug_mask="all")
# Block B is not compute_at to Block C, so rolling_buffer injection is invalid.
_, _ = _tile_nd(sch, [1, 4, 8, 16], "C")
_, _ = _tile_nd(sch, [1, 4, 8, 16], "B")
with pytest.raises(tvm.s_tir.ScheduleError):
sch.rolling_buffer(sch.get_sblock("B"), 0)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,244 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ruff: noqa: F401
import sys
from collections import defaultdict
import numpy
import pytest
import tvm.testing
from tvm import tirx
from tvm.s_tir.schedule.testing import verify_trace_roundtrip
from tvm.script import tirx as T
# pylint: disable=no-member,invalid-name,unused-variable
@T.prim_func(s_tir=True)
def elementwise(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 257, 1470))
B = T.match_buffer(b, (128, 257, 1470))
for i, j, k in T.grid(128, 257, 1470):
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def tiled_conv2d_with_padding(
inputs: T.Buffer((1, 224, 224, 3), "float32"),
weight: T.Buffer((7, 7, 3, 64), "float32"),
conv2d_nhwc: T.Buffer((1, 112, 112, 64), "float32"),
) -> None:
PadInput = T.sblock_alloc_buffer([1, 230, 230, 3], dtype="float32")
for i0, i1, i2, i3 in T.grid(1, 230, 230, 3):
with T.sblock("PadInput"):
i0_1, i1_1, i2_1, i3_1 = T.axis.remap("SSSS", [i0, i1, i2, i3])
T.reads(inputs[i0_1, i1_1 - 3, i2_1 - 3, i3_1])
T.writes(PadInput[i0_1, i1_1, i2_1, i3_1])
PadInput[i0_1, i1_1, i2_1, i3_1] = T.if_then_else(
3 <= i1_1 and i1_1 < 227 and 3 <= i2_1 and i2_1 < 227,
inputs[i0_1, i1_1 - 3, i2_1 - 3, i3_1],
T.float32(0),
dtype="float32",
)
for (
i0_0,
i1_0,
i2_0,
i3_0,
i0_1_1,
i1_1_1,
i2_1_1,
i3_1_1,
i4_0,
i5_0,
i6_0,
i0_2,
i1_2,
i2_2,
i3_2,
i4_1,
i5_1,
i6_1,
i0_3,
i1_3,
i2_3,
i3_3,
) in T.grid(1, 1, 4, 1, 1, 2, 4, 1, 7, 7, 1, 1, 1, 1, 1, 1, 1, 3, 1, 56, 7, 64):
with T.sblock("conv2d_nhwc"):
n = T.axis.spatial(1, 0)
h = T.axis.spatial(112, i1_1_1 * 56 + i1_3)
w = T.axis.spatial(112, i2_0 * 28 + i2_1_1 * 7 + i2_3)
co, rh, rw, rc = T.axis.remap("SRRR", [i3_3, i4_0, i5_0, i6_1])
T.reads(
conv2d_nhwc[n, h, w, co],
PadInput[n, h * 2 + rh, w * 2 + rw, co // 64 * 3 + rc],
weight[rh, rw, rc, co],
)
T.writes(conv2d_nhwc[n, h, w, co])
with T.init():
conv2d_nhwc[n, h, w, co] = T.float32(0)
conv2d_nhwc[n, h, w, co] = (
conv2d_nhwc[n, h, w, co]
+ PadInput[n, h * 2 + rh, w * 2 + rw, co // 64 * 3 + rc] * weight[rh, rw, rc, co]
)
# pylint: enable=no-member,invalid-name,unused-variable
def test_sample_categorical():
"""Test sample categorical sampling function"""
n = 1000
sch = tvm.s_tir.Schedule(elementwise, seed=42, debug_mask="all")
counter = defaultdict(int)
candidates = [5, 2, 7, 1]
probs = [0.15, 0.55, 0.05, 0.25]
for _ in range(n):
v = sch.get(sch.sample_categorical(candidates, probs))
counter[v] += 1
for i, prob in enumerate(probs):
assert (prob - 0.07) * n <= counter[candidates[i]] <= (prob + 0.07) * n
verify_trace_roundtrip(sch, mod=elementwise)
def test_sample_categorical_copy():
"""Check the random variable sampling results after schedule copy"""
n = 100
sch = tvm.s_tir.Schedule(elementwise, seed=42, debug_mask="all")
candidates = [1, 2, 3, 4]
probs = [0.1, 0.2, 0.3, 0.4]
rv_decisions = []
for _ in range(n):
rv = sch.sample_categorical(candidates, probs) # pylint: disable=invalid-name
rv_decisions.append((rv, sch.get(rv)))
sch_copy = sch.copy()
for rv, decision in rv_decisions: # pylint: disable=invalid-name
decision_copy = sch_copy.get(rv)
assert int(decision) == int(decision_copy)
def test_sample_categorical_serialize():
"""Check the random variable sampling results after schedule serialization"""
n = 100
sch = tvm.s_tir.Schedule(elementwise, seed=42, debug_mask="all")
candidates = [5, 6, 7, 8]
probs = [0.23, 0.19, 0.37, 0.21]
decisions = []
for _ in range(n):
rv = sch.get(sch.sample_categorical(candidates, probs)) # pylint: disable=invalid-name
decisions.append(rv)
new_sch = verify_trace_roundtrip(sch, mod=elementwise)
for i, new_inst in enumerate(new_sch.trace.insts):
assert decisions[i] == candidates[new_sch.trace.decisions[new_inst]]
def test_sample_perfect_tile_power_of_two():
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
i, _, _ = sch.get_loops(sch.get_sblock("B"))
factors = sch.sample_perfect_tile(i, n=4)
factors = [sch.get(i) for i in factors]
prod = factors[0] * factors[1] * factors[2] * factors[3]
assert prod == 128
verify_trace_roundtrip(sch, mod=elementwise)
def test_sample_perfect_tile_prime():
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
_, i, _ = sch.get_loops(sch.get_sblock("B"))
factors = sch.sample_perfect_tile(i, n=4)
factors = [sch.get(i) for i in factors]
prod = factors[0] * factors[1] * factors[2] * factors[3]
assert prod == 257
verify_trace_roundtrip(sch, mod=elementwise)
def test_sample_perfect_tile_composite():
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
_, _, i = sch.get_loops(sch.get_sblock("B"))
factors = sch.sample_perfect_tile(i, n=4)
factors = [sch.get(i) for i in factors]
prod = factors[0] * factors[1] * factors[2] * factors[3]
assert prod == 1470
verify_trace_roundtrip(sch, mod=elementwise)
use_sugared_block = tvm.testing.parameter(by_dict={"block_obj": False, "block_name": True})
def test_sample_compute_location(use_sugared_block):
n = 100
sch = tvm.s_tir.Schedule(tiled_conv2d_with_padding, seed=42, debug_mask="all")
if use_sugared_block:
pad_input = "PadInput"
else:
pad_input = sch.get_sblock("PadInput")
decision_dict = dict()
for _ in range(n):
_ = sch.sample_compute_location(pad_input) # pylint: disable=invalid-name
decision = sch.trace.decisions[sch.trace.insts[-1]]
decision_dict[decision] = decision_dict[decision] + 1 if decision in decision_dict else 1
n_candidates = 8
expected_rate = 1.0 / n_candidates
for _, cnt in decision_dict.items():
numpy.testing.assert_allclose(expected_rate, cnt / n, atol=0.04)
def test_sample_perfect_tile_after_copy():
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
sch_copy = sch.copy()
_, _, i = sch.get_loops(sch.get_sblock("B"))
sch.sample_perfect_tile(i, n=4)
_, _, i = sch_copy.get_loops(sch_copy.get_sblock("B"))
# Hangs if ForkSeed is not invoked when copying a schedule
sch_copy.sample_perfect_tile(i, n=4)
def test_sample_perfect_tile_on_dynamic_loops():
"""Currently dynamic loop is trivially tiled"""
@T.prim_func(s_tir=True)
def workload(a: T.handle) -> None:
n = T.int32()
A = T.match_buffer(a, (n, 1024))
for i, j in T.grid(n, 1024):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
A[vi, vj] = 1.0
sch = tvm.s_tir.Schedule(workload, debug_mask="all")
di, si = sch.get_loops(sch.get_sblock("B"))
factors = sch.sample_perfect_tile(si, n=4)
factors = [sch.get(i) for i in factors]
prod = factors[0] * factors[1] * factors[2] * factors[3]
assert prod == 1024
factors = sch.sample_perfect_tile(di, n=4)
assert factors[0] is None
factors = [sch.get(i) for i in factors[1:]]
prod = factors[0] * factors[1] * factors[2]
assert prod == 1
verify_trace_roundtrip(sch, mod=workload)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,208 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: E501, F401
import pytest
import tvm
import tvm.testing
from tvm import tirx
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.script import ir as I
from tvm.script import tirx as T
from tvm.tirx import IndexMap
# fmt: off
# pylint: disable=no-member,invalid-name,unused-variable,unexpected-keyword-arg
@T.prim_func(s_tir=True)
def element_wise(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")) -> None:
B = T.sblock_alloc_buffer((128, 128), dtype="float32")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def element_wise_set_axis_separator(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")) -> None:
B = T.sblock_alloc_buffer([128, 128], dtype="float32", axis_separators=[1])
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * T.float32(2)
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + T.float32(1)
@T.prim_func(s_tir=True)
def element_wise_set_axis_separator_input_buffer(A: T.Buffer(shape=(128, 128), dtype="float32", axis_separators=(1,)), C: T.Buffer((128, 128), "float32")) -> None:
B = T.sblock_alloc_buffer([128, 128], dtype="float32")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * T.float32(2)
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + T.float32(1)
@T.prim_func(s_tir=True)
def element_wise_subregion_match(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")) -> None:
B = T.sblock_alloc_buffer((128, 128), dtype="float32")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B_subregion0 = T.match_buffer(B[vi, vj], [], offset_factor=1)
B_subregion0[()] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
B_subregion1 = T.match_buffer(B[vi, vj], [], offset_factor=1)
C[vi, vj] = B_subregion1[()] + 1.0
@T.prim_func(s_tir=True)
def element_wise_subregion_match_set_axis_separator(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")) -> None:
B = T.sblock_alloc_buffer([128, 128], dtype="float32", axis_separators=[1])
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B_subregion0 = T.match_buffer(B[vi, vj], [], dtype="float32", offset_factor=1, axis_separators=[0])
B_subregion0[()] = A[vi, vj] * T.float32(2)
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
B_subregion1 = T.match_buffer(B[vi, vj], [], dtype="float32", offset_factor=1, axis_separators=[0])
C[vi, vj] = B_subregion1[()] + T.float32(1)
# pylint: enable=no-member,invalid-name,unused-variable,unexpected-keyword-arg
argument_style = tvm.testing.parameter('set_axis_separators',
'transform_layout_named',
'transform_layout_buffer_object',
)
def test_set_axis_separator(argument_style):
func = element_wise
s = tvm.s_tir.Schedule(func, debug_mask='all')
if argument_style=='set_axis_separators':
s.set_axis_separator(s.get_sblock("B"), ("write",0), [1])
elif argument_style=='transform_layout_named':
s.transform_layout(block='B', buffer='B', index_map=lambda i,j: [i,IndexMap.AXIS_SEPARATOR,j])
elif argument_style =='transform_layout_buffer_object':
B = s.get(s.get_sblock('B')).writes[0].buffer
s.transform_layout(block='B', buffer=B, index_map=lambda i,j: [i,IndexMap.AXIS_SEPARATOR,j])
else:
raise ValueError(f'Unexpected argument_style: {argument_style}')
assert_structural_equal_ignore_global_symbol(element_wise_set_axis_separator, s.mod["main"])
verify_trace_roundtrip(sch=s, mod=func)
def test_set_scope_fail_on_index_out_of_bound():
func = element_wise
s = tvm.s_tir.Schedule(func, debug_mask='all')
with pytest.raises(AssertionError):
s.set_axis_separator(s.get_sblock("B"), ("write",1),[1])
with pytest.raises(AssertionError):
s.set_axis_separator(s.get_sblock("B"), ("read",-1),[1])
def test_set_axis_separator_input_buffer(argument_style):
func = element_wise
s = tvm.s_tir.Schedule(func, debug_mask='all')
if argument_style=='set_axis_separators':
s.set_axis_separator(s.get_sblock("B"), ("read",0), [1])
elif argument_style=='transform_layout_named':
s.transform_layout(block='B', buffer='A', index_map=lambda i,j: [i,IndexMap.AXIS_SEPARATOR,j])
elif argument_style =='transform_layout_buffer_object':
A = s.get(s.get_sblock('B')).reads[0].buffer
s.transform_layout(block='B', buffer=A, index_map=lambda i,j: [i,IndexMap.AXIS_SEPARATOR,j])
else:
raise ValueError(f'Unexpected argument_style: {argument_style}')
assert_structural_equal_ignore_global_symbol(element_wise_set_axis_separator_input_buffer, s.mod["main"])
verify_trace_roundtrip(sch=s, mod=func)
def test_set_axis_separator_subregion(argument_style):
func = element_wise_subregion_match
s = tvm.s_tir.Schedule(func, debug_mask='all')
if argument_style=='set_axis_separators':
s.set_axis_separator(s.get_sblock("B"), ("write",0), [1])
elif argument_style=='transform_layout_named':
s.transform_layout(block='B', buffer='B', index_map=lambda i,j: [i,IndexMap.AXIS_SEPARATOR,j])
elif argument_style =='transform_layout_buffer_object':
B = s.get(s.get_sblock('B')).writes[0].buffer
s.transform_layout(block='B', buffer=B, index_map=lambda i,j: [i,IndexMap.AXIS_SEPARATOR,j])
else:
raise ValueError(f'Unexpected argument_style: {argument_style}')
assert_structural_equal_ignore_global_symbol(element_wise_subregion_match_set_axis_separator, s.mod["main"])
verify_trace_roundtrip(sch=s, mod=func)
def test_indexed_lookup():
@I.ir_module(s_tir=True)
class Before:
@T.prim_func(s_tir=True)
def main():
A = T.sblock_alloc_buffer([4,4], dtype="int32")
B = T.sblock_alloc_buffer([1,1], dtype="int32")
for j in T.serial(4):
with T.sblock('block'):
A[B[0,0],j] = 0
@I.ir_module(s_tir=True)
class Expected:
@T.prim_func(s_tir=True)
def main():
A = T.sblock_alloc_buffer([4,4], dtype="int32")
B = T.sblock_alloc_buffer([1,1], dtype="int32", axis_separators=[1])
for j in T.serial(4):
with T.sblock('block'):
A[B[0,0],j] = 0
sch = tvm.s_tir.Schedule(Before)
sch.set_axis_separator('block', 'B', [1])
After = sch.mod
tvm.ir.assert_structural_equal(After, Expected)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,130 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: E501, F401
import pytest
import tvm
import tvm.testing
from tvm import tirx
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.script import tirx as T
# fmt: off
# pylint: disable=no-member,invalid-name,unused-variable,unexpected-keyword-arg
@T.prim_func(s_tir=True)
def element_wise(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")) -> None:
B = T.sblock_alloc_buffer((128, 128), dtype="float32")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def element_wise_set_dtype(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")):
B = T.sblock_alloc_buffer((128, 128), "float16")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(A[vi, vj])
T.writes(B[vi, vj])
B[vi, vj] = T.cast(A[vi, vj] * 2.0, "float16")
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(B[vi, vj])
T.writes(C[vi, vj])
C[vi, vj] = T.cast(B[vi, vj], "float32") + 1.0
@T.prim_func(s_tir=True)
def element_wise_subregion_match(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")) -> None:
B = T.sblock_alloc_buffer((128, 128), dtype="float32")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B_subregion0 = T.match_buffer(B[vi, vj], [], offset_factor=1)
B_subregion0[()] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
B_subregion1 = T.match_buffer(B[vi, vj], [], offset_factor=1)
C[vi, vj] = B_subregion1[()] + 1.0
@T.prim_func(s_tir=True)
def element_wise_subregion_match_set_dtype(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")) -> None:
B = T.sblock_alloc_buffer((128, 128), "float16")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(A[vi, vj])
T.writes(B[vi, vj])
B_subregion0 = T.match_buffer(B[vi, vj], (), "float16", offset_factor=1)
B_subregion0[()] = T.cast(A[vi, vj] * 2.0, "float16")
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads(B[vi, vj])
T.writes(C[vi, vj])
B_subregion1 = T.match_buffer(B[vi, vj], (), "float16", offset_factor=1)
C[vi, vj] = T.cast(B_subregion1[()], "float32") + 1.0
use_block_name = tvm.testing.parameter(by_dict={"block_obj": False, "block_name": True})
def test_set_dtype(use_block_name):
func = element_wise
sch = tvm.s_tir.Schedule(func, debug_mask="all")
sch.unsafe_set_dtype("B" if use_block_name else sch.get_sblock("B"), 0, "float16")
assert_structural_equal_ignore_global_symbol(element_wise_set_dtype, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=func)
def test_set_dtype_fail_on_output_buffer(use_block_name):
func = element_wise
sch = tvm.s_tir.Schedule(func, debug_mask='all')
with pytest.raises(tvm.s_tir.ScheduleError):
sch.unsafe_set_dtype('C' if use_block_name else sch.get_sblock("C"), 0, "float16")
def test_set_dtype_fail_on_index_out_of_bound():
func = element_wise
sch = tvm.s_tir.Schedule(func, debug_mask='all')
with pytest.raises(tvm.s_tir.ScheduleError):
sch.unsafe_set_dtype(sch.get_sblock("B"), 1, "float64")
with pytest.raises(tvm.s_tir.ScheduleError):
sch.unsafe_set_dtype(sch.get_sblock("B"), -1, "float64")
def test_set_dtype_subregion():
func = element_wise_subregion_match
sch = tvm.s_tir.Schedule(func, debug_mask='all')
sch.unsafe_set_dtype(sch.get_sblock("B"), 0, "float16")
assert_structural_equal_ignore_global_symbol(element_wise_subregion_match_set_dtype, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=func)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,138 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: E501, F401
import pytest
import tvm
import tvm.testing
from tvm import tirx
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.script import tirx as T
# fmt: off
# pylint: disable=no-member,invalid-name,unused-variable,unexpected-keyword-arg
@T.prim_func(s_tir=True)
def element_wise(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")) -> None:
B = T.sblock_alloc_buffer((128, 128), dtype="float32")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def element_wise_set_scope(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")) -> None:
B_shared = T.sblock_alloc_buffer([128, 128], dtype="float32", scope="shared")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B_shared[vi, vj] = A[vi, vj] * T.float32(2)
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B_shared[vi, vj] + T.float32(1)
@T.prim_func(s_tir=True)
def element_wise_subregion_match(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")) -> None:
B = T.sblock_alloc_buffer((128, 128), dtype="float32")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B_subregion0 = T.match_buffer(B[vi, vj], [], offset_factor=1)
B_subregion0[()] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
B_subregion1 = T.match_buffer(B[vi, vj], [], offset_factor=1)
C[vi, vj] = B_subregion1[()] + 1.0
@T.prim_func(s_tir=True)
def element_wise_subregion_match_set_scope(A: T.Buffer((128, 128), "float32"), C: T.Buffer((128, 128), "float32")) -> None:
B_shared = T.sblock_alloc_buffer([128, 128], dtype="float32", scope="shared")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B_subregion0_shared = T.match_buffer(B_shared[vi, vj], [], dtype="float32", scope="shared", offset_factor=1)
B_subregion0_shared[()] = A[vi, vj] * T.float32(2)
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
B_subregion1_shared = T.match_buffer(B_shared[vi, vj], [], dtype="float32", scope="shared", offset_factor=1)
C[vi, vj] = B_subregion1_shared[()] + T.float32(1)
# pylint: enable=no-member,invalid-name,unused-variable,unexpected-keyword-arg
use_block_name = tvm.testing.parameter(by_dict={"block_obj": False, "block_name": True})
use_buffer_name = tvm.testing.parameter(by_dict={"buffer_index": False, "buffer_name": True})
def test_set_scope(use_block_name, use_buffer_name):
func = element_wise
s = tvm.s_tir.Schedule(func, debug_mask='all')
s.set_scope('B' if use_block_name else s.get_sblock("B"), 'B' if use_buffer_name else 0, "shared")
assert_structural_equal_ignore_global_symbol(element_wise_set_scope, s.mod["main"])
verify_trace_roundtrip(sch=s, mod=func)
def test_set_scope_fail_on_output_buffer(use_block_name, use_buffer_name):
func = element_wise
s = tvm.s_tir.Schedule(func, debug_mask='all')
with pytest.raises(tvm.s_tir.ScheduleError):
s.set_scope('C' if use_block_name else s.get_sblock("C"), 'C' if use_buffer_name else 0, "shared")
def test_set_scope_fail_on_index_out_of_bound():
func = element_wise
s = tvm.s_tir.Schedule(func, debug_mask='all')
with pytest.raises(tvm.s_tir.ScheduleError):
s.set_scope(s.get_sblock("B"), 1, "shared")
with pytest.raises(tvm.s_tir.ScheduleError):
s.set_scope(s.get_sblock("B"), -1, "shared")
def test_set_scope_fail_on_invalid_scope():
func = element_wise
s = tvm.s_tir.Schedule(func, debug_mask='all')
with pytest.raises(tvm.s_tir.ScheduleError):
s.set_scope(s.get_sblock("B"), 0, "test_scope")
def test_set_scope_subregion():
func = element_wise_subregion_match
s = tvm.s_tir.Schedule(func, debug_mask='all')
s.set_scope(s.get_sblock("B"), 0, "shared")
assert_structural_equal_ignore_global_symbol(element_wise_subregion_match_set_scope, s.mod["main"])
verify_trace_roundtrip(sch=s, mod=func)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,748 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: F401, F841
import pytest
import tvm
import tvm.testing
from tvm import te, tirx
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.script import tirx as T
from tvm.tirx.expr import IntImm
# pylint: disable=no-member,invalid-name,unused-variable
@T.prim_func(s_tir=True)
def elementwise(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
B = T.match_buffer(b, (128, 128, 128))
for i, j, k in T.grid(128, 128, 128):
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_dependent_loops(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
B = T.match_buffer(b, (128, 128, 128))
for i in T.serial(0, 128):
for j, k in T.grid(i, 128):
with T.sblock("B"):
vi = T.axis.S(128, i)
vj = T.axis.S(i, j)
vk = T.axis.S(128, k)
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_symbolic(a: T.handle, b: T.handle, n: T.int32) -> None:
A = T.match_buffer(a, (128, 128, n))
B = T.match_buffer(b, (128, 128, n))
for i, j, k in T.grid(128, 128, n):
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_symbolic_fused(a: T.handle, b: T.handle, n: T.int32) -> None:
A = T.match_buffer(a, (128, 128, n))
B = T.match_buffer(b, (128, 128, n))
for i_j_k_fused in T.serial(0, (n * 16384)):
with T.sblock("B"):
vi = T.axis.S(128, T.floordiv(i_j_k_fused, n * 128))
vj = T.axis.S(128, T.floordiv(T.floormod(i_j_k_fused, n * 128), n))
vk = T.axis.S(n, T.floormod(i_j_k_fused, n))
T.reads([A[vi, vj, vk]])
T.writes([B[vi, vj, vk]])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_symbolic_split(a: T.handle, b: T.handle, n: T.int32) -> None:
A = T.match_buffer(a, (128, 128, n))
B = T.match_buffer(b, (128, 128, n))
for i, j, k0, k1 in T.grid(128, 128, 10, T.floordiv((n + 9), 10)):
with T.sblock("B"):
T.where(((k0 * T.floordiv((n + 9), 10)) + k1) < n)
vi, vj = T.axis.remap("SS", [i, j])
vk = T.axis.S(n, k0 * T.floordiv(n + 9, 10) + k1)
T.reads([A[vi, vj, vk]])
T.writes([B[vi, vj, vk]])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_with_seq(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
B = T.match_buffer(b, (128, 128, 128))
C = T.sblock_alloc_buffer((128, 128, 128))
for i, j in T.grid(128, 128):
for k in T.serial(0, 128):
with T.sblock("C"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
C[vi, vj, vk] = A[vi, vj, vk] * 2.0
for k in T.serial(0, 128):
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
B[vi, vj, vk] = C[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_with_anno(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
B = T.match_buffer(b, (128, 128, 128))
for i, j in T.grid(128, 128):
for k in T.serial(0, 128, annotations={"useless_annotation": True}):
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
T.reads([A[vi, vj, vk]])
T.writes([B[vi, vj, vk]])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_with_thread_binding(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
B = T.match_buffer(b, (128, 128, 128))
for i, j in T.grid(128, 128):
for k in T.thread_binding(0, 128, thread="threadIdx.x"):
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
T.reads([A[vi, vj, vk]])
T.writes([B[vi, vj, vk]])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_with_starting_point(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
B = T.match_buffer(b, (128, 128, 128))
for i, j in T.grid(128, 128):
for k in T.serial(10, 128):
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
T.reads([A[vi, vj, vk]])
T.writes([B[vi, vj, vk]])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_with_opaque_block(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
B = T.match_buffer(b, (128, 128, 128))
for i, j, k in T.grid(128, 128, 128):
with T.sblock("opaque"):
T.reads([A[i, j, k]])
T.writes([B[i, j, k]])
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
T.reads([A[vi, vj, vk]])
T.writes([B[vi, vj, vk]])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_fused(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128))
B = T.match_buffer(b, (128, 128, 128))
for fused in T.serial(0, 2097152):
with T.sblock("B"):
vi = T.axis.S(128, T.floordiv(fused, 16384))
vj = T.axis.S(128, T.floordiv(T.floormod(fused, 16384), 128))
vk = T.axis.S(128, T.floormod(fused, 128))
T.reads([A[vi, vj, vk]])
T.writes([B[vi, vj, vk]])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_split_case0(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [128, 128, 128])
B = T.match_buffer(b, [128, 128, 128])
for i1, i2, i3, j1, j2, k1, k2 in T.grid(2, 1, 64, 4, 32, 16, 8):
with T.sblock("B"):
vi = T.axis.S(128, i1 * 64 + i2 * 64 + i3)
vj = T.axis.S(128, j1 * 32 + j2)
vk = T.axis.S(128, k1 * 8 + k2)
T.reads([A[vi, vj, vk]])
T.writes([B[vi, vj, vk]])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_split_case1(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [128, 128, 128])
B = T.match_buffer(b, [128, 128, 128])
for i1, i2, i3, j1, j2, j3, k1, k2, k3 in T.grid(2, 1, 64, 2, 1, 64, 2, 1, 64):
with T.sblock("B"):
vi = T.axis.S(128, i1 * 64 + i2 * 64 + i3)
vj = T.axis.S(128, j1 * 64 + j2 * 64 + j3)
vk = T.axis.S(128, k1 * 64 + k2 * 64 + k3)
T.reads([A[vi, vj, vk]])
T.writes([B[vi, vj, vk]])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_split_with_predicate(a: T.handle, b: T.handle) -> None:
B = T.match_buffer(b, [128, 128, 128])
A = T.match_buffer(a, [128, 128, 128])
for i0, i1, i2, j0, j1, k0, k1 in T.grid(1000, 2, 3, 1, 129, 3, 43):
with T.sblock("B"):
vi = T.axis.S(128, i0 * 6 + i1 * 3 + i2)
vj = T.axis.S(128, j0 * 129 + j1)
vk = T.axis.S(128, k0 * 43 + k1)
T.where((i0 * 2 + i1) * 3 + i2 < 128 and j0 * 129 + j1 < 128 and k0 * 43 + k1 < 128)
T.reads([A[vi, vj, vk]])
T.writes([B[vi, vj, vk]])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_fuse_with_opaque_block(a: T.handle, b: T.handle) -> None:
B = T.match_buffer(b, [128, 128, 128])
A = T.match_buffer(a, [128, 128, 128])
for i_j_k_fused in T.serial(0, 2097152):
with T.sblock("opaque"):
T.reads(
[
A[
T.floordiv(i_j_k_fused, 16384),
T.floordiv(T.floormod(i_j_k_fused, 16384), 128),
T.floormod(i_j_k_fused, 128),
]
]
)
T.writes(
[
B[
T.floordiv(i_j_k_fused, 16384),
T.floordiv(T.floormod(i_j_k_fused, 16384), 128),
T.floormod(i_j_k_fused, 128),
]
]
)
with T.sblock("B"):
vi = T.axis.S(128, T.floordiv(i_j_k_fused, 16384))
vj = T.axis.S(128, T.floordiv(T.floormod(i_j_k_fused, 16384), 128))
vk = T.axis.S(128, T.floormod(i_j_k_fused, 128))
T.reads([A[vi, vj, vk]])
T.writes([B[vi, vj, vk]])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def elementwise_split_with_opaque_block(a: T.handle, b: T.handle) -> None:
B = T.match_buffer(b, [128, 128, 128])
A = T.match_buffer(a, [128, 128, 128])
for i0, i1, j, k in T.grid(8, 16, 128, 128):
with T.sblock("opaque"):
T.reads([A[i0 * 16 + i1, j, k]])
T.writes([B[i0 * 16 + i1, j, k]])
with T.sblock("B"):
vi = T.axis.S(128, i0 * 16 + i1)
vj, vk = T.axis.remap("SS", [j, k])
T.reads([A[vi, vj, vk]])
T.writes([B[vi, vj, vk]])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def opaque_access(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [16, 16], "float32")
B = T.match_buffer(b, [16, 16], "float32")
for i, j in T.grid(16, 16):
with T.sblock("A"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads([])
T.writes([A[0:16, 0:16]])
A[vi, vj] = 1
for i, j in T.grid(16, 16):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads([])
T.writes([B[0:16, 0:16]])
T.evaluate(T.tvm_fill_fragment(B.data, 16, 16, 16, 0, vi * 16 + vj, dtype="handle"))
@T.prim_func(s_tir=True)
def opaque_access_fused(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [16, 16])
B = T.match_buffer(b, [16, 16])
for i_j_fused in T.serial(0, 256):
with T.sblock("A"):
vi = T.axis.S(16, T.floordiv(i_j_fused, 16))
vj = T.axis.S(16, T.floormod(i_j_fused, 16))
T.reads([])
T.writes([A[0:16, 0:16]])
A[vi, vj] = 1
for i_j_fused in T.serial(0, 256):
with T.sblock("B"):
vi = T.axis.S(16, T.floordiv(i_j_fused, 16))
vj = T.axis.S(16, T.floormod(i_j_fused, 16))
T.reads([])
T.writes([B[0:16, 0:16]])
T.evaluate(T.tvm_fill_fragment(B.data, 16, 16, 16, 0, ((vi * 16) + vj), dtype="handle"))
@T.prim_func(s_tir=True)
def opaque_access_split(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (16, 16))
B = T.match_buffer(b, (16, 16))
for i, j0, j1 in T.grid(16, 4, 4):
with T.sblock("A"):
vi = T.axis.S(16, i)
vj = T.axis.S(16, j0 * 4 + j1)
T.reads([])
T.writes([A[0:16, 0:16]])
A[vi, vj] = 1
for i, j0, j1 in T.grid(16, 4, 4):
with T.sblock("B"):
vi = T.axis.S(16, i)
vj = T.axis.S(16, j0 * 4 + j1)
T.reads([])
T.writes([B[0:16, 0:16]])
T.evaluate(T.tvm_fill_fragment(B.data, 16, 16, 16, 0, ((vi * 16) + vj), dtype="handle"))
@T.prim_func(s_tir=True)
def elementwise_not_affine(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (127, 128))
B = T.match_buffer(b, (127, 128))
for i in T.serial(0, 4):
for j, k in T.grid(T.min(31, 126 - i * 32) + 1, 128):
with T.sblock("B"):
vi = T.axis.S(127, i * 32 + j)
vj = T.axis.S(128, k)
B[vi, vj] = A[vi, vj]
@T.prim_func(s_tir=True)
def elementwise_not_affine_fused(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [127, 128])
B = T.match_buffer(b, [127, 128])
for i in T.grid(4):
for j_k_fused in T.serial(0, T.min(31, 126 - i * 32) * 128 + 128):
with T.sblock("B"):
vi = T.axis.S(
127,
i * 32 + T.floordiv(j_k_fused, 128),
)
vj = T.axis.S(128, T.floormod(j_k_fused, 128))
T.reads([A[vi, vj]])
T.writes([B[vi, vj]])
B[vi, vj] = A[vi, vj]
# pylint: enable=no-member,invalid-name,unused-variable
def test_fuse():
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, k = sch.get_loops(block_b)
sch.fuse(i, j, k)
assert_structural_equal_ignore_global_symbol(elementwise_fused, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=elementwise)
@pytest.mark.parametrize("disable_predication", [True, False])
def test_split(disable_predication):
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, k = sch.get_loops(block_b)
sch.split(i, factors=[2, 1, 64], disable_predication=disable_predication)
sch.split(j, factors=[4, 32], disable_predication=disable_predication)
sch.split(k, factors=[16, 8], disable_predication=disable_predication)
assert_structural_equal_ignore_global_symbol(elementwise_split_case0, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=elementwise)
def test_split_with_inferred_factor():
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, k = sch.get_loops(block_b)
sch.split(i, factors=[None, 1, 64])
sch.split(j, factors=[2, None, 64])
sch.split(k, factors=[2, 1, None])
assert_structural_equal_ignore_global_symbol(elementwise_split_case1, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=elementwise)
def test_split_with_dynamic_inferred_factor():
@T.prim_func(s_tir=True)
def before(a: T.handle, b: T.handle) -> None:
N = T.int32()
M = T.int32()
A = T.match_buffer(a, (N, 128, M))
B = T.match_buffer(b, (N, 128, M))
for i, j, k in T.grid(N, 128, M):
with T.sblock("B"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
B[vi, vj, vk] = A[vi, vj, vk] * 2.0
@T.prim_func(s_tir=True)
def expected(a: T.handle, b: T.handle) -> None:
N, M = T.int32(), T.int32()
A = T.match_buffer(a, (N, 128, M))
B = T.match_buffer(b, (N, 128, M))
for i_0, i_1, j_0, j_1, k_0, k_1 in T.grid((N + 15) // 16, 16, 4, 32, 16, (M + 15) // 16):
with T.sblock("B"):
vi = T.axis.spatial(N, i_0 * 16 + i_1)
vj = T.axis.spatial(128, j_0 * 32 + j_1)
vk = T.axis.spatial(M, k_0 * ((M + 15) // 16) + k_1)
T.where(i_0 * 16 + i_1 < N and k_0 * ((M + 15) // 16) + k_1 < M)
B[vi, vj, vk] = A[vi, vj, vk] * T.float32(2.0)
sch = tvm.s_tir.Schedule(before, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, k = sch.get_loops(block_b)
sch.split(i, factors=[None, 16])
sch.split(j, factors=[4, 32])
sch.split(k, factors=[16, None])
assert_structural_equal_ignore_global_symbol(expected, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=before)
def test_split_with_predicate():
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, k = sch.get_loops(block_b)
sch.split(i, factors=[1000, 2, 3])
sch.split(j, factors=[None, 129])
sch.split(k, factors=[3, None])
assert_structural_equal_ignore_global_symbol(elementwise_split_with_predicate, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=elementwise)
def test_fuse_fail_not_only_child():
sch = tvm.s_tir.Schedule(elementwise_with_seq, debug_mask="all")
block_b = sch.get_sblock("B")
_, j, k = sch.get_loops(block_b)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.fuse(j, k)
def test_fuse_split_fail_with_annotation():
sch = tvm.s_tir.Schedule(elementwise_with_anno, debug_mask="all")
block_b = sch.get_sblock("B")
_, j, k = sch.get_loops(block_b)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.fuse(j, k)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.split(k, factors=[None, 10])
def test_fuse_split_fail_not_start_with_zero():
sch = tvm.s_tir.Schedule(elementwise_with_anno, debug_mask="all")
block_b = sch.get_sblock("B")
_, j, k = sch.get_loops(block_b)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.fuse(j, k)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.split(k, factors=[None, 10])
def test_fuse_with_opaque_block():
sch = tvm.s_tir.Schedule(elementwise_with_opaque_block, debug_mask="all")
block_opaque = sch.get_sblock("opaque")
i, j, k = sch.get_loops(block_opaque)
sch.fuse(i, j, k)
assert_structural_equal_ignore_global_symbol(
elementwise_fuse_with_opaque_block, sch.mod["main"]
)
verify_trace_roundtrip(sch=sch, mod=elementwise_with_opaque_block)
def test_fuse_with_opaque_access():
sch = tvm.s_tir.Schedule(opaque_access, debug_mask="all")
block_a = sch.get_sblock("A")
i, j = sch.get_loops(block_a)
sch.fuse(i, j)
block_b = sch.get_sblock("B")
i, j = sch.get_loops(block_b)
sch.fuse(i, j)
assert_structural_equal_ignore_global_symbol(opaque_access_fused, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=opaque_access)
def test_split_with_opaque_block():
sch = tvm.s_tir.Schedule(elementwise_with_opaque_block, debug_mask="all")
block_opaque = sch.get_sblock("opaque")
i, _, _ = sch.get_loops(block_opaque)
sch.split(i, factors=[None, 16])
assert_structural_equal_ignore_global_symbol(
elementwise_split_with_opaque_block, sch.mod["main"]
)
verify_trace_roundtrip(sch=sch, mod=elementwise_with_opaque_block)
def test_split_with_opaque_access():
sch = tvm.s_tir.Schedule(opaque_access, debug_mask="all")
block_a = sch.get_sblock("A")
_, j = sch.get_loops(block_a)
sch.split(j, factors=[None, 4])
block_b = sch.get_sblock("B")
_, j = sch.get_loops(block_b)
sch.split(j, factors=[None, 4])
assert_structural_equal_ignore_global_symbol(opaque_access_split, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=opaque_access)
def test_split_with_non_positive_factors():
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, k = sch.get_loops(block_b)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.split(i, factors=[-2, -64])
with pytest.raises(tvm.s_tir.ScheduleError):
sch.split(j, factors=[0, None])
with pytest.raises(tvm.s_tir.ScheduleError):
sch.split(k, factors=[None, -16])
def test_fuse_split_fail_with_thread_binding():
sch = tvm.s_tir.Schedule(elementwise_with_thread_binding, debug_mask="all")
block_b = sch.get_sblock("B")
_, j, k = sch.get_loops(block_b)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.fuse(j, k)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.split(k, factors=[None, 10])
def test_fuse_symbolic():
sch = tvm.s_tir.Schedule(elementwise_symbolic, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, k = sch.get_loops(block_b)
sch.fuse(i, j, k)
assert_structural_equal_ignore_global_symbol(elementwise_symbolic_fused, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=elementwise_symbolic)
def test_split_symbolic():
sch = tvm.s_tir.Schedule(elementwise_symbolic, debug_mask="all")
block_b = sch.get_sblock("B")
_, _, k = sch.get_loops(block_b)
sch.split(k, factors=[10, None])
assert_structural_equal_ignore_global_symbol(elementwise_symbolic_split, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=elementwise_symbolic)
def test_fuse_fail_with_dependent_loops():
sch = tvm.s_tir.Schedule(elementwise_dependent_loops, debug_mask="all")
block_b = sch.get_sblock("B")
i, j, _ = sch.get_loops(block_b)
with pytest.raises(tvm.s_tir.ScheduleError):
sch.fuse(i, j)
def test_fuse_not_affine():
sch = tvm.s_tir.Schedule(elementwise_not_affine, debug_mask="all")
block_b = sch.get_sblock("B")
_, j, k = sch.get_loops(block_b)
sch.fuse(j, k)
assert_structural_equal_ignore_global_symbol(elementwise_not_affine_fused, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=elementwise_not_affine)
def test_add_unit_loop_above_block():
@T.prim_func(s_tir=True)
def zero_dim(
A: T.Buffer((), "int32"),
B: T.Buffer((), "int32"),
C: T.Buffer((), "int32"),
) -> None:
with T.sblock("C"):
vi = T.axis.spatial(1, 0)
C[()] = A[()] + B[()]
@T.prim_func(s_tir=True)
def zero_dim_added(
A: T.Buffer((), "int32"),
B: T.Buffer((), "int32"),
C: T.Buffer((), "int32"),
) -> None:
for u in range(1):
with T.sblock("C"):
vi = T.axis.spatial(1, 0)
C[()] = A[()] + B[()]
sch = tvm.s_tir.Schedule(zero_dim, debug_mask="all")
block = sch.get_sblock("C")
sch.add_unit_loop(block)
assert_structural_equal_ignore_global_symbol(zero_dim_added, sch.mod["main"])
def test_add_unit_loop_above_loop():
@T.prim_func(s_tir=True)
def zero_dim(
A: T.Buffer((), "int32"),
B: T.Buffer((), "int32"),
C: T.Buffer((), "int32"),
) -> None:
for u in range(1):
with T.sblock("C"):
vi = T.axis.spatial(1, 0)
C[()] = A[()] + B[()]
@T.prim_func(s_tir=True)
def zero_dim_added(
A: T.Buffer((), "int32"),
B: T.Buffer((), "int32"),
C: T.Buffer((), "int32"),
) -> None:
for u1, u2 in T.grid(1, 1):
with T.sblock("C"):
vi = T.axis.spatial(1, 0)
C[()] = A[()] + B[()]
sch = tvm.s_tir.Schedule(zero_dim, debug_mask="all")
block = sch.get_sblock("C")
(loop,) = sch.get_loops(block)
sch.add_unit_loop(loop)
assert_structural_equal_ignore_global_symbol(zero_dim_added, sch.mod["main"])
@pytest.mark.skip("Pending fix in affine analysis")
def test_fuse_int64():
def _create_prim_func():
n = te.const(16, "int32")
m = te.const(32, "int64")
A = te.placeholder((n, m), name="A", dtype="int32")
B = te.compute((n, m), lambda i, j: A[i, j] + 1, name="B")
return te.create_prim_func([A, B])
mod = _create_prim_func()
sch = tvm.s_tir.Schedule(mod, debug_mask="all")
i, j = sch.get_loops(sch.get_sblock("B"))
sch.fuse(i, j)
verify_trace_roundtrip(sch=sch, mod=mod)
def test_split_int64_extent_with_mixed_factors():
def _create_prim_func():
m = te.const(384, "int64")
A = te.placeholder((m,), name="A", dtype="float32")
B = te.compute((m,), lambda i: A[i] + 1, name="B")
return te.create_prim_func([A, B])
mod = _create_prim_func()
sch = tvm.s_tir.Schedule(mod, debug_mask="all")
(i,) = sch.get_loops(sch.get_sblock("B"))
sch.split(
i,
factors=[
te.const(1, "int64"),
te.const(512, "int32"),
],
)
def test_split_int64_extent_with_int32_factors():
def _create_prim_func():
m = te.const(12, "int64")
A = te.placeholder((m,), name="A", dtype="float32")
B = te.compute((m,), lambda i: A[i] + 1, name="B")
return te.create_prim_func([A, B])
mod = _create_prim_func()
sch = tvm.s_tir.Schedule(mod, debug_mask="all")
(i,) = sch.get_loops(sch.get_sblock("B"))
sch.split(
i,
factors=[
te.const(1, "int32"),
te.const(1, "int32"),
te.const(3, "int32"),
te.const(1, "int32"),
te.const(4, "int32"),
],
)
def test_split_int64_factors():
sch = tvm.s_tir.Schedule(elementwise_symbolic, debug_mask="all")
block_b = sch.get_sblock("B")
_, _, k = sch.get_loops(block_b)
sch.split(k, factors=[IntImm(dtype="int64", value=10), None])
assert_structural_equal_ignore_global_symbol(elementwise_symbolic_split, sch.mod["main"])
def test_unsupported_target_scalable_split():
@T.prim_func(s_tir=True)
def before(a: T.handle):
A = T.match_buffer(a, (128,), "float32")
T.func_attr({"global_symbol": "my_module", "tirx.noalias": True})
for i in T.serial(128):
with T.sblock("A"):
v_i = T.axis.remap("S", [i])
A[v_i] = 1.0
sch = tvm.s_tir.Schedule(before)
(a,) = sch.get_loops("A")
err_msg = "The product of factors is not larger than or equal to the extent of loop tirx.For#0"
with pytest.raises(tvm.s_tir.schedule.ScheduleError, match=err_msg):
sch.split(a, factors=[T.ceildiv(128, 4 * T.vscale()), 4 * T.vscale()])
def test_fused_symbolic_2D_tiling():
@T.prim_func(s_tir=True)
def before(a: T.handle, b: T.handle, M: T.int32, N: T.int32) -> None:
A = T.match_buffer(a, (M, N))
B = T.match_buffer(b, (M, N))
for i, j in T.grid(M, N):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
@T.prim_func(s_tir=True)
def expected(a: T.handle, b: T.handle, M: T.int32, N: T.int32) -> None:
A = T.match_buffer(a, (M, N))
B = T.match_buffer(b, (M, N))
for i_0_j_0_fused, i_1, j_1 in T.grid(((M + 63) // 64) * ((N + 15) // 16), 64, 16):
with T.sblock("B"):
vi = T.axis.spatial(M, i_0_j_0_fused // ((N + 15) // 16) * 64 + i_1)
vj = T.axis.spatial(N, i_0_j_0_fused % ((N + 15) // 16) * 16 + j_1)
T.where(
i_0_j_0_fused // ((N + 15) // 16) * 64 + i_1 < M
and i_0_j_0_fused % ((N + 15) // 16) * 16 + j_1 < N
)
B[vi, vj] = A[vi, vj] * T.float32(2.0)
sch = tvm.s_tir.Schedule(before, debug_mask="all")
block_b = sch.get_sblock("B")
i, j = sch.get_loops(block_b)
i0, i1 = sch.split(i, factors=[None, 64])
j0, j1 = sch.split(j, factors=[None, 16])
sch.reorder(i0, j0, i1, j1)
sch.fuse(i0, j0)
assert_structural_equal_ignore_global_symbol(expected, sch.mod["main"])
verify_trace_roundtrip(sch=sch, mod=before)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,358 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: F401
import gc
import sys
import pytest
import tvm_ffi
import tvm
import tvm.testing
from tvm import tirx
from tvm.ir import IRModule
from tvm.script import tirx as T
# pylint: disable=no-member,invalid-name,unused-variable
@T.prim_func(s_tir=True)
def elementwise(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128), "float32")
C = T.match_buffer(c, (128, 128), "float32")
B = T.sblock_alloc_buffer((128, 128), "float32")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def matmul(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
B = T.match_buffer(b, [128, 128])
C = T.match_buffer(c, [128, 128])
for i, j in T.grid(128, 128):
with T.sblock("init"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = T.float32(0)
for k in range(0, 128):
with T.sblock("update"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
@T.prim_func(s_tir=True)
def block_in_opaque_block(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128), "float32")
B = T.match_buffer(b, (128, 128), "float32")
for i in range(128):
with T.sblock("B"):
vi = T.axis.S(128, i)
T.reads([A[0:128, 0:128]])
T.writes([B[0:128, 0:128]])
B[vi, 0] = A[vi, 0]
if A[vi, 0] == 0.0:
with T.sblock("C"):
T.reads([A[0:128, 0:128]])
T.writes([B[0:128, 0:128]])
for j in range(128):
with T.sblock("D"):
vj = T.axis.S(128, j)
B[vi, vj] = A[vi, vj] * 3.0
else:
with T.sblock("E"):
T.reads([A[0:128, 0:128]])
T.writes([B[0:128, 0:128]])
for j in range(128):
with T.sblock("F"):
vj = T.axis.S(128, j)
B[vi, vj] = A[vi, vj] * 2.0
# pylint: enable=no-member,invalid-name,unused-variable
def replace_ir_builder(deep_copy=False, realize=False):
new_func = tvm.script.from_source(elementwise.script())
s = tvm.s_tir.ScheduleState(new_func, debug_mask="all")
target = tvm.tirx.SBlock(
iter_vars=[],
reads=[],
writes=[],
name_hint="target",
body=s.mod["main"].body.block.body[1],
init=None,
alloc_buffers=None,
match_buffers=None,
annotations=None,
)
if realize:
target = tvm.tirx.SBlockRealize(
iter_values=[],
predicate=True,
block=target,
)
if deep_copy:
target.__setstate__(target.__getstate__())
gc.collect()
return s, target
def replace_ir_builder_module(deep_copy=False, realize=False):
new_func = tvm.script.from_source(elementwise.script())
other_func = tvm.script.from_source(elementwise.script())
mod = IRModule(functions={"main": new_func, "other": other_func})
s = tvm.s_tir.ScheduleState(mod, debug_mask="all")
target = tvm.tirx.SBlock(
iter_vars=[],
reads=[],
writes=[],
name_hint="target",
body=s.mod["main"].body.block.body[1],
init=None,
alloc_buffers=None,
match_buffers=None,
annotations=None,
)
if realize:
target = tvm.tirx.SBlockRealize(
iter_values=[],
predicate=True,
block=target,
)
if deep_copy:
target.__setstate__(target.__getstate__())
gc.collect()
return s, target
def replace_ir_builder_with_opaque():
func = tvm.script.from_source(block_in_opaque_block.script())
s = tvm.s_tir.ScheduleState(func, debug_mask="all")
gc.collect()
return s
def test_replace_direct_write0():
s, target = replace_ir_builder(realize=True)
old_hash = s.mod["main"].__hash__()
sref = s.get_sref(s.mod["main"].body.block.body[1])
s.replace(sref, target)
# Check the replaced part is equal to the target
tvm.ir.assert_structural_equal(s.mod["main"].body.block.body[1], target)
# There is no other reference so the AST node can be written directly
assert old_hash == s.mod["main"].__hash__()
# The target reuse the stmt of the sref, so the sref won't be None
assert sref.stmt is not None
def test_replace_direct_write1():
s, target = replace_ir_builder(realize=True)
old_hash = s.mod["main"].body.block.body.__hash__()
hold_ref = s.mod["main"].body.block.body[1]
sref = s.get_sref(s.mod["main"].body.block.body[1])
s.replace(sref, target)
# There is no other reference so the AST node can be written directly
assert old_hash == s.mod["main"].body.block.body.__hash__()
assert not tvm_ffi.structural_equal(hold_ref.body, target)
# Check the replaced part is equal to the target
tvm.ir.assert_structural_equal(s.mod["main"].body.block.body[1], target)
# The target reuse `sref.stmt`, so the sref won't be None
assert sref.stmt is not None
def test_replace_copy():
s, target = replace_ir_builder(deep_copy=True, realize=True)
old_hash = s.mod["main"].__hash__()
# We hold another reference of func
old_func = s.mod["main"]
sref = s.get_sref(s.mod["main"].body.block.body[0])
s.replace(sref, target)
# We need to copy the whole func to remain the old_func unchanged
assert old_hash != s.mod["main"].__hash__()
assert not tvm_ffi.structural_equal(old_func.body, s.mod["main"].body)
assert old_hash == old_func.__hash__()
# Check the replaced part is equal to the target
tvm.ir.assert_structural_equal(s.mod["main"].body.block.body[0], target)
# The replaced AST node will be deleted, so the ref will be None
assert sref.stmt is None
def test_replace_partial_copy0():
s, target = replace_ir_builder(deep_copy=True, realize=True)
func_old_hash = s.mod["main"].__hash__()
hold_ref = s.mod["main"].body.block.body[0]
ref_old_hash = hold_ref.__hash__()
sref = s.get_sref(s.mod["main"].body.block.body[0].body)
other_part_hash = s.mod["main"].body.block.body[1].__hash__()
s.replace(sref, target)
# The stmt is held by `hold_sref`, so it will be coped in copy-on-write
# because the ref count is not unique
assert ref_old_hash != s.mod["main"].body.block.body[0].__hash__()
assert not tvm_ffi.structural_equal(hold_ref.body, target)
# The function and the other part stmt can be directly written
assert func_old_hash == s.mod["main"].__hash__()
assert other_part_hash == s.mod["main"].body.block.body[1].__hash__()
# Check the replaced part is equal to the target
tvm.ir.assert_structural_equal(s.mod["main"].body.block.body[0].body, target)
# The replaced AST node will be deleted, so the ref will be None
assert sref.stmt is None
def test_replace_partial_copy1():
s, target = replace_ir_builder(deep_copy=True)
func_old_hash = s.mod["main"].__hash__()
hold_ref = s.mod["main"].body.block.body[0].body
stmt_old_hash = s.mod["main"].body.block.body[0].__hash__()
sref = s.get_sref(s.mod["main"].body.block.body[0].body.body.block)
other_part_hash = s.mod["main"].body.block.body[1].__hash__()
s.replace(sref, target)
# The parent stmt will change since there is only one reference
assert stmt_old_hash == s.mod["main"].body.block.body[0].__hash__()
assert not tvm_ffi.structural_equal(hold_ref.body, target)
# The function and the other part stmt can be directly written
assert func_old_hash == s.mod["main"].__hash__()
assert other_part_hash == s.mod["main"].body.block.body[1].__hash__()
# Check the replaced part is equal to the target
tvm.ir.assert_structural_equal(s.mod["main"].body.block.body[0].body.body.block, target)
# The replaced AST node will be deleted, so the ref will be None
assert sref.stmt is None
def test_replace_root_write():
s, target = replace_ir_builder()
old_hash = s.mod["main"].__hash__()
sref = s.get_sref(s.mod["main"].body.block)
s.replace(sref, target)
# Check no copy and the new body equals to target
assert old_hash == s.mod["main"].__hash__()
tvm.ir.assert_structural_equal(s.mod["main"].body.block, target)
def test_replace_root_copy0():
s, target = replace_ir_builder(deep_copy=True)
old_hash = s.mod["main"].__hash__()
func_ref = s.mod["main"]
sref = s.get_sref(s.mod["main"].body.block)
s.replace(sref, target)
# Check the new body equals to target
assert old_hash != s.mod["main"].__hash__()
tvm.ir.assert_structural_equal(s.mod["main"].body.block, target)
# Check the original func remains unchanged
assert old_hash == func_ref.__hash__()
assert not tvm_ffi.structural_equal(func_ref.body, target)
def test_replace_root_copy1():
s, target = replace_ir_builder(deep_copy=True, realize=True)
old_hash = s.mod["main"].body.block.__hash__()
func_ref = s.mod["main"].body.block
sref = s.get_sref(s.mod["main"].body.block.body[0])
s.replace(sref, target)
# Check the new body equals to target
assert old_hash != s.mod["main"].body.block.__hash__()
tvm.ir.assert_structural_equal(s.mod["main"].body.block.body[0], target)
# Check the original func remains unchanged
assert old_hash == func_ref.__hash__()
assert not tvm_ffi.structural_equal(func_ref.body, target)
def test_replace_root_copy2():
s, target = replace_ir_builder(deep_copy=True)
old_hash = s.mod.functions.__hash__()
func_ref = s.mod.functions
sref = s.get_sref(s.mod["main"].body.block)
s.replace(sref, target)
# Check the new body equals to target
assert old_hash != s.mod.functions.__hash__()
tvm.ir.assert_structural_equal(s.mod["main"].body.block, target)
# Check the original func remains unchanged
assert old_hash == func_ref.__hash__()
for _, v in func_ref.items():
assert not tvm_ffi.structural_equal(v.body.block, target)
def test_replace_root_copy3():
s, target = replace_ir_builder(deep_copy=True)
old_hash = s.mod.__hash__()
func_ref = s.mod
sref = s.get_sref(s.mod["main"].body.block)
s.replace(sref, target)
# Check the new body equals to target
assert old_hash != s.mod.__hash__()
tvm.ir.assert_structural_equal(s.mod["main"].body.block, target)
# Check the original func remains unchanged
assert old_hash == func_ref.__hash__()
assert not tvm_ffi.structural_equal(func_ref["main"].body.block, target)
def test_replace_block_remap():
func = elementwise
s = tvm.s_tir.ScheduleState(func, debug_mask="all")
# The target stmt
target = matmul.body.block.body.body.body[0].block
sref = s.get_sref(s.mod["main"].body.block.body[0].body.body.block)
s.replace(sref, target, {sref.stmt: target})
sref_new = s.get_sref(s.mod["main"].body.block.body[0].body.body.block)
# Check the original sref has been remapped
assert sref.__hash__() == sref_new.__hash__()
tvm.ir.assert_structural_equal(sref.stmt, target)
def test_replace_block_in_opaque_block():
s = replace_ir_builder_with_opaque()
root_hash = s.mod["main"].__hash__()
for_loop = s.mod["main"].body.block.body.body.block.body[1].then_case.block.body
sref = s.get_sref(for_loop)
new_for_loop = tirx.For(
loop_var=for_loop.loop_var,
min=0,
extent=128,
kind=tirx.ForKind.SERIAL,
body=tirx.Evaluate(0),
thread_binding=None,
annotations=None,
)
s.replace(sref, new_for_loop)
assert root_hash == s.mod["main"].__hash__()
tvm.ir.assert_structural_equal(sref.stmt, new_for_loop)
def test_replace_ir_module():
s, target = replace_ir_builder_module(deep_copy=True)
old_hash = s.mod["main"].__hash__()
other_func_hash = s.mod["other"].__hash__()
func_ref = s.mod["main"]
sref = s.get_sref(s.mod["main"].body.block)
s.replace(sref, target)
# Check the new body equals to target
assert old_hash != s.mod["main"].__hash__()
tvm.ir.assert_structural_equal(s.mod["main"].body.block, target)
# Check the original func remains unchanged
assert old_hash == func_ref.__hash__()
assert not tvm_ffi.structural_equal(func_ref.body, target)
assert other_func_hash == s.mod["other"].__hash__()
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,901 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: E501, E741, F401
import sys
import pytest
import tvm
import tvm.testing
from tvm import s_tir, tirx
from tvm.s_tir.schedule.state import CachedFlags
from tvm.script import tirx as T
from tvm.tirx.stmt_functor import post_order_visit
# pylint: disable=no-member,invalid-name,unused-variable,unexpected-keyword-arg
# fmt: off
@T.prim_func(s_tir=True)
def elementwise(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128), "float32")
C = T.match_buffer(c, (128, 128), "float32")
B = T.sblock_alloc_buffer((128, 128), "float32")
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def matmul(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
B = T.match_buffer(b, [128, 128])
C = T.match_buffer(c, [128, 128])
for i, j in T.grid(128, 128):
with T.sblock("init"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = 0.0
for k in range(0, 128):
with T.sblock("update"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
@T.prim_func(s_tir=True)
def block_in_opaque_block(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128, 128), "float32")
B = T.match_buffer(b, (128, 128), "float32")
for i in range(128):
with T.sblock("B"):
vi = T.axis.S(128, i)
T.reads([A[0:128, 0:128]])
T.writes([B[0:128, 0:128]])
B[vi, 0] = A[vi, 0]
if A[vi, 0] == 0.0:
with T.sblock("C"):
T.reads([A[0:128, 0:128]])
T.writes([B[0:128, 0:128]])
for j in range(128):
with T.sblock("D"):
vj = T.axis.S(128, j)
B[vi, vj] = A[vi, vj] * 3.0
else:
with T.sblock("E"):
T.reads([A[0:128, 0:128]])
T.writes([B[0:128, 0:128]])
for j in range(128):
with T.sblock("F"):
vj = T.axis.S(128, j)
B[vi, vj] = A[vi, vj] * 2.0
@T.prim_func(s_tir=True)
def write_after_read(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
B = T.match_buffer(b, (128, 128))
C = T.match_buffer(c, (128, 128))
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + 1.0
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
@T.prim_func(s_tir=True)
def loop_carried_dependency(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128,))
B = T.match_buffer(b, (128,))
C = T.match_buffer(c, (128,))
for i in range(0, 128):
with T.sblock("B"):
vi = T.axis.S(128, i)
B[vi] = A[vi] * 2.0
with T.sblock("C"):
vi = T.axis.S(128, i)
C[vi] = T.if_then_else(vi >= 1, B[vi - 1] + 1.0, 0.0, dtype="float32")
@T.prim_func(s_tir=True)
def concatenate_multi_producer(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128,))
B = T.match_buffer(b, (128,))
for i in range(0, 64):
with T.sblock("A_0"):
vi = T.axis.S(64, i)
A[vi] = vi + 1
for i in range(0, 64):
with T.sblock("A_1"):
vi = T.axis.S(64, i + 64)
A[vi] = vi + 2
for i in range(0, 128):
with T.sblock("B"):
vi = T.axis.S(128, i)
B[vi] = A[vi] * 2.0
@T.prim_func(s_tir=True)
def concatenate_multi_producer_uncovered(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128,))
B = T.match_buffer(b, (128,))
for i in range(0, 63):
with T.sblock("A_0"):
vi = T.axis.S(63, i)
A[vi] = vi + 1
for i in range(0, 64):
with T.sblock("A_1"):
vi = T.axis.S(64, i + 64)
A[vi] = vi + 2
for i in range(0, 128):
with T.sblock("B"):
vi = T.axis.S(128, i)
B[vi] = A[vi] * 2.0
@T.prim_func(s_tir=True)
def lca_at_loop(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128,))
B = T.match_buffer(b, (128,))
C = T.match_buffer(c, (128,))
for i in range(0, 128):
with T.sblock("B"):
vi = T.axis.S(128, i)
B[vi] = A[vi] * 2.0
with T.sblock("C"):
vi = T.axis.S(128, i)
C[vi] = B[vi] + 1.0
@T.prim_func(s_tir=True)
def multi_producer_consumer(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (128,))
B = T.match_buffer(b, (128,))
for i in range(0, 64):
with T.sblock("A_0"):
vi = T.axis.S(64, i)
A[vi] = vi + 1
for i in range(0, 64):
with T.sblock("A_1"):
vi = T.axis.S(64, i + 64)
A[vi] = vi + 2
for i in range(0, 64):
with T.sblock("B_0"):
vi = T.axis.S(64, i)
B[vi] = A[vi] + 2.0
for i in range(0, 64):
with T.sblock("B_1"):
vi = T.axis.S(64, i + 64)
B[vi] = A[vi] + 3.0
@T.prim_func(s_tir=True)
def elementwise_affine_producer(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128), "float32")
C = T.match_buffer(c, (128, 128), "float32")
B = T.sblock_alloc_buffer((128, 128), "float32")
for i, j, k, l in T.grid(16, 2, 32, 16):
with T.sblock("B"):
vi = T.axis.S(128, i * 8 + j * 4 + k // 8)
vj = T.axis.S(128, k % 8 * 16 + l)
B[vi, vj] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def elementwise_subblock(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128), "float32")
C = T.match_buffer(c, (128, 128), "float32")
B = T.sblock_alloc_buffer((128, 128), "float32")
for i, j in T.grid(32, 32):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads([A[vi * 4 : vi * 4 + 4, vj * 4 : vj * 4 + 4]])
T.writes([B[vi * 4 : vi * 4 + 4, vj * 4 : vj * 4 + 4]])
for ii, jj in T.grid(4, 4):
with T.sblock("B_sub"):
vi_i, vj_i = T.axis.remap("SS", [ii, jj])
B[vi * 4 + vi_i, vj * 4 + vj_i] = A[vi * 4 + vi_i, vj * 4 + vj_i] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def elementwise_subblock_uncovered(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128), "float32")
C = T.match_buffer(c, (128, 128), "float32")
B = T.sblock_alloc_buffer((128, 128), "float32")
for i, j in T.grid(32, 32):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
T.reads([A[vi * 4 : vi * 4 + 2, vj * 4 : vj * 4 + 2]])
T.writes([B[vi * 4 : vi * 4 + 2, vj * 4 : vj * 4 + 2]])
for ii, jj in T.grid(2, 2):
with T.sblock("B_sub"):
vi_i, vj_i = T.axis.remap("SS", [ii, jj])
B[vi * 4 + vi_i, vj * 4 + vj_i] = A[vi * 4 + vi_i, vj * 4 + vj_i] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def bound_to_thread(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
C = T.match_buffer(c, [128, 128])
B = T.sblock_alloc_buffer([128, 128], scope="shared")
for i in T.thread_binding(0, 128, thread="threadIdx.x"):
for j in T.serial(0, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
for j in T.serial(0, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vj, vi] = B[vj, vi] + 1.0
@T.prim_func(s_tir=True)
def equal_ranked_threads(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
C = T.match_buffer(c, [128, 128])
B = T.sblock_alloc_buffer([128, 128], scope="shared")
for i_o in T.thread_binding(0, 16, thread="threadIdx.x"):
for i_i in T.thread_binding(0, 8, thread="threadIdx.y"):
for j in T.serial(0, 128):
with T.sblock("B"):
vi = T.axis.S(128, i_o * 8 + i_i)
vj = T.axis.S(128, j)
B[vi, vj] = A[vi, vj] * 2.0
for j in T.serial(0, 128):
with T.sblock("C"):
vi = T.axis.S(128, i_o * 8 + i_i)
vj = T.axis.S(128, j)
C[vj, vi] = B[vj, vi] + 1.0
@T.prim_func(s_tir=True)
def warp_memory(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
C = T.match_buffer(c, [128, 128])
B = T.sblock_alloc_buffer([128, 4, 32], scope="warp")
for i_o in T.thread_binding(0, 4, thread="threadIdx.y"):
for i_i in T.thread_binding(0, 32, thread="threadIdx.x"):
for j in T.serial(0, 128):
with T.sblock("B"):
warp_id, lane_id, vj = T.axis.remap("SSS", [i_o, i_i, j])
B[vj, warp_id, lane_id] = A[warp_id * 32 + lane_id, vj] * 2.0
for j in T.serial(0, 128):
with T.sblock("C"):
warp_id, lane_id, vj = T.axis.remap("SSS", [i_o, i_i, j])
C[warp_id * 32 + lane_id, vj] = B[vj, warp_id, lane_id] + 1.0
@T.prim_func(s_tir=True)
def warp_memory_negative(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
C = T.match_buffer(c, [128, 128])
B = T.sblock_alloc_buffer([128, 4, 32], scope="warp")
for i_o in T.thread_binding(0, 4, thread="threadIdx.y"):
for i_i in T.thread_binding(0, 32, thread="threadIdx.x"):
for j in T.serial(0, 128):
with T.sblock("B"):
warp_id, lane_id, vj = T.axis.remap("SSS", [i_o, i_i, j])
B[vj, warp_id, lane_id] = A[warp_id * 32 + lane_id, vj] * 2.0
for i_o_prime in T.thread_binding(0, 4, thread="threadIdx.y"):
for j in T.serial(0, 128):
with T.sblock("C"):
_warp_id, warp_id, lane_id, vj = T.axis.remap(
"SSSS", [i_o, i_i, i_o_prime, j]
)
C[warp_id * 32 + lane_id, vj] = B[vj, warp_id, lane_id] + 1.0
@T.prim_func(s_tir=True)
def non_perfect_tiling_cache(a: T.handle, b: T.handle) -> None:
X = T.match_buffer(a, [224, 224], dtype="float32")
Y = T.match_buffer(b, [224, 224], dtype="float32")
cache = T.sblock_alloc_buffer([224, 224], dtype="float32")
for hh_0, ww_0 in T.grid(28, 28):
for ax0 in T.serial(0, 10):
for ax1 in T.serial(0, 10):
with T.sblock("cache"):
h = T.axis.spatial(224, hh_0 * 8 - 1 + ax0)
w = T.axis.spatial(224, ww_0 * 8 - 1 + ax1)
T.where(
1 <= hh_0 * 8 + ax0
and hh_0 * 8 + ax0 < 225
and 1 <= ww_0 * 8 + ax1
and ww_0 * 8 + ax1 < 225
)
cache[h, w] = X[h, w]
for hh_1, ww_1, khh, kww in T.grid(8, 8, 3, 3):
with T.sblock("compute"):
h = T.axis.spatial(224, hh_0 * 8 + hh_1)
w = T.axis.spatial(224, ww_0 * 8 + ww_1)
kh, kw = T.axis.remap("RR", [khh, kww])
with T.init():
Y[h, w] = 0.0
Y[h, w] = T.max(
Y[h, w],
T.if_then_else(
T.likely(1 <= h + kh, dtype="bool")
and T.likely(h + kh < 225, dtype="bool")
and T.likely(1 <= w + kw, dtype="bool")
and T.likely(w + kw < 225, dtype="bool"),
cache[h + kh - 1, w + kw - 1],
0.0,
dtype="float32",
),
)
@T.prim_func(s_tir=True)
def uncovered_producer_region(A: T.Buffer((128,), "float32"), B: T.Buffer((128,), "float32")):
for i in range(120):
with T.sblock("producer"):
vi = T.axis.S((0, 120), i)
A[vi] = 1.0
for i in range(120):
with T.sblock("consumer"):
vi = T.axis.S((8, 128), i + 8)
B[vi] = A[vi]
@T.prim_func(s_tir=True)
def matmul_relu_padding(A: T.Buffer((127, 127), "float16"), B: T.Buffer((127, 127), "float16"), compute: T.Buffer((127, 127), "float32")) -> None:
# function attr dict
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
# body
# with T.sblock("root")
C = T.sblock_alloc_buffer([127, 127], dtype="float32")
A_reindex = T.sblock_alloc_buffer([128, 128], dtype="float16")
B_reindex = T.sblock_alloc_buffer([128, 128], dtype="float16")
C_reindex_shared = T.sblock_alloc_buffer([128, 128], dtype="float32", scope="shared")
C_reindex_shared_wmma_accumulator = T.sblock_alloc_buffer([128, 128], dtype="float32", scope="wmma.accumulator")
for ax0, ax1, ax2 in T.grid(128, 1, 128):
with T.sblock("A_reindex"):
v0, v1, v2 = T.axis.remap("SSS", [ax0, ax1, ax2])
T.reads(A[v0, v2])
T.writes(A_reindex[v0, v2])
A_reindex[v0, v2] = T.if_then_else(v0 < 127 and v2 < 127, A[v0, v2], T.float16(0), dtype="float16")
for ax0, ax1, ax2 in T.grid(1, 128, 128):
with T.sblock("B_reindex"):
v0, v1, v2 = T.axis.remap("SSS", [ax0, ax1, ax2])
T.reads(B[v2, v1])
T.writes(B_reindex[v2, v1])
B_reindex[v2, v1] = T.if_then_else(v2 < 127 and v1 < 127, B[v2, v1], T.float16(0), dtype="float16")
for ax0_0_0_ax1_0_0_fused in T.thread_binding(2, thread="blockIdx.y"):
for ax0_0_1_ax1_0_1_fused in T.thread_binding(1, thread="blockIdx.x"):
for ax0_0_2_ax1_0_2_fused in T.thread_binding(16, thread="threadIdx.y"):
for ax2_0_0, ax2_0_1, ax0_0_3, ax1_0_3, ax2_0_2, ax0_0_4, ax1_0_4 in T.grid(2, 2, 1, 2, 2, 1, 1):
with T.sblock("C_o"):
v0_o = T.axis.spatial(8, ax0_0_2_ax1_0_2_fused // 2 + ax0_0_3 + ax0_0_4)
v1_o = T.axis.spatial(8, ax1_0_4 + ax0_0_0_ax1_0_0_fused * 4 + ax0_0_2_ax1_0_2_fused % 2 * 2 + ax1_0_3)
v2_o = T.axis.reduce(8, ax2_0_0 * 4 + ax2_0_1 * 2 + ax2_0_2)
T.reads(A_reindex[v0_o * 16 : v0_o * 16 + 16, v2_o * 16 : v2_o * 16 + 16], B_reindex[v2_o * 16 : v2_o * 16 + 16, v1_o * 16 : v1_o * 16 + 16])
T.writes(C_reindex_shared_wmma_accumulator[v0_o * 16 : v0_o * 16 + 16, v1_o * 16 : v1_o * 16 + 16])
T.sblock_attr({"meta_schedule.auto_tensorize":"wmma_sync_16x16x16_f16f16f32", "meta_schedule.auto_tensorize_init":"wmma_fill_16x16x16_f32", "warp_execution":1})
with T.init():
for ax0_1, ax1_1 in T.grid(16, 16):
with T.sblock("C_init"):
v0_i_init, v1_i_init = T.axis.remap("SS", [ax0_1, ax1_1])
T.reads()
T.writes(C_reindex_shared_wmma_accumulator[v0_o * 16 + v0_i_init, v1_o * 16 + v1_i_init])
C_reindex_shared_wmma_accumulator[v0_o * 16 + v0_i_init, v1_o * 16 + v1_i_init] = T.float32(0)
for ax0_1, ax1_1, ax2_1 in T.grid(16, 16, 16):
with T.sblock("C"):
v0_i, v1_i, v2_i = T.axis.remap("SSR", [ax0_1, ax1_1, ax2_1])
T.reads(C_reindex_shared_wmma_accumulator[v0_o * 16 + v0_i, v1_o * 16 + v1_i], A_reindex[v0_o * 16 + v0_i, v2_o * 16 + v2_i], B_reindex[v2_o * 16 + v2_i, v1_o * 16 + v1_i])
T.writes(C_reindex_shared_wmma_accumulator[v0_o * 16 + v0_i, v1_o * 16 + v1_i])
T.sblock_attr({"meta_schedule.tiling_structure":"SSSRRSRS"})
C_reindex_shared_wmma_accumulator[v0_o * 16 + v0_i, v1_o * 16 + v1_i] = C_reindex_shared_wmma_accumulator[v0_o * 16 + v0_i, v1_o * 16 + v1_i] + T.cast(A_reindex[v0_o * 16 + v0_i, v2_o * 16 + v2_i], "float32") * T.cast(B_reindex[v2_o * 16 + v2_i, v1_o * 16 + v1_i], "float32")
for ax0, ax1 in T.grid(16, 32):
with T.sblock("C_reindex_shared_wmma.accumulator"):
v0 = T.axis.spatial(128, ax0_0_2_ax1_0_2_fused // 2 * 16 + ax0)
v1 = T.axis.spatial(128, ax0_0_0_ax1_0_0_fused * 64 + ax0_0_2_ax1_0_2_fused % 2 * 32 + ax1)
T.reads(C_reindex_shared_wmma_accumulator[v0, v1])
T.writes(C_reindex_shared[v0, v1])
C_reindex_shared[v0, v1] = C_reindex_shared_wmma_accumulator[v0, v1]
for ax0, ax1 in T.grid(128, 64):
with T.sblock("C_reindex_shared"):
v0 = T.axis.spatial(128, ax0)
v1 = T.axis.spatial(128, ax0_0_0_ax1_0_0_fused * 64 + ax1)
T.where(ax0 < 127 and ax0_0_0_ax1_0_0_fused * 64 + ax1 < 127)
T.reads(C_reindex_shared[v0, v1])
T.writes(C[v0, v1])
T.sblock_attr({"meta_schedule.cooperative_fetch":3})
C[v0, v1] = C_reindex_shared[v0, v1]
for i0, i1 in T.grid(127, 127):
with T.sblock("compute"):
i0_1, i1_1 = T.axis.remap("SS", [i0, i1])
T.reads(C[i0_1, i1_1])
T.writes(compute[i0_1, i1_1])
compute[i0_1, i1_1] = T.max(C[i0_1, i1_1], T.float32(0))
@T.prim_func(s_tir=True)
def splitted_square_sum_with_predicate(
A: T.Buffer((1, 7, 7, 512), "float32"), B: T.Buffer((1, 1, 1, 512), "float32")
) -> None:
for i0_i1_i2_i3_0_fused, ax0, ax1, ax2, ax3 in T.grid(2, 1, 1, 1, 256):
for ax4_ax5_fused_0, ax4_ax5_fused_1 in T.grid(1, 256):
with T.sblock("B"):
T.where(ax4_ax5_fused_0 * 256 + ax4_ax5_fused_1 < 49)
ax0_1, ax1_1, ax2_1 = T.axis.remap("SSS", [ax0, ax1, ax2])
ax3_1 = T.axis.spatial(512, i0_i1_i2_i3_0_fused * 256 + ax3)
rv0 = T.axis.reduce(7, (ax4_ax5_fused_0 * 256 + ax4_ax5_fused_1) // 7)
rv1 = T.axis.reduce(7, (ax4_ax5_fused_0 * 256 + ax4_ax5_fused_1) % 7)
T.reads(A[ax0_1, ax1_1 * 7 + rv0, ax2_1 * 7 + rv1, ax3_1])
T.writes(B[ax0_1, ax1_1, ax2_1, ax3_1])
with T.init():
B[ax0_1, ax1_1, ax2_1, ax3_1] = T.float32(0)
B[ax0_1, ax1_1, ax2_1, ax3_1] += A[ax0_1, ax1_1 * 7 + rv0, ax2_1 * 7 + rv1, ax3_1]
# pylint: enable=no-member,invalid-name,unused-variable,unexpected-keyword-arg
# fmt: on
def _get_sblock(s: s_tir.ScheduleState, name_hint: str) -> s_tir.StmtSRef:
result = None
def f_visit(node):
nonlocal result
if isinstance(node, tvm.tirx.SBlock) and node.name_hint == name_hint:
result = node
func = s.mod["main"]
post_order_visit(func.body, f_visit)
assert result is not None and isinstance(result, tvm.tirx.SBlock)
return s.get_sref(result)
def test_elementwise():
s = tvm.s_tir.ScheduleState(elementwise, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "B")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "C")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "root")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
# pylint: enable=protected-access
def test_matmul():
s = tvm.s_tir.ScheduleState(matmul, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "init")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "update")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "root")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
# pylint: enable=protected-access
def test_block_in_opaque_block():
s = tvm.s_tir.ScheduleState(block_in_opaque_block, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "B")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "C")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "E")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "F")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "root")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
# pylint: enable=protected-access
def test_write_after_read():
s = tvm.s_tir.ScheduleState(write_after_read, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "B")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "C")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "root")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=False,
)
# pylint: enable=protected-access
def test_loop_carried_dependency():
s = tvm.s_tir.ScheduleState(loop_carried_dependency, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "B")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "C")) == CachedFlags(
affine_binding=True,
region_cover=False,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "root")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=False,
)
# pylint: enable=protected-access
def test_concatenate_multi_producer_covered(): # pylint: disable=invalid-name
s = tvm.s_tir.ScheduleState(concatenate_multi_producer, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "A_0")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "A_1")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "B")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "root")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
# pylint: enable=protected-access
def test_concatenate_multi_producer_uncovered(): # pylint: disable=invalid-name
s = tvm.s_tir.ScheduleState(concatenate_multi_producer_uncovered, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "A_0")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "A_1")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "B")) == CachedFlags(
affine_binding=True,
region_cover=False,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "root")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=False,
)
# pylint: enable=protected-access
def test_lca_at_loop():
s = tvm.s_tir.ScheduleState(lca_at_loop, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "B")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "C")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "root")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
# pylint: enable=protected-access
def test_multi_producer_consumer():
s = tvm.s_tir.ScheduleState(multi_producer_consumer, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "A_0")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "A_1")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "B_0")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "B_1")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
# pylint: enable=protected-access
def test_elementwise_affine_producer():
s = tvm.s_tir.ScheduleState(elementwise_affine_producer, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "root")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "B")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "C")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
# pylint: enable=protected-access
def test_subblock():
s = tvm.s_tir.ScheduleState(elementwise_subblock, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "root")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "B")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "B_sub")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "C")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
# pylint: enable=protected-access
def test_subblock_uncovered():
s = tvm.s_tir.ScheduleState(elementwise_subblock_uncovered, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "root")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=False,
)
assert s._get_cached_flags(_get_sblock(s, "B")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "B_sub")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "C")) == CachedFlags(
affine_binding=True,
region_cover=False,
stage_pipeline=True,
)
# pylint: enable=protected-access
def test_thread_binding():
s = tvm.s_tir.ScheduleState(bound_to_thread, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "root")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "B")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "C")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
# pylint: enable=protected-access
def test_equal_ranked_threads():
s = tvm.s_tir.ScheduleState(equal_ranked_threads, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "root")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "B")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "C")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
# pylint: enable=protected-access
def test_warp_memory():
s = tvm.s_tir.ScheduleState(warp_memory, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "root")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "B")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "C")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
# pylint: enable=protected-access
def test_warp_memory_negative():
s = tvm.s_tir.ScheduleState(warp_memory_negative, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "root")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=False,
)
assert s._get_cached_flags(_get_sblock(s, "B")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "C")) == CachedFlags(
affine_binding=True,
region_cover=False,
stage_pipeline=True,
)
# pylint: enable=protected-access
def test_non_perfect_tiling_cache():
s = tvm.s_tir.ScheduleState(non_perfect_tiling_cache, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "cache")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
assert s._get_cached_flags(_get_sblock(s, "compute")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
# pylint: enable=protected-access
def test_uncovered_producer_region():
s = tvm.s_tir.ScheduleState(uncovered_producer_region, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "consumer")) == CachedFlags(
affine_binding=True,
region_cover=False,
stage_pipeline=True,
)
# pylint: enable=protected-access
def test_matmul_relu_padding():
s = tvm.s_tir.ScheduleState(matmul_relu_padding, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "C_reindex_shared")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
# pylint: enable=protected-access
def test_splitted_square_sum_with_predicate():
s = tvm.s_tir.ScheduleState(splitted_square_sum_with_predicate, debug_mask="all")
# pylint: disable=protected-access
assert s._get_cached_flags(_get_sblock(s, "B")) == CachedFlags(
affine_binding=True,
region_cover=True,
stage_pipeline=True,
)
# pylint: enable=protected-access
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,183 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: F401
import pytest
import tvm
from tvm import tirx
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.script import tirx as T
# fmt: off
# pylint: disable=no-member,invalid-name,unused-variable,line-too-long,redefined-outer-name
@T.prim_func(s_tir=True)
def element_wise(a: T.handle, c: T.handle) -> None:
C = T.match_buffer(c, [128, 128], elem_offset=0, align=64, offset_factor=1)
A = T.match_buffer(a, [128, 128], elem_offset=0, align=64, offset_factor=1)
# body
with T.sblock("root"):
T.reads([])
T.writes([])
B = T.sblock_alloc_buffer([128, 128], elem_offset=0, align=64, offset_factor=1)
for i0 in T.serial(0, 128):
for ax1 in T.serial(0, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i0, ax1])
T.reads([A[vi, vj]])
T.writes([B[vi, vj]])
B[vi, vj] = (A[vi, vj]*T.float32(2))
for i1 in T.serial(0, 128):
with T.sblock("C"):
vi_1, vj_1 = T.axis.remap("SS", [i0, i1])
T.reads([B[vi_1, vj_1]])
T.writes([C[vi_1, vj_1]])
C[vi_1, vj_1] = (B[vi_1, vj_1] + T.float32(1))
@T.prim_func(s_tir=True)
def element_wise_storage_align(a: T.handle, c: T.handle) -> None:
C = T.match_buffer(c, [128, 128], elem_offset=0, align=64, offset_factor=1)
A = T.match_buffer(a, [128, 128], elem_offset=0, align=64, offset_factor=1)
# body
with T.sblock("root"):
T.reads([])
T.writes([])
B = T.sblock_alloc_buffer([128, 128], elem_offset=0, align=64, offset_factor=1)
for i0 in T.serial(0, 128):
for ax1 in T.serial(0, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i0, ax1])
T.reads([A[vi, vj]])
T.writes([B[vi, vj]])
T.sblock_attr({"buffer_dim_align":[[0, 0, 128, 127]]})
B[vi, vj] = (A[vi, vj]*T.float32(2))
for i1 in T.serial(0, 128):
with T.sblock("C"):
vi_1, vj_1 = T.axis.remap("SS", [i0, i1])
T.reads([B[vi_1, vj_1]])
T.writes([C[vi_1, vj_1]])
C[vi_1, vj_1] = (B[vi_1, vj_1] + T.float32(1))
@T.prim_func(s_tir=True)
def element_wise_invalid_annotation(a: T.handle, c: T.handle) -> None:
C = T.match_buffer(c, [128, 128], elem_offset=0, align=64, offset_factor=1)
A = T.match_buffer(a, [128, 128], elem_offset=0, align=64, offset_factor=1)
# body
with T.sblock("root"):
T.reads([])
T.writes([])
B = T.sblock_alloc_buffer([128, 128], elem_offset=0, align=64, offset_factor=1)
for i0 in T.serial(0, 128):
for ax1 in T.serial(0, 128):
with T.sblock("B"):
T.sblock_attr({"buffer_dim_align": [0]})
vi, vj = T.axis.remap("SS", [i0, ax1])
T.reads([A[vi, vj]])
T.writes([B[vi, vj]])
B[vi, vj] = (A[vi, vj]*T.float32(2))
for i1 in T.serial(0, 128):
with T.sblock("C"):
vi_1, vj_1 = T.axis.remap("SS", [i0, i1])
T.reads([B[vi_1, vj_1]])
T.writes([C[vi_1, vj_1]])
C[vi_1, vj_1] = (B[vi_1, vj_1] + T.float32(1))
use_block_name = tvm.testing.parameter(by_dict={"block_obj": False, "block_name": True})
def test_storage_align(use_block_name):
func = element_wise
s = tvm.s_tir.Schedule(func, debug_mask='all')
B = 'B' if use_block_name else s.get_sblock("B")
s.storage_align(B, 0, axis=0, factor=128, offset=127)
assert_structural_equal_ignore_global_symbol(element_wise_storage_align, s.mod["main"])
verify_trace_roundtrip(sch=s, mod=func)
def test_storage_align_update():
func = element_wise
s = tvm.s_tir.Schedule(func, debug_mask='all')
B = s.get_sblock("B")
s.storage_align(B, 0, axis=0, factor=128, offset=0)
s.storage_align(B, 0, axis=0, factor=128, offset=127)
assert_structural_equal_ignore_global_symbol(element_wise_storage_align, s.mod["main"])
verify_trace_roundtrip(sch=s, mod=func)
def test_storage_align_invalid_factor1():
func = element_wise
s = tvm.s_tir.Schedule(func, debug_mask='all')
B = s.get_sblock("B")
with pytest.raises(tvm.s_tir.ScheduleError):
s.storage_align(B, 0, axis=0, factor=0, offset=127)
def test_storage_align_invalid_factor2():
func = element_wise
s = tvm.s_tir.Schedule(func, debug_mask='all')
B = s.get_sblock("B")
with pytest.raises(tvm.s_tir.ScheduleError):
s.storage_align(B, 0, axis=0, factor=-1, offset=127)
def test_storage_align_invalid_buffer():
func = element_wise
s = tvm.s_tir.Schedule(func, debug_mask='all')
C = s.get_sblock("C")
with pytest.raises(tvm.s_tir.ScheduleError):
s.storage_align(C, 0, axis=0, factor=128, offset=127)
def test_storage_align_invalid_buffer_index():
func = element_wise
s = tvm.s_tir.Schedule(func, debug_mask='all')
B = s.get_sblock("B")
with pytest.raises(tvm.s_tir.ScheduleError):
s.storage_align(B, 2, axis=0, factor=128, offset=127)
def test_storage_align_invalid_axis():
func = element_wise
s = tvm.s_tir.Schedule(func, debug_mask='all')
B = s.get_sblock("B")
with pytest.raises(tvm.s_tir.ScheduleError):
s.storage_align(B, 0, axis=2, factor=128, offset=127)
def test_storage_align_invalid_annotation():
func = element_wise_invalid_annotation
s = tvm.s_tir.Schedule(func, debug_mask='all')
B = s.get_sblock("B")
with pytest.raises(tvm.s_tir.ScheduleError):
s.storage_align(B, 0, axis=2, factor=128, offset=127)
if __name__ == "__main__":
test_storage_align()
test_storage_align_update()
test_storage_align_invalid_factor1()
test_storage_align_invalid_factor2()
test_storage_align_invalid_buffer()
test_storage_align_invalid_buffer_index()
test_storage_align_invalid_axis()
test_storage_align_invalid_annotation()
@@ -0,0 +1,961 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: E501
import pytest
import tvm
import tvm.testing
from tvm import te, tirx
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.s_tir.tensor_intrin.arm_cpu import (
DP4A_S8S8S32_INTRIN,
DP4A_S8U8S32_INTRIN,
DP4A_U8S8S32_INTRIN,
DP4A_U8U8U32_INTRIN,
ARM_DOT_4x4_i8_NEON_INTRIN,
ARM_DOT_4x4_i8_SDOT_INTRIN,
)
from tvm.s_tir.tensor_intrin.hexagon import VDMPY_i16i16i32_INTRIN, VRMPY_u8u8i32_INTRIN
from tvm.s_tir.tensor_intrin.rocm import AMDGPU_SDOT4_INTRIN
from tvm.s_tir.tensor_intrin.x86 import AVX512_DOT_16x4_INTRIN, VNNI_DOT_16x4_INTRIN
from tvm.script import tirx as T
# fmt: off
# pylint: disable=no-member,invalid-name,unused-variable,line-too-long,redefined-outer-name,unexpected-keyword-arg,too-many-nested-blocks
@T.prim_func(s_tir=True)
def mma_desc(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (16, 16), align=64, offset_factor=1)
B = T.match_buffer(b, (16, 16), align=64, offset_factor=1)
C = T.match_buffer(c, (16, 16), align=64, offset_factor=1)
with T.sblock("root"):
T.reads(C[0 : 16, 0 : 16], A[0 : 16, 0 : 16], B[0 : 16, 0 : 16])
T.writes(C[0 : 16, 0 : 16])
for i, j, k in T.grid(16, 16, 16):
with T.sblock("update"):
vii, vjj, vkk = T.axis.remap("SSR", [i, j, k])
C[vii, vjj] = C[vii, vjj] + A[vii, vkk] * B[vjj, vkk]
@T.prim_func(s_tir=True)
def mma_intrin(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (16, 16), align=64, offset_factor=1)
B = T.match_buffer(b, (16, 16), align=64, offset_factor=1)
C = T.match_buffer(c, (16, 16), align=64, offset_factor=1)
with T.sblock("root"):
T.reads(C[0 : 16, 0 : 16], A[0 : 16, 0 : 16], B[0 : 16, 0 : 16])
T.writes(C[0 : 16, 0 : 16])
T.evaluate(
T.tvm_mma_sync(
C.data,
C.elem_offset // 256,
A.data,
A.elem_offset // 256,
B.data,
B.elem_offset // 256,
C.data,
C.elem_offset // 256,
dtype="handle",
)
)
@T.prim_func(s_tir=True)
def dot_product_desc(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (4,))
B = T.match_buffer(b, (4,))
C = T.match_buffer(c, ())
with T.sblock("root"):
T.reads(C[()], A[0 : 4], B[0 : 4])
T.writes(C[()])
for i in range(0, 4):
with T.sblock("update"):
vi = T.axis.remap("R", [i])
C[()] = C[()] + A[vi] * B[vi]
@T.prim_func(s_tir=True)
def dot_product_intrin(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (4,), offset_factor=1)
B = T.match_buffer(b, (4,), offset_factor=1)
C = T.match_buffer(c, (), offset_factor=1)
with T.sblock("root"):
T.reads(C[()], A[0 : 4], B[0 : 4])
T.writes(C[()])
T.evaluate(
T.call_extern(
"vec4add",
C.data,
C.elem_offset,
A.data,
A.elem_offset,
B.data,
B.elem_offset,
dtype="int32",
)
)
@T.prim_func(s_tir=True)
def dot_product_intrin_annotated(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (4,), offset_factor=1)
B = T.match_buffer(b, (4,), offset_factor=1)
C = T.match_buffer(c, (), offset_factor=1)
with T.sblock("root"):
T.reads(C[()], A[0 : 4], B[0 : 4])
T.writes(C[()])
T.sblock_attr({"test_annotation": True})
T.evaluate(
T.call_extern(
"vec4add",
C.data,
C.elem_offset,
A.data,
A.elem_offset,
B.data,
B.elem_offset,
dtype="int32",
)
)
@T.prim_func(s_tir=True)
def outer_product_desc(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (16, 1), offset_factor=1)
B = T.match_buffer(b, (16, 1), offset_factor=1)
C = T.match_buffer(c, (16, 16), offset_factor=1)
with T.sblock("root"):
T.reads(
C[0 : 16, 0 : 16],
A[0 : 16, 0 : 1],
B[0 : 16, 0 : 1],
)
T.writes(C[0 : 16, 0 : 16])
for i, j in T.grid(16, 16):
with T.sblock("update"):
vii, vjj = T.axis.remap("SS", [i, j])
C[vii, vjj] = C[vii, vjj] + A[vii, 0] * B[vjj, 0]
@T.prim_func(s_tir=True)
def outer_product_intrin(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (16, 1), offset_factor=1)
B = T.match_buffer(b, (16, 1), offset_factor=1)
C = T.match_buffer(c, (16, 16), offset_factor=1)
with T.sblock("root"):
T.reads(
C[0 : 16, 0 : 16],
A[0 : 16, 0 : 1],
B[0 : 16, 0 : 1],
)
T.writes(C[0 : 16, 0 : 16])
T.evaluate(
T.call_extern(
"outer_product",
C.data,
C.elem_offset,
A.data,
A.elem_offset,
B.data,
B.elem_offset,
dtype="int32",
)
)
@T.prim_func(s_tir=True)
def matmul(
A: T.Buffer((128, 128), "float32"),
B: T.Buffer((128, 128), "float32"),
C: T.Buffer((128, 128), "float32"),
) -> None:
for i, j, k in T.grid(128, 128, 128):
with T.sblock("update"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
C[vi, vj] = T.float32(0)
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
@T.prim_func(s_tir=True)
def tensorized_matmul(a: T.handle, b: T.handle, c: T.handle) -> None:
C = T.match_buffer(c, [128, 128], elem_offset=0, align=64, offset_factor=1)
B = T.match_buffer(b, [128, 128], elem_offset=0, align=64, offset_factor=1)
A = T.match_buffer(a, [128, 128], elem_offset=0, align=64, offset_factor=1)
for i_outer, j_outer in T.grid(8, 8):
for i_inner_init, j_inner_init in T.grid(16, 16):
with T.sblock("init"):
vi_init = T.axis.S(128, ((i_outer * 16) + i_inner_init))
vj_init = T.axis.S(128, ((j_outer * 16) + j_inner_init))
C[vi_init, vj_init] = T.float32(0)
for k_outer in T.grid(8):
with T.sblock("update"):
vi, vj, vk = T.axis.remap("SSR", [i_outer, j_outer, k_outer])
T.reads(
[
C[vi * 16 : vi * 16 + 16, vj * 16 : vj * 16 + 16],
A[vi * 16 : vi * 16 + 16, vk * 16 : vk * 16 + 16],
B[vj * 16 : vj * 16 + 16, vk * 16 : vk * 16 + 16],
]
)
T.writes(C[vi * 16 : vi * 16 + 16, vj * 16 : vj * 16 + 16])
A_elem_offset = T.int32()
B_elem_offset = T.int32()
C_elem_offset = T.int32()
A_sub = T.match_buffer(
A[vi * 16 : vi * 16 + 16, vk * 16 : vk * 16 + 16],
[16, 16],
elem_offset=A_elem_offset,
)
B_sub = T.match_buffer(
B[vj * 16 : vj * 16 + 16, vk * 16 : vk * 16 + 16],
[16, 16],
elem_offset=B_elem_offset,
)
C_sub = T.match_buffer(
C[vi * 16 : vi * 16 + 16, vj * 16 : vj * 16 + 16],
[16, 16],
elem_offset=C_elem_offset,
)
T.evaluate(
T.tvm_mma_sync(
C_sub.data,
T.floordiv(C_sub.elem_offset, 256),
A_sub.data,
T.floordiv(A_sub.elem_offset, 256),
B_sub.data,
T.floordiv(B_sub.elem_offset, 256),
C_sub.data,
T.floordiv(C_sub.elem_offset, 256),
dtype="handle",
)
)
@T.prim_func(s_tir=True)
def batch_matmul(
A: T.Buffer((16, 128, 128), "float32"),
B: T.Buffer((16, 128, 128), "float32"),
C: T.Buffer((16, 128, 128), "float32"),
) -> None:
for n, i, j in T.grid(16, 128, 128):
with T.sblock("init"):
vn, vi, vj = T.axis.remap("SSS", [n, i, j])
C[vn, vi, vj] = T.float32(0)
for n, i, j, k in T.grid(16, 128, 128, 128):
with T.sblock("update"):
vn, vi, vj, vk = T.axis.remap("SSSR", [n, i, j, k])
C[vn, vi, vj] = C[vn, vi, vj] + A[vn, vi, vk] * B[vn, vj, vk]
@T.prim_func(s_tir=True)
def tensorized_batch_matmul_mma(
A: T.Buffer((16, 128, 128), "float32"),
B: T.Buffer((16, 128, 128), "float32"),
C: T.Buffer((16, 128, 128), "float32"),
) -> None:
for n, i, j in T.grid(16, 128, 128):
with T.sblock("init"):
vn, vi, vj = T.axis.remap("SSS", [n, i, j])
T.reads()
T.writes(C[vn, vi, vj])
C[vn, vi, vj] = T.float32(0)
for n in range(0, 16):
for i, j, k in T.grid(8, 8, 8):
with T.sblock("update"):
vn, vi, vj, vk = T.axis.remap("SSSR", [n, i, j, k])
T.reads(
C[vn : vn + 1, vi * 16 : vi * 16 + 16, vj * 16 : vj * 16 + 16],
A[vn : vn + 1, vi * 16 : vi * 16 + 16, vk * 16 : vk * 16 + 16],
B[vn : vn + 1, vj * 16 : vj * 16 + 16, vk * 16 : vk * 16 + 16],
)
T.writes(C[vn : vn + 1, vi * 16 : vi * 16 + 16, vj * 16 : vj * 16 + 16])
A_elem_offset = T.int32()
B_elem_offset = T.int32()
C_elem_offset = T.int32()
A_sub = T.match_buffer(
A[vn : vn + 1, vi * 16 : vi * 16 + 16, vk * 16 : vk * 16 + 16],
(16, 16),
elem_offset=A_elem_offset,
)
B_sub = T.match_buffer(
B[vn : vn + 1, vj * 16 : vj * 16 + 16, vk * 16 : vk * 16 + 16],
(16, 16),
elem_offset=B_elem_offset,
)
C_sub = T.match_buffer(
C[vn : vn + 1, vi * 16 : vi * 16 + 16, vj * 16 : vj * 16 + 16],
(16, 16),
elem_offset=C_elem_offset,
)
T.evaluate(
T.tvm_mma_sync(
C_sub.data,
T.floordiv(C_sub.elem_offset, 256),
A_sub.data,
T.floordiv(A_sub.elem_offset, 256),
B_sub.data,
T.floordiv(B_sub.elem_offset, 256),
C_sub.data,
T.floordiv(C_sub.elem_offset, 256),
dtype="handle",
)
)
@T.prim_func(s_tir=True)
def tensorized_batch_matmul_dot_product(
A: T.Buffer((16, 128, 128), "float32"),
B: T.Buffer((16, 128, 128), "float32"),
C: T.Buffer((16, 128, 128), "float32"),
) -> None:
for n, i, j in T.grid(16, 128, 128):
with T.sblock("init"):
vn, vi, vj = T.axis.remap("SSS", [n, i, j])
T.reads()
T.writes(C[vn, vi, vj])
C[vn, vi, vj] = T.float32(0)
for n, i, j, k_0 in T.grid(16, 128, 128, 32):
with T.sblock("blockized_update"):
vn, vi, vj, vko = T.axis.remap("SSSR", [n, i, j, k_0])
T.reads(
C[vn, vi, vj], A[vn, vi, vko * 4 : vko * 4 + 4], B[vn, vj, vko * 4 : vko * 4 + 4]
)
T.writes(C[vn, vi, vj])
A_1 = T.match_buffer(
A[vn, vi, vko * 4 : vko * 4 + 4], [4], dtype="float32", offset_factor=1
)
B_1 = T.match_buffer(
B[vn, vj, vko * 4 : vko * 4 + 4], [4], dtype="float32", offset_factor=1
)
C_1 = T.match_buffer(C[vn, vi, vj], [], dtype="float32", offset_factor=1)
T.evaluate(
T.call_extern(
"vec4add",
C_1.data,
C_1.elem_offset,
A_1.data,
A_1.elem_offset,
B_1.data,
B_1.elem_offset,
dtype="int32",
)
)
@T.prim_func(s_tir=True)
def tensorized_batch_matmul_outer_product(
A: T.Buffer((16, 128, 128), "float32"),
B: T.Buffer((16, 128, 128), "float32"),
C: T.Buffer((16, 128, 128), "float32"),
) -> None:
for n, i, j in T.grid(16, 128, 128):
with T.sblock("init"):
vn, vi, vj = T.axis.remap("SSS", [n, i, j])
T.reads()
T.writes(C[vn, vi, vj])
C[vn, vi, vj] = T.float32(0)
for n, i_0, j_0, k in T.grid(16, 8, 8, 128):
with T.sblock("blockized_update"):
vn, vio, vjo, vk = T.axis.remap("SSSR", [n, i_0, j_0, k])
T.reads(
C[vn, vio * 16 : vio * 16 + 16, vjo * 16 : vjo * 16 + 16],
A[vn, vio * 16 : vio * 16 + 16, vk],
B[vn, vjo * 16 : vjo * 16 + 16, vk],
)
T.writes(C[vn, vio * 16 : vio * 16 + 16, vjo * 16 : vjo * 16 + 16])
A_1 = T.match_buffer(A[vn, vio * 16 : vio * 16 + 16, vk], [16, 1], dtype="float32", offset_factor=1)
B_1 = T.match_buffer(B[vn, vjo * 16 : vjo * 16 + 16, vk], [16, 1], dtype="float32", offset_factor=1
)
C_1 = T.match_buffer(
C[vn, vio * 16 : vio * 16 + 16, vjo * 16 : vjo * 16 + 16], [16, 16], dtype="float32", offset_factor=1
)
T.evaluate(
T.call_extern("outer_product", C_1.data, C_1.elem_offset, A_1.data, A_1.elem_offset,
B_1.data, B_1.elem_offset, dtype="int32"
)
)
@T.prim_func(s_tir=True)
def annotated_mma_desc(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (16, 16), align=64, offset_factor=1)
B = T.match_buffer(b, (16, 16), align=64, offset_factor=1)
C = T.match_buffer(c, (16, 16), align=64, offset_factor=1)
with T.sblock("root"):
T.reads(C[0 : 16, 0 : 16], A[0 : 16, 0 : 16], B[0 : 16, 0 : 16])
T.writes(C[0 : 16, 0 : 16])
for i, j, k in T.grid(16, 16, 16):
with T.sblock("update"):
T.sblock_attr({"test_annotation": True})
vii, vjj, vkk = T.axis.remap("SSR", [i, j, k])
C[vii, vjj] = C[vii, vjj] + A[vii, vkk] * B[vjj, vkk]
@T.prim_func(s_tir=True)
def annotated_matmul(
A: T.Buffer((128, 128), "float32"),
B: T.Buffer((128, 128), "float32"),
C: T.Buffer((128, 128), "float32"),
) -> None:
for i, j, k in T.grid(128, 128, 128):
with T.sblock("update"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
T.sblock_attr({"test_annotation": True})
with T.init():
C[vi, vj] = T.float32(0)
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
@T.prim_func(s_tir=True)
def annotated_tensorized_matmul(a: T.handle, b: T.handle, c: T.handle) -> None:
C = T.match_buffer(c, [128, 128], elem_offset=0, align=64, offset_factor=1)
B = T.match_buffer(b, [128, 128], elem_offset=0, align=64, offset_factor=1)
A = T.match_buffer(a, [128, 128], elem_offset=0, align=64, offset_factor=1)
for i_outer, j_outer in T.grid(8, 8):
for i_inner_init, j_inner_init in T.grid(16, 16):
with T.sblock("init"):
vi_init = T.axis.S(128, ((i_outer * 16) + i_inner_init))
vj_init = T.axis.S(128, ((j_outer * 16) + j_inner_init))
T.sblock_attr({"test_annotation": True})
C[vi_init, vj_init] = T.float32(0)
for k_outer in T.grid(8):
with T.sblock("update"):
vi, vj, vk = T.axis.remap("SSR", [i_outer, j_outer, k_outer])
T.reads(
[
C[vi * 16 : vi * 16 + 16, vj * 16 : vj * 16 + 16],
A[vi * 16 : vi * 16 + 16, vk * 16 : vk * 16 + 16],
B[vj * 16 : vj * 16 + 16, vk * 16 : vk * 16 + 16],
]
)
T.writes(C[vi * 16 : vi * 16 + 16, vj * 16 : vj * 16 + 16])
A_elem_offset = T.int32()
B_elem_offset = T.int32()
C_elem_offset = T.int32()
A_sub = T.match_buffer(
A[vi * 16 : vi * 16 + 16, vk * 16 : vk * 16 + 16],
[16, 16],
elem_offset=A_elem_offset,
)
B_sub = T.match_buffer(
B[vj * 16 : vj * 16 + 16, vk * 16 : vk * 16 + 16],
[16, 16],
elem_offset=B_elem_offset,
)
C_sub = T.match_buffer(
C[vi * 16 : vi * 16 + 16, vj * 16 : vj * 16 + 16],
[16, 16],
elem_offset=C_elem_offset,
)
T.evaluate(
T.tvm_mma_sync(
C_sub.data,
T.floordiv(C_sub.elem_offset, 256),
A_sub.data,
T.floordiv(A_sub.elem_offset, 256),
B_sub.data,
T.floordiv(B_sub.elem_offset, 256),
C_sub.data,
T.floordiv(C_sub.elem_offset, 256),
dtype="handle",
)
)
# fmt: off
# pylint: disable=no-member,invalid-name,unused-variable,line-too-long,redefined-outer-name,unexpected-keyword-arg,too-many-nested-blocks
tirx.TensorIntrin.register("test_mma_intrin", mma_desc, mma_intrin)
tirx.TensorIntrin.register("test_annotated_mma_intrin", annotated_mma_desc, mma_intrin)
tirx.TensorIntrin.register("test_dot_product_intrin", dot_product_desc, dot_product_intrin)
tirx.TensorIntrin.register("test_outer_product_intrin", outer_product_desc, outer_product_intrin)
tirx.TensorIntrin.register("test_dot_product_intrin_annotated", dot_product_desc, dot_product_intrin_annotated)
def test_tensorize_matmul():
func = matmul
# schedule
s = tvm.s_tir.Schedule(func, debug_mask="all")
update = s.get_sblock("update")
i, j, k = s.get_loops(update)
io, ii = s.split(i, factors=[None, 16])
jo, ji = s.split(j, factors=[None, 16])
ko, ki = s.split(k, factors=[None, 16])
s.reorder(io, jo, ko, ii, ji, ki)
s.decompose_reduction(update, ko)
s.tensorize(ii, "test_mma_intrin")
assert_structural_equal_ignore_global_symbol(tensorized_matmul, s.mod["main"])
verify_trace_roundtrip(sch=s, mod=func)
def test_tensorize_batch_matmul():
func = batch_matmul
s = tvm.s_tir.Schedule(func, debug_mask="all")
update = s.get_sblock("update")
_, i, j, k = s.get_loops(update)
io, ii = s.split(i, factors=[None, 16])
jo, ji = s.split(j, factors=[None, 16])
ko, ki = s.split(k, factors=[None, 16])
s.reorder(io, jo, ko, ii, ji, ki)
s.tensorize(ii, "test_mma_intrin")
assert_structural_equal_ignore_global_symbol(tensorized_batch_matmul_mma, s.mod["main"])
verify_trace_roundtrip(sch=s, mod=batch_matmul)
def test_tensorize_dot_product():
func = batch_matmul
s = tvm.s_tir.Schedule(func, debug_mask="all")
C = s.get_sblock("update")
_, _, _, k = s.get_loops(C)
_, ki = s.split(k, factors=[None, 4])
s.tensorize(ki, "test_dot_product_intrin")
assert_structural_equal_ignore_global_symbol(tensorized_batch_matmul_dot_product, s.mod["main"])
verify_trace_roundtrip(sch=s, mod=func)
def test_tensorize_outer_product():
func = batch_matmul
s = tvm.s_tir.Schedule(func, debug_mask="all")
C = s.get_sblock("update")
_, i, j, k = s.get_loops(C)
io, ii = s.split(i, factors=[None, 16])
jo, ji = s.split(j, factors=[None, 16])
s.reorder(io, jo, k, ii, ji)
s.tensorize(ii, "test_outer_product_intrin")
assert_structural_equal_ignore_global_symbol(tensorized_batch_matmul_outer_product, s.mod["main"])
verify_trace_roundtrip(sch=s, mod=func)
def test_tensorize_with_annotation():
func = annotated_matmul
s = tvm.s_tir.Schedule(func, debug_mask="all")
update = s.get_sblock("update")
i, j, k = s.get_loops(update)
io, ii = s.split(i, factors=[None, 16])
jo, ji = s.split(j, factors=[None, 16])
ko, ki = s.split(k, factors=[None, 16])
s.reorder(io, jo, ko, ii, ji, ki)
s.decompose_reduction(update, ko)
s.tensorize(ii, "test_annotated_mma_intrin")
assert_structural_equal_ignore_global_symbol(annotated_tensorized_matmul, s.mod["main"])
verify_trace_roundtrip(sch=s, mod=func)
def test_tensorize_intrinsic_with_annotation():
func = matmul
s = tvm.s_tir.Schedule(func, debug_mask="all")
update = s.get_sblock("update")
_, _, k = s.get_loops(update)
ko, ki = s.split(k, factors=[None, 4])
s.decompose_reduction(update, ko)
s.tensorize(ki, "test_dot_product_intrin_annotated")
b = s.get(s.get_sblock("update_update_o"))
assert b.annotations["test_annotation"] == T.bool(True)
verify_trace_roundtrip(sch=s, mod=func)
def get_matmul_packed(m, n, k, lhs_type, rhs_dtype="int8"):
X = te.placeholder((m, k), name="X", dtype=lhs_type)
W = te.placeholder((n, k), name="W", dtype=rhs_dtype)
ak = te.reduce_axis((0, k), name="k")
matmul = te.compute(
(m, n),
lambda i, j: te.sum(
X[i, ak].astype("int32") * W[j, ak].astype("int32"),
axis=ak,
),
name="compute",
)
return te.create_prim_func([X, W, matmul])
def tensorize_16x4_test(intrin=VNNI_DOT_16x4_INTRIN):
m, n, k = 128, 128, 128
func = get_matmul_packed(m, n, k, "uint8")
sch = tvm.s_tir.Schedule(func, debug_mask="all")
block = sch.get_sblock("compute")
sch.transform_layout(block, "W", lambda i, j: [i//16, j//4, i%16, j%4])
_, j, k = sch.get_loops(block)
_, ji = sch.split(j, factors=[None, 16])
ko, ki = sch.split(k, factors=[None, 4])
sch.reorder(ko, ji, ki)
sch.decompose_reduction(block, ko)
sch.tensorize(ji, intrin)
verify_trace_roundtrip(sch=sch, mod=func)
def test_tensorize_vnni():
tensorize_16x4_test()
def test_tensorize_avx512():
tensorize_16x4_test(AVX512_DOT_16x4_INTRIN)
def test_tensorize_arm_dot():
m, n, k = 128, 128, 128
func = get_matmul_packed(m, n, k, "int8")
for intrin in [ARM_DOT_4x4_i8_SDOT_INTRIN, ARM_DOT_4x4_i8_NEON_INTRIN]:
sch = tvm.s_tir.Schedule(func, debug_mask="all")
block = sch.get_sblock("compute")
sch.transform_layout(block, "W", lambda i, j: [i//4, j//4, i%4, j%4])
_, j, k = sch.get_loops(block)
_, ji = sch.split(j, factors=[None, 4])
ko, ki = sch.split(k, factors=[None, 4])
sch.reorder(ko, ji, ki)
sch.decompose_reduction(block, ko)
sch.tensorize(ji, intrin)
verify_trace_roundtrip(sch=sch, mod=func)
def test_tensorize_vrmpy():
m, n, k = 128, 128, 128
func = get_matmul_packed(m, n, k, "uint8", "uint8")
sch = tvm.s_tir.Schedule(func, debug_mask="all")
block = sch.get_sblock("compute")
sch.transform_layout(block, "W", lambda i, j: [i//32, j//4, i%32, j%4])
_, j, k = sch.get_loops(block)
_, ji = sch.split(j, factors=[None, 32])
ko, ki = sch.split(k, factors=[None, 4])
sch.reorder(ko, ji, ki)
sch.decompose_reduction(block, ko)
sch.tensorize(ji, VRMPY_u8u8i32_INTRIN)
verify_trace_roundtrip(sch=sch, mod=func)
def test_tensorize_vdmpy():
m, n, k = 128, 128, 128
func = get_matmul_packed(m, n, k, "int16", "int16")
sch = tvm.s_tir.Schedule(func, debug_mask="all")
block = sch.get_sblock("compute")
sch.transform_layout(block, "W", lambda i, j: [i//32, j//2, i%32, j%2])
_, j, k = sch.get_loops(block)
_, ji = sch.split(j, factors=[None, 32])
ko, ki = sch.split(k, factors=[None, 2])
sch.reorder(ko, ji, ki)
sch.decompose_reduction(block, ko)
sch.tensorize(ji, VDMPY_i16i16i32_INTRIN)
verify_trace_roundtrip(sch=sch, mod=func)
def test_tensorize_dp4a():
# pylint: disable=too-many-locals
def _test_intrin(dtype_a, dtype_b, dtype_c, intrin):
m, n, k = 128, 128, 128
X = te.placeholder((m, k), name="X", dtype=dtype_a)
W = te.placeholder((n, k), name="W", dtype=dtype_b)
ak = te.reduce_axis((0, k), name="k")
matmul = te.compute(
(m, n),
lambda i, j: te.sum(
X[i, ak].astype(dtype_c) * W[j, ak].astype(dtype_c),
axis=ak,
),
name="compute",
)
func = te.create_prim_func([X, W, matmul])
sch = tvm.s_tir.Schedule(func, debug_mask="all")
block = sch.get_sblock("compute")
i, j, k = sch.get_loops(block)
by, ty, yi = sch.split(i, factors=sch.sample_perfect_tile(i, n=3))
bx, tx, xi = sch.split(j, factors=sch.sample_perfect_tile(j, n=3))
ko, ki = sch.split(k, [None, 4])
ko, kt = sch.split(ko, factors=sch.sample_perfect_tile(ko, n=2))
sch.reorder(by, bx, ty, tx, yi, xi)
CC = sch.cache_write(block, 0, "local")
sch.reverse_compute_at(CC, tx)
def fetch_to_shared(block, idx):
block_read = sch.cache_read(block, idx, "shared")
sch.compute_at(block_read, ko, True)
return block_read
fetch_to_shared(block, 0)
fetch_to_shared(block, 1)
sch.decompose_reduction(block, ko)
sch.tensorize(ki, intrin)
verify_trace_roundtrip(sch=sch, mod=func)
for args in [
("int8", "int8", "int32", AMDGPU_SDOT4_INTRIN),
("int8", "int8", "int32", DP4A_S8S8S32_INTRIN),
("int8", "uint8", "int32", DP4A_S8U8S32_INTRIN),
("uint8", "int8", "int32", DP4A_U8S8S32_INTRIN),
("uint8", "uint8", "uint32", DP4A_U8U8U32_INTRIN),
]:
_test_intrin(*args)
def test_tensor_intrin_look_up():
intrin_name = 'non_existent_intrin'
assert tirx.TensorIntrin.get(intrin_name, allow_missing=True) is None
with pytest.raises(ValueError):
tirx.TensorIntrin.get(intrin_name)
def test_tensorize_matmul_mixed_dtype():
# fmt: off
@T.prim_func(s_tir=True)
def matmul_int64_shape(
A: T.Buffer((T.int64(128), T.int64(128)), "float32"),
B: T.Buffer((T.int64(128), T.int64(128)), "float32"),
C: T.Buffer((T.int64(128), T.int64(128)), "float32")
) -> None:
for i_0, j_0 in T.grid(T.int64(8), T.int64(8)):
for i_1_init, j_1_init in T.grid(T.int64(16), T.int64(16)):
with T.sblock("init"):
vi = T.axis.spatial(T.int64(128), i_0 * T.int64(16) + i_1_init)
vj = T.axis.spatial(T.int64(128), j_0 * T.int64(16) + j_1_init)
C[vi, vj] = T.float32(0)
for k_0, i_1, j_1, k_1 in T.grid(T.int64(8), T.int64(16), T.int64(16), T.int64(16)):
with T.sblock("update"):
vi = T.axis.spatial(T.int64(128), i_0 * T.int64(16) + i_1)
vj = T.axis.spatial(T.int64(128), j_0 * T.int64(16) + j_1)
vk = T.axis.reduce(T.int64(128), k_0 * T.int64(16) + k_1)
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
@T.prim_func(s_tir=True)
def tensorized_matmul_int64_shape(
A: T.Buffer((T.int64(128), T.int64(128)), "float32"),
B: T.Buffer((T.int64(128), T.int64(128)), "float32"),
C: T.Buffer((T.int64(128), T.int64(128)), "float32")
) -> None:
for i_outer, j_outer in T.grid(T.int64(8), T.int64(8)):
for i_inner_init, j_inner_init in T.grid(T.int64(16), T.int64(16)):
with T.sblock("init"):
vi = T.axis.spatial(T.int64(128), i_outer * T.int64(16) + i_inner_init)
vj = T.axis.spatial(T.int64(128), j_outer * T.int64(16) + j_inner_init)
C[vi, vj] = T.float32(0)
for k_outer in T.grid(T.int64(8)):
with T.sblock("update"):
vi, vj, vk = T.axis.remap("SSR", [i_outer, j_outer, k_outer])
T.reads(
[
C[vi * T.int64(16) : vi * T.int64(16) + T.int64(16), vj * T.int64(16) : vj * T.int64(16) + T.int64(16)],
A[vi * T.int64(16) : vi * T.int64(16) + T.int64(16), vk * T.int64(16) : vk * T.int64(16) + T.int64(16)],
B[vj * T.int64(16) : vj * T.int64(16) + T.int64(16), vk * T.int64(16) : vk * T.int64(16) + T.int64(16)],
]
)
T.writes(C[vi * T.int64(16) : vi * T.int64(16) + T.int64(16), vj * T.int64(16) : vj * T.int64(16) + T.int64(16)])
A_elem_offset = T.int64()
B_elem_offset = T.int64()
C_elem_offset = T.int64()
A_sub = T.match_buffer(
A[vi * T.int64(16) : vi * T.int64(16) + T.int64(16), vk * T.int64(16) : vk * T.int64(16) + T.int64(16)],
[T.int64(16), T.int64(16)],
elem_offset=A_elem_offset,
)
B_sub = T.match_buffer(
B[vj * T.int64(16) : vj * T.int64(16) + T.int64(16), vk * T.int64(16) : vk * T.int64(16) + T.int64(16)],
[T.int64(16), T.int64(16)],
elem_offset=B_elem_offset,
)
C_sub = T.match_buffer(
C[vi * T.int64(16) : vi * T.int64(16) + T.int64(16), vj * T.int64(16) : vj * T.int64(16) + T.int64(16)],
[T.int64(16), T.int64(16)],
elem_offset=C_elem_offset,
)
T.evaluate(
T.tvm_mma_sync(
C_sub.data,
T.floordiv(C_sub.elem_offset, T.int64(256)),
A_sub.data,
T.floordiv(A_sub.elem_offset, T.int64(256)),
B_sub.data,
T.floordiv(B_sub.elem_offset, T.int64(256)),
C_sub.data,
T.floordiv(C_sub.elem_offset, T.int64(256)),
dtype="handle",
)
)
# fmt: on
s = tvm.s_tir.Schedule(matmul_int64_shape, debug_mask="all")
update = s.get_sblock("update")
ii = s.get_loops(update)[-3]
s.tensorize(ii, "test_mma_intrin")
assert_structural_equal_ignore_global_symbol(s.mod["main"], tensorized_matmul_int64_shape)
verify_trace_roundtrip(sch=s, mod=matmul_int64_shape)
def _tir_packed_int_to_int_to_float(storage_nbit: int):
storage_dtype = "int" + str(storage_nbit)
def f_convert(nbit: int, val: tirx.Expr, pos: tirx.Expr, dtype: str):
assert val.ty.dtype == storage_dtype
mask = tirx.const((1 << nbit) - 1, "int32")
unextended = (val >> (pos.astype("int32") * tirx.const(nbit, "int32"))) & mask
return tirx.Cast(dtype, (unextended << tirx.const(32 - nbit, "int32")) >> tirx.const(32 - nbit, "int32"))
return f_convert
@T.prim_func(s_tir=True)
def decode_i4s_to_f16_desc(compressed: T.handle, decompressed: T.handle) -> None:
Compressed = T.match_buffer(
compressed,
[
1,
],
dtype="int32",
scope="local",
)
Decompressed = T.match_buffer(
decompressed,
[
8,
],
dtype="float16",
scope="local",
)
with T.sblock("root"):
T.reads(Compressed[0:1])
T.writes(Decompressed[0:8])
for i in T.grid(8):
with T.sblock("decode"):
vi = T.axis.remap("S", [i])
Decompressed[vi] = _tir_packed_int_to_int_to_float(32)(
4,
Compressed[vi // 8],
vi % 8,
dtype="float16",
)
@T.prim_func(s_tir=True)
def decode_i4s_to_f16_impl(compressed: T.handle, decompressed: T.handle) -> None:
Compressed = T.match_buffer(
compressed,
[
1,
],
dtype="int32",
scope="local",
)
Decompressed = T.match_buffer(
decompressed,
[
8,
],
dtype="float16",
scope="local",
)
with T.sblock("root"):
T.reads(Compressed[0:1])
T.writes(Decompressed[0:8])
T.call_extern(
"handle",
"test_decode_i4s_to_f16",
Compressed.data,
Decompressed.data,
8,
)
tirx.TensorIntrin.register("test_decode_i4s_to_f16_intrin", decode_i4s_to_f16_desc, decode_i4s_to_f16_impl)
def test_tensorize_arith_simplification():
# fmt: off
@T.prim_func(s_tir=True)
def decode_i4s_to_int32_to_f16():
B_decode_local = T.sblock_alloc_buffer((16384, 16384), "float16", scope="local")
B_local = T.sblock_alloc_buffer((16384, 2048), "int32", scope="local")
for ax0_0 in T.thread_binding(8192, thread="blockIdx.x"):
for ax0_1 in T.thread_binding(2, thread="threadIdx.y"):
for ax1_0 in range(32):
for ax1_1 in T.thread_binding(64, thread="threadIdx.x"):
for ax0, ax1 in T.grid(1, 8):
with T.sblock("B_decode_local"):
v0 = T.axis.spatial(16384, ax0_0 * 2 + ax0_1 + ax0)
v1 = T.axis.spatial(16384, ax1_0 * 512 + ax1_1 * 8 + ax1)
T.reads(B_local[v0, v1 // 8])
T.writes(B_decode_local[v0, v1])
B_decode_local[v0, v1] = T.Cast("float16", T.shift_right(T.shift_left(T.bitwise_and(T.shift_right(B_local[v0, v1 // 8], v1 % 8 * 4), 15), 28), 28))
@T.prim_func(s_tir=True)
def tensorized_decode_i4s_to_int32_to_f16():
B_decode_local = T.sblock_alloc_buffer((16384, 16384), "float16", scope="local")
B_local = T.sblock_alloc_buffer((16384, 2048), "int32", scope="local")
for ax0_0 in T.thread_binding(8192, thread="blockIdx.x"):
for ax0_1 in T.thread_binding(2, thread="threadIdx.y"):
for ax1_0 in range(32):
for ax1_1 in T.thread_binding(64, thread="threadIdx.x"):
for ax0 in range(1):
with T.sblock("B_decode_local_o"):
v0_o = T.axis.spatial(16384, ax0_0 * 2 + ax0_1 + ax0)
v1_o = T.axis.spatial(2048, ax1_0 * 64 + ax1_1)
T.reads(B_local[v0_o, v1_o])
T.writes(B_decode_local[v0_o, v1_o * 8:v1_o * 8 + 8])
Compressed = T.match_buffer(B_local[v0_o, v1_o], (1,), "int32", scope="local")
Decompressed = T.match_buffer(B_decode_local[v0_o, v1_o * 8:v1_o * 8 + 8], (8,), "float16", scope="local")
T.call_extern("handle", "test_decode_i4s_to_f16", Compressed.data, Decompressed.data, 8)
s = tvm.s_tir.Schedule(decode_i4s_to_int32_to_f16, debug_mask="all")
update = s.get_sblock("B_decode_local")
ii = s.get_loops(update)[-1]
s.tensorize(ii, "test_decode_i4s_to_f16_intrin")
assert_structural_equal_ignore_global_symbol(s.mod["main"], tensorized_decode_i4s_to_int32_to_f16)
verify_trace_roundtrip(sch=s, mod=decode_i4s_to_int32_to_f16)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,464 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-docstring
import numpy as np
import pytest
import tvm
import tvm.testing
from tvm import te
from tvm.s_tir.tensor_intrin.cuda import (
LDMATRIX_e4m3_A_INTRIN,
LDMATRIX_e4m3_B_TRANS_INTRIN,
LDMATRIX_e5m2_A_INTRIN,
LDMATRIX_e5m2_B_TRANS_INTRIN,
LDMATRIX_f16_A_INTRIN,
LDMATRIX_f16_B_INTRIN,
LDMATRIX_f16_B_TRANS_INTRIN,
LDMATRIX_i8_A_INTRIN,
LDMATRIX_i8_B_INTRIN,
LDMATRIX_i8_B_TRANS_INTRIN,
MMA_e4m3e4m3f32_TRANS_B_INTRIN,
MMA_e5m2e5m2f32_TRANS_B_INTRIN,
MMA_f16f16f16_INTRIN,
MMA_f16f16f16_TRANS_B_INTRIN,
MMA_f16f16f32_INTRIN,
MMA_f16f16f32_TRANS_B_INTRIN,
MMA_fill_16x16_f16_INTRIN,
MMA_fill_16x16_f32_INTRIN,
MMA_fill_16x16_i32_INTRIN,
MMA_i8i8i32_INTRIN,
MMA_i8i8i32_TRANS_B_INTRIN,
MMA_store_16x16_f16_global_INTRIN,
MMA_store_16x16_f32_global_INTRIN,
MMA_store_16x16_i32_global_INTRIN,
shared_16x16_to_ldmatrix_32x8_layout,
shared_16x32_to_ldmatrix_32x16_layout,
shared_32x16_to_ldmatrix_32x16_layout,
)
from tvm.testing import env
from tvm.testing.tir import mma_schedule
M = 4096
N = 4096
K = 4096
measure_perf = False
gflops = (N * M * K) * 2 / 1e9
def matmul(m, n, k, in_dtype, out_dtype, b_transposed):
b_shape = (n, k) if b_transposed else (k, n)
a = te.placeholder((m, k), name="A", dtype=in_dtype)
b = te.placeholder(b_shape, name="B", dtype=in_dtype)
k = te.reduce_axis((0, k), name="k")
def maybe_cast(v):
if in_dtype != out_dtype:
return tvm.tirx.Cast(out_dtype, v)
return v
def maybe_swap(i, j):
if b_transposed:
return j, i
return i, j
c = te.compute(
(m, n),
lambda i, j: te.sum(maybe_cast(a[i, k]) * maybe_cast(b[maybe_swap(k, j)]), axis=[k]),
name="C",
)
return (a, b, c)
def run_test(
k_inner,
in_dtype,
out_dtype,
b_transposed,
i_factors,
j_factors,
k_factors,
index_map_A,
index_map_B,
index_map_C,
ldmatrix_a_intrin,
ldmatrix_b_intrin,
mma_intrin,
mma_fill_intrin,
mma_store_intrin,
):
sch = mma_schedule(
te.create_prim_func(matmul(M, N, K, in_dtype, out_dtype, b_transposed)),
k_inner,
in_dtype,
b_transposed,
i_factors,
j_factors,
k_factors,
index_map_A,
index_map_B,
index_map_C,
ldmatrix_a_intrin,
ldmatrix_b_intrin,
mma_intrin,
mma_fill_intrin,
mma_store_intrin,
)
f = tvm.compile(sch.mod["main"], target="cuda")
if in_dtype == "float16":
a_np = np.random.normal(size=(M, K)).astype("float16")
if b_transposed:
b_np = np.random.normal(size=(N, K)).astype("float16")
c_np = np.dot(a_np.astype("float32"), b_np.astype("float32").transpose()).astype(
out_dtype
)
else:
b_np = np.random.normal(size=(K, N)).astype("float16")
c_np = np.dot(a_np.astype("float32"), b_np.astype("float32")).astype(out_dtype)
elif in_dtype in ["float8_e4m3fn", "float8_e5m2"]:
typemap = {
"float8_e4m3fn": "float8_e4m3fn",
"float8_e5m2": "float8_e5m2",
}
a_np = (
np.random.uniform(low=-5, high=5, size=(M * K))
.reshape((M, K))
.astype(typemap[in_dtype])
)
if b_transposed:
b_np = (
np.random.uniform(low=-5, high=5, size=(N * K))
.reshape((N, K))
.astype(typemap[in_dtype])
)
c_np = np.dot(a_np.astype("float32"), b_np.T.astype("float32")).astype(out_dtype)
else:
b_np = (
np.random.uniform(low=-5, high=5, size=(N * K))
.reshape((K, N))
.astype(typemap[in_dtype])
)
c_np = np.dot(a_np.astype("float32"), b_np.astype("float32")).astype(out_dtype)
else:
a_np = np.random.randint(-128, 128, (M, K)).astype("int8")
if b_transposed:
b_np = np.random.randint(-128, 128, (N, K)).astype("int8")
c_np = np.dot(a_np.astype("float32"), b_np.astype("float32").transpose()).astype(
"int32"
)
else:
b_np = np.random.randint(-128, 128, (K, N)).astype("int8")
c_np = np.dot(a_np.astype("float32"), b_np.astype("float32")).astype("int32")
def run_and_check(measure=False):
dev = tvm.device("cuda", 0)
a = tvm.runtime.tensor(a_np, dev)
b = tvm.runtime.tensor(b_np, dev)
c = tvm.runtime.tensor(np.zeros((M, N), dtype=out_dtype), dev)
if measure:
return f.time_evaluator(f.entry_name, dev, number=500)(a, b, c)
f(a, b, c)
dev.sync()
if out_dtype != "float16" and in_dtype not in ["float8_e4m3fn", "float8_e5m2"]:
tvm.testing.assert_allclose(c.numpy(), c_np, rtol=1e-2, atol=1e-2)
tvm.testing.run_with_gpu_lock(run_and_check)
return lambda: tvm.testing.run_with_gpu_lock(run_and_check, True)
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(8), reason="need cuda compute >= 8.0")
def test_f16f16f32_m16n16k16():
def index_map(i, j):
return (
i // 16,
j // 16,
*shared_16x16_to_ldmatrix_32x8_layout(i % 16, j % 16),
)
k_inner = 16
in_dtype = "float16"
out_dtype = "float32"
i_factors, j_factors, k_factors = [4, 8, 2, 4, 1], [1, 64, 2, 1, 2], [128, 2, 1]
timer = run_test(
k_inner,
in_dtype,
out_dtype,
False, # b_transposed
i_factors,
j_factors,
k_factors,
index_map,
index_map,
index_map,
LDMATRIX_f16_A_INTRIN,
LDMATRIX_f16_B_INTRIN,
MMA_f16f16f32_INTRIN,
MMA_fill_16x16_f32_INTRIN,
MMA_store_16x16_f32_global_INTRIN,
)
if measure_perf and timer:
print("f16f16f32_m16n16k16: %f GFLOPS" % (gflops / (timer().mean)))
timer = run_test(
k_inner,
in_dtype,
out_dtype,
True, # b_transposed
i_factors,
j_factors,
k_factors,
index_map,
index_map,
index_map,
LDMATRIX_f16_A_INTRIN,
LDMATRIX_f16_B_TRANS_INTRIN,
MMA_f16f16f32_TRANS_B_INTRIN,
MMA_fill_16x16_f32_INTRIN,
MMA_store_16x16_f32_global_INTRIN,
)
if measure_perf and timer:
print("f16f16f32_m16n16k16_trans: %f GFLOPS" % (gflops / (timer().mean)))
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(8), reason="need cuda compute >= 8.0")
def test_f16f16f16_m16n16k16():
def index_map(i, j):
return (
i // 16,
j // 16,
*shared_16x16_to_ldmatrix_32x8_layout(i % 16, j % 16),
)
k_inner = 16
in_dtype = "float16"
out_dtype = "float16"
i_factors, j_factors, k_factors = [16, 2, 1, 4, 2], [16, 2, 2, 1, 4], [128, 2, 1]
timer = run_test(
k_inner,
in_dtype,
out_dtype,
False, # b_transposed
i_factors,
j_factors,
k_factors,
index_map,
index_map,
index_map,
LDMATRIX_f16_A_INTRIN,
LDMATRIX_f16_B_INTRIN,
MMA_f16f16f16_INTRIN,
MMA_fill_16x16_f16_INTRIN,
MMA_store_16x16_f16_global_INTRIN,
)
if measure_perf and timer:
print("f16f16f16_m16n16k16: %f GFLOPS" % (gflops / (timer().mean)))
timer = run_test(
k_inner,
in_dtype,
out_dtype,
True, # b_transposed
i_factors,
j_factors,
k_factors,
index_map,
index_map,
index_map,
LDMATRIX_f16_A_INTRIN,
LDMATRIX_f16_B_TRANS_INTRIN,
MMA_f16f16f16_TRANS_B_INTRIN,
MMA_fill_16x16_f16_INTRIN,
MMA_store_16x16_f16_global_INTRIN,
)
if measure_perf and timer:
print("f16f16f16_m16n16k16_trans: %f GFLOPS" % (gflops / (timer().mean)))
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(8), reason="need cuda compute >= 8.0")
def test_i8i8i32_m16n16k32():
def index_map_A(i, j):
return (
i // 16,
j // 32,
*shared_16x32_to_ldmatrix_32x16_layout(i % 16, j % 32),
)
def index_map_B(i, j):
return (
i // 32,
j // 16,
*shared_32x16_to_ldmatrix_32x16_layout(i % 32, j % 16),
)
def index_map_C(i, j):
return (
i // 16,
j // 16,
*shared_16x16_to_ldmatrix_32x8_layout(i % 16, j % 16),
)
k_inner = 32
in_dtype = "int8"
out_dtype = "int32"
i_factors, j_factors, k_factors = [1, 32, 1, 4, 2], [8, 4, 4, 2, 1], [32, 2, 2]
timer = run_test(
k_inner,
in_dtype,
out_dtype,
False, # b_transposed
i_factors,
j_factors,
k_factors,
index_map_A,
index_map_B,
index_map_C,
LDMATRIX_i8_A_INTRIN,
LDMATRIX_i8_B_INTRIN,
MMA_i8i8i32_INTRIN,
MMA_fill_16x16_i32_INTRIN,
MMA_store_16x16_i32_global_INTRIN,
)
if measure_perf and timer:
print("i8i8i32_m16n16k32: %f GOPS" % (gflops / (timer().mean)))
timer = run_test(
k_inner,
in_dtype,
out_dtype,
True, # b_transposed
i_factors,
j_factors,
k_factors,
index_map_A,
index_map_A,
index_map_C,
LDMATRIX_i8_A_INTRIN,
LDMATRIX_i8_B_TRANS_INTRIN,
MMA_i8i8i32_TRANS_B_INTRIN,
MMA_fill_16x16_i32_INTRIN,
MMA_store_16x16_i32_global_INTRIN,
)
if measure_perf and timer:
print("i8i8i32_m16n16k32_trans: %f GOPS" % (gflops / (timer().mean)))
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(8, 9), reason="need cuda compute >= 8.9")
def test_e4m3e4m3f32_m16n16k32():
def index_map_A(i, j):
return (
i // 16,
j // 32,
*shared_16x32_to_ldmatrix_32x16_layout(i % 16, j % 32),
)
def index_map_C(i, j):
return (
i // 16,
j // 16,
*shared_16x16_to_ldmatrix_32x8_layout(i % 16, j % 16),
)
k_inner = 32
in_dtype = "float8_e4m3fn"
out_dtype = "float32"
i_factors, j_factors, k_factors = [1, 32, 1, 4, 2], [8, 4, 4, 2, 1], [32, 2, 2]
timer = run_test(
k_inner,
in_dtype,
out_dtype,
True, # b_transposed
i_factors,
j_factors,
k_factors,
index_map_A,
index_map_A,
index_map_C,
LDMATRIX_e4m3_A_INTRIN,
LDMATRIX_e4m3_B_TRANS_INTRIN,
MMA_e4m3e4m3f32_TRANS_B_INTRIN,
MMA_fill_16x16_f32_INTRIN,
MMA_store_16x16_f32_global_INTRIN,
)
if measure_perf and timer:
print("e4m3e4m3f32_m16n16k32_trans: %f GOPS" % (gflops / (timer().mean)))
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(8, 9), reason="need cuda compute >= 8.9")
def test_e5m2e5m2f32_m16n16k32():
def index_map_A(i, j):
return (
i // 16,
j // 32,
*shared_16x32_to_ldmatrix_32x16_layout(i % 16, j % 32),
)
def index_map_C(i, j):
return (
i // 16,
j // 16,
*shared_16x16_to_ldmatrix_32x8_layout(i % 16, j % 16),
)
k_inner = 32
in_dtype = "float8_e5m2"
out_dtype = "float32"
i_factors, j_factors, k_factors = [1, 32, 1, 4, 2], [8, 4, 4, 2, 1], [32, 2, 2]
timer = run_test(
k_inner,
in_dtype,
out_dtype,
True, # b_transposed
i_factors,
j_factors,
k_factors,
index_map_A,
index_map_A,
index_map_C,
LDMATRIX_e5m2_A_INTRIN,
LDMATRIX_e5m2_B_TRANS_INTRIN,
MMA_e5m2e5m2f32_TRANS_B_INTRIN,
MMA_fill_16x16_f32_INTRIN,
MMA_store_16x16_f32_global_INTRIN,
)
if measure_perf and timer:
print("e5m2e5m2f32_m16n16k32_trans: %f GOPS" % (gflops / (timer().mean)))
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,319 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-docstring
import numpy as np
import pytest
import tvm
import tvm.testing
from tvm import te
from tvm.s_tir.tensor_intrin.rocm import (
ROCM_MFMA_f16f16f32_INTRIN,
ROCM_MFMA_f32f32f32_INTRIN,
ROCM_MFMA_fill_16x16_f32_INTRIN,
ROCM_MFMA_fill_16x16_i32_INTRIN,
ROCM_MFMA_LOAD_16x4_A_SHARED_f32_INTRIN,
ROCM_MFMA_LOAD_16x4_B_SHARED_f32_INTRIN,
ROCM_MFMA_LOAD_16x16_A_SHARED_f16_INTRIN,
ROCM_MFMA_LOAD_16x16_A_SHARED_s8_INTRIN,
ROCM_MFMA_LOAD_16x16_B_SHARED_f16_INTRIN,
ROCM_MFMA_LOAD_16x16_B_SHARED_s8_INTRIN,
ROCM_MFMA_s8s8s32_INTRIN,
ROCM_MFMA_STORE_16x16_f32_INTRIN,
ROCM_MFMA_STORE_16x16_s32_INTRIN,
shared_4x16_to_local_64x1_layout_B,
shared_16x4_to_local_64x1_layout_A,
shared_16x16_to_local_64x4_layout_A,
shared_16x16_to_local_64x4_layout_B,
shared_16x16_to_local_64x4_layout_C,
)
from tvm.testing import env
from tvm.testing.tir import mfma_schedule
M = 1024
N = 1024
K = 1024
measure_perf = False
gflops = (N * M * K) * 2 / 1e9
def matmul(m, n, k, in_dtype, out_dtype, b_transposed):
b_shape = (n, k) if b_transposed else (k, n)
a = te.placeholder((m, k), name="A", dtype=in_dtype)
b = te.placeholder(b_shape, name="B", dtype=in_dtype)
k = te.reduce_axis((0, k), name="k")
def maybe_cast(v):
if in_dtype != out_dtype:
return tvm.tirx.Cast(out_dtype, v)
return v
def maybe_swap(i, j):
if b_transposed:
return j, i
return i, j
c = te.compute(
(m, n),
lambda i, j: te.sum(maybe_cast(a[i, k]) * maybe_cast(b[maybe_swap(k, j)]), axis=[k]),
name="C",
)
return (a, b, c)
def run_test(
k_inner,
in_dtype,
out_dtype,
b_transposed,
i_factors,
j_factors,
k_factors,
index_map_A,
index_map_B,
index_map_C,
ldmatrix_a_intrin,
ldmatrix_b_intrin,
mma_intrin,
mma_fill_intrin,
mma_store_intrin,
):
sch = mfma_schedule(
te.create_prim_func(matmul(M, N, K, in_dtype, out_dtype, b_transposed)),
k_inner,
in_dtype,
b_transposed,
i_factors,
j_factors,
k_factors,
index_map_A,
index_map_B,
index_map_C,
ldmatrix_a_intrin,
ldmatrix_b_intrin,
mma_intrin,
mma_fill_intrin,
mma_store_intrin,
)
f = tvm.compile(sch.mod["main"], target="rocm")
if in_dtype == "float32":
a_np = np.random.uniform(size=(M, K)).astype("float32")
if b_transposed:
b_np = np.random.uniform(size=(N, K)).astype("float32")
c_np = np.dot(a_np.astype("float32"), b_np.astype("float32").transpose()).astype(
out_dtype
)
else:
b_np = np.random.uniform(size=(K, N)).astype("float32")
c_np = np.dot(a_np.astype("float32"), b_np.astype("float32")).astype(out_dtype)
elif in_dtype == "float16":
a_np = np.random.uniform(size=(M, K)).astype("float16")
if b_transposed:
b_np = np.random.uniform(size=(N, K)).astype("float16")
c_np = np.dot(a_np.astype("float32"), b_np.astype("float32").transpose()).astype(
out_dtype
)
else:
b_np = np.random.uniform(size=(K, N)).astype("float16")
c_np = np.dot(a_np.astype("float32"), b_np.astype("float32")).astype(out_dtype)
else:
a_np = np.random.randint(-128, 128, (M, K)).astype("int8")
if b_transposed:
b_np = np.random.randint(-128, 128, (N, K)).astype("int8")
c_np = np.dot(a_np.astype("float32"), b_np.astype("float32").transpose()).astype(
"int32"
)
else:
b_np = np.random.randint(-128, 128, (K, N)).astype("int8")
c_np = np.dot(a_np.astype("float32"), b_np.astype("float32")).astype("int32")
def run_and_check(measure=False):
dev = tvm.device("rocm", 0)
a = tvm.runtime.tensor(a_np, dev)
b = tvm.runtime.tensor(b_np, dev)
c = tvm.runtime.tensor(np.zeros((M, N), dtype=out_dtype), dev)
if measure:
return f.time_evaluator(f.entry_name, dev, number=500)(a, b, c)
f(a, b, c)
dev.sync()
if in_dtype != "float16":
tvm.testing.assert_allclose(c.numpy(), c_np, rtol=1e-2, atol=1e-2)
tvm.testing.run_with_gpu_lock(run_and_check)
return lambda: tvm.testing.run_with_gpu_lock(run_and_check, True)
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_matrixcore(), reason="need matrixcore")
def test_i8i8i32_m16n16k16():
def index_map_A(i, j):
return (
i // 16,
j // 16,
*shared_16x16_to_local_64x4_layout_A(i % 16, j % 16),
)
def index_map_B(i, j):
return (
i // 16,
j // 16,
*shared_16x16_to_local_64x4_layout_B(i % 16, j % 16),
)
def index_map_C(i, j):
return (
i // 16,
j // 16,
*shared_16x16_to_local_64x4_layout_C(i % 16, j % 16),
)
k_inner = 16
in_dtype = "int8"
out_dtype = "int32"
i_factors, j_factors, k_factors = [1, 8, 2, 4, 1], [1, 16, 2, 1, 2], [32, 2, 1]
timer = run_test(
k_inner,
in_dtype,
out_dtype,
False, # b_transposed
i_factors,
j_factors,
k_factors,
index_map_A,
index_map_B,
index_map_C,
ROCM_MFMA_LOAD_16x16_A_SHARED_s8_INTRIN,
ROCM_MFMA_LOAD_16x16_B_SHARED_s8_INTRIN,
ROCM_MFMA_s8s8s32_INTRIN,
ROCM_MFMA_fill_16x16_i32_INTRIN,
ROCM_MFMA_STORE_16x16_s32_INTRIN,
)
if measure_perf and timer:
print("test_i8i8i32_m16n16k16: %f GFLOPS" % (gflops / (timer().mean)))
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_matrixcore(), reason="need matrixcore")
def test_f16f16f32_m16n16k16():
def index_map_A(i, j):
return (
i // 16,
j // 16,
*shared_16x16_to_local_64x4_layout_A(i % 16, j % 16),
)
def index_map_B(i, j):
return (
i // 16,
j // 16,
*shared_16x16_to_local_64x4_layout_B(i % 16, j % 16),
)
def index_map_C(i, j):
return (
i // 16,
j // 16,
*shared_16x16_to_local_64x4_layout_C(i % 16, j % 16),
)
k_inner = 16
in_dtype = "float16"
out_dtype = "float32"
i_factors, j_factors, k_factors = [1, 8, 2, 4, 1], [1, 16, 2, 1, 2], [32, 2, 1]
timer = run_test(
k_inner,
in_dtype,
out_dtype,
False, # b_transposed
i_factors,
j_factors,
k_factors,
index_map_A,
index_map_B,
index_map_C,
ROCM_MFMA_LOAD_16x16_A_SHARED_f16_INTRIN,
ROCM_MFMA_LOAD_16x16_B_SHARED_f16_INTRIN,
ROCM_MFMA_f16f16f32_INTRIN,
ROCM_MFMA_fill_16x16_f32_INTRIN,
ROCM_MFMA_STORE_16x16_f32_INTRIN,
)
if measure_perf and timer:
print("f16f16f32_m16n16k16: %f GFLOPS" % (gflops / (timer().mean)))
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_matrixcore(), reason="need matrixcore")
def test_f32f32f32_m16n16k4():
def index_map_A(i, j):
return (
i // 16,
j // 16,
*shared_16x4_to_local_64x1_layout_A(i % 16, j % 16),
)
def index_map_B(i, j):
return (
i // 16,
j // 16,
*shared_4x16_to_local_64x1_layout_B(i % 16, j % 16),
)
def index_map_C(i, j):
return (
i // 16,
j // 16,
*shared_16x16_to_local_64x4_layout_C(i % 16, j % 16),
)
k_inner = 4
in_dtype = "float32"
out_dtype = "float32"
i_factors, j_factors, k_factors = [4, 2, 1, 4, 2], [4, 2, 2, 1, 4], [128, 2, 1]
timer = run_test(
k_inner,
in_dtype,
out_dtype,
False, # b_transposed
i_factors,
j_factors,
k_factors,
index_map_A,
index_map_B,
index_map_C,
ROCM_MFMA_LOAD_16x4_A_SHARED_f32_INTRIN,
ROCM_MFMA_LOAD_16x4_B_SHARED_f32_INTRIN,
ROCM_MFMA_f32f32f32_INTRIN,
ROCM_MFMA_fill_16x16_f32_INTRIN,
ROCM_MFMA_STORE_16x16_f32_INTRIN,
)
if measure_perf and timer:
print("test_f32f32f32_m16n16k4: %f GFLOPS" % (gflops / (timer().mean)))
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,399 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# mypy: ignore-errors
# ruff: noqa: E501, F401
import sys
import pytest
import tvm
import tvm.testing
from tvm import s_tir, tirx
from tvm.s_tir.schedule import Instruction, InstructionKind, LoopRV, SBlockRV, Trace
from tvm.s_tir.schedule.testing import assert_structural_equal_ignore_global_symbol
from tvm.script import tirx as T
# pylint: disable=no-member,invalid-name,unused-variable
@T.prim_func(s_tir=True)
def elementwise(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
B = T.sblock_alloc_buffer((128, 128))
C = T.match_buffer(c, (128, 128))
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def elementwise_inlined(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
C = T.match_buffer(c, (128, 128))
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = A[vi, vj] * 2.0 + 1.0
# pylint: enable=no-member,invalid-name,unused-variable
def _make_get_sblock(name, output):
return Instruction(
kind=InstructionKind.get("GetSBlock"),
inputs=[],
attrs=[name, "main"],
outputs=[output],
)
def _make_get_loops(input, outputs): # pylint: disable=redefined-builtin
return Instruction(
kind=InstructionKind.get("GetLoops"),
inputs=[input],
attrs=[],
outputs=outputs,
)
def _make_compute_inline(input): # pylint: disable=redefined-builtin
return Instruction(
kind=InstructionKind.get("ComputeInline"),
inputs=[input],
attrs=[],
outputs=[],
)
def _make_split(inputs, outputs): # pylint: disable=redefined-builtin
return Instruction(
kind=InstructionKind.get("Split"),
inputs=inputs,
attrs=[T.bool(True), T.bool(False)],
outputs=outputs,
)
def _make_enter_postproc():
return Instruction(
kind=InstructionKind.get("EnterPostproc"),
inputs=[],
attrs=[],
outputs=[],
)
def _make_annotate(block: SBlockRV, annotation: str):
return Instruction(
kind=InstructionKind.get("Annotate"),
inputs=[block, annotation],
attrs=["meta_schedule.auto_tensorize"],
outputs=[],
)
def _make_trace_1(b0, l1, l2): # pylint: disable=invalid-name
return Trace(
insts=[
_make_get_sblock(name="block", output=b0),
_make_get_loops(input=b0, outputs=[l1, l2]),
],
decisions={},
)
def _make_trace_2(b0): # pylint: disable=invalid-name
return Trace(
insts=[
_make_get_sblock(name="B", output=b0),
_make_compute_inline(input=b0),
],
decisions={},
)
def _make_trace_3(b0, b1, add_postproc): # pylint: disable=invalid-name
if add_postproc:
insts = [
_make_get_sblock(name="B", output=b0),
_make_compute_inline(input=b0),
_make_get_sblock(name="C", output=b1),
_make_enter_postproc(),
_make_compute_inline(input=b1),
]
else:
insts = [
_make_get_sblock(name="B", output=b0),
_make_compute_inline(input=b0),
_make_get_sblock(name="C", output=b1),
]
return Trace(insts=insts, decisions={})
def _make_trace_4(b0, l1, l2, l3): # pylint: disable=invalid-name
return Trace(
insts=[
_make_get_sblock(name="B", output=b0),
_make_get_loops(input=b0, outputs=[l1]),
_make_split([l1, None, T.int32(32)], [l2, l3]),
],
decisions={},
)
def test_trace_construct_1():
trace = _make_trace_1(SBlockRV(), LoopRV(), LoopRV())
assert str(trace) == "\n".join(
(
"# from tvm import s_tir",
"def apply_trace(sch: s_tir.Schedule) -> None:",
' b0 = sch.get_sblock(name="block", func_name="main")',
" l1, l2 = sch.get_loops(block=b0)",
)
)
assert len(trace.insts) == 2
assert len(trace.decisions) == 0
def test_trace_construct_get_decision_1():
trace = _make_trace_1(SBlockRV(), LoopRV(), LoopRV())
assert trace.get_decision(trace.insts[0]) is None
assert trace.get_decision(trace.insts[1]) is None
def test_trace_construct_append_1():
trace = _make_trace_1(SBlockRV(), LoopRV(), LoopRV())
trace.append(inst=_make_get_sblock("block2", SBlockRV()))
assert str(trace) == "\n".join(
(
"# from tvm import s_tir",
"def apply_trace(sch: s_tir.Schedule) -> None:",
' b0 = sch.get_sblock(name="block", func_name="main")',
" l1, l2 = sch.get_loops(block=b0)",
' b3 = sch.get_sblock(name="block2", func_name="main")',
)
)
def test_trace_construct_pop_1():
trace = _make_trace_1(SBlockRV(), LoopRV(), LoopRV())
last_inst = trace.insts[-1]
assert trace.pop().same_as(last_inst)
assert str(trace) == "\n".join(
(
"# from tvm import s_tir",
"def apply_trace(sch: s_tir.Schedule) -> None:",
' b0 = sch.get_sblock(name="block", func_name="main")',
)
)
def test_trace_construct_pop_2():
trace = Trace([], {})
assert str(trace) == "\n".join(
(
"# from tvm import s_tir",
"def apply_trace(sch: s_tir.Schedule) -> None:",
" pass",
)
)
assert trace.pop() is None
assert str(trace) == "\n".join(
(
"# from tvm import s_tir",
"def apply_trace(sch: s_tir.Schedule) -> None:",
" pass",
)
)
def test_trace_apply_to_schedule():
trace = _make_trace_2(SBlockRV())
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
trace.apply_to_schedule(sch, remove_postproc=False, decision_provider=None)
assert_structural_equal_ignore_global_symbol(elementwise_inlined, sch.mod["main"])
def test_trace_as_json_1():
trace = _make_trace_1(SBlockRV(), LoopRV(), LoopRV())
obj = trace.as_json()
assert obj == [
[
["GetSBlock", [], ["block", "main"], ["b0"]],
["GetLoops", ["b0"], [], ["l1", "l2"]],
],
[],
]
def test_trace_simplified_1():
trace = _make_trace_3(SBlockRV(), SBlockRV(), add_postproc=True)
assert str(trace) == "\n".join(
(
"# from tvm import s_tir",
"def apply_trace(sch: s_tir.Schedule) -> None:",
' b0 = sch.get_sblock(name="B", func_name="main")',
" sch.compute_inline(block=b0)",
' b1 = sch.get_sblock(name="C", func_name="main")',
" sch.enter_postproc()",
" sch.compute_inline(block=b1)",
)
)
trace = trace.simplified(remove_postproc=True)
assert str(trace) == "\n".join(
(
"# from tvm import s_tir",
"def apply_trace(sch: s_tir.Schedule) -> None:",
' b0 = sch.get_sblock(name="B", func_name="main")',
" sch.compute_inline(block=b0)",
)
)
def test_trace_simplified_2():
trace = _make_trace_3(SBlockRV(), SBlockRV(), add_postproc=True)
assert str(trace) == "\n".join(
(
"# from tvm import s_tir",
"def apply_trace(sch: s_tir.Schedule) -> None:",
' b0 = sch.get_sblock(name="B", func_name="main")',
" sch.compute_inline(block=b0)",
' b1 = sch.get_sblock(name="C", func_name="main")',
" sch.enter_postproc()",
" sch.compute_inline(block=b1)",
)
)
trace = trace.simplified(remove_postproc=False)
assert str(trace) == "\n".join(
(
"# from tvm import s_tir",
"def apply_trace(sch: s_tir.Schedule) -> None:",
' b0 = sch.get_sblock(name="B", func_name="main")',
" sch.compute_inline(block=b0)",
' b1 = sch.get_sblock(name="C", func_name="main")',
" sch.enter_postproc()",
" sch.compute_inline(block=b1)",
)
)
def test_trace_simplified_3():
trace = _make_trace_4(SBlockRV(), LoopRV(), LoopRV(), LoopRV()).simplified(
remove_postproc=False
)
assert str(trace) == "\n".join(
(
"# from tvm import s_tir",
"def apply_trace(sch: s_tir.Schedule) -> None:",
' b0 = sch.get_sblock(name="B", func_name="main")',
" l1, = sch.get_loops(block=b0)",
" l2, l3 = sch.split(loop=l1, factors=[None, 32], preserve_unit_iters=True, disable_predication=False)",
)
)
def test_apply_json_to_schedule_1():
trace = _make_trace_2(SBlockRV())
json_obj = trace.as_json()
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
Trace.apply_json_to_schedule(json_obj, sch)
assert_structural_equal_ignore_global_symbol(elementwise_inlined, sch.mod["main"])
def test_apply_json_to_schedule_sample_categorical():
var = tirx.Var("v", "int32")
trace1 = Trace(
insts=[
Instruction(
kind=InstructionKind.get("SampleCategorical"),
inputs=[],
attrs=[[tvm.tirx.IntImm("int32", 3)], [tvm.tirx.FloatImm("float32", 1.0)]],
outputs=[var],
)
],
decisions={},
)
json = trace1.as_json()
assert str(json) == "[[['SampleCategorical', [], [[3], [T.float32(1.0)]], ['v0']]], []]"
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
# As long as the application does not fail, it is fine.
Trace.apply_json_to_schedule(json, sch)
python_str = sch.trace.as_python()
assert len(python_str) == 1
assert python_str[0] == "v0 = sch.sample_categorical(candidates=[3], probs=[1], decision=0)"
def _test_apply_annotation_trace_from_json(annotation: str):
"""Test applying an annotation works without crashing.
Designed to handle some previously failing edge cases like the
empty string.
"""
b0 = SBlockRV()
trace = Trace(
insts=[
_make_get_sblock(name="B", output=b0),
_make_annotate(block=b0, annotation=annotation),
],
decisions={},
)
json_obj = trace.as_json()
sch = tvm.s_tir.Schedule(elementwise, debug_mask="all")
Trace.apply_json_to_schedule(json_obj, sch)
@T.prim_func(s_tir=True)
def elementwise_expected(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (128, 128))
B = T.sblock_alloc_buffer((128, 128))
C = T.match_buffer(c, (128, 128))
for i, j in T.grid(128, 128):
with T.sblock("B"):
T.sblock_attr({"meta_schedule.auto_tensorize": annotation})
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
for i, j in T.grid(128, 128):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + 1.0
assert_structural_equal_ignore_global_symbol(elementwise_expected, sch.mod["main"])
def test_apply_annotation_from_json():
# Something reasonable
_test_apply_annotation_trace_from_json("SSRSSR")
# The empty string
_test_apply_annotation_trace_from_json("")
# A string of two quotation marks
_test_apply_annotation_trace_from_json('""')
# A string of one quotation mark
_test_apply_annotation_trace_from_json('"')
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,179 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import tvm
from tvm.s_tir import Schedule
from tvm.s_tir.schedule.transform import tile_with_tensor_intrin
from tvm.s_tir.tensor_intrin.x86 import AVX512_DOT_16x4_INTRIN, VNNI_DOT_16x4_INTRIN
from tvm.script import tirx as T
@tvm.script.ir_module
class DenseTIRModule:
@T.prim_func(s_tir=True)
def main(
placeholder: T.Buffer((1024, 1024), "uint8"),
placeholder_1: T.Buffer((64, 256, 16, 4), "int8"),
compute: T.Buffer((1024, 1024), "int32"),
) -> None:
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
with T.sblock("root"):
T.reads()
T.writes()
for i0, i1, i2 in T.grid(1024, 1024, 1024):
with T.sblock("compute"):
i, j, k = T.axis.remap("SSR", [i0, i1, i2])
T.reads(placeholder[i, k], placeholder_1[j // 16, k // 4, j % 16, k % 4])
T.writes(compute[i, j])
with T.init():
compute[i, j] = 0
compute[i, j] = compute[i, j] + T.cast(placeholder[i, k], "int32") * T.cast(
placeholder_1[j // 16, k // 4, j % 16, k % 4], "int32"
)
@tvm.script.ir_module
class DenseTIRModuleTiled:
@T.prim_func(s_tir=True)
def main(
placeholder: T.Buffer((1024, 1024), "uint8"),
placeholder_1: T.Buffer((64, 256, 16, 4), "int8"),
compute: T.Buffer((1024, 1024), "int32"),
) -> None:
# function attr dict
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
# body
# with T.sblock("root")
for i0, i1_0, i2_0, i1_1, i2_1 in T.grid(1024, 64, 256, 16, 4):
with T.sblock("compute"):
i = T.axis.spatial(1024, i0)
j = T.axis.spatial(1024, i1_0 * 16 + i1_1)
k = T.axis.reduce(1024, i2_0 * 4 + i2_1)
T.reads(placeholder[i, k], placeholder_1[j // 16, k // 4, j % 16, k % 4])
T.writes(compute[i, j])
with T.init():
compute[i, j] = 0
compute[i, j] = compute[i, j] + T.cast(placeholder[i, k], "int32") * T.cast(
placeholder_1[j // 16, k // 4, j % 16, k % 4], "int32"
)
@tvm.script.ir_module
class Conv2dNCHWcTIRModule:
@T.prim_func(s_tir=True)
def main(
placeholder: T.Buffer((1, 4, 56, 56, 16), "uint8"),
placeholder_1: T.Buffer((16, 4, 1, 1, 4, 16, 4), "int8"),
conv2d_NCHWc_int8: T.Buffer((1, 16, 56, 56, 16), "int32"),
) -> None:
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
for i0, i1, i2, i3, i4, i5, i6, i7, i8, i9 in T.grid(1, 16, 56, 56, 16, 1, 1, 4, 4, 4):
with T.sblock("conv2d_NCHWc_int8"):
(
n,
oc_chunk,
oh,
ow,
oc_block,
kh,
kw,
ic_outer,
ic_f_inner,
ic_s_inner,
) = T.axis.remap("SSSSSRRRRR", [i0, i1, i2, i3, i4, i5, i6, i7, i8, i9])
T.reads(
placeholder[n, ic_outer, oh + kh, ow + kw, ic_f_inner * 4 + ic_s_inner],
placeholder_1[oc_chunk, ic_outer, kh, kw, ic_f_inner, oc_block, ic_s_inner],
)
T.writes(conv2d_NCHWc_int8[n, oc_chunk, oh, ow, oc_block])
with T.init():
conv2d_NCHWc_int8[n, oc_chunk, oh, ow, oc_block] = 0
conv2d_NCHWc_int8[n, oc_chunk, oh, ow, oc_block] = conv2d_NCHWc_int8[
n, oc_chunk, oh, ow, oc_block
] + T.cast(
placeholder[n, ic_outer, oh + kh, ow + kw, ic_f_inner * 4 + ic_s_inner],
"int32",
) * T.cast(
placeholder_1[oc_chunk, ic_outer, kh, kw, ic_f_inner, oc_block, ic_s_inner],
"int32",
)
@tvm.script.ir_module
class Conv2dNCHWcTIRModuleTiled:
@T.prim_func(s_tir=True)
def main(
placeholder: T.Buffer((1, 4, 56, 56, 16), "uint8"),
placeholder_1: T.Buffer((16, 4, 1, 1, 4, 16, 4), "int8"),
conv2d_NCHWc_int8: T.Buffer((1, 16, 56, 56, 16), "int32"),
) -> None:
# function attr dict
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
# body
# with T.sblock("root")
for i0, i1, i2, i3, i4_0, i5, i6, i7, i8, i9_0, i4_1, i9_1 in T.grid(
1, 16, 56, 56, 1, 1, 1, 4, 4, 1, 16, 4
):
with T.sblock("conv2d_NCHWc_int8"):
n, oc_chunk, oh, ow = T.axis.remap("SSSS", [i0, i1, i2, i3])
oc_block = T.axis.spatial(16, i4_0 * 16 + i4_1)
kh, kw, ic_outer, ic_f_inner = T.axis.remap("RRRR", [i5, i6, i7, i8])
ic_s_inner = T.axis.reduce(4, i9_0 * 4 + i9_1)
T.reads(
placeholder[n, ic_outer, oh + kh, ow + kw, ic_f_inner * 4 + ic_s_inner],
placeholder_1[oc_chunk, ic_outer, kh, kw, ic_f_inner, oc_block, ic_s_inner],
)
T.writes(conv2d_NCHWc_int8[n, oc_chunk, oh, ow, oc_block])
with T.init():
conv2d_NCHWc_int8[n, oc_chunk, oh, ow, oc_block] = 0
conv2d_NCHWc_int8[n, oc_chunk, oh, ow, oc_block] = conv2d_NCHWc_int8[
n, oc_chunk, oh, ow, oc_block
] + T.cast(
placeholder[n, ic_outer, oh + kh, ow + kw, ic_f_inner * 4 + ic_s_inner],
"int32",
) * T.cast(
placeholder_1[oc_chunk, ic_outer, kh, kw, ic_f_inner, oc_block, ic_s_inner],
"int32",
)
def test_tile_with_tensor_intrin_dense(intrin=VNNI_DOT_16x4_INTRIN):
s = Schedule(DenseTIRModule)
block = s.get_sblock("compute")
tiled_loop = tile_with_tensor_intrin(s, block, intrin)
_, _, _, i1_1, _ = s.get_loops(block)
assert s.get(tiled_loop) == s.get(i1_1)
tvm.ir.assert_structural_equal(s.mod, DenseTIRModuleTiled)
def test_tile_with_tensor_intrin_conv2d_nchwc(intrin=VNNI_DOT_16x4_INTRIN):
s = Schedule(Conv2dNCHWcTIRModule)
block = s.get_sblock("conv2d_NCHWc_int8")
tiled_loop = tile_with_tensor_intrin(s, block, intrin)
tiled_loops = s.get_loops(block)
assert len(tiled_loops) == 12
assert s.get(tiled_loop) == s.get(tiled_loops[-2])
tvm.ir.assert_structural_equal(s.mod, Conv2dNCHWcTIRModuleTiled)
if __name__ == "__main__":
test_tile_with_tensor_intrin_dense()
test_tile_with_tensor_intrin_dense(AVX512_DOT_16x4_INTRIN)
test_tile_with_tensor_intrin_conv2d_nchwc()
test_tile_with_tensor_intrin_conv2d_nchwc(AVX512_DOT_16x4_INTRIN)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,427 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=missing-function-docstring,missing-module-docstring
# ruff: noqa: F401, F841
import sys
import pytest
import tvm
import tvm.testing
from tvm import tirx
from tvm.ir import IRModule
from tvm.s_tir.schedule.testing import (
assert_structural_equal_ignore_global_symbol,
verify_trace_roundtrip,
)
from tvm.script import tirx as T
# pylint: disable=no-member,invalid-name,unused-variable
@T.prim_func(s_tir=True)
def matmul(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
B = T.match_buffer(b, [128, 128])
C = T.match_buffer(c, [128, 128])
for i, j in T.grid(128, 128):
with T.sblock("init"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = 0.0
for k in range(0, 128):
with T.sblock("update"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
@T.prim_func(s_tir=True)
def matmul_relu(a: T.handle, b: T.handle, d: T.handle) -> None:
A = T.match_buffer(a, (1024, 1024))
B = T.match_buffer(b, (1024, 1024))
C = T.sblock_alloc_buffer((1024, 1024))
D = T.match_buffer(d, (1024, 1024))
for i, j, k in T.grid(1024, 1024, 1024):
with T.sblock("matmul"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
C[vi, vj] = 0.0
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vk, vj]
for i, j in T.grid(1024, 1024):
with T.sblock("relu"):
vi, vj = T.axis.remap("SS", [i, j])
D[vi, vj] = T.max(C[vi, vj], 0.0)
@T.prim_func(s_tir=True)
def matmul_relu_ann1(a: T.handle, b: T.handle, d: T.handle) -> None:
A = T.match_buffer(a, (1024, 1024))
B = T.match_buffer(b, (1024, 1024))
C = T.sblock_alloc_buffer((1024, 1024))
D = T.match_buffer(d, (1024, 1024))
for i in T.serial(0, 1024, annotations={"test1": "aaa", "test4": {"arr": [0, 0], "key": 3}}):
for j in T.serial(0, 1024, annotations={"test2": 612, "test3": ["aa", 1]}):
for k in T.serial(0, 1024):
with T.sblock("matmul"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
C[vi, vj] = 0.0
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vk, vj]
for i, j in T.grid(1024, 1024):
with T.sblock("relu"):
vi, vj = T.axis.remap("SS", [i, j])
D[vi, vj] = T.max(C[vi, vj], 0.0)
@T.prim_func(s_tir=True)
def matmul_relu_ann2(a: T.handle, b: T.handle, d: T.handle) -> None:
A = T.match_buffer(a, (1024, 1024))
B = T.match_buffer(b, (1024, 1024))
C = T.sblock_alloc_buffer((1024, 1024))
D = T.match_buffer(d, (1024, 1024))
for i, j, k in T.grid(1024, 1024, 1024):
with T.sblock("matmul"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
C[vi, vj] = 0.0
T.sblock_attr({"test1": "aaa", "test4": {"arr": [0, 0], "key": 3}})
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vk, vj]
for i, j in T.grid(1024, 1024):
with T.sblock("relu"):
vi, vj = T.axis.remap("SS", [i, j])
T.sblock_attr({"test2": 0.22, "test3": ["aa", 1]})
D[vi, vj] = T.max(C[vi, vj], 0.0)
@tvm.script.ir_module
class ModuleWithMultipleFuncs:
@T.prim_func(s_tir=True)
def vector_add(
A: T.Buffer(128, "float32"),
B: T.Buffer(128, "float32"),
) -> None:
for i in range(128):
with T.sblock("init"):
vi = T.axis.remap("S", [i])
B[vi] = A[vi]
@T.prim_func(s_tir=True)
def vector_add_2(
A: T.Buffer(128, "float32"),
B: T.Buffer(128, "float32"),
) -> None:
for i in range(128):
with T.sblock("init"):
vi = T.axis.remap("S", [i])
B[vi] = A[vi]
@T.prim_func(s_tir=True)
def tuple_reduction(data: T.Buffer((4, 32), "float32"), T_add: T.Buffer((4,), "float32")) -> None:
# function attr dict
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
# body
with T.sblock("root"):
T.reads()
T.writes()
data_red_temp_v0 = T.sblock_alloc_buffer([4], dtype="float32")
data_red_temp_v1 = T.sblock_alloc_buffer([4], dtype="float32")
for i0, i1 in T.grid(4, 32):
with T.sblock("data_red_temp"):
ax0, k1 = T.axis.remap("SR", [i0, i1])
T.reads(data[ax0, k1])
T.writes(data_red_temp_v0[ax0], data_red_temp_v1[ax0])
with T.init():
data_red_temp_v0[ax0] = T.float32(0)
data_red_temp_v1[ax0] = T.float32(0)
v_data_red_temp_v0: T.let[T.float32] = data_red_temp_v0[ax0] + data[ax0, k1]
v_data_red_temp_v1: T.let[T.float32] = (
data_red_temp_v1[ax0] + data[ax0, k1] * data[ax0, k1]
)
data_red_temp_v0[ax0] = v_data_red_temp_v0
data_red_temp_v1[ax0] = v_data_red_temp_v1
for i0 in range(4):
with T.sblock("T_add"):
ax0 = T.axis.remap("S", [i0])
T.reads(data_red_temp_v0[ax0], data_red_temp_v1[ax0])
T.writes(T_add[ax0])
T_add[ax0] = data_red_temp_v0[ax0] + data_red_temp_v1[ax0]
# pylint: enable=no-member,invalid-name,unused-variable
use_block_name = tvm.testing.parameter(by_dict={"block_obj": False, "block_name": True})
def test_tir_schedule_creation():
# Tests:
# - Schedule.__init__ for PrimFunc and IRModule
# - Schedule.mod
# - Schedule.state
sch_1 = tvm.s_tir.Schedule(matmul, debug_mask="all")
sch_2 = tvm.s_tir.Schedule(IRModule({"main": matmul}), debug_mask="all")
assert sch_1.mod["main"].same_as(sch_2.mod["main"])
assert sch_1.state.mod["main"].same_as(sch_2.state.mod["main"])
def test_tir_schedule_get_sblock():
# Tests:
# - Schedule.get_sblock
# - Schedule.get_sref
# - Schedule.get
sch = tvm.s_tir.Schedule(matmul, debug_mask="all")
block_rv = sch.get_sblock(name="update")
block_sref = sch.get_sref(block_rv)
block = sch.get(block_rv)
assert block.name_hint == "update"
assert block_sref.stmt.same_as(block)
assert sch.state.get_sref(block).same_as(block_sref)
assert block.same_as(matmul.body.block.body.body.body[1].body.block)
def test_tir_schedule_work_on():
sch = tvm.s_tir.Schedule(ModuleWithMultipleFuncs, debug_mask="all")
with pytest.raises(ValueError, match="does not know which function to be working on"):
sch.get_sblock(name="init")
sch.work_on(func_name="vector_add")
sch.get_sblock(name="init")
assert sch.func_working_on == sch.mod.get_global_var("vector_add")
def test_tir_schedule_get_loops(use_block_name):
# Tests:
# - Schedule.get_loops
# - Schedule.get
sch = tvm.s_tir.Schedule(matmul, debug_mask="all")
block = "update" if use_block_name else sch.get_sblock(name="update")
i, j, k = sch.get_loops(block)
assert sch.get(i).loop_var.name == "i"
assert sch.get(j).loop_var.name == "j"
assert sch.get(k).loop_var.name == "k"
def test_tir_schedule_copy_1(use_block_name):
# Tests:
# - Schedule.copy
sch_1 = tvm.s_tir.Schedule(matmul, debug_mask="all")
block_rv = sch_1.get_sblock(name="update")
i, j, k = sch_1.get_loops(block="update" if use_block_name else block_rv)
assert sch_1.get(i).loop_var.name == "i"
assert sch_1.get(j).loop_var.name == "j"
assert sch_1.get(k).loop_var.name == "k"
sch_2 = sch_1.copy()
assert sch_2.get(block_rv).name_hint == "update"
assert sch_2.get(i).loop_var.name == "i"
assert sch_2.get(j).loop_var.name == "j"
assert sch_2.get(k).loop_var.name == "k"
def test_tir_schedule_copy_2():
sch = tvm.s_tir.Schedule(mod=matmul, debug_mask="all")
i, j, k = sch.get_loops(sch.get_sblock("update"))
sch_copy = sch.copy()
assert not sch.get_sref(i).same_as(sch_copy.get_sref(i))
assert not sch.get_sref(j).same_as(sch_copy.get_sref(j))
assert not sch.get_sref(k).same_as(sch_copy.get_sref(k))
assert sch.get_sref(i).stmt.same_as(sch_copy.get_sref(i).stmt)
assert sch.get_sref(j).stmt.same_as(sch_copy.get_sref(j).stmt)
assert sch.get_sref(k).stmt.same_as(sch_copy.get_sref(k).stmt)
i_0, i_1 = sch.split(i, factors=[None, 64])
j_0, j_1 = sch_copy.split(j, factors=[None, 32])
assert sch.get_sref(i_0).stmt.extent == 2
assert sch.get_sref(i_1).stmt.extent == 64
with pytest.raises(IndexError):
sch_copy.get_sref(i_0)
with pytest.raises(IndexError):
sch_copy.get_sref(i_1)
with pytest.raises(IndexError):
sch.get_sref(j_0)
with pytest.raises(IndexError):
sch.get_sref(j_1)
assert sch_copy.get_sref(j_0).stmt.extent == 4
assert sch_copy.get_sref(j_1).stmt.extent == 32
verify_trace_roundtrip(sch, mod=matmul)
verify_trace_roundtrip(sch_copy, mod=matmul)
def test_tir_schedule_remove_rv():
# Tests:
# - Schedule.remove_rv
sch = tvm.s_tir.Schedule(matmul, debug_mask="all")
block_rv = sch.get_sblock(name="update")
assert sch.get(block_rv).name_hint == "update"
sch.remove_rv(block_rv)
with pytest.raises(IndexError):
sch.get(block_rv)
def test_get_child_blocks():
s = tvm.s_tir.Schedule(matmul, debug_mask="all")
init = s.get_sblock("init")
update = s.get_sblock("update")
# loop
blocks = s.get_child_blocks(s.get_loops(init)[0])
assert len(blocks) == 2
assert s.get(init) == s.get(blocks[0])
assert s.get(update) == s.get(blocks[1])
# block
root = s.get_sblock("root")
blocks = s.get_child_blocks(root)
assert len(blocks) == 2
assert s.get(init) == s.get(blocks[0])
assert s.get(update) == s.get(blocks[1])
def test_get_producers(use_block_name):
sch = tvm.s_tir.Schedule(mod=matmul_relu, debug_mask="all")
block = "relu" if use_block_name else sch.get_sblock("relu")
(producer,) = sch.get_producers(block)
tvm.ir.assert_structural_equal(
sch.get_sref(producer).stmt,
sch.get_sref(sch.get_sblock("matmul")).stmt,
)
verify_trace_roundtrip(sch, mod=matmul_relu)
def test_get_producers_multiple_buffer_depdencies(use_block_name):
sch = tvm.s_tir.Schedule(mod=tuple_reduction, debug_mask="all")
block = "T_add" if use_block_name else sch.get_sblock("T_add")
(producer,) = sch.get_producers(block)
tvm.ir.assert_structural_equal(
sch.get_sref(producer).stmt,
sch.get_sref(sch.get_sblock("data_red_temp")).stmt,
)
def test_get_consumers(use_block_name):
sch = tvm.s_tir.Schedule(mod=matmul_relu, debug_mask="all")
block = "matmul" if use_block_name else sch.get_sblock("matmul")
(consumer,) = sch.get_consumers(block)
tvm.ir.assert_structural_equal(
sch.get_sref(consumer).stmt,
sch.get_sref(sch.get_sblock("relu")).stmt,
)
verify_trace_roundtrip(sch, mod=matmul_relu)
def test_get_consumers_multiple_buffer_depdencies(use_block_name):
sch = tvm.s_tir.Schedule(mod=tuple_reduction, debug_mask="all")
block = "data_red_temp" if use_block_name else sch.get_sblock("data_red_temp")
(consumer,) = sch.get_consumers(block)
tvm.ir.assert_structural_equal(
sch.get_sref(consumer).stmt,
sch.get_sref(sch.get_sblock("T_add")).stmt,
)
def test_annotate_unannotate_loop():
sch = tvm.s_tir.Schedule(mod=matmul_relu, debug_mask="all")
matmul = sch.get_sblock("matmul")
relu = sch.get_sblock("relu")
sch.annotate(sch.get_loops(matmul)[0], "test1", "aaa")
sch.annotate(sch.get_loops(matmul)[1], "test2", 612)
sch.annotate(sch.get_loops(matmul)[1], "test3", ["aa", 1])
sch.annotate(sch.get_loops(matmul)[0], "test4", {"arr": [0, 0], "key": 3})
assert_structural_equal_ignore_global_symbol(sch.mod["main"], matmul_relu_ann1)
verify_trace_roundtrip(sch=sch, mod=matmul_relu)
sch.unannotate(sch.get_loops(matmul)[0], "test1")
sch.unannotate(sch.get_loops(matmul)[1], "test2")
sch.unannotate(sch.get_loops(matmul)[1], "test3")
sch.unannotate(sch.get_loops(matmul)[0], "test4")
verify_trace_roundtrip(sch=sch, mod=matmul_relu)
def test_annotate_unannotate_block():
sch = tvm.s_tir.Schedule(mod=matmul_relu, debug_mask="all")
matmul = sch.get_sblock("matmul")
relu = sch.get_sblock("relu")
sch.annotate(matmul, "test1", "aaa")
sch.annotate(relu, "test2", 0.22)
sch.annotate(relu, "test3", ["aa", 1])
sch.annotate(matmul, "test4", {"arr": [0, 0], "key": 3})
assert_structural_equal_ignore_global_symbol(sch.mod["main"], matmul_relu_ann2)
verify_trace_roundtrip(sch=sch, mod=matmul_relu)
sch.unannotate(matmul, "test1")
sch.unannotate(relu, "test2")
sch.unannotate(relu, "test3")
sch.unannotate(matmul, "test4")
verify_trace_roundtrip(sch=sch, mod=matmul_relu)
def test_get_output_blocks_single_output():
sch = tvm.s_tir.Schedule(mod=matmul_relu, debug_mask="all")
output_blocks = sch.get_output_blocks("root")
assert len(output_blocks) == 1, "Unexpected number of blocks when 1 was expected"
block = sch.get(output_blocks[0])
assert block.name_hint == "relu"
relu_block = sch.get_sblock("relu")
assert sch.get(relu_block).same_as(block)
def test_get_output_blocks_multiple_outputs():
sch = tvm.s_tir.Schedule(mod=matmul, debug_mask="all")
output_blocks = sch.get_output_blocks("root")
assert len(output_blocks) == 2, "Unexpected number of blocks when 2 were expected"
block_1 = sch.get(output_blocks[0])
assert block_1.name_hint == "init"
block_2 = sch.get(output_blocks[1])
assert block_2.name_hint == "update"
init_block = sch.get_sblock("init")
assert sch.get(init_block).same_as(block_1)
update_block = sch.get_sblock("update")
assert sch.get(update_block).same_as(block_2)
def test_get_output_blocks_nested():
@T.prim_func(s_tir=True)
def blockized(
A: T.Buffer((128, 128), "float32"),
B: T.Buffer((128, 128), "float32"),
) -> None:
with T.sblock("blockized_B"):
vio = T.axis.spatial(1, 0)
vjo = T.axis.spatial(1, 0)
for i, j in T.grid(128, 128):
with T.sblock("B"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] * 2.0
sch = tvm.s_tir.Schedule(mod=blockized, debug_mask="all")
output_blocks = sch.get_output_blocks("root")
assert len(output_blocks) == 2, "Unexpected number of blocks when 2 were expected"
block_1 = sch.get(output_blocks[0])
assert block_1.name_hint == "blockized_B"
block_2 = sch.get(output_blocks[1])
assert block_2.name_hint == "B"
blockized_block = sch.get_sblock("blockized_B")
assert sch.get(blockized_block).same_as(block_1)
b_block = sch.get_sblock("B")
assert sch.get(b_block).same_as(block_2)
sch = tvm.s_tir.Schedule(mod=blockized, debug_mask="all")
output_blocks = sch.get_output_blocks("blockized_B")
assert len(output_blocks) == 1, "Unexpected number of blocks when 1 were expected"
block = sch.get(output_blocks[0])
assert block.name_hint == "B"
b_block = sch.get_sblock("B")
assert sch.get(b_block).same_as(block)
if __name__ == "__main__":
tvm.testing.main()