chore: import upstream snapshot with attribution
Lint / lint (push) Waiting to run
CI / MacOS (push) Waiting to run
CI / Windows (push) Waiting to run

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,347 @@
# 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.testing
from tvm.ir import Range
from tvm.script import tirx as T
@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] = T.float32(0)
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
@T.prim_func(s_tir=True)
def matmul_original(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(32, 32):
with T.sblock("init"):
vi, vj = T.axis.remap("SS", [i, j])
for ii, jj in T.grid(4, 4):
C[vi * 4 + ii, vj * 4 + jj] = T.float32(0)
for k in range(0, 32):
with T.sblock("update"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
for ii, jj, kk in T.grid(4, 4, 4):
C[vi * 4 + ii, vj * 4 + jj] = (
C[vi * 4 + ii, vj * 4 + jj]
+ A[vi * 4 + ii, vk * 4 + kk] * B[vj * 4 + jj, vk * 4 + kk]
)
@T.prim_func(s_tir=True)
def elementwise_with_root(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])
with T.sblock():
for i, j in T.grid(128, 128):
with T.sblock():
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] + T.float32(1)
for i, j in T.grid(128, 128):
with T.sblock():
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + T.float32(1)
def func_with_opaque_block(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])
with T.sblock():
with T.sblock():
B[0, 0] = A[0, 0] + T.float32(1)
for i, j in T.grid(128, 128):
with T.sblock():
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + T.float32(1)
@T.prim_func(s_tir=True)
def func_with_part_access_region(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])
with T.sblock():
for i, j in T.grid(128, 128):
with T.sblock():
vi, vj = T.axis.remap("SS", [i, j])
T.reads(A[vi, vj])
B[vi, vj] = A[vi, vj] + T.float32(1)
for i, j in T.grid(128, 128):
with T.sblock():
vi, vj = T.axis.remap("SS", [i, j])
T.writes(C[vi, vj])
C[vi, vj] = B[vi, vj] + T.float32(1)
def test_complete_matmul():
func = matmul
A, B, C = [func.buffer_map[x] for x in func.params]
block = func.body.block.body.body.body.body.block
assert isinstance(block, tvm.tirx.SBlock)
vi, vj, vk = [x.var for x in block.iter_vars]
access_A = tvm.tirx.BufferRegion(
A, [Range.from_min_extent(vi, 1), Range.from_min_extent(vk, 1)]
)
access_B = tvm.tirx.BufferRegion(
B, [Range.from_min_extent(vj, 1), Range.from_min_extent(vk, 1)]
)
access_C = tvm.tirx.BufferRegion(
C, [Range.from_min_extent(vi, 1), Range.from_min_extent(vj, 1)]
)
tvm.ir.assert_structural_equal(block.reads, [access_A, access_B])
tvm.ir.assert_structural_equal(block.writes, [access_C])
def test_complete_matmul_original():
func = matmul_original
A, B, C = [func.buffer_map[x] for x in func.params]
block1 = func.body.block.body.body.body[0].block
assert isinstance(block1, tvm.tirx.SBlock)
vi, vj = [x.var for x in block1.iter_vars]
access_C = tvm.tirx.BufferRegion(
C, [Range.from_min_extent(vi * 4, 4), Range.from_min_extent(vj * 4, 4)]
)
tvm.ir.assert_structural_equal(block1.reads, [])
tvm.ir.assert_structural_equal(block1.writes, [access_C])
block2 = func.body.block.body.body.body[1].body.block
assert isinstance(block2, tvm.tirx.SBlock)
vi, vj, vk = [x.var for x in block2.iter_vars]
access_A = tvm.tirx.BufferRegion(
A, [Range.from_min_extent(vi * 4, 4), Range.from_min_extent(vk * 4, 4)]
)
access_B = tvm.tirx.BufferRegion(
B, [Range.from_min_extent(vj * 4, 4), Range.from_min_extent(vk * 4, 4)]
)
access_C = tvm.tirx.BufferRegion(
C, [Range.from_min_extent(vi * 4, 4), Range.from_min_extent(vj * 4, 4)]
)
tvm.ir.assert_structural_equal(block2.reads, [access_C, access_A, access_B])
tvm.ir.assert_structural_equal(block2.writes, [access_C])
def _check_elementwise(func):
A, B, C = [func.buffer_map[x] for x in func.params]
root_block = func.body.block
assert len(root_block.reads) == 0
assert len(root_block.writes) == 0
block1 = func.body.block.body[0].body.body.block
assert isinstance(block1, tvm.tirx.SBlock)
vi, vj = [x.var for x in block1.iter_vars]
tvm.ir.assert_structural_equal(
block1.reads,
[tvm.tirx.BufferRegion(A, [Range.from_min_extent(vi, 1), Range.from_min_extent(vj, 1)])],
)
tvm.ir.assert_structural_equal(
block1.writes,
[tvm.tirx.BufferRegion(B, [Range.from_min_extent(vi, 1), Range.from_min_extent(vj, 1)])],
)
block2 = func.body.block.body[1].body.body.block
assert isinstance(block2, tvm.tirx.SBlock)
vi, vj = [x.var for x in block2.iter_vars]
tvm.ir.assert_structural_equal(
block2.reads,
[tvm.tirx.BufferRegion(B, [Range.from_min_extent(vi, 1), Range.from_min_extent(vj, 1)])],
)
tvm.ir.assert_structural_equal(
block2.writes,
[tvm.tirx.BufferRegion(C, [Range.from_min_extent(vi, 1), Range.from_min_extent(vj, 1)])],
)
def test_complete_with_root():
_check_elementwise(elementwise_with_root)
def test_complete_part_region():
_check_elementwise(func_with_part_access_region)
@T.prim_func(s_tir=True)
def func_with_bufferslice_indices(data: T.handle, index: T.handle) -> None:
data_buf = T.match_buffer(data, (16, 16), "float32")
index_buf = T.match_buffer(index, (1,), "int32")
out_buf = T.sblock_alloc_buffer((16, 16), "float32")
for i, j in T.grid(16, 16):
with T.sblock():
vi, vj = T.axis.remap("SS", [i, j])
out_buf[vi, vj] = data_buf[vi, index_buf[0]]
@T.prim_func(s_tir=True)
def expected_bufferslice_indices(data: T.handle, index: T.handle) -> None:
index_buf = T.match_buffer(index, [1], dtype="int32", elem_offset=0, align=64, offset_factor=1)
data_buf = T.match_buffer(data, [16, 16], elem_offset=0, align=64, offset_factor=1)
with T.sblock("root"):
T.reads([])
T.writes([])
out_buf = T.sblock_alloc_buffer([16, 16], elem_offset=0, align=64, offset_factor=1)
for i0, i1 in T.grid(16, 16):
with T.sblock():
vi, vj = T.axis.remap("SS", [i0, i1])
T.reads([data_buf[vi, index_buf[0]], index_buf[0]])
T.writes([out_buf[vi, vj]])
out_buf[vi, vj] = data_buf[vi, index_buf[0]]
@T.prim_func(s_tir=True)
def func_with_recursive_bufferslice_indices(data: T.handle, index: T.handle) -> None:
data_buf = T.match_buffer(data, (16, 16), "float32")
index_buf = T.match_buffer(index, (1,), "int32")
out_buf = T.sblock_alloc_buffer((16, 16), "float32")
for i, j in T.grid(16, 16):
with T.sblock():
vi, vj = T.axis.remap("SS", [i, j])
out_buf[vi, vj] = data_buf[index_buf[index_buf[0]], index_buf[0]]
@T.prim_func(s_tir=True)
def expected_recursive_bufferslice_indices(data: T.handle, index: T.handle) -> None:
index_buf = T.match_buffer(index, [1], dtype="int32", elem_offset=0, align=64, offset_factor=1)
data_buf = T.match_buffer(data, [16, 16], elem_offset=0, align=64, offset_factor=1)
with T.sblock("root"):
T.reads([])
T.writes([])
out_buf = T.sblock_alloc_buffer([16, 16], elem_offset=0, align=64, offset_factor=1)
for i0, i1 in T.grid(16, 16):
with T.sblock():
vi, vj = T.axis.remap("SS", [i0, i1])
T.reads(
[
data_buf[index_buf[index_buf[0]], index_buf[0]],
index_buf[T.min(index_buf[0], 0) : T.max(index_buf[0], 0) + 1],
]
)
T.writes([out_buf[vi, vj]])
out_buf[vi, vj] = data_buf[index_buf[index_buf[0]], index_buf[0]]
def test_complete_buffer_indices():
new_func = tvm.script.from_source(func_with_bufferslice_indices.script()).with_attr(
"global_symbol", "main"
)
tvm.ir.assert_structural_equal(
new_func, expected_bufferslice_indices.with_attr("global_symbol", "main")
)
new_func = tvm.script.from_source(func_with_recursive_bufferslice_indices.script()).with_attr(
"global_symbol", "main"
)
tvm.ir.assert_structural_equal(
new_func, expected_recursive_bufferslice_indices.with_attr("global_symbol", "main")
)
@T.prim_func(s_tir=True)
def match_buffer_func(a: T.handle) -> None:
A = T.match_buffer(a, (16, 16))
for i in range(0, 16):
with T.sblock():
A0 = T.match_buffer(A[i, 0:16], (16))
with T.sblock():
for j in range(0, 16):
with T.sblock():
A1 = T.match_buffer(A0[j], ())
A1[()] = 1.0
@T.prim_func(s_tir=True)
def expected_match_buffer_func(a: T.handle) -> None:
A = T.match_buffer(a, (16, 16))
for i in range(0, 16):
with T.sblock():
T.reads([])
T.writes(A[i, 0:16])
A0 = T.match_buffer(A[i, 0:16], (16))
with T.sblock():
T.reads([])
T.writes(A0[0:16])
for j in range(0, 16):
with T.sblock():
T.reads([])
T.writes(A0[j])
A1 = T.match_buffer(A0[j], ())
A1[()] = 1.0
def test_complete_match_buffer():
tvm.ir.assert_structural_equal(
match_buffer_func.with_attr("global_symbol", "main"),
expected_match_buffer_func.with_attr("global_symbol", "main"),
)
@T.prim_func(s_tir=True)
def alloc_buffer_func(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [2, 2], dtype="float32")
B = T.match_buffer(b, [2, 2], dtype="float32")
C = T.sblock_alloc_buffer([2, 2], dtype="float32")
A[(0, 0)] = T.float32(2)
C[(0, 0)] = A[(0, 0)] + B[(0, 0)]
B[(0, 0)] = C[(0, 0)]
@T.prim_func(s_tir=True)
def expect_alloc_buffer_func(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [2, 2], dtype="float32", elem_offset=0, align=64, offset_factor=1)
B = T.match_buffer(b, [2, 2], dtype="float32", elem_offset=0, align=64, offset_factor=1)
with T.sblock("root"):
T.reads([])
T.writes([])
C = T.sblock_alloc_buffer([2, 2], dtype="float32", elem_offset=0, align=64, offset_factor=1)
A[(0, 0)] = T.float32(2)
C[(0, 0)] = A[(0, 0)] + B[(0, 0)]
B[(0, 0)] = C[(0, 0)]
def test_complete_alloc_buffer():
rt_func = tvm.script.from_source(alloc_buffer_func.script()).with_attr("global_symbol", "main")
tvm.ir.assert_structural_equal(
rt_func, expect_alloc_buffer_func.with_attr("global_symbol", "main")
)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,666 @@
# 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: E741, F401, F821, F841, RUF005
import inspect
import re
import pytest
import tvm
import tvm.testing
from tvm import tirx
from tvm.script import from_source
from tvm.script import tirx as T
def check_error(func, rel_lineno):
"""check if TIR script throws error"""
check_error_re = re.compile(r"^.*# check_error: (.+)$")
source_code = inspect.getsource(func)
indent = len(re.match(r"^\s*", source_code).group(0))
source_code = "@T.prim_func(s_tir=True)\n" + "\n".join(
line[indent:] for line in source_code.splitlines()
)
# Parse errors now raise DiagnosticError with formatted source location.
with pytest.raises(tvm.error.DiagnosticError) as execinfo:
from_source(source_code)
err_str = str(execinfo.value)
if rel_lineno is None:
return
# The error message contains " --> <source>:<lineno>:<col>" formatted by Diagnostics.error().
# Accept either rel_lineno or rel_lineno+1 to match old tolerance.
assert f":{rel_lineno}:" in err_str or f":{rel_lineno + 1}:" in err_str, (
f"Expected error message to contain line {rel_lineno}, got:\n{err_str}"
)
error_line = source_code.split("\n")[rel_lineno]
m = check_error_re.match(error_line)
if m:
expected_error_text = m.group(1)
assert expected_error_text in err_str, (
f'check_error expects "{expected_error_text}" in error: {err_str}'
)
def test_buffer_bind():
def buffer_bind_missing_args(a: T.handle) -> None:
A = T.match_buffer((16, 16), "float32") # error
check_error(buffer_bind_missing_args, 2)
def test_undefined_buffer():
def undefined_buffer(a: T.handle) -> None:
A = T.match_buffer(a, (16, 16), "float32")
for i in T.serial(16):
for j in T.serial(0, 16):
C[i, j] = 0.0 # error
check_error(undefined_buffer, 6)
def test_unsupported_function_call():
def unsupported_function_call(a: T.handle) -> None:
A = T.match_buffer(a, (16, 16), "float32")
for i in T.const_range(16): # error
for j in T.serial(0, 16):
A[i, j] = 0.0
check_error(unsupported_function_call, 4)
def test_missing_type_annotation():
def missing_type_annotation(a) -> None: # error
T.evaluate(0.0)
check_error(missing_type_annotation, 1)
def test_invalid_for_function():
def invalid_for_function(a: T.handle) -> None:
A = T.match_buffer(a, (16, 16), "float32")
for i in T.evaluate(0.0): # error
for j in T.serial(0, 16):
A[i, j] = 0.0
check_error(invalid_for_function, 4)
def test_invalid_block_function():
def invalid_block_function(a: T.handle) -> None:
A = T.match_buffer(a, (16, 16), "float32")
with T.evaluate(0.0): # error
T.evaluate(1.0)
check_error(invalid_block_function, 4)
def test_return_not_allowed():
def return_not_allowed(a: T.handle) -> None:
return T.evaluate(0) # error
check_error(return_not_allowed, 2)
def test_no_body():
def no_body(a: T.handle) -> None:
A = T.match_buffer(a, (16, 16), "float32")
T.realize(A, "") # error
check_error(no_body, 3)
def test_inconsistent_binding():
def inconsistent_binding_value() -> None:
for i, j in T.grid(16, 16):
vi, vj = T.axis.remap("SS", [i]) # error
T.evaluate(1.0)
def inconsistent_binding_type() -> None:
for i, j in T.grid(16, 16):
vi, vj = T.axis.remap("S", [i, j]) # error
T.evaluate(1.0)
check_error(inconsistent_binding_value, 3)
check_error(inconsistent_binding_type, 3)
def test_error_remap_args():
def error_remap_type() -> None:
for i, j in T.grid(16, 16):
with T.sblock():
vi, vj = T.axis.remap("TT", [i, j]) # error
T.evaluate(1.0)
def error_remap_value() -> None:
for i, j in T.grid(16, 16):
with T.sblock():
vi, vj = T.axis.remap("SS", [i + j, j]) # error
T.evaluate(1.0)
check_error(error_remap_type, 4)
check_error(error_remap_value, 4)
def test_invalid_block_axes():
def invalid_block_axes(a: T.handle) -> None:
A = T.match_buffer(a, (16, 16), "float32")
for i, j in T.grid(16, 16):
with T.sblock():
vi = T.axis.S(i, A) # error
T.evaluate(1.0)
check_error(invalid_block_axes, 5)
def test_duplicate_block_axes():
def duplicate_block_axes() -> None:
for i, j in T.grid(16, 16):
with T.sblock():
vi = T.axis.S(16, i)
vi = T.axis.S(16, j) # error
T.evaluate(1.0)
def duplicate_block_axes_remap() -> None:
for i, j in T.grid(16, 16):
with T.sblock():
vi, vi = T.axis.remap("SS", [i, j]) # error
T.evaluate(1.0)
check_error(duplicate_block_axes, 5)
check_error(duplicate_block_axes_remap, 4)
def test_miss_block_bind():
def miss_block_bind_value() -> None:
for i, j in T.grid(128, 128):
with T.sblock():
vi = T.axis.S(i) # error
T.evaluate(1.0)
check_error(miss_block_bind_value, 4)
def test_invalid_loop_var():
def invalid_loop_var() -> None:
for i, j in range(0, 16): # error
T.evaluate(1.0)
check_error(invalid_loop_var, 2)
def test_inconsistent_grid():
def inconsistent_grid(A: T.Buffer(16)) -> None:
for i in T.grid(16, 16): # valid, i is a tuple (iter0, iter1)
T.evaluate(A[i]) # error
check_error(inconsistent_grid, 3)
def test_invalid_match_buffer_region():
def invalid_match_buffer_region() -> None:
for i, j in T.grid(128, 128):
with T.sblock():
vi, vj = T.axis.remap("SS", [i, j])
A = T.match_buffer(vi) # error
T.evaluate(1.0)
check_error(invalid_match_buffer_region, 5)
def test_duplicate_buffer():
def duplicate_buffer() -> None:
A = T.sblock_alloc_buffer((128, 128), "float32")
A = T.sblock_alloc_buffer((128, 128), "float32") # error
check_error(duplicate_buffer, 3)
def test_duplicate_block_signature():
def duplicate_reads() -> None:
A = T.sblock_alloc_buffer((128, 128), "float32")
for i, j in T.grid(128, 128):
with T.sblock():
vi, vj = T.axis.remap("SS", [i, j])
T.reads(A[0:8, 0:8])
T.reads(A[0:16, 0:16]) # error
T.evaluate(1.0)
def duplicate_writes() -> None:
A = T.sblock_alloc_buffer((128, 128), "float32")
for i, j in T.grid(128, 128):
with T.sblock():
vi, vj = T.axis.remap("SS", [i, j])
T.writes(A[0:8, 0:8])
T.writes(A[0:16, 0:16]) # error
T.evaluate(1.0)
def duplicate_predicate() -> None:
for i, j in T.grid(16, 16):
with T.sblock():
vi, vj = T.axis.remap("SS", [i, j])
T.where(1)
T.where(0) # error
def duplicate_init() -> None:
for i, j in T.grid(16, 16):
with T.sblock():
vi, vj = T.axis.remap("SS", [i, j])
with T.init():
T.evaluate(1.0)
with T.init(): # error
T.evaluate(1.0)
def duplicate_axes() -> None:
for i, j in T.grid(16, 16):
with T.sblock():
vi, vj = T.axis.remap("SS", [i, j])
vi = T.axis.S(i, 16) # error
T.evaluate(1.0)
def duplicate_sblock_attrs_with_same_key_diff_value() -> None:
for i, j in T.grid(16, 16):
with T.sblock():
vi, vj = T.axis.remap("SS", [i, j])
T.sblock_attr({"key1": "block1"})
T.sblock_attr({"key1": "block2"}) # error
T.evaluate(1.0)
check_error(duplicate_reads, 7)
check_error(duplicate_writes, 7)
check_error(duplicate_predicate, 6)
check_error(duplicate_init, 7)
check_error(duplicate_axes, 5)
check_error(duplicate_sblock_attrs_with_same_key_diff_value, 6)
def test_opaque_access_during_complete():
def opaque_access_during_complete(a: T.handle) -> None: # error
A = T.match_buffer(a, (16, 16), "float32")
for i, j in T.grid(16, 16):
with T.sblock():
T.evaluate(T.call_extern("dummy_extern_function", A.data, dtype="int32"))
check_error(opaque_access_during_complete, None)
def test_convert_slice_to_bufferload():
def convert_slice_to_bufferload() -> None:
A = T.sblock_alloc_buffer((128, 128), "float32")
for i, j in T.grid(128, 128):
with T.sblock():
vi, vj = T.axis.remap("SS", [i, j])
A[vi, vj] = A[vi : vi + 2, vj] + 1 # error
check_error(convert_slice_to_bufferload, 6)
def test_tvm_exception_catch_from_special_stmt():
def special_stmt_except() -> None:
A = T.sblock_alloc_buffer("(128, 128)", "float32") # error
T.evaluate(1.0)
check_error(special_stmt_except, 2)
def test_tvm_exception_catch_from_scope_handler():
def scope_handler_except() -> None:
for i in T.serial("1", "1"): # error
T.evaluate(1)
check_error(scope_handler_except, 2)
def test_tvm_exception_catch_from_bare_intrin():
def intrin_except_unassign(a: T.handle) -> None:
A = T.match_buffer(a, (16, 16), "float32")
T.evaluate(A) # error
check_error(intrin_except_unassign, 3)
def test_tvm_exception_catch_from_assigned_intrin():
def intrin_except_assign(a: T.handle) -> None:
A = T.match_buffer(a, (16, 16), "float32")
A[0, 0] = A[A] # error
check_error(intrin_except_assign, 3)
def test_match_buffer_shape_mismatch():
def buffer_shape_mismatch(a: T.handle) -> None:
A = T.match_buffer(a, (8, 8))
for i, j in T.grid(8, 2):
with T.sblock():
T.reads([])
T.writes([A[i, j * 4 : j * 4 + 4]])
sub_A = T.match_buffer(
A[i, j * 4 : j * 4 + 4], (5)
) # error: shape mismatched between 4 and 5
for jj in range(0, 4):
sub_A[i, j * 4 + jj] = 1
check_error(buffer_shape_mismatch, 7)
def test_high_dim_store():
def high_dim_store() -> None:
with T.sblock("root"):
B = T.alloc_buffer((256,), "float32")
for i, j in T.grid(16, 16):
B[i, j] = 1.0 # error: Store is only allowed with one index
check_error(high_dim_store, 5)
def test_block_has_option_vars():
def block_has_option_vars() -> None:
with T.sblock("root") as x: # error: block does not support option_vars
T.evaluate(0.0)
check_error(block_has_option_vars, 2)
def test_implicit_root_has_attrs():
def implicit_root_has_read():
T.reads([]) # error: implicit root does not support reads
T.evaluate(0.0)
def implicit_root_has_write():
T.writes([]) # error: implicit root does not support writes
T.evaluate(0.0)
def implicit_root_has_attrs():
T.sblock_attr({}) # error: implicit root does not support sblock_attr
T.evaluate(0.0)
def implicit_root_has_predicate():
T.where(True) # error: implicit root does not support predicate
T.evaluate(0.0)
def implicit_root_has_axes():
v = T.axis.S(0, 0) # error: implicit root does not support axis define
T.evaluate(0.0)
check_error(implicit_root_has_read, 2)
check_error(implicit_root_has_write, 2)
check_error(implicit_root_has_attrs, 2)
check_error(implicit_root_has_predicate, 2)
check_error(implicit_root_has_axes, 2)
@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_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
def test_reorder_fail_block():
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) as execinfo:
sch.reorder(l, i)
expected_sub_error_message = (
" # tirx.SBlock#0\n"
' with T.sblock("B"):\n'
" ^^^^^^^^^^^^^^^^^^^\n"
)
assert expected_sub_error_message in str(execinfo.value)
def test_reorder_fail_nested_loop_inner():
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) as execinfo:
sch.reorder(k, i)
expected_sub_error_message = (
" for i in range(128):\n"
" # tirx.For#0\n"
" for j in range(128):\n"
" ^^^^^^^^^^^^^^^^^^^^\n"
)
assert expected_sub_error_message in str(execinfo.value)
def test_fuse_fail_nested_loop_outer():
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) as execinfo:
sch.fuse(k, i)
expected_sub_error_message = (
" # tirx.For#1\n"
" for i in range(128):\n"
" ^^^^^^^^^^^^^^^^^^^^\n"
" for j in range(128):\n"
)
assert expected_sub_error_message in str(execinfo.value)
def test_report_error_root_block():
sch = tvm.s_tir.Schedule(elementwise_non_single_branch, debug_mask="all")
root = sch.get_sblock("root")
with pytest.raises(tvm.s_tir.ScheduleError) as execinfo:
sch.compute_inline(root)
expected_sub_error_message = (
' # tirx.SBlock#0\n with T.sblock("root"):\n ^^^^^^^^^^^^^^^^^^^^^^\n'
)
assert expected_sub_error_message in str(execinfo.value)
def test_load_var():
def load_var_multiple() -> None:
d = T.float32()
d[2] = d[2, 1] # error cannot provide two indices to load
check_error(load_var_multiple, 3)
def test_store_var():
def store_var_multiple() -> None:
d = T.float32()
d[2, 1] = d[1] # error cannot provide two indices to store
check_error(store_var_multiple, 3)
def test_load_handle():
def load_handle(h: T.handle) -> None:
h_ = T.match_buffer(h, [1])
h_[0] = h[0] # error cannot load from handle
check_error(load_handle, 3)
def test_store_handle():
def store_handle(h: T.handle) -> None:
h_ = T.match_buffer(h, [1])
h[0] = h_[0] # error cannot store to handle
check_error(store_handle, 3)
def test_binop_bad_ast_type():
def binop_bad_ast_type(h: T.handle):
h_ = T.match_buffer(h, [1])
h_[0] = h + [2] # error rhs should be a primexpr
check_error(binop_bad_ast_type, 3)
def test_binop_bad_type():
def binop_bad_type(h: T.handle):
h_ = T.match_buffer(h, [1])
h_[0] = h + 2 # error lhs and rhs should be the same type
check_error(binop_bad_type, 3)
def test_non_integer_typed_block_iter():
def non_integer_typed_block_iter():
with T.sblock():
i = T.axis.S(0.1, 0.1) # error IterVar requires an integer dtype
check_error(non_integer_typed_block_iter, 3)
def test_illegal_buffer_slice():
def strided_buffer_region(A: T.handle):
# do not allow stride in buffer region
A = T.match_buffer((128, 128), "int32")
with T.sblock():
T.reads([])
T.writes([A[0:128:2, 0:128:3]]) # error
T.evaluate(T.call_extern("strided_compute", dtype=""))
def access_reversed_slice(A: T.handle):
# do not allow reversed slice step
A = T.match_buffer((128,), "int32")
A[0:128:-1] = T.broadcast(1, 128) # error
def access_non_const_slice_length(A: T.handle):
# do not allow non-constant slice length
A = T.match_buffer((128,), "int32")
for i in range(4):
T.evaluate(A[0:i:1]) # error
check_error(strided_buffer_region, 3)
check_error(access_reversed_slice, 3)
check_error(access_non_const_slice_length, 3)
def test_syntax_sugar_fail():
def loop_syntax_sugar_fail(a: T.handle) -> None:
A = T.match_buffer(a, (128,))
for i in T.thread_binding(128, 128):
A[i] = A[i] * 2.0
check_error(loop_syntax_sugar_fail, 3)
def test_multi_line_error_report():
"""A parse error whose offending AST node spans several physical source
lines must render ALL spanned lines (each with its own gutter line number
and an underline covering the span), not just the first line."""
# The offending call (`T.axis.remap(...)`) is deliberately split across
# four physical lines so its AST node spans lineno..end_lineno > lineno.
source_code = "\n".join(
[
"@T.prim_func(s_tir=True)",
"def f() -> None:",
" for i, j in T.grid(16, 16):",
" vi, vj = T.axis.remap(",
' "S",',
" [i, j],",
" ) # error",
" T.evaluate(1.0)",
]
)
with pytest.raises(tvm.error.DiagnosticError) as execinfo:
from_source(source_code)
err_str = str(execinfo.value)
# All four spanned source lines must appear in the rendered snippet.
assert "T.axis.remap(" in err_str, err_str
assert '"S",' in err_str, err_str
assert "[i, j]," in err_str, err_str
# The trailing `)` closing line is also part of the span.
rendered_lines = err_str.splitlines()
assert any(" 7 " in line and ")" in line for line in rendered_lines), err_str
# The underline carets must be present on more than one line (multi-line).
marker_lines = [line for line in rendered_lines if "^" in line]
assert len(marker_lines) >= 2, err_str
# The gutter must show distinct line numbers for the spanned lines.
assert " 4 " in err_str and " 5 " in err_str and " 6 " in err_str, err_str
def test_format_source_snippet_multi_line():
"""Unit-level check that _format_source_snippet renders every line in a
multi-line span, with the underline covering start-col..EOL on the first
line, full interior lines, and col-1..end-col on the last line."""
from tvm.script.parser.core.diagnostics import _format_source_snippet
source_lines = [
"first ignored line\n",
" foo(bar,\n",
" baz,\n",
" qux)\n",
"last ignored line\n",
]
# Span lines 2..4 (1-based), starting at col 5 ('foo'), ending at col 13
# (exclusive) on line 4.
snippet = _format_source_snippet(
source_lines, lineno=2, col_offset=5, end_lineno=4, end_col_offset=13
)
lines = snippet.splitlines()
# All three spanned source lines must be present.
assert any("foo(bar," in line for line in lines), snippet
assert any("baz," in line for line in lines), snippet
assert any("qux)" in line for line in lines), snippet
# Underline carets present on the first line under 'foo(bar,'.
assert "^" in snippet, snippet
# The line numbers 2, 3, 4 appear in the gutter.
assert " 2 |" in snippet and " 3 |" in snippet and " 4 |" in snippet, snippet
def test_format_source_snippet_single_line_unchanged():
"""A single-line span (end_lineno == lineno) underlines only the
[col_offset, end_col_offset) columns on that one line."""
from tvm.script.parser.core.diagnostics import _format_source_snippet
source_lines = ["ignored\n", " abc + def\n", "ignored\n"]
# Underline just 'abc' (cols 5..8 exclusive) on line 2.
snippet = _format_source_snippet(
source_lines, lineno=2, col_offset=5, end_lineno=2, end_col_offset=8
)
lines = snippet.splitlines()
# Exactly one source-text line and one marker line (plus the leading gutter).
text_lines = [line for line in lines if "abc + def" in line]
assert len(text_lines) == 1, snippet
marker_line = next(line for line in lines if "^" in line)
assert marker_line.count("^") == 3, snippet
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,44 @@
# 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.
"""Unittests for tvm.script.ir_builder.base"""
import pytest
from tvm.script.ir_builder import IRBuilder
def test_ir_builder_scope():
with IRBuilder() as ib: # pylint: disable=invalid-name
assert IRBuilder.current() == ib
def test_ir_builder_multi_scope():
with IRBuilder() as ib: # pylint: disable=invalid-name
with IRBuilder() as ib2: # pylint: disable=invalid-name
assert IRBuilder.current() == ib2
assert IRBuilder.current() == ib
def test_ir_builder_no_scope():
with pytest.raises(ValueError):
IRBuilder.current()
if __name__ == "__main__":
test_ir_builder_scope()
test_ir_builder_multi_scope()
test_ir_builder_no_scope()
@@ -0,0 +1,44 @@
# 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
"""Unittests for tvm.script.ir_builder.ir"""
import pytest
import tvm.testing
from tvm import ir
from tvm.ir.base import assert_structural_equal
from tvm.script.ir_builder import IRBuilder
from tvm.script.ir_builder import ir as I
def test_ir_builder_irmodule():
with IRBuilder() as ib: # pylint: disable=invalid-name
with I.ir_module():
pass
# the ir_module generated by IRBuilder
ir_module_actual = ib.get()
# the expected prim_func
ir_module_expected = ir.IRModule(None, None)
assert_structural_equal(ir_module_actual, ir_module_expected, map_free_vars=True)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,510 @@
# 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=invalid-name, missing-docstring
# ruff: noqa: F401, F841
"""Unittests for tvm.script.ir_builder.tirx"""
import numpy as np
import pytest
import tvm
import tvm.runtime
import tvm.testing
from tvm import tirx
from tvm.ir.base import assert_structural_equal
from tvm.script.ir_builder import IRBuilder
from tvm.script.ir_builder import tirx as T
def test_ir_builder_tir_primfunc_base():
with IRBuilder() as ib:
with T.prim_func(s_tir=True):
T.evaluate(0)
# the prim_func generated by IRBuilder
prim_func_actual = ib.get()
# the expected prim_func
prim_func_expected = tirx.PrimFunc(
params=[],
body=tirx.Evaluate(0),
ret_type=None,
buffer_map=None,
attrs=tvm.ir.make_node("ir.DictAttrs", s_tir=True),
)
# Check if the generated ir is expected
assert_structural_equal(prim_func_actual, prim_func_expected, map_free_vars=True)
def test_ir_builder_tir_primfunc_complete():
with IRBuilder() as ib:
with T.prim_func(s_tir=True):
T.arg("a", T.handle())
T.arg("b", T.int64())
T.arg("c", T.Buffer((128, 128), "float32"))
d = T.arg("d", T.handle())
e = T.arg("e", T.Buffer((1024,), "int8"))
T.func_attr({"key": "value"})
T.func_ret(tvm.ir.PrimType("int64"))
buffer_d = T.match_buffer(d, (64, 64), "int64")
T.evaluate(0)
# the prim_func generated by IRBuilder
prim_func_actual = ib.get()
# the expected prim_func
c_handle, c_buffer = (
tirx.Var("c_handle", tvm.ir.PointerType(tvm.ir.PrimType("void"))),
tirx.decl_buffer((128, 128), "float32", name="c", layout=None),
)
d_handle, d_buffer = (
tirx.Var("d", tvm.ir.PointerType(tvm.ir.PrimType("void"))),
tirx.decl_buffer((64, 64), "int64", name="d", layout=None),
)
e_handle, e_buffer = (
tirx.Var("e_handle", tvm.ir.PointerType(tvm.ir.PrimType("void"))),
tirx.decl_buffer((1024,), "int8", name="e", layout=None),
)
prim_func_expected = tirx.PrimFunc(
params=[
tirx.Var("a", tvm.ir.PointerType(tvm.ir.PrimType("void"))),
tirx.Var("b", "int64"),
c_handle,
d_handle,
e_handle,
],
body=tirx.Evaluate(0),
ret_type=tvm.ir.PrimType("int64"),
buffer_map={c_handle: c_buffer, d_handle: d_buffer, e_handle: e_buffer},
attrs=tvm.ir.make_node("ir.DictAttrs", key="value", s_tir=True),
)
# Check if the generated ir is expected
assert_structural_equal(prim_func_actual, prim_func_expected, map_free_vars=True)
def test_ir_builder_tir_block_base():
with IRBuilder() as ib:
with T.sblock("block"):
T.evaluate(0)
# the block generated by IRBuilder
block_realize_actual = ib.get()
# the expected block
block_expected = tirx.SBlock(
iter_vars=[],
reads=[],
writes=[],
name_hint="block",
body=tirx.Evaluate(0),
alloc_buffers=None,
match_buffers=None,
annotations={"tirx.script_parsing_detect_access": tirx.IntImm("int64", 3)},
)
block_realize_expected = tirx.SBlockRealize(
iter_values=[],
predicate=True,
block=block_expected,
)
# Check if the generated ir is expected
assert_structural_equal(block_realize_actual, block_realize_expected, map_free_vars=True)
def test_ir_builder_tir_block_complete():
with IRBuilder() as ib:
a = T.int64()
b = T.Buffer((128, 128), "float32")
c = T.Buffer((128, 128), "float32")
d = T.int32()
e = T.Buffer((128, 128), "float32")
f = T.int32()
with T.sblock("block"):
T.where(a > 1)
T.reads(b[0:16, 0:16])
T.writes(c[d:128, d:128])
T.sblock_attr({"key": "value"})
T.sblock_alloc_buffer((128, 128), "float32")
T.match_buffer(e[0:32, 0:32], (32, 32), "float32")
T.axis.spatial(128, f)
T.evaluate(0)
# the block generated by IRBuilder
block_realize_actual = ib.get()
# the expected block
var_a = tirx.Var("a", "int64")
buffer_b = tirx.decl_buffer((128, 128), "float32", name="b")
buffer_c = tirx.decl_buffer((128, 128), "float32", name="c")
var_d = tirx.Var("d", "int32")
buffer_e = tirx.decl_buffer((128, 128), "float32", name="c")
var_f = tirx.Var("f", "int32")
block_expected = tirx.SBlock(
iter_vars=[tirx.IterVar((0, 128), tirx.Var("", "int32"), iter_type=tirx.IterVar.DataPar)],
reads=[buffer_b[0:16, 0:16]],
writes=[buffer_c[var_d:128, var_d:128]],
name_hint="block",
body=tirx.Evaluate(0),
alloc_buffers=[tirx.decl_buffer((128, 128), "float32")],
match_buffers=[
tirx.MatchBufferRegion(tirx.decl_buffer((32, 32), "float32"), buffer_e[0:32, 0:32])
],
annotations={"key": "value"},
)
block_realize_expected = tirx.SBlockRealize(
iter_values=[var_f],
predicate=var_a > 1,
block=block_expected,
)
# Check if the generated ir is expected
assert_structural_equal(block_realize_actual, block_realize_expected, map_free_vars=True)
def test_ir_builder_tir_axis():
with IRBuilder() as ib:
a = T.int32()
b = T.int32()
c = T.int32()
d = T.int32()
with T.sblock("block"):
T.axis.spatial(8, a)
T.axis.reduce(16, b)
T.axis.scan(32, c)
T.axis.opaque(64, d)
T.evaluate(0)
# the block generated by IRBuilder
block_realize_actual = ib.get()
# the expected block
var_a = tirx.Var("a", "int32")
var_b = tirx.Var("b", "int32")
var_c = tirx.Var("c", "int32")
var_d = tirx.Var("d", "int32")
block_expected = tirx.SBlock(
iter_vars=[
tirx.IterVar((0, 8), tirx.Var("", "int32"), iter_type=tirx.IterVar.DataPar),
tirx.IterVar((0, 16), tirx.Var("", "int32"), iter_type=tirx.IterVar.CommReduce),
tirx.IterVar((0, 32), tirx.Var("", "int32"), iter_type=tirx.IterVar.Ordered),
tirx.IterVar((0, 64), tirx.Var("", "int32"), iter_type=tirx.IterVar.Opaque),
],
reads=[],
writes=[],
name_hint="block",
body=tirx.Evaluate(0),
annotations={"tirx.script_parsing_detect_access": tirx.IntImm("int64", 3)},
)
block_realize_expected = tirx.SBlockRealize(
iter_values=[var_a, var_b, var_c, var_d],
predicate=True,
block=block_expected,
)
# Check if the generated ir is expected
assert_structural_equal(block_realize_actual, block_realize_expected, map_free_vars=True)
def test_ir_builder_tir_for():
with IRBuilder() as ib:
with T.serial(128) as a:
with T.parallel(64) as b:
with T.vectorized(32) as c:
with T.unroll(16) as d:
with T.thread_binding(8, thread="threadIdx.x") as e:
T.evaluate(0)
# the for generated by IRBuilder
for_actual = ib.get()
# the expected for
thread_binding_expected = tirx.For(
loop_var=tirx.Var("", "int32"),
min=0,
extent=8,
kind=tirx.ForKind.THREAD_BINDING,
body=tirx.Evaluate(0),
thread_binding=tirx.IterVar(
None, tirx.Var("", "int32"), tirx.IterVar.ThreadIndex, "threadIdx.x"
),
)
unroll_expected = tirx.For(
loop_var=tirx.Var("", "int32"),
min=0,
extent=16,
kind=tirx.ForKind.UNROLLED,
body=thread_binding_expected,
)
vectorized_expected = tirx.For(
loop_var=tirx.Var("", "int32"),
min=0,
extent=32,
kind=tirx.ForKind.VECTORIZED,
body=unroll_expected,
)
parallel_expected = tirx.For(
loop_var=tirx.Var("", "int32"),
min=0,
extent=64,
kind=tirx.ForKind.PARALLEL,
body=vectorized_expected,
)
for_expected = tirx.For(
loop_var=tirx.Var("", "int32"),
min=0,
extent=128,
kind=tirx.ForKind.SERIAL,
body=parallel_expected,
)
# Check if the generated ir is expected
assert_structural_equal(for_actual, for_expected, map_free_vars=True)
def test_ir_builder_tir_for_uint():
with IRBuilder() as ib:
with T.serial(tirx.const(128, "uint32")) as a:
T.evaluate(0)
# the for generated by IRBuilder
for_actual = ib.get()
for_expected = tirx.For(
loop_var=tirx.Var("", "uint32"),
min=tirx.const(0, "uint32"),
extent=tirx.const(128, "uint32"),
kind=tirx.ForKind.SERIAL,
body=tirx.Evaluate(0),
)
# Check if the generated ir is expected
assert_structural_equal(for_actual, for_expected, map_free_vars=True)
def test_ir_builder_tir_assert():
with IRBuilder() as ib:
with T.Assert(T.int32() == 0, message="a is 0"):
T.evaluate(0)
# the assert generated by IRBuilder
assert_actual = ib.get()
# AssertStmt is a leaf. The frame emits the assert and then the body stmts as siblings.
assert_expected = tirx.SeqStmt(
[
tirx.AssertStmt(
T.int32() == 0,
tirx.StringImm("RuntimeError"),
[tirx.StringImm("a is 0")],
),
tirx.Evaluate(0),
]
)
# Check if the generated ir is expected
assert_structural_equal(assert_actual, assert_expected, map_free_vars=True)
def test_ir_builder_tir_bind():
# Test that T.bind emits a flat Bind statement and returns the Var.
with IRBuilder() as ib:
v = T.bind(tirx.IntImm("int32", 2))
# the let binding generated by IRBuilder
let_actual = ib.get()
# Bind is now flat (no body), so a single Bind stmt is emitted.
let_expected = tirx.Bind(T.int32(), tirx.IntImm("int32", 2))
# Check if the generated ir is expected
assert_structural_equal(let_actual, let_expected, map_free_vars=True)
# Check that the returned value is a Var
assert isinstance(v, tirx.Var)
def test_ir_builder_tir_thread():
with IRBuilder() as ib:
with T.prim_func(s_tir=True):
brow = T.env_thread("blockIdx.y")
with T.launch_thread(brow, 1):
T.evaluate(0)
# the prim_func generated by IRBuilder
ir_actual = ib.get()
# the expected prim_func
iter_var = tirx.IterVar((0, 1), "v", iter_type=1, thread_tag="blockIdx.y")
attr_stmt = tirx.AttrStmt(iter_var, "thread_extent", 1, tirx.Evaluate(0))
func = tirx.PrimFunc([], attr_stmt).with_attr("s_tir", True)
# Check if the generated ir is expected
assert_structural_equal(ir_actual, func, map_free_vars=True)
def test_ir_builder_tir_allocate():
with IRBuilder() as ib:
with T.prim_func(s_tir=True):
T.func_name("test")
buf = T.alloc_buffer([10], "float32", scope="local")
T.evaluate(1)
# the allocate generated by IRBuilder
ir_actual = ib.get()
body = ir_actual.body
# AllocBuffer is flat: body should be a SeqStmt with [AllocBuffer, Evaluate(1)]
assert isinstance(body, tirx.SeqStmt), f"Expected SeqStmt but got {type(body)}"
assert len(body) == 2
assert isinstance(body[0], tirx.AllocBuffer)
assert isinstance(body[1], tirx.Evaluate)
assert body[1].value.value == 1
def test_ir_builder_tir_while():
with IRBuilder() as ib:
with T.While(T.int32() > 0):
T.evaluate(0)
# the while generated by IRBuilder
ir_actual = ib.get()
# the expected while
ir_expected = tirx.While(tirx.Var("x", "int32") > 0, tirx.Evaluate(0))
# Check if the generated ir is expected
assert_structural_equal(ir_actual, ir_expected, map_free_vars=True)
def test_ir_builder_tir_if_then_else():
with IRBuilder() as ib:
with T.If(T.int32() < 12):
with T.Then():
T.evaluate(T.int32(0))
with T.Else():
T.evaluate(T.int32(1))
# the if_then_else generated by IRBuilder
ir_actual = ib.get()
# the expected if_then_else
ir_expected = tirx.IfThenElse(
tirx.Var("c", "int32") < 12,
tirx.Evaluate(tirx.IntImm("int32", 0)),
tirx.Evaluate(tirx.IntImm("int32", 1)),
)
# Check if the generated ir is expected
assert_structural_equal(ir_actual, ir_expected, map_free_vars=True)
def test_ir_builder_tir_buffer_store():
buffer_a = T.Buffer((10, 10), "float32")
i = T.int32()
with IRBuilder() as ib:
T.buffer_store(buffer_a, 0.1, [0, i])
# the buffer store generated by IRBuilder
ir_actual = ib.get()
# the expected buffer store
ir_expected = tirx.BufferStore(buffer_a, 0.1, [0, i])
# Check if the generated ir is expected
assert_structural_equal(ir_actual, ir_expected, map_free_vars=True)
def test_ir_builder_tir_buffer_store_scalable_vec():
buffer_a = T.Buffer((30,), "float32")
value = T.broadcast(0.11, 4 * tvm.tirx.vscale())
index = T.ramp(0, 1, 4 * tvm.tirx.vscale())
with IRBuilder() as ib:
T.buffer_store(buffer_a, value, [index])
# the buffer store generated by IRBuilder
ir_actual = ib.get()
# the expected buffer store
ir_expected = tirx.BufferStore(buffer_a, value, [index])
# Check if the generated ir is expected
assert_structural_equal(ir_actual, ir_expected, map_free_vars=True)
def test_ir_builder_tir_buffer_store_predicate():
buffer_a = T.Buffer((30,), "float32")
value = T.broadcast(0.11, T.vscale() * 4)
index = T.ramp(0, 1, T.vscale() * 4)
predicate = T.broadcast(T.bool(True), T.vscale() * 4)
with IRBuilder() as ib:
T.buffer_store(buffer_a, value, [index], predicate)
ir_actual = ib.get()
ir_expected = tirx.BufferStore(buffer_a, value, [index], predicate)
assert_structural_equal(ir_actual, ir_expected, map_free_vars=True)
def test_ir_builder_tir_evaluate():
with IRBuilder() as ib:
T.evaluate(0)
# the evaluate generated by IRBuilder
eval_actual = ib.get()
# the expected evaluate
eval_expected = tirx.Evaluate(0)
# Check if the generated ir is expected
assert_structural_equal(eval_actual, eval_expected, map_free_vars=True)
def test_ir_builder_tir_decl_buffer():
with IRBuilder() as ib:
with T.prim_func(s_tir=True):
T.func_name("test")
buf = T.decl_buffer([128, 128], "float32")
T.evaluate(1)
# the decl_buffer generated by IRBuilder
ir_actual = ib.get()
body = ir_actual.body
# decl_buffer without data emits AllocBuffer (flat): body should be SeqStmt
assert isinstance(body, tirx.SeqStmt), f"Expected SeqStmt but got {type(body)}"
assert len(body) == 2
assert isinstance(body[0], tirx.AllocBuffer)
assert isinstance(body[1], tirx.Evaluate)
assert body[1].value.value == 1
def test_ir_builder_tir_inline():
with IRBuilder() as ib:
m, n = T.meta_var(1), T.meta_var(2)
a, b = T.meta_var([3, 4])
T.evaluate(m.value + n.value + a.value + b.value)
# the evaluate generated by IRBuilder
eval_actual = ib.get()
# the expected evaluate
eval_expected = tirx.Evaluate(10)
# Check if the generated ir is expected
assert_structural_equal(eval_actual, eval_expected, map_free_vars=True)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,86 @@
# 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.script import tirx as T
def test_meta_programming_matmul():
def matmul_generator(M: int, N: int, K: int, dtype: str):
@T.prim_func(s_tir=True)
def matmul(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [M, K], dtype=dtype)
B = T.match_buffer(b, [N, K], dtype=dtype)
C = T.match_buffer(c, [M, N], dtype=dtype)
for i, j, k in T.grid(M, N, K):
with T.sblock():
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]
return matmul
@T.prim_func(s_tir=True)
def matmul_128_128_128_fp16(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [128, 128], dtype="float16")
B = T.match_buffer(b, [128, 128], dtype="float16")
C = T.match_buffer(c, [128, 128], dtype="float16")
for i, j, k in T.grid(128, 128, 128):
with T.sblock():
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]
f = matmul_generator(128, 128, 128, "float16").with_attr("global_symbol", "main")
tvm.ir.assert_structural_equal(f, matmul_128_128_128_fp16.with_attr("global_symbol", "main"))
def test_meta_programming_uncaptured_var():
def generate_erf(dtype):
@T.prim_func(s_tir=True)
def main(A: T.Buffer((1,), dtype), C: T.Buffer((1,), dtype)):
for i in range(1):
with T.sblock("C"):
C[i] = T.erf(A[i])
return main
@T.prim_func(s_tir=True)
def fp32(A: T.Buffer((1,), "float32"), C: T.Buffer((1,), "float32")):
for i in range(1):
with T.sblock("C"):
C[i] = T.erf(A[i])
@T.prim_func(s_tir=True)
def fp16(A: T.Buffer((1,), "float16"), C: T.Buffer((1,), "float16")):
for i in range(1):
with T.sblock("C"):
C[i] = T.erf(A[i])
f1 = generate_erf("float32").with_attr("global_symbol", "main")
tvm.ir.assert_structural_equal(f1, fp32.with_attr("global_symbol", "main"))
f2 = generate_erf("float16").with_attr("global_symbol", "main")
tvm.ir.assert_structural_equal(f2, fp16.with_attr("global_symbol", "main"))
if __name__ == "__main__":
test_meta_programming_matmul()
test_meta_programming_uncaptured_var()
@@ -0,0 +1,267 @@
# 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 numpy as np
import pytest
import tvm
import tvm.testing
from tvm.script import tirx as T
from tvm.testing import env
@T.prim_func(s_tir=True)
def get_valid_counts(
data: T.handle,
valid_count: T.handle,
out: T.handle,
out_indices: T.handle,
score_threshold: T.float32,
id_index: T.int32,
score_index: T.int32,
) -> None:
data_buf = T.match_buffer(data, (1, 2500, 6), "float32")
valid_count_buf = T.match_buffer(valid_count, (1,), "int32")
out_buf = T.match_buffer(out, (1, 2500, 6), "float32")
out_indices_buf = T.match_buffer(out_indices, (1, 2500), "int32")
with T.sblock("init"):
vi = T.axis.S(1, 0)
valid_count_buf[vi] = T.int32(0)
for j in range(2500):
with T.sblock("update"):
vj = T.axis.S(2500, j)
T.reads([data_buf[vi, vj, 6]])
T.writes([valid_count_buf[vi], out_indices_buf[vi, vj], out_buf[vi, vj, 6]])
if (data_buf[vi, vj, score_index] > score_threshold) and (
(id_index < 0) or (data_buf[vi, vj, id_index] >= T.float32(0))
):
for k in T.serial(0, 6):
out_buf[vi, valid_count_buf[vi], k] = data_buf[vi, vj, k]
out_indices_buf[vi, valid_count_buf[vi]] = vj
valid_count_buf[vi] = valid_count_buf[vi] + 1
if vj >= valid_count_buf[vi]:
for k in T.serial(0, 6):
out_buf[vi, vj, k] = T.float32(-1)
out_indices_buf[vi, vj] = T.int32(-1)
def _check_get_valid_counts_with_numpy(f, dshape, score_threshold, id_index, score_index):
dtype = "float32"
ctx = tvm.cpu()
batch_size, num_anchor, elem_length = dshape
np_data = np.random.uniform(low=-2, high=2, size=dshape).astype(dtype)
np_out1 = np.zeros(shape=(batch_size,), dtype="int32")
np_out2 = np.zeros(shape=dshape).astype(dtype)
np_out3 = np.zeros(shape=(batch_size, num_anchor), dtype="int32")
for i in range(batch_size):
np_out1[i] = 0
inter_idx = 0
for j in range(num_anchor):
score = np_data[i, j, score_index]
if score > score_threshold and (id_index < 0 or np_data[i, j, id_index] >= 0):
for k in range(elem_length):
np_out2[i, inter_idx, k] = np_data[i, j, k]
np_out1[i] += 1
np_out3[i, inter_idx] = j
inter_idx += 1
if j >= np_out1[i]:
for k in range(elem_length):
np_out2[i, j, k] = -1.0
np_out3[i, j] = -1
in_data = tvm.runtime.tensor(np_data, ctx)
out1 = tvm.runtime.tensor(np_out1, ctx)
out2 = tvm.runtime.tensor(np_out2, ctx)
out3 = tvm.runtime.tensor(np_out3, ctx)
f(in_data, out1, out2, out3, score_threshold, id_index, score_index)
tvm.testing.assert_allclose(out1.numpy(), np_out1, rtol=1e-5)
tvm.testing.assert_allclose(out2.numpy(), np_out2, rtol=1e-5)
tvm.testing.assert_allclose(out3.numpy(), np_out3, rtol=1e-5)
print("test get_valid_counts end")
def test_get_valid_counts_script_func():
device = "llvm"
# check lowering
print(get_valid_counts.script())
mod = tvm.ir.IRModule({"get_valid_counts": get_valid_counts})
print(mod.script())
# check building
f = tvm.compile(mod["get_valid_counts"], target=device)
_check_get_valid_counts_with_numpy(f, (1, 2500, 6), 0.0, 0, 1)
@T.prim_func(s_tir=True)
def alloc_zero_dim_buffer(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [], dtype="float32")
B = T.match_buffer(b, [], dtype="float32")
# body
# tirx.with block("root")
C = T.sblock_alloc_buffer([], dtype="float32")
A[()] = T.float32(2)
C[()] = A[()] + B[()]
B[()] = C[()]
@T.prim_func(s_tir=True)
def alloc_zero_dim_buffer_block(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (), "float32")
B = T.match_buffer(b, (), "float32")
with T.sblock("root"):
T.reads([])
T.writes([])
C = T.sblock_alloc_buffer((), "float32")
A[()] = T.float32(2)
C[()] = A[()] + B[()]
B[()] = C[()]
def _check_alloc_zero_dim_buffer(f):
dtype = "float32"
ctx = tvm.cpu()
np_data = np.zeros(shape=()).astype(dtype)
np_out = np.zeros(shape=()).astype(dtype)
tvm_data = tvm.runtime.tensor(np_data, ctx)
tvm_out = tvm.runtime.tensor(np_out, ctx)
# np func exection
np_inter = np.array(1)
np_data[()] = 2.0
np_inter[()] = np_data[()] + np_out[()]
np_out[()] = np_inter[()]
# tvm func execution
f(tvm_data, tvm_out)
tvm.testing.assert_allclose(tvm_out.numpy(), np_out, rtol=1e-5)
def test_alloc_zero_dim_buffer_round_trip():
func = alloc_zero_dim_buffer
func_with_block = alloc_zero_dim_buffer_block
rt_func = tvm.script.from_source(func.script())
rt_func_with_block = tvm.script.from_source(func_with_block.script())
rt_mod = tvm.compile(rt_func, "llvm")
rt_mod_with_block = tvm.compile(rt_func_with_block, "llvm")
tvm.ir.assert_structural_equal(
func.with_attr("global_symbol", "main"), func_with_block.with_attr("global_symbol", "main")
)
tvm.ir.assert_structural_equal(
rt_func.with_attr("global_symbol", "main"),
rt_func_with_block.with_attr("global_symbol", "main"),
)
_check_alloc_zero_dim_buffer(rt_mod)
_check_alloc_zero_dim_buffer(rt_mod_with_block)
@T.prim_func(s_tir=True)
def ceildiv_test(A: T.Buffer(16, "int32")):
for i in range(16):
A[i] = T.ceildiv(A[i], 4)
@pytest.mark.skipif(not env.has_llvm(), reason="need llvm")
def test_ceildiv():
f = tvm.compile(ceildiv_test, "llvm")
a = tvm.runtime.tensor(np.arange(16).astype("int32"))
f(a)
ref = (np.arange(16) + 3) // 4
tvm.testing.assert_allclose(a.numpy(), ref)
try:
@T.prim_func(s_tir=True)
def slice_op_test(
A: T.Buffer((10,), "float32"), B: T.Buffer((10,), "float32"), C: T.Buffer((10,), "uint32")
):
B[0:5] = A[0:5] + B[0:5]
B[0:5] = A[0:5] - B[0:5]
B[0:5] = A[0:5] * B[0:5]
B[0:5] = A[0:5] / B[0:5]
C[0:5] = C[0:5] % T.broadcast(T.uint32(5), 5)
B[0:5] = -B[0:5]
C[0:5] = C[0:5] >> 4
C[0:5] = C[0:5] << 4
C[0:5] = C[0:5] << C[0:5]
C[0:5] = C[0:5] >> C[0:5]
T.evaluate(A[0:5] > B[0:5])
T.evaluate(A[0:5] > 5)
T.evaluate(A[0:5] >= B[0:5])
T.evaluate(A[0:5] >= 5)
T.evaluate(A[0:5] < B[0:5])
T.evaluate(A[0:5] < 5)
T.evaluate(A[0:5] <= B[0:5])
T.evaluate(A[0:5] <= 5)
T.evaluate(A[0:5] == B[0:5])
T.evaluate(A[0:5] == 5)
T.evaluate(A[0:5] != B[0:5])
T.evaluate(A[0:5] != 5)
T.evaluate((A[0:5] > 0) and (B[0:5] > 0))
T.evaluate((A[0:5] > 0) or (B[0:5] > 0))
T.evaluate((A[0:5] < 0) and (1 > 0))
T.evaluate((A[0:5] > 0) or (1 > 0))
@T.prim_func(s_tir=True)
def slice_op_test_ref(
A: T.Buffer((10,), "float32"), B: T.Buffer((10,), "float32"), C: T.Buffer((10,), "uint32")
):
B[0:5] = A[0:5] + B[0:5]
B[0:5] = A[0:5] - B[0:5]
B[0:5] = A[0:5] * B[0:5]
B[0:5] = A[0:5] / B[0:5]
C[0:5] = C[0:5] % T.Broadcast(T.uint32(5), 5)
B[0:5] = B[0:5] * T.Broadcast(T.float32(-1), 5)
C[0:5] = T.shift_right(C[0:5], T.Broadcast(T.uint32(4), 5))
C[0:5] = T.shift_left(C[0:5], T.Broadcast(T.uint32(4), 5))
C[0:5] = T.shift_left(C[0:5], C[0:5])
C[0:5] = T.shift_right(C[0:5], C[0:5])
T.evaluate(A[0:5] > B[0:5])
T.evaluate(A[0:5] > T.Broadcast(T.float32(5), 5))
T.evaluate(A[0:5] >= B[0:5])
T.evaluate(A[0:5] >= T.Broadcast(T.float32(5), 5))
T.evaluate(A[0:5] < B[0:5])
T.evaluate(A[0:5] < T.Broadcast(T.float32(5), 5))
T.evaluate(A[0:5] <= B[0:5])
T.evaluate(A[0:5] <= T.Broadcast(T.float32(5), 5))
T.evaluate(A[0:5] == B[0:5])
T.evaluate(A[0:5] == T.Broadcast(T.float32(5), 5))
T.evaluate(A[0:5] != B[0:5])
T.evaluate(A[0:5] != T.Broadcast(T.float32(5), 5))
T.bitwise_and(A[0:5] > T.Broadcast(T.float32(0), 5), B[0:5] > T.Broadcast(T.float32(0), 5))
T.bitwise_or(A[0:5] > T.Broadcast(T.float32(0), 5), B[0:5] > T.Broadcast(T.float32(0), 5))
T.bitwise_and(A[0:5] < T.Broadcast(T.float32(0), 5), T.Broadcast(T.bool(1), 5))
T.bitwise_or(A[0:5] > T.Broadcast(T.float32(0), 5), T.Broadcast(T.bool(1), 5))
except tvm.error.DiagnosticError:
slice_op_test = None
slice_op_test_ref = None
def test_slice_op():
if slice_op_test is None:
pytest.skip("slice arithmetic on BufferRegion is not defined")
tvm.ir.assert_structural_equal(
slice_op_test.with_attr("global_symbol", "main"),
slice_op_test_ref.with_attr("global_symbol", "main"),
)
if __name__ == "__main__":
test_get_valid_counts_script_func()
test_alloc_zero_dim_buffer_round_trip()
test_slice_op()
@@ -0,0 +1,66 @@
# 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
"""Unittests for tvm.script.parser.evaluator"""
import pytest
import tvm.testing
from tvm.script.parser.core.diagnostics import Source
from tvm.script.parser.core.evaluator import ExprEvaluator
def _calc(expr, extra_vars=None):
if extra_vars is None:
extra_vars = {}
source = Source(expr)
mod_ast = source.as_ast()
mod_body_ast = mod_ast.body
expr_stmt_ast = mod_body_ast[0]
expr_ast = expr_stmt_ast.value
return ExprEvaluator.eval(None, extra_vars, expr_ast)
def test_evaluator_basic():
assert _calc("1, 3.14, True, 'str'") == (1, 3.14, True, "str")
def test_evaluator_op():
assert _calc("1 + 2, 1 - 2, 1 * 2, 1 / 2") == (3, -1, 2, 0.5)
def test_evaluator_value_table():
res = _calc("a + b, a - b, a * b, a / b", {"a": 1, "b": 2})
a, b = 1, 2
assert res == (a + b, a - b, a * b, a / b)
def test_evaluator_func_call():
def func(a, b):
return a + b, a - b, a * b, a / b
assert _calc("func(1, 2)", {"func": func}) == func(1, 2)
def test_evaluator_slice():
res = _calc("a, a[1:], a[:5], a[1: 5], a[1: 5: 2]", {"a": [1, 2, 3, 4, 5, 6]})
a = [1, 2, 3, 4, 5, 6]
assert res == (a, a[1:], a[:5], a[1:5], a[1:5:2])
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,39 @@
# 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
"""Unittests for tvm.script.parser.ir"""
import inspect
import pytest
import tvm.testing
from tvm.ir import IRModule
from tvm.script.parser import ir_module
def test_ir_base():
@ir_module
class BlankIRModule:
pass
assert isinstance(BlankIRModule, IRModule) and len(BlankIRModule.functions.items()) == 0
assert BlankIRModule.__name__ == "BlankIRModule"
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,105 @@
# 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
"""Unittests for tvm.script.parser.core"""
import inspect
import pytest
import tvm.testing
from tvm.script import tirx as T
from tvm.script.parser.core import doc_core as doc
from tvm.script.parser.core.diagnostics import Source
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])
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
def test_source_base():
source = Source(matmul)
assert (
source.source_name == inspect.getsourcefile(matmul)
and source.start_line is not None
and source.start_column == 0
and source.source == inspect.getsource(matmul)
and source.full_source == inspect.getsource(inspect.getmodule(matmul))
)
def test_source_ast():
source = Source(matmul)
mod = source.as_ast()
assert isinstance(mod, doc.Module)
func_def = mod.body[0]
assert isinstance(func_def, doc.FunctionDef)
assert func_def.name == "matmul"
func_args = func_def.args
assert (
len(func_args.args) == 3
and func_args.args[0].arg == "a"
and func_args.args[1].arg == "b"
and func_args.args[2].arg == "c"
)
func_body = func_def.body
assert len(func_body) == 4
func_assigns = func_body[:3]
assert (
isinstance(func_assigns[0], doc.Assign)
and func_assigns[0].targets[0].id == "A"
and isinstance(func_assigns[1], doc.Assign)
and func_assigns[1].targets[0].id == "B"
and isinstance(func_assigns[2], doc.Assign)
and func_assigns[2].targets[0].id == "C"
)
func_for = func_body[3]
assert (
len(func_for.target.elts) == 3
and func_for.target.elts[0].id == "i"
and func_for.target.elts[1].id == "j"
and func_for.target.elts[2].id == "k"
)
for_body = func_for.body
assert len(for_body) == 1
for_block = for_body[0]
assert isinstance(for_block, doc.With) and len(for_block.body) == 2
def test_nesting_parsing():
class dummy:
pass
for i in range(1):
@tvm.script.ir_module
class Module:
@T.prim_func(s_tir=True)
def impl(
A: T.Buffer((12, 196, 64), "float32"),
) -> None:
T.evaluate(0)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,709 @@
# 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.
"""Unittests for tvm.script.parser.tirx"""
import pytest
import tvm_ffi
import tvm.testing
from tvm import ir, tirx
from tvm.script.parser import tirx as T
def test_tir_buffer_proxy():
buffer_0 = T.Buffer((128, 128), "float32")
assert (
isinstance(buffer_0, tirx.Buffer)
and list(buffer_0.shape) == [128, 128]
and buffer_0.dtype == ir.PrimType("float32")
)
buffer_1 = T.Buffer((64, 64, 64), "int32")
assert (
isinstance(buffer_1, tirx.Buffer)
and list(buffer_1.shape) == [64, 64, 64]
and buffer_1.dtype == ir.PrimType("int32")
)
def test_tir_ptr_proxy():
ptr_0 = T.handle("int32", "global")
assert (
isinstance(ptr_0, tirx.Var)
and isinstance(ptr_0.ty, ir.PointerType)
and ptr_0.ty.element_type == ir.PrimType("int32")
and ptr_0.ty.storage_scope == "global"
)
ptr_1 = T.handle("float32", "shared")
assert (
isinstance(ptr_1, tirx.Var)
and isinstance(ptr_1.ty, ir.PointerType)
and ptr_1.ty.element_type == ir.PrimType("float32")
and ptr_1.ty.storage_scope == "shared"
)
def test_tir_func_name():
@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])
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
assert matmul.__name__ == "matmul"
assert matmul.attrs["global_symbol"] == "matmul"
def test_tir_func_private_attrs():
@T.prim_func(private=True, s_tir=True)
def matmul(a: T.handle, b: T.handle, c: T.handle) -> None:
T.func_attr({"attr": "value"})
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])
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
assert "global_symbol" not in matmul.attrs
def test_tir_func_private_manual_global_symbol_fail():
with pytest.raises(tvm.error.DiagnosticError):
@T.prim_func(private=True, s_tir=True)
def matmul(a: T.handle, b: T.handle, c: T.handle) -> None:
T.func_attr({"global_symbol": "matmul"})
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])
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
# should not execute
assert matmul.__name__ == "matmul"
def test_tir_macro_decorator_signature():
@T.prim_func(private=True, s_tir=True)
def evaluate0():
T.evaluate(0)
# Ok, no parentheses
@T.inline
def func1():
T.evaluate(0)
@T.prim_func(private=True, s_tir=True)
def use1():
func1()
tvm.ir.assert_structural_equal(use1, evaluate0)
# Ok, empty parentheses
@T.inline()
def func2():
T.evaluate(0)
@T.prim_func(private=True, s_tir=True)
def use2():
func2()
tvm.ir.assert_structural_equal(use1, evaluate0)
with pytest.raises(ValueError):
# Wrong: non-keyword argument
@T.inline(True)
def func3():
T.evaluate()
def test_tir_macro_signature():
@T.inline
def assign(i, *args, t1, **kwargs):
vi, vj, vk = T.axis.remap("SSR", [i, args[0], args[1]])
kwargs["t3"][vi, vj] = kwargs["t3"][vi, vj] + t1[vi, vk] * kwargs["t2"][vj, vk]
@T.prim_func(private=True, s_tir=True)
def matmul_w_macro(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"):
assign(i, j, k, t1=A, t2=B, t3=C)
@T.prim_func(private=True, s_tir=True)
def matmul_no_macro(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])
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
tvm.ir.assert_structural_equal(matmul_no_macro, matmul_w_macro)
def test_tir_macro_hygienic():
x_value = 128
@T.inline
def static_capture(A, B):
B[()] = A[x_value]
@T.prim_func(private=True, s_tir=True)
def use_hygienic(A: T.Buffer((1024,), "int32"), B: T.Buffer((), "int32")) -> None:
for x_value in T.serial(10):
static_capture(A, B)
@T.prim_func(private=True, s_tir=True)
def expected_hygienic(A: T.Buffer((1024,), "int32"), B: T.Buffer((), "int32")) -> None:
for x_value in range(10):
B[()] = A[128]
tvm.ir.assert_structural_equal(use_hygienic, expected_hygienic)
def test_tir_inline_late_binding():
"""Inline defined inside prim_func uses LEGB late binding:
it sees the current value of variables from its enclosing scope at call time."""
@T.prim_func(private=True, s_tir=True)
def use_late_binding(A: T.Buffer((1024,), "int32"), B: T.Buffer((), "int32")) -> None:
for x_value in T.serial(10):
@T.inline
def capture(A, B):
B[()] = A[x_value]
capture(A, B)
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((1024,), "int32"), B: T.Buffer((), "int32")) -> None:
for x_value in range(10):
B[()] = A[x_value]
tvm.ir.assert_structural_equal(use_late_binding, expected)
def test_tir_macro_in_class():
class Object:
def __init__(self, x: T.Buffer):
self.local_x = T.sblock_alloc_buffer(x.shape, x.dtype)
@T.inline
def load(self, x: T.Buffer):
N, M = T.meta_var(self.local_x.shape)
for i, j in T.grid(N, M):
with T.sblock("update"):
vi, vj = T.axis.remap("SS", [i, j])
self.local_x[vi, vj] = x[vi, vj]
@T.prim_func(private=True, s_tir=True)
def func_w_macro(a: T.handle):
A = T.match_buffer(a, [128, 128])
o1 = T.meta_var(Object(A))
o1.load(A)
o2 = T.meta_var(Object(A))
o2.load(o1.local_x)
@T.prim_func(private=True, s_tir=True)
def func_no_macro(a: T.handle):
A = T.match_buffer(a, [128, 128])
local_a = T.sblock_alloc_buffer([128, 128])
for i, j in T.grid(128, 128):
with T.sblock("update"):
vi, vj = T.axis.remap("SS", [i, j])
local_a[vi, vj] = A[vi, vj]
local_b = T.sblock_alloc_buffer([128, 128])
for i, j in T.grid(128, 128):
with T.sblock("update"):
vi, vj = T.axis.remap("SS", [i, j])
local_b[vi, vj] = local_a[vi, vj]
tvm.ir.assert_structural_equal(func_no_macro, func_w_macro)
def test_tir_starred_expression():
dims = (128, 128)
@T.prim_func(private=True, s_tir=True)
def starred(a: T.handle) -> None:
A = T.match_buffer(a, [128, *dims], "int32")
for i, j, k in T.grid(128, *dims):
A[i, j, k] = T.int32(1)
@T.prim_func(private=True, s_tir=True)
def non_starred(a: T.handle) -> None:
A = T.match_buffer(a, [128, 128, 128], "int32")
for i, j, k in T.grid(128, 128, 128):
A[i, j, k] = T.int32(1)
tvm.ir.assert_structural_equal(starred, non_starred)
def test_tir_starred_shape_expression():
dims = (128, 128)
@T.prim_func(private=True, s_tir=True)
def starred(a: T.handle) -> None:
A = T.match_buffer(a, [128, *dims], "int32")
for i, j, k in T.grid(*A.shape):
A[i, j, k] = T.int32(1)
@T.prim_func(private=True, s_tir=True)
def non_starred(a: T.handle) -> None:
A = T.match_buffer(a, [128, 128, 128], "int32")
for i, j, k in T.grid(128, 128, 128):
A[i, j, k] = T.int32(1)
tvm.ir.assert_structural_equal(starred, non_starred)
def test_tir_dynamic_for_loop():
dims = (128, 128)
@T.prim_func(private=True, s_tir=True)
def starred(a: T.handle) -> None:
A = T.match_buffer(a, [128, *dims], "int32")
for iters in T.grid(*A.shape):
A[iters] = T.int32(1)
@T.prim_func(private=True, s_tir=True)
def non_starred(a: T.handle) -> None:
A = T.match_buffer(a, [128, 128, 128], "int32")
for i, j, k in T.grid(128, 128, 128):
A[i, j, k] = T.int32(1)
tvm.ir.assert_structural_equal(starred, non_starred)
def test_tir_starred_for_loop():
dims = (128, 128)
@T.prim_func(private=True, s_tir=True)
def starred(a: T.handle, b: T.handle):
A = T.match_buffer(a, [*dims, 128], "int32")
B = T.match_buffer(b, dims, "int32")
for *spatial, reduction in T.grid(*A.shape):
with T.sblock("reduce"):
with T.init():
B[spatial] = T.int32(0)
B[spatial] = B[spatial] + A[(*spatial, reduction)]
@T.prim_func(private=True, s_tir=True)
def non_starred(a: T.handle, b: T.handle):
A = T.match_buffer(a, [128, 128, 128], "int32")
B = T.match_buffer(b, [128, 128], "int32")
for i, j, k in T.grid(128, 128, 128):
with T.sblock("reduce"):
with T.init():
B[i, j] = T.int32(0)
B[i, j] = B[i, j] + A[i, j, k]
tvm.ir.assert_structural_equal(starred, non_starred)
def test_tir_loop_steps():
N = T.Var("N", "int32")
@T.prim_func(private=True, s_tir=True)
def loop_with_steps(
A: T.Buffer((N,)), B: T.Buffer((N,)), C: T.Buffer((N,)), tid: T.int32, v: T.int32
):
for i in T.serial(tid, N, step=2):
C[i] = A[i] + B[i]
for i in T.unroll(tid, N, step=3):
C[i] = A[i] + B[i]
for i in T.vectorized(tid, N, step=4):
C[i] = A[i] + B[i]
for i in T.parallel(tid, N, step=5):
C[i] = A[i] + B[i]
for i in T.serial(tid, N, step=v):
C[i] = A[i] + B[i]
stmts = loop_with_steps.body.seq
assert stmts[0].step == 2
assert stmts[1].step == 3
assert stmts[2].step == 4
assert stmts[3].step == 5
assert stmts[4].step.name == "v"
def test_tir_empty_tuple_index():
@T.inline
def bar(val):
T.evaluate(val)
@T.prim_func(private=True, s_tir=True)
def func_with_empty_tuple(A: T.Buffer((), "int32"), B: T.Buffer((), "int32")):
bar(val=A[()])
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((), "int32"), B: T.Buffer((), "int32")):
T.evaluate(A[()])
tvm.ir.assert_structural_equal(func_with_empty_tuple, expected)
def test_tir_builtin_expression():
dims = (128, 128)
@T.prim_func(private=True, s_tir=True)
def with_builtin(a: T.handle) -> None:
A = T.match_buffer(a, [len(dims), *dims], "int32")
for i, j, k in T.grid(*A.shape):
A[i, j, k] = T.int32(1 + len(A.shape))
@T.prim_func(private=True, s_tir=True)
def evaluated(A: T.Buffer((2, 128, 128), "int32")):
for i, j, k in T.grid(2, 128, 128):
A[i, j, k] = 4
tvm.ir.assert_structural_equal(with_builtin, evaluated)
def test_thread_binding_dtype():
@T.prim_func(private=True, s_tir=True)
def func(A: T.Buffer((128, 128)), B: T.Buffer((128, 128))):
for i in T.thread_binding(T.int64(128), "threadIdx.x"):
for j in T.thread_binding(128, "threadIdx.y"):
B[i, j] = A[i, j]
loop_i = func.body
loop_j = loop_i.body
assert loop_i.loop_var.ty.dtype == "int64"
assert loop_i.thread_binding.var.ty.dtype == "int64"
assert loop_j.loop_var.ty.dtype == "int32"
assert loop_j.thread_binding.var.ty.dtype == "int32"
def test_inferred_ty_with_prim_args():
"""A PrimFunc may have inferred Type"""
@T.prim_func(s_tir=True)
def func(M: T.int32, N: T.int32) -> T.int32:
T.ret(M * N)
expected = tvm.relax.FuncType(
[
tvm.ir.PrimType("int32"),
tvm.ir.PrimType("int32"),
],
tvm.ir.PrimType("int32"),
purity=True,
)
tvm.ir.assert_structural_equal(func.ty, expected)
def test_inferred_ty_with_buffer_args():
"""PrimFunc buffer arguments are inferred as R.Tensor"""
@T.prim_func(s_tir=True)
def func(A: T.Buffer([16, 16], "float32"), B: T.Buffer([256], "int32")) -> T.float32:
T.ret(T.float32(42.0))
expected = tvm.relax.FuncType(
[
tvm.relax.TensorType([16, 16], "float32"),
tvm.relax.TensorType([256], "int32"),
],
tvm.ir.PrimType("float32"),
purity=True,
)
tvm.ir.assert_structural_equal(func.ty, expected)
def test_inferred_ty_with_internal_allocation():
"""A pure function may still write to internal allocations.
Whether a function writes to internal allocations is not a visible
effect, and does not impact the purity of a function.
"""
@T.prim_func(s_tir=True)
def func(A: T.Buffer([16, 16], "float32")) -> T.float32:
Sum = T.decl_buffer([], "float32")
Sum[()] = 0.0
for i, j in T.grid(16, 16):
Sum[()] = Sum[()] + A[i, j]
T.ret(Sum[()])
expected = tvm.relax.FuncType(
[
tvm.relax.TensorType([16, 16], "float32"),
],
tvm.ir.PrimType("float32"),
purity=True,
)
tvm.ir.assert_structural_equal(func.ty, expected)
def test_inferred_ty_with_output_buffer():
"""A pure function may not write to an argument buffer
If an argument buffer is written to, the function must be impure.
"""
@T.prim_func(s_tir=True)
def func(A: T.Buffer(16, "float32"), B: T.Buffer(16, "float32")):
for i in range(16):
B[i] = A[i]
expected = tvm.relax.FuncType(
[
tvm.relax.TensorType([16], "float32"),
tvm.relax.TensorType([16], "float32"),
],
tvm.relax.TupleType([]),
purity=False,
)
tvm.ir.assert_structural_equal(func.ty, expected)
def test_inferred_ty_with_dynamic_buffer():
"""The inferred Type may contain dynamic shapes"""
@T.prim_func(s_tir=True)
def func(a_handle: T.handle, b_handle: T.handle):
M = T.int64()
N = T.int64()
A = T.match_buffer(a_handle, [M, N], "float32")
B = T.match_buffer(b_handle, [M * N], "float32")
for i, j in T.grid(M, N):
B[i * N + j] = A[i, j]
M = tvm.tirx.Var("M", "int64")
N = tvm.tirx.Var("N", "int64")
expected = tvm.relax.FuncType(
[
tvm.relax.TensorType([M, N], "float32"),
tvm.relax.TensorType([M * N], "float32"),
],
tvm.relax.TupleType([]),
purity=False,
)
tvm.ir.assert_structural_equal(func.ty, expected)
def test_reinterpret_nop():
"""Test builtin reinterpret op"""
@T.prim_func(s_tir=True)
def func(A: T.Buffer((32,), "float32"), B: T.Buffer((32,), "float32")) -> None:
T.func_attr({"global_symbol": "main"})
for i in T.serial(0, 32):
with T.sblock():
vi = T.axis.remap("S", [i])
B[vi] = T.reinterpret("float32", A[vi])
@T.prim_func(s_tir=True)
def expected(A: T.Buffer((32,), "float32"), B: T.Buffer((32,), "float32")) -> None:
T.func_attr({"global_symbol": "main"})
for i in T.serial(0, 32):
with T.sblock():
vi = T.axis.remap("S", [i])
B[vi] = A[vi]
tvm.ir.assert_structural_equal(func, expected)
def test_launch_thread_i64():
"""Test launching thread with int64"""
@T.prim_func(s_tir=True)
def func() -> None:
blockIdx_x = T.launch_thread("blockIdx.x", T.int64(1))
if blockIdx_x == T.int64(0):
T.evaluate(T.int64(0))
else:
T.evaluate(T.int64(1))
assert func.body.node.dom.min.ty.dtype == "int64"
assert func.body.node.dom.extent.ty.dtype == "int64"
def test_deterministic_branch():
"""Test deterministic branch"""
def create_func(predicate: bool):
@T.prim_func(private=True, s_tir=True)
def func() -> None:
if predicate:
T.evaluate(0)
else:
T.evaluate(1)
return func
def create_expected(value):
@T.prim_func(private=True, s_tir=True)
def expected() -> None:
T.evaluate(value)
return expected
tvm.ir.assert_structural_equal(create_func(True), create_expected(0))
tvm.ir.assert_structural_equal(create_func(False), create_expected(1))
def test_block_annotation_merge():
def _to_dict(anno: tvm_ffi.container.Map):
result = {}
for k, v in anno.items():
result[k] = _to_dict(v) if isinstance(v, tvm_ffi.container.Map) else v
return result
@T.prim_func(s_tir=True)
def func0():
with T.sblock():
T.sblock_attr({"key1": "block1"})
T.sblock_attr({"key2": "block2"})
T.evaluate(0)
assert _to_dict(func0.body.block.annotations) == {"key1": "block1", "key2": "block2"}
@T.prim_func(s_tir=True)
def func1():
with T.sblock():
T.sblock_attr({"key": {"key1": "block1"}})
T.sblock_attr({"key": {"key2": "block2"}})
T.evaluate(0)
assert _to_dict(func1.body.block.annotations) == {"key": {"key1": "block1", "key2": "block2"}}
@T.prim_func(s_tir=True)
def func2():
with T.sblock():
T.sblock_attr({"key1": "block1"})
T.sblock_attr({"key1": "block1"})
T.evaluate(0)
assert _to_dict(func2.body.block.annotations) == {"key1": "block1"}
with pytest.raises(RuntimeError):
@T.prim_func(s_tir=True)
def func3():
with T.sblock():
T.sblock_attr({"key1": "block1"})
T.sblock_attr({"key1": "block2"})
T.evaluate(0)
def test_alloc_inside_block():
@T.prim_func(private=True, s_tir=True)
def func() -> None:
with T.sblock():
A = T.sblock_alloc_buffer([10], "float32")
for i in T.serial(0, 10):
B = T.sblock_alloc_buffer([10], "float32")
for j in T.serial(0, 10):
B[j] = T.float32(j)
A[i] += B[j]
@T.prim_func(private=True, s_tir=True)
def expected() -> None:
with T.sblock():
A = T.sblock_alloc_buffer([10], "float32")
B = T.sblock_alloc_buffer([10], "float32")
for i, j in T.grid(10, 10):
B[j] = T.float32(j)
A[i] += B[j]
tvm.ir.assert_structural_equal(func, expected)
def test_tir_macro_block_name_suffix():
@T.inline
def operation(A, idx):
with T.sblock("op"):
v = T.axis.remap("S", [idx])
A[v] = A[v] * T.float32(2)
@T.prim_func(private=True, s_tir=True)
def func_w_macro(a: T.handle) -> None:
A = T.match_buffer(a, [10])
for i in T.serial(0, 10):
operation(A, i)
operation(A, i)
operation(A, i)
@T.prim_func(private=True, s_tir=True)
def expected(a: T.handle) -> None:
A = T.match_buffer(a, [10])
for i in T.serial(0, 10):
with T.sblock("op"):
v = T.axis.remap("S", [i])
A[v] = A[v] * T.float32(2)
with T.sblock("op_1"):
v = T.axis.remap("S", [i])
A[v] = A[v] * T.float32(2)
with T.sblock("op_2"):
v = T.axis.remap("S", [i])
A[v] = A[v] * T.float32(2)
tvm.ir.assert_structural_equal(func_w_macro, expected)
def test_ifexp():
@T.prim_func(private=True, s_tir=True)
def func(A: T.buffer((128, 128), "float32")):
for i, j in T.grid(128, 128):
A[i, j] = i if i < j else j
@T.prim_func(private=True, s_tir=True)
def expected(A: T.buffer((128, 128), "float32")):
for i, j in T.grid(128, 128):
A[i, j] = T.if_then_else(i < j, i, j)
tvm.ir.assert_structural_equal(func, expected)
def test_sequence_compare():
@T.prim_func(private=True, s_tir=True)
def tir_func(A: T.Buffer((128, 128), "float32")):
for i, j in T.grid(128, 128):
if 0 < i < 128 and 0 < j < 128:
A[i, j] = 1
else:
A[i, j] = 0
@T.prim_func(private=True, s_tir=True)
def expected(A: T.buffer((128, 128), "float32")):
for i, j in T.grid(128, 128):
if (0 < i and i < 128) and (0 < j and j < 128):
A[i, j] = 1
else:
A[i, j] = 0
tvm.ir.assert_structural_equal(tir_func, expected)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,158 @@
# 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.
"""Test TVMScript with PEP 563 (from __future__ import annotations).
IMPORTANT: The `from __future__ import annotations` import below is the
test condition itself, because we need to test compatibility with it.
"""
from __future__ import annotations
import tvm
import tvm.testing
from tvm.script import ir as I
from tvm.script import tirx as T
def _normalize(func):
"""Strip the global_symbol so function names do not affect structural equality."""
return func.with_attr("global_symbol", "")
def test_prim_func_closure_shape():
"""Closure variable used in Buffer shape annotation."""
def f(M=16):
@T.prim_func(s_tir=True)
def func(A: T.Buffer((M,), "float32")):
T.evaluate(0)
return func
@T.prim_func(s_tir=True)
def expected_16(A: T.Buffer((16,), "float32")):
T.evaluate(0)
@T.prim_func(s_tir=True)
def expected_32(A: T.Buffer((32,), "float32")):
T.evaluate(0)
tvm.ir.assert_structural_equal(_normalize(f(16)), _normalize(expected_16))
tvm.ir.assert_structural_equal(_normalize(f(32)), _normalize(expected_32))
def test_prim_func_closure_dtype():
"""Closure variable used as Buffer dtype."""
def f(dtype="float32"):
@T.prim_func(s_tir=True)
def func(A: T.Buffer((16,), dtype)):
T.evaluate(0)
return func
@T.prim_func(s_tir=True)
def expected_f32(A: T.Buffer((16,), "float32")):
T.evaluate(0)
@T.prim_func(s_tir=True)
def expected_f16(A: T.Buffer((16,), "float16")):
T.evaluate(0)
tvm.ir.assert_structural_equal(_normalize(f("float32")), _normalize(expected_f32))
tvm.ir.assert_structural_equal(_normalize(f("float16")), _normalize(expected_f16))
def test_prim_func_nested_closure():
"""Variables from enclosing scope active on the call stack (grandparent frame fallback).
With PEP 563, closure-only variables are missing from __closure__ unless they
appear in the function body. The ChainMap fallback walks the live call stack,
so this works when the enclosing frames are still active (outer calls middle
which applies the decorator, keeping outer's frame alive on the stack).
"""
def outer(M=16):
def middle(N=8):
@T.prim_func(s_tir=True)
def func(A: T.Buffer((M, N), "float32")):
T.evaluate(0)
return func
return middle()
@T.prim_func(s_tir=True)
def expected_16_8(A: T.Buffer((16, 8), "float32")):
T.evaluate(0)
@T.prim_func(s_tir=True)
def expected_32_8(A: T.Buffer((32, 8), "float32")):
T.evaluate(0)
tvm.ir.assert_structural_equal(_normalize(outer(16)), _normalize(expected_16_8))
tvm.ir.assert_structural_equal(_normalize(outer(32)), _normalize(expected_32_8))
def test_ir_module_closure():
"""Closure variable in @I.ir_module class method."""
def f(M=16):
@I.ir_module
class Mod:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((M,), "float32")):
T.evaluate(0)
return Mod
@T.prim_func(s_tir=True)
def expected_16(A: T.Buffer((16,), "float32")):
T.evaluate(0)
@T.prim_func(s_tir=True)
def expected_32(A: T.Buffer((32,), "float32")):
T.evaluate(0)
tvm.ir.assert_structural_equal(_normalize(f(16)["main"]), _normalize(expected_16))
tvm.ir.assert_structural_equal(_normalize(f(32)["main"]), _normalize(expected_32))
def test_mixed_closure_usage():
"""Closure var used in both annotation AND body -- regression check."""
def f(M=16):
@T.prim_func(s_tir=True)
def func(A: T.Buffer((M,), "float32")):
T.evaluate(M)
return func
@T.prim_func(s_tir=True)
def expected_16(A: T.Buffer((16,), "float32")):
T.evaluate(16)
@T.prim_func(s_tir=True)
def expected_32(A: T.Buffer((32,), "float32")):
T.evaluate(32)
tvm.ir.assert_structural_equal(_normalize(f(16)), _normalize(expected_16))
tvm.ir.assert_structural_equal(_normalize(f(32)), _normalize(expected_32))
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,118 @@
# 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
from typing import Optional
import pytest
from tvm_ffi.access_path import AccessPath
from tvm.script import tirx as T
@T.prim_func(s_tir=True)
def _func():
T.evaluate(-1)
T.evaluate(1)
T.evaluate(2)
T.evaluate(3)
T.evaluate(4)
T.evaluate(5)
T.evaluate(6)
T.evaluate(7)
def test_annotation_multi_access_paths():
result = _func.with_attr("global_symbol", "main").script(
path_to_annotate={
AccessPath.root().attr("body").attr("seq").array_item(1): "annotation 1",
AccessPath.root().attr("body").attr("seq").array_item(3): "annotation 3",
AccessPath.root().attr("body").attr("seq").array_item(5): "annotation 5",
AccessPath.root().attr("body").attr("seq").array_item(7): "annotation 7",
}
)
assert (
result
== """# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis
@T.prim_func(s_tir=True)
def main():
T.evaluate(-1)
T.evaluate(1) # annotation 1
T.evaluate(2)
T.evaluate(3) # annotation 3
T.evaluate(4)
T.evaluate(5) # annotation 5
T.evaluate(6)
T.evaluate(7) # annotation 7"""
)
def test_annotate_from_multi_obj():
result = _func.with_attr("global_symbol", "main").script(
obj_to_annotate={
_func.body.seq[1]: "annotation 1",
_func.body.seq[3]: "annotation 3",
_func.body.seq[5]: "annotation 5",
_func.body.seq[7]: "annotation 7",
}
)
assert (
result
== """# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis
@T.prim_func(s_tir=True)
def main():
T.evaluate(-1)
T.evaluate(1) # annotation 1
T.evaluate(2)
T.evaluate(3) # annotation 3
T.evaluate(4)
T.evaluate(5) # annotation 5
T.evaluate(6)
T.evaluate(7) # annotation 7"""
)
def test_disable_concise_scoping_when_scope_annotated():
@T.prim_func(s_tir=True)
def _func():
x = 1
y = x + 1
T.evaluate(y - 1)
# In fork, each bare `x = expr` lowers to AllocBuffer + BufferStore (local_scalar);
# the printer fuses each pair into a single `y: T.int32 = x + 1` line. Annotate the
# AllocBuffer that originates this fused line.
result = _func.with_attr("global_symbol", "main").script(
obj_to_annotate={
_func.body.seq[2]: "annotation 1",
}
)
assert (
result
== """# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis
@T.prim_func(s_tir=True)
def main():
x: T.int32 = 1
y: T.int32 = x + 1 # annotation 1
T.evaluate(y - 1)"""
)
@@ -0,0 +1,563 @@
# 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: F841
"""
In this test file, we want to make sure the Python code can construct
Doc objects, then access and modify their attributes correctly.
"""
import pytest
from tvm_ffi.access_path import AccessPath
import tvm
from tvm.script.printer.doc import (
AssertDoc,
AssignDoc,
AttrAccessDoc,
CallDoc,
ClassDoc,
CommentDoc,
DictDoc,
DocStringDoc,
ExprStmtDoc,
ForDoc,
FunctionDoc,
IdDoc,
IfDoc,
IndexDoc,
LambdaDoc,
ListDoc,
LiteralDoc,
OperationDoc,
OperationKind,
ReturnDoc,
ScopeDoc,
SliceDoc,
StmtBlockDoc,
TupleDoc,
WhileDoc,
)
@pytest.mark.parametrize(
"value",
[None, "test", 0, 1, -2, 0.0, 1.5, -1.3, True, False],
)
def test_literal_doc_construction(value):
doc = LiteralDoc(value)
if isinstance(value, float):
# FloatImm cannot be compared with Python's float directly
assert float(doc.value) == pytest.approx(value)
else:
assert doc.value == value
def test_id_doc():
doc = IdDoc("name")
assert doc.name == "name"
def test_attr_access_doc():
target = IdDoc("x")
doc = AttrAccessDoc(target, "attribute")
assert doc.value == target
assert doc.name == "attribute"
@pytest.mark.parametrize(
"indices",
[
[],
[LiteralDoc(1)],
[LiteralDoc(2), IdDoc("x")],
[SliceDoc(LiteralDoc(1), LiteralDoc(2))],
[SliceDoc(LiteralDoc(1)), IdDoc("y")],
],
)
def test_index_doc(indices):
target = IdDoc("x")
doc = IndexDoc(target, indices)
assert doc.value == target
assert list(doc.indices) == indices
@pytest.mark.parametrize(
"args, kwargs",
[
([], {}),
([LiteralDoc("arg")], {}),
([LiteralDoc("arg"), IdDoc("x")], {}),
([], {"x": LiteralDoc("x")}),
([], {"x": LiteralDoc("x"), "y": LiteralDoc("y")}),
([LiteralDoc("arg")], {"x": LiteralDoc("x"), "y": LiteralDoc("y")}),
([LiteralDoc("arg"), IdDoc("x")], {"x": LiteralDoc("x"), "y": LiteralDoc("y")}),
],
)
def test_call_doc(args, kwargs):
target = IdDoc("x")
doc = CallDoc(target, *args, **kwargs)
assert doc.callee == target
assert list(doc.args) == args
assert dict(zip(doc.kwargs_keys, doc.kwargs_values)) == kwargs
@pytest.mark.parametrize(
"operands",
[
[],
[LiteralDoc(1)],
[LiteralDoc(2), IdDoc("x")],
[LiteralDoc(2), IdDoc("x"), LiteralDoc("y")],
],
)
def test_operation_doc(operands):
# Here we just test the contructor and attr visitor of OperationDoc
# so the choice of OperationKind doesn't matter
operator = OperationKind.Add
doc = OperationDoc(OperationKind.Add, operands)
assert doc.kind == operator
assert list(doc.operands) == operands
@pytest.mark.parametrize(
"args",
[
[],
[IdDoc("x")],
[IdDoc("x"), IdDoc("y")],
],
)
def test_lambda_doc(args):
body = LiteralDoc(1)
doc = LambdaDoc(args, body)
assert doc.body == body
assert list(doc.args) == args
@pytest.mark.parametrize(
"elements",
[
[],
[IdDoc("x")],
[IdDoc("x"), IdDoc("y")],
],
)
def test_tuple_doc(elements):
doc = TupleDoc(elements)
assert list(doc.elements) == elements
@pytest.mark.parametrize(
"elements",
[
[],
[IdDoc("x")],
[IdDoc("x"), IdDoc("y")],
],
)
def test_list_doc(elements):
doc = ListDoc(elements)
assert list(doc.elements) == elements
@pytest.mark.parametrize(
"content",
[
{},
{LiteralDoc("k"): IdDoc("v")},
{LiteralDoc("k"): IdDoc("v"), LiteralDoc("k2"): IdDoc("v2")},
],
)
def test_dict_doc(content):
doc = DictDoc(content)
assert dict(zip(doc.keys, doc.values)) == content
@pytest.mark.parametrize("start", [LiteralDoc(1), None])
@pytest.mark.parametrize("stop", [LiteralDoc(2), None])
@pytest.mark.parametrize("step", [LiteralDoc(3), None])
def test_slice_doc(start, stop, step):
doc = SliceDoc(start, stop)
assert doc.start == start
assert doc.stop == stop
def test_expr_doc_attr_access():
target = IdDoc("x")
attr = "test"
doc = target.attr(attr)
assert doc.value == target
assert doc.name == attr
@pytest.mark.parametrize(
"indices",
[
(),
LiteralDoc(1),
SliceDoc(LiteralDoc(1), LiteralDoc(2)),
(LiteralDoc(1),),
(LiteralDoc(2), IdDoc("x")),
(SliceDoc(LiteralDoc(1), LiteralDoc(2)),),
(SliceDoc(LiteralDoc(1)), IdDoc("y")),
],
)
def test_expr_doc_get_item(indices):
target = IdDoc("x")
doc = target[indices]
assert doc.value == target
if not isinstance(indices, tuple):
indices = (indices,)
assert tuple(doc.indices) == indices
@pytest.mark.parametrize(
"args, kwargs",
[
([], {}),
([LiteralDoc("arg")], {}),
([LiteralDoc("arg"), IdDoc("x")], {}),
([], {"x": LiteralDoc("x")}),
([], {"x": LiteralDoc("x"), "y": LiteralDoc("y")}),
([LiteralDoc("arg")], {"x": LiteralDoc("x"), "y": LiteralDoc("y")}),
([LiteralDoc("arg"), IdDoc("x")], {"x": LiteralDoc("x"), "y": LiteralDoc("y")}),
],
)
def test_expr_doc_call_with(args, kwargs):
target = IdDoc("x")
doc = target.call(*args, **kwargs)
assert doc.callee == target
assert list(doc.args) == args
assert dict(zip(doc.kwargs_keys, doc.kwargs_values)) == kwargs
@pytest.mark.parametrize(
"stmts",
[
[],
[ExprStmtDoc(IdDoc("x"))],
[ExprStmtDoc(IdDoc("x")), ExprStmtDoc(IdDoc("y"))],
],
)
def test_stmt_block_doc(stmts):
doc = StmtBlockDoc(stmts)
assert list(doc.stmts) == stmts
@pytest.mark.parametrize(
"lhs, rhs, annotation",
[
(IdDoc("x"), IdDoc("y"), None),
(IdDoc("x"), None, IdDoc("int")),
(IdDoc("x"), IdDoc("y"), IdDoc("int")),
],
)
def test_assign_doc(lhs, rhs, annotation):
doc = AssignDoc(lhs, rhs, annotation)
assert doc.lhs == lhs
assert doc.rhs == rhs
assert doc.annotation == annotation
@pytest.mark.parametrize(
"lhs, rhs, annotation",
[
(IdDoc("x"), None, None),
(TupleDoc([IdDoc("x"), IdDoc("y")]), None, IdDoc("int")),
(TupleDoc([IdDoc("x"), IdDoc("y")]), IdDoc("u"), IdDoc("int")),
],
)
def test_invalid_assign_doc(lhs, rhs, annotation):
with pytest.raises(ValueError) as e:
AssignDoc(lhs, rhs, annotation)
@pytest.mark.parametrize(
"else_branch",
[
[],
[ExprStmtDoc(IdDoc("x"))],
[ExprStmtDoc(IdDoc("x")), ExprStmtDoc(IdDoc("y"))],
],
)
@pytest.mark.parametrize(
"then_branch",
[
[],
[ExprStmtDoc(IdDoc("x"))],
[ExprStmtDoc(IdDoc("x")), ExprStmtDoc(IdDoc("y"))],
],
)
def test_if_doc(then_branch, else_branch):
predicate = IdDoc("x")
if not then_branch and not else_branch:
with pytest.raises(ValueError) as e:
IfDoc(predicate, then_branch, else_branch)
return
else:
doc = IfDoc(predicate, then_branch, else_branch)
assert doc.predicate == predicate
assert list(doc.then_branch) == then_branch
assert list(doc.else_branch) == else_branch
@pytest.mark.parametrize(
"body",
[
[],
[ExprStmtDoc(IdDoc("x"))],
[ExprStmtDoc(IdDoc("x")), ExprStmtDoc(IdDoc("y"))],
],
)
def test_while_doc(body):
predicate = IdDoc("x")
doc = WhileDoc(predicate, body)
assert doc.predicate == predicate
assert list(doc.body) == body
@pytest.mark.parametrize(
"body",
[
[],
[ExprStmtDoc(IdDoc("x"))],
[ExprStmtDoc(IdDoc("x")), ExprStmtDoc(IdDoc("y"))],
],
)
def test_for_doc(body):
lhs = IdDoc("x")
rhs = IdDoc("y")
doc = ForDoc(lhs, rhs, body)
assert doc.lhs == lhs
assert doc.rhs == rhs
assert list(doc.body) == body
@pytest.mark.parametrize(
"lhs",
[
None,
IdDoc("x"),
],
)
@pytest.mark.parametrize(
"body",
[
[],
[ExprStmtDoc(IdDoc("x"))],
[ExprStmtDoc(IdDoc("x")), ExprStmtDoc(IdDoc("y"))],
],
)
def test_scope_doc(lhs, body):
rhs = IdDoc("y")
doc = ScopeDoc(lhs, rhs, body)
assert doc.lhs == lhs
assert doc.rhs == rhs
assert list(doc.body) == body
def test_expr_stmt_doc():
expr = IdDoc("x")
doc = ExprStmtDoc(expr)
assert doc.expr == expr
@pytest.mark.parametrize(
"msg",
[
None,
LiteralDoc("msg"),
],
)
def test_assert_doc(msg):
test = IdDoc("x")
doc = AssertDoc(test, msg)
assert doc.test == test
assert doc.msg == msg
def test_return_doc():
value = IdDoc("x")
doc = ReturnDoc(value)
assert doc.value == value
@pytest.mark.parametrize(
"args",
[
[],
[AssignDoc(IdDoc("x"), None, IdDoc("int"))],
[
AssignDoc(IdDoc("x"), None, IdDoc("int")),
AssignDoc(IdDoc("y"), LiteralDoc(1), IdDoc("int")),
],
],
)
@pytest.mark.parametrize(
"decorators",
[
[],
[IdDoc("test")],
[IdDoc("test"), IdDoc("test2")],
],
)
@pytest.mark.parametrize(
"return_type",
[
None,
LiteralDoc(None),
],
)
@pytest.mark.parametrize(
"body",
[
[],
[ExprStmtDoc(IdDoc("x"))],
[ExprStmtDoc(IdDoc("x")), ExprStmtDoc(IdDoc("y"))],
],
)
def test_function_doc(args, decorators, return_type, body):
name = IdDoc("name")
doc = FunctionDoc(name, args, decorators, return_type, body)
assert doc.name == name
assert list(doc.args) == args
assert list(doc.decorators) == decorators
assert doc.return_type == return_type
assert list(doc.body) == body
@pytest.mark.parametrize(
"decorators",
[
[],
[IdDoc("test")],
[IdDoc("test"), IdDoc("test2")],
],
)
@pytest.mark.parametrize(
"body",
[
[],
[ExprStmtDoc(IdDoc("x"))],
[ExprStmtDoc(IdDoc("x")), ExprStmtDoc(IdDoc("y"))],
],
)
def test_class_doc(decorators, body):
name = IdDoc("name")
doc = ClassDoc(name, decorators, body)
assert doc.name == name
assert list(doc.decorators) == decorators
assert list(doc.body) == body
@pytest.mark.parametrize(
"comment",
[
"",
"test comment 1",
"test comment 1\ntest comment 1",
],
)
def test_comment_doc(comment):
doc = CommentDoc(comment)
assert doc.comment == comment
@pytest.mark.parametrize(
"comment",
[
"",
"test comment 1",
"test comment 1\ntest comment 1",
],
)
def test_doc_string_doc(comment):
doc = DocStringDoc(comment)
assert doc.comment == comment
def test_stmt_doc_comment():
doc = ExprStmtDoc(IdDoc("x"))
assert doc.comment is None
comment = "test comment"
doc.comment = comment
# Make sure the previous statement doesn't set attribute
# as if it's an ordinary Python object (__slots__ enforces this).
assert not hasattr(doc, "__dict__") or "comment" not in doc.__dict__
assert doc.comment == comment
def test_doc_source_paths():
doc = IdDoc("x")
assert len(doc.source_paths) == 0
source_paths = [AccessPath.root(), AccessPath.root().attr("x")]
doc.source_paths = source_paths
# This should triggers the __getattr__ and gets a tvm_ffi.Array
assert not isinstance(doc.source_paths, list)
assert list(doc.source_paths) == source_paths
doc.source_paths = []
assert len(doc.source_paths) == 0
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,55 @@
# 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.script import tirx as T
from tvm.script.highlight import _format, cprint
def test_highlight_script():
@tvm.script.ir_module
class Module:
@T.prim_func(s_tir=True)
def main( # type: ignore
a: T.handle,
b: T.handle,
c: T.handle,
) -> None: # pylint: disable=no-self-argument
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
A = T.match_buffer(a, [16, 128, 128])
B = T.match_buffer(b, [16, 128, 128])
C = T.match_buffer(c, [16, 128, 128])
for n, i, j, k in T.grid(16, 128, 128, 128):
with T.sblock("matmul"):
vn, vi, vj, vk = T.axis.remap("SSSR", [n, i, j, k])
with T.init():
C[vn, vi, vj] = 0.0 # type: ignore
C[vn, vi, vj] = C[vn, vi, vj] + A[vn, vi, vk] * B[vn, vj, vk]
Module.show()
Module["main"].show()
Module["main"].show(style="light")
Module["main"].show(style="dark")
Module["main"].show(style="ansi")
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,68 @@
# 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.
from tvm_ffi.access_path import AccessPath
import tvm
import tvm.testing
from tvm.runtime.script_printer import PrinterConfig, _script
def test_render_invisible_path_info_defaults_to_true():
config = PrinterConfig()
assert config.render_invisible_path_info is True
result = tvm.ir.PrimType("int32").script(path_to_underline=[AccessPath.root().attr("dtype")])
assert result == (
"Access path: <root>.dtype\n"
"Note: The underlined object is the nearest visible parent of this path.\n\n"
"T.int32\n"
"^^^^^^^"
)
def test_render_invisible_path_info_without_target_path_is_unchanged():
assert tvm.ir.PrimType("int32").script() == "T.int32"
def test_render_invisible_path_info_explicit_false_uses_legacy_output():
result = tvm.ir.PrimType("int32").script(
path_to_underline=[AccessPath.root().attr("dtype")],
extra_config={"render_invisible_path_info": False},
)
assert result == "T.int32\n^^^^^^^"
def test_render_invisible_path_info_exact_visible_path_omits_note():
result = tvm.ir.PrimType("int32").script(path_to_underline=[AccessPath.root()])
assert result == "Access path: <root>\n\nT.int32\n^^^^^^^"
def test_render_invisible_path_info_without_visible_path():
result = _script(
tvm.runtime.ShapeTuple([1, 2]),
PrinterConfig(path_to_underline=[AccessPath.root().attr("missing")]),
)
assert result == (
"Access path: <root>.missing\n"
"Note: No visible object for this path is rendered in TVMScript.\n\n"
"Shape(1, 2)"
)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,68 @@
# 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 pytest
from tvm import IRModule
from tvm.script.ir_builder import IRBuilder
from tvm.script.ir_builder import ir as I
from tvm.script.ir_builder import tirx as T
def _assert_print(obj, expected):
assert str(obj).strip() == expected.strip()
assert repr(obj).strip() == expected.strip()
if isinstance(obj, IRModule):
assert obj.script().strip() == expected.strip()
def test_ir_module():
with IRBuilder() as ib: # pylint: disable=invalid-name
with I.ir_module():
with T.prim_func(s_tir=True):
T.func_name("foo")
mod = ib.get()
_assert_print(
mod,
"""
# from tvm.script import ir as I
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis
@I.ir_module
class Module:
@T.prim_func(s_tir=True)
def foo():
T.evaluate(0)""",
)
def test_failed_invalid_prefix():
with IRBuilder() as ib: # pylint: disable=invalid-name
with I.ir_module():
with T.prim_func():
T.func_name("foo")
mod = ib.get()
with pytest.raises(RuntimeError):
mod.script(ir_prefix="2I")
if __name__ == "__main__":
test_ir_module()
test_failed_invalid_prefix()
@@ -0,0 +1,48 @@
# 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: F841
import tvm.testing
from tvm.script.parser import ir as I
from tvm.script.parser import tirx as T
def test_str_metadata():
# This test is to check we reuse the existing metadata element for the same tirx.StringImm
# So metadata["tirx.StringImm"][0] will occur in the printed script for three times
str_imm = T.StringImm("aaa\nbbb\n")
@I.ir_module
class Module:
@T.prim_func(s_tir=True)
def foo() -> None:
A = str_imm
B = str_imm
@T.prim_func(s_tir=True)
def foo1() -> None:
A = str_imm
printed_str = Module.script(verbose_expr=True)
assert (
printed_str.count('metadata["tirx.StringImm"][0]') == 3
and printed_str.count('metadata["tirx.StringImm"][1]') == 0
)
if __name__ == "__main__":
tvm.testing.main()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,174 @@
# 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: F841
import pytest
from tvm_ffi.access_path import AccessPath
import tvm
from tvm.ir import assert_structural_equal
from tvm.script import ir as I
from tvm.script import tirx as T
def _error_message(exception):
return str(exception)
def _expected_result(func1, func2, objpath1, objpath2):
return f"""StructuralEqual check failed, caused by lhs at {objpath1}:
{func1.script(path_to_underline=[objpath1], syntax_sugar=False)}
and rhs at {objpath2}:
{func2.script(path_to_underline=[objpath2], syntax_sugar=False)}"""
def test_prim_type_hidden_path_exact_message():
with pytest.raises(ValueError) as exc_info:
assert_structural_equal(tvm.ir.PrimType("int32"), tvm.ir.PrimType("float32"))
assert str(exc_info.value) == (
"StructuralEqual check failed, caused by lhs at <root>.dtype:\n"
"Access path: <root>.dtype\n"
"Note: The underlined object is the nearest visible parent of this path.\n\n"
"T.int32\n"
"^^^^^^^\n"
"and rhs at <root>.dtype:\n"
"Access path: <root>.dtype\n"
"Note: The underlined object is the nearest visible parent of this path.\n\n"
"T.float32\n"
"^^^^^^^^^"
)
def test_prim_func_buffer_map():
@T.prim_func(s_tir=True)
def func1(a: T.handle, b: T.handle):
A = T.match_buffer(a, (128, 128))
B = T.match_buffer(b, (128, 128))
@T.prim_func(s_tir=True)
def func2(a: T.handle, b: T.handle):
A = T.match_buffer(a, (128, 128))
B = T.match_buffer(b, (128, 256))
func1 = func1.with_attr("global_symbol", "main")
func2 = func2.with_attr("global_symbol", "main")
with pytest.raises(ValueError) as ve:
assert_structural_equal(func1, func2)
assert _error_message(ve.value) == _expected_result(
func1,
func2,
AccessPath.root()
.attr("buffer_map")
.map_item(func1.params[1])
.attr("shape")
.array_item(1)
.attr("value"),
AccessPath.root()
.attr("buffer_map")
.map_item(func2.params[1])
.attr("shape")
.array_item(1)
.attr("value"),
)
def test_evaluate():
@I.ir_module(s_tir=True)
class module1:
@T.prim_func(s_tir=True)
def func():
T.evaluate(0)
@I.ir_module(s_tir=True)
class module2:
@T.prim_func(s_tir=True)
def func():
T.evaluate(1)
with pytest.raises(ValueError) as ve:
assert_structural_equal(module1, module2)
assert _error_message(ve.value) == _expected_result(
module1,
module2,
AccessPath.root()
.attr("functions")
.map_item(module1.get_global_var("func"))
.attr("body")
.attr("value")
.attr("value"),
AccessPath.root()
.attr("functions")
.map_item(module2.get_global_var("func"))
.attr("body")
.attr("value")
.attr("value"),
)
def test_allocate():
@T.prim_func(s_tir=True)
def func1():
a = T.alloc_buffer((128, 128), dtype="float32")
@T.prim_func(s_tir=True)
def func2():
a = T.alloc_buffer((256, 128), dtype="float32")
func1 = func1.with_attr("global_symbol", "main")
func2 = func2.with_attr("global_symbol", "main")
with pytest.raises(ValueError) as ve:
assert_structural_equal(func1, func2)
assert _error_message(ve.value) == _expected_result(
func1,
func2,
AccessPath.root().attr("body").attr("buffer").attr("shape").array_item(0).attr("value"),
AccessPath.root().attr("body").attr("buffer").attr("shape").array_item(0).attr("value"),
)
def test_for():
@T.prim_func(s_tir=True)
def func1():
for i, j in T.grid(128, 128):
with T.sblock():
pass
@T.prim_func(s_tir=True)
def func2():
for i, j, k in T.grid(128, 128, 128):
with T.sblock():
pass
func1 = func1.with_attr("global_symbol", "main")
func2 = func2.with_attr("global_symbol", "main")
with pytest.raises(ValueError) as ve:
assert_structural_equal(func1, func2)
assert _error_message(ve.value) == _expected_result(
func1,
func2,
AccessPath.root().attr("body").attr("block").attr("body").attr("body").attr("body"),
AccessPath.root().attr("body").attr("block").attr("body").attr("body").attr("body"),
)
if __name__ == "__main__":
tvm.testing.main()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,590 @@
# 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 pytest
from tvm_ffi.access_path import AccessPath
from tvm.script import ir as I
from tvm.script import tirx as T
from tvm.script.printer.doc import (
ExprStmtDoc,
IdDoc,
OperationDoc,
OperationKind,
StmtBlockDoc,
)
from tvm.script.printer.doc_printer import to_python_script
def make_path(name: str) -> AccessPath:
return AccessPath.root().attr(name)
def make_id_doc(name: str, path_name: str | None = None) -> IdDoc:
if path_name is None:
path_name = name
doc = IdDoc(name)
doc.source_paths = [make_path(path_name)]
return doc
def format_script(s: str) -> str:
"""
Remove leading and trailing blank lines, and make the minimum idention 0
"""
s = s.strip("\n")
non_empty_lines = [line for line in s.splitlines() if line and not line.isspace()]
if not non_empty_lines:
# no actual content
return "\n"
line_indents = [len(line) - len(line.lstrip(" ")) for line in non_empty_lines]
spaces_to_remove = min(line_indents)
cleaned_lines = "\n".join(line[spaces_to_remove:] for line in s.splitlines())
if not cleaned_lines.endswith("\n"):
cleaned_lines += "\n"
return cleaned_lines.strip()
def format_script_with_path_info(s: str, *path_info: str) -> str:
return "\n".join(path_info) + "\n\n" + format_script(s)
def test_underline_basic():
doc = StmtBlockDoc(
[
ExprStmtDoc(make_id_doc("foo")),
ExprStmtDoc(OperationDoc(OperationKind.Add, [make_id_doc("bar"), make_id_doc("baz")])),
ExprStmtDoc(make_id_doc("qux")),
]
)
assert to_python_script(
doc, path_to_underline=[make_path("baz")]
) == format_script_with_path_info(
"""
foo
bar + baz
^^^
qux
""",
"Access path: <root>.baz",
)
def test_underline_multiple_spans():
doc = StmtBlockDoc(
[
ExprStmtDoc(make_id_doc("foo")),
ExprStmtDoc(make_id_doc("bar")),
ExprStmtDoc(OperationDoc(OperationKind.Add, [make_id_doc("foo"), make_id_doc("foo")])),
]
)
assert to_python_script(
doc, path_to_underline=[make_path("foo")]
) == format_script_with_path_info(
"""
foo
^^^
bar
foo + foo
^^^ ^^^
""",
"Access path: <root>.foo",
)
def test_underline_multiple_spans_with_line_numbers():
doc = StmtBlockDoc(
[
ExprStmtDoc(make_id_doc("foo")),
ExprStmtDoc(make_id_doc("bar")),
ExprStmtDoc(OperationDoc(OperationKind.Add, [make_id_doc("foo"), make_id_doc("foo")])),
]
)
assert to_python_script(
doc, print_line_numbers=True, path_to_underline=[make_path("foo")]
) == format_script_with_path_info(
"""
1 foo
^^^
2 bar
3 foo + foo
^^^ ^^^
""",
"Access path: <root>.foo",
)
def test_underline_multiline():
doc = StmtBlockDoc(
[
ExprStmtDoc(IdDoc("foo")),
ExprStmtDoc(IdDoc("bar")),
]
)
doc.source_paths = [make_path("whole_doc")]
assert to_python_script(
doc, path_to_underline=[make_path("whole_doc")]
) == format_script_with_path_info(
"""
foo
^^^
bar
^^^
""",
"Access path: <root>.whole_doc",
)
@pytest.mark.parametrize(
"to_underline, expected_text",
[
(
[0],
"""
x0
^^
x1
x2
(... 7 lines skipped ...)
""",
),
(
[1],
"""
x0
x1
^^
x2
x3
(... 6 lines skipped ...)
""",
),
(
[3],
"""
x0
x1
x2
x3
^^
x4
x5
(... 4 lines skipped ...)
""",
),
(
[4],
"""
(... 2 lines skipped ...)
x2
x3
x4
^^
x5
x6
(... 3 lines skipped ...)
""",
),
(
[6],
"""
(... 4 lines skipped ...)
x4
x5
x6
^^
x7
x8
x9
""",
),
(
[9],
"""
(... 7 lines skipped ...)
x7
x8
x9
^^
""",
),
(
[0, 9],
"""
x0
^^
x1
x2
(... 4 lines skipped ...)
x7
x8
x9
^^
""",
),
(
[0, 3, 9],
"""
x0
^^
x1
x2
x3
^^
x4
x5
x6
x7
x8
x9
^^
""",
),
(
[0, 6, 9],
"""
x0
^^
x1
x2
x3
x4
x5
x6
^^
x7
x8
x9
^^
""",
),
(
[33],
"""
x0
x1
x2
x3
x4
x5
x6
x7
x8
x9
""",
),
],
)
def test_print_two_context_lines(to_underline, expected_text):
doc = StmtBlockDoc(
[ExprStmtDoc(make_id_doc(f"x{i}", "yes" if i in to_underline else "no")) for i in range(10)]
)
result = to_python_script(doc, num_context_lines=2, path_to_underline=[make_path("yes")])
path_info = ["Access path: <root>.yes"]
if to_underline == [33]:
path_info.append("Note: No visible object for this path is rendered in TVMScript.")
assert result == format_script_with_path_info(expected_text, *path_info)
def test_underline_and_print_line_numbers():
doc = StmtBlockDoc([ExprStmtDoc(make_id_doc(f"line{i + 1}")) for i in range(12)])
result = to_python_script(doc, print_line_numbers=True, path_to_underline=[make_path("line6")])
assert result == "Access path: <root>.line6\n\n " + format_script(
"""
1 line1
2 line2
3 line3
4 line4
5 line5
6 line6
^^^^^
7 line7
8 line8
9 line9
10 line10
11 line11
12 line12
"""
)
def test_underline_multi_access_paths():
doc = StmtBlockDoc([ExprStmtDoc(make_id_doc(f"line{i + 1}")) for i in range(10)])
result = to_python_script(
doc,
path_to_underline=[
make_path("line1"),
make_path("line3"),
make_path("line5"),
make_path("line7"),
make_path("line9"),
],
)
assert result == format_script_with_path_info(
"""
line1
^^^^^
line2
line3
^^^^^
line4
line5
^^^^^
line6
line7
^^^^^
line8
line9
^^^^^
line10
""",
"Access path: <root>.line1",
"Access path: <root>.line3",
"Access path: <root>.line5",
"Access path: <root>.line7",
"Access path: <root>.line9",
)
def test_underline_and_print_line_numbers_with_context():
doc = StmtBlockDoc([ExprStmtDoc(make_id_doc(f"line{i + 1}")) for i in range(12)])
result = to_python_script(
doc, print_line_numbers=True, num_context_lines=2, path_to_underline=[make_path("line8")]
)
assert result == format_script_with_path_info(
"""
(... 5 lines skipped ...)
6 line6
7 line7
8 line8
^^^^^
9 line9
10 line10
(... 2 lines skipped ...)
""",
"Access path: <root>.line8",
)
def test_underline_based_on_path_prefix():
doc = StmtBlockDoc([ExprStmtDoc(make_id_doc("foo")), ExprStmtDoc(make_id_doc("bar"))])
result = to_python_script(doc, path_to_underline=[make_path("foo").attr("x").attr("y")])
# There is no document that matches the desired path exactly,
# but path of "foo" is a prefix of the desired path, and thus should be underlined.
assert result == format_script_with_path_info(
"""
foo
^^^
bar
""",
"Access path: <root>.foo.x.y",
"Note: The underlined object is the nearest visible parent of this path.",
)
def test_longer_prefix_must_win():
foo_x = IdDoc("foo_x")
foo_x.source_paths = [make_path("foo").attr("x")]
doc = StmtBlockDoc(
[ExprStmtDoc(make_id_doc("foo")), ExprStmtDoc(make_id_doc("bar")), ExprStmtDoc(foo_x)]
)
result = to_python_script(doc, path_to_underline=[make_path("foo").attr("x").attr("y")])
# "foo" should not be underlined because there is a document with a more specific path prefix
assert result == format_script_with_path_info(
"""
foo
bar
foo_x
^^^^^
""",
"Access path: <root>.foo.x.y",
"Note: The underlined object is the nearest visible parent of this path.",
)
def test_underline_from_obj():
@T.prim_func(s_tir=True)
def func(a: T.int32, b: T.int32):
T.evaluate(a)
T.evaluate(b)
T.evaluate(a)
T.evaluate(b)
T.evaluate(a)
T.evaluate(b)
result = func.with_attr("global_symbol", "main").script(
obj_to_underline=[func.params[0]],
extra_config={"render_invisible_path_info": False},
)
assert result == format_script(
"""
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis
@T.prim_func(s_tir=True)
def main(a: T.int32, b: T.int32):
T.evaluate(a)
^
T.evaluate(b)
T.evaluate(a)
^
T.evaluate(b)
T.evaluate(a)
^
T.evaluate(b)
"""
)
def test_underline_from_multi_obj():
@T.prim_func(s_tir=True)
def func():
T.evaluate(-1)
T.evaluate(1)
T.evaluate(2)
T.evaluate(3)
T.evaluate(4)
T.evaluate(5)
T.evaluate(6)
T.evaluate(7)
result = func.with_attr("global_symbol", "main").script(
obj_to_underline=[
func.body.seq[1],
func.body.seq[3],
func.body.seq[5],
func.body.seq[7],
],
extra_config={"render_invisible_path_info": False},
)
assert result == format_script(
"""
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis
@T.prim_func(s_tir=True)
def main():
T.evaluate(-1)
T.evaluate(1)
^^^^^^^^^^^^^
T.evaluate(2)
T.evaluate(3)
^^^^^^^^^^^^^
T.evaluate(4)
T.evaluate(5)
^^^^^^^^^^^^^
T.evaluate(6)
T.evaluate(7)
^^^^^^^^^^^^^
"""
)
def test_underline_func():
@T.prim_func(s_tir=True)
def func():
T.evaluate(0)
result = func.with_attr("global_symbol", "main").script(
path_to_underline=[
AccessPath.root(),
],
extra_config={"render_invisible_path_info": False},
)
assert result == format_script(
"""
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis
@T.prim_func(s_tir=True)
^^^^^^^^^^^^^^^^^^^^^^^^
def main():
^^^^^^^^^^^
T.evaluate(0)
^^^^^^^^^^^^^
"""
)
def test_underline_func_in_irmodule():
@I.ir_module
class irmodule:
@T.prim_func(s_tir=True)
def func():
T.evaluate(0)
result = irmodule.script(
path_to_underline=[
AccessPath.root().attr("functions").map_item(irmodule.get_global_var("func")),
],
extra_config={"render_invisible_path_info": False},
)
assert result == format_script(
"""
# from tvm.script import ir as I
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis
@I.ir_module
class Module:
@T.prim_func(s_tir=True)
^^^^^^^^^^^^^^^^^^^^^^^^
def func():
^^^^^^^^^^^
T.evaluate(0)
^^^^^^^^^^^^^
"""
)
def test_underline_irmodule():
@I.ir_module
class irmodule:
@T.prim_func(s_tir=True)
def func():
T.evaluate(0)
result = irmodule.script(
path_to_underline=[
AccessPath.root(),
],
extra_config={"render_invisible_path_info": False},
)
assert result == format_script(
"""
# from tvm.script import ir as I
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis
@I.ir_module
^^^^^^^^^^^^
class Module:
^^^^^^^^^^^^^
@T.prim_func(s_tir=True)
^^^^^^^^^^^^^^^^^^^^^^^^
def func():
^^^^^^^^^^^
T.evaluate(0)
^^^^^^^^^^^^^
"""
)
@@ -0,0 +1,94 @@
# 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: F841
import numpy
import tvm
import tvm.testing
from tvm.script import tirx as T
# This numpy array is used to test the comparison between the global objects and the
# `tvm.script.tirx` submodule.
np_array = numpy.array([0, 1, 2, 3])
@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] = T.float32(0)
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
def test_multi_element_array_in_outmost_namespace():
func = matmul
rt_func = tvm.script.from_source(func.script())
tvm.ir.assert_structural_equal(func, rt_func)
def test_different_dtype_assignment_to_var():
@T.prim_func(s_tir=True)
def test_case():
a = T.sblock_alloc_buffer((10, 10), dtype="int8")
@T.prim_func(s_tir=True)
def func_ref():
a = T.sblock_alloc_buffer([10, 10], dtype="int8")
T.evaluate(0)
tvm.ir.assert_structural_equal(
test_case.with_attr("global_symbol", "main"), func_ref.with_attr("global_symbol", "main")
)
def test_var_capturing_order():
b = 2
@T.prim_func(s_tir=True)
def test_case():
k: T.let[T.int32] = b
@T.prim_func(s_tir=True)
def func_ref():
k: T.let[T.int32] = 2
T.evaluate(0)
tvm.ir.assert_structural_equal(
test_case.with_attr("global_symbol", "main"), func_ref.with_attr("global_symbol", "main")
)
def test_tir_buffer_region_extent_correct_dtype():
@T.prim_func(s_tir=True)
def func(A: T.Buffer((T.int64(16), T.int64(1)), "float32")):
for i in T.grid(T.int64(16)):
with T.sblock("block"):
vi = T.axis.remap("S", [i])
T.reads(A[vi, T.int64(0) : T.int64(1)])
T.evaluate(0)
assert func.body.block.body.body.block.reads[0].region[0].extent.ty.dtype == "int64"
if __name__ == "__main__":
tvm.testing.main()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,545 @@
# 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,invalid-name,pointless-string-statement
# ruff: noqa: E741, F401, F841
import sys
from typing import Any
import pytest
import tvm.testing
from tvm.s_tir.schedule.testing import assert_structural_equal_ignore_global_symbol
from tvm.script import from_source
from tvm.script import tirx as T
@T.prim_func(s_tir=True)
def transformed_matmul_no_syntax_sugar(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 i0, i1, i2_outer, i2_inner_outer, i2_inner_inner in T.grid(128, 128, 4, 8, 4):
with T.sblock("update"):
vi, vj = T.axis.remap("SS", [i0, i1])
vk = T.axis.R(128, i2_outer * 32 + i2_inner_outer * 4 + i2_inner_inner)
T.reads([C[vi, vj], A[vi, vk], B[vj, vk]])
T.writes([C[vi, vj], A[vi, vk]])
with T.init():
C[vi, vj] = 0.0
A[vi, vk] = A[vi, vk] + B[vj, vk]
C[vi, vj] = C[vi, vj] + (A[vi, vk] * B[vj, vk])
@T.prim_func(s_tir=True)
def transformed_matmul_syntax_sugar(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 i0, i1, i2_outer, i2_inner_outer, i2_inner_inner in T.grid(128, 128, 4, 8, 4):
with T.sblock("update"):
vi, vj = T.axis.remap("SS", [i0, i1])
vk = T.axis.R(128, i2_outer * 32 + i2_inner_outer * 4 + i2_inner_inner)
T.reads(C[vi, vj], A[vi, vk], B[vj, vk])
T.writes(C[vi, vj], A[vi, vk])
with T.init():
C[vi, vj] = 0.0
A[vi, vk] = A[vi, vk] + B[vj, vk]
C[vi, vj] = C[vi, vj] + (A[vi, vk] * B[vj, vk])
def test_reads_writes_syntax_sugar():
assert_structural_equal_ignore_global_symbol(
transformed_matmul_no_syntax_sugar, transformed_matmul_syntax_sugar
)
@T.prim_func(s_tir=True)
def loop_no_syntax_sugar(a: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128, 128))
for i in T.serial(0, 128):
for j in T.parallel(0, 128):
for k in T.vectorized(0, 128):
for x in T.unroll(0, 128):
for y in T.thread_binding(0, 128, thread="threadIdx.x"):
for z in T.thread_binding(0, 128, thread="threadIdx.x"):
A[i, j, k, x] = A[i, j, k, x] * 2.0
@T.prim_func(s_tir=True)
def loop_syntax_sugar(a: T.handle) -> None:
A = T.match_buffer(a, (128, 128, 128, 128))
for i in T.serial(128):
for j in T.parallel(128):
for k in T.vectorized(128):
for x in T.unroll(128):
for y in T.thread_binding(128, "threadIdx.x"):
for z in T.thread_binding(128, thread="threadIdx.x"):
A[i, j, k, x] = A[i, j, k, x] * 2.0
def test_loop_syntax_sugar():
assert_structural_equal_ignore_global_symbol(loop_no_syntax_sugar, loop_syntax_sugar)
# match buffer - use kwargs
@T.prim_func(s_tir=True)
def elementwise_handle(
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
# match buffer - use buffer with kwargs
@T.prim_func(s_tir=True)
def elementwise_buffer_kwargs(
a: T.Buffer(shape=(128, 128, 128, 128), dtype="float32"),
b: T.Buffer(shape=(128, 128, 128, 128), dtype="float32"),
) -> None:
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
# match buffer - use buffer without kwargs
@T.prim_func(s_tir=True)
def elementwise_buffer_no_kwargs(
a: T.Buffer((128, 128, 128, 128), "float32"),
b: T.Buffer((128, 128, 128, 128), "float32"),
) -> None:
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
def test_match_buffer_syntax_sugar():
# with kwargs
assert_structural_equal_ignore_global_symbol(elementwise_handle, elementwise_buffer_kwargs)
# without kwargs
assert_structural_equal_ignore_global_symbol(elementwise_handle, elementwise_buffer_no_kwargs)
def test_match_buffer_1d():
@T.prim_func(s_tir=True)
def func_no_sugar(a: T.handle):
A = T.match_buffer(a, shape=(16,))
for i in T.serial(16):
A[i] = 0.0
@T.prim_func(s_tir=True)
def func_with_sugar(A: T.Buffer(16, "float32")):
for i in T.serial(16):
A[i] = 0.0
assert_structural_equal_ignore_global_symbol(func_no_sugar, func_with_sugar)
# dynamic shape gemm
@T.prim_func(s_tir=True)
def gemm_dyn_shape(a: T.handle, b: T.handle, c: T.handle):
N = T.int32()
M = T.int32()
K = T.int32()
A = T.match_buffer(a, (N, K), "float32")
B = T.match_buffer(b, (K, M), "float32")
C = T.match_buffer(c, (N, M), "float32")
for i, j, k in T.grid(N, M, K):
with T.sblock("gemm"):
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]
def test_dynamic_shape_gemm():
gemm_dyn_shape_roundtrip = from_source(gemm_dyn_shape.script())
assert_structural_equal_ignore_global_symbol(gemm_dyn_shape, gemm_dyn_shape_roundtrip)
@T.prim_func(s_tir=True)
def match_buffer_int64(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (T.int64(128), T.int64(128)), dtype="float32")
B = T.sblock_alloc_buffer((T.int64(128), T.int64(128)), dtype="float32")
C = T.match_buffer(c, (T.int64(128), T.int64(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(T.int64(128), T.int64(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 match_buffer_int64_after_roundtrip(
A: T.Buffer((T.int64(128), T.int64(128)), "float32"),
C: T.Buffer((T.int64(128), T.int64(128)), "float32"),
) -> None:
B = T.sblock_alloc_buffer((T.int64(128), T.int64(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(T.int64(128), T.int64(128)):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
C[vi, vj] = B[vi, vj] + 1.0
def test_match_buffer_int64():
original = match_buffer_int64
after_roundtrip = match_buffer_int64_after_roundtrip
assert_structural_equal_ignore_global_symbol(original, after_roundtrip, True)
def test_match_buffer_region_has_implicit_shape_dtype():
@T.prim_func(s_tir=True)
def explicit_shape_dtype(A: T.Buffer((16, 64), "int32")):
with T.sblock():
B = T.match_buffer(A[8:16, 32:64], shape=(8, 32), dtype="int32")
T.evaluate(0)
@T.prim_func(s_tir=True)
def implicit_shape_dtype(A: T.Buffer((16, 64), "int32")):
with T.sblock():
B = T.match_buffer(A[8:16, 32:64])
T.evaluate(0)
assert_structural_equal_ignore_global_symbol(explicit_shape_dtype, implicit_shape_dtype)
def test_match_buffer_input_requires_shape_arg():
with pytest.raises(tvm.error.DiagnosticError):
@T.prim_func(s_tir=True)
def func(a: T.handle):
A = T.match_buffer(a, dtype="int32")
T.evaluate(0)
def test_bind_bufferload_without_type_annotation():
# Variable assignment of Expr types uses the dtype of the
# Expr to determine the variable's dtype. Parsing of
# buf[indices] is done by generating a BufferSlice object, which
# handles both store and load cases. BufferSlice is not a
# Expr, and implements BufferSlice.dtype explicitly.
# Failure occurred during parsing of the tvmscript.
@T.prim_func(s_tir=True)
def func_without_type_annotation(A: T.Buffer((1,), "int32")):
x = A[0]
T.evaluate(x)
def test_bind_with_constant():
@T.prim_func(s_tir=True)
def constant_binds():
x = T.meta_var(1)
y = T.meta_var(42.0)
T.evaluate(T.cast(x, "float32") + y)
@T.prim_func(s_tir=True)
def constant_binds_wrapped():
x = T.meta_var(T.int32(1))
y = T.meta_var(T.float32(42.0))
T.evaluate(T.cast(x, "float32") + y)
assert_structural_equal_ignore_global_symbol(constant_binds, constant_binds_wrapped)
def test_func_call():
def shared_16x16_to_ldmatrix_32x8_layout(i, j):
thread_id = (i % 8) * 4 + (j % 8) // 2
return T.meta_var((thread_id, (j // 8) * 4 + (i // 8) * 2 + (j % 2)))
@T.prim_func(s_tir=True)
def mma_sync_m16n16k16_desc(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (32, 8), "float16", align=64, offset_factor=16, scope="warp")
B = T.match_buffer(b, (32, 8), "float16", align=64, offset_factor=16, scope="warp")
C = T.match_buffer(c, (32, 8), "float16", align=64, offset_factor=16, scope="warp")
with T.sblock("root"):
T.reads(C[0:32, 0:8], A[0:32, 0:8], B[0:32, 0:8])
T.writes(C[0:32, 0:8])
for i, j, k in T.grid(16, 16, 16):
with T.sblock("C"):
i, j, k = T.axis.remap("SSR", [i, j, k])
thread_id_C, local_id_C = shared_16x16_to_ldmatrix_32x8_layout(i, j)
thread_id_A, local_id_A = shared_16x16_to_ldmatrix_32x8_layout(i, k)
thread_id_B, local_id_B = shared_16x16_to_ldmatrix_32x8_layout(k, j)
T.reads(
C[thread_id_C, local_id_C],
A[thread_id_A, local_id_A],
B[thread_id_B, local_id_B],
)
T.writes(C[thread_id_C, local_id_C])
C[thread_id_C, local_id_C] += (
A[thread_id_A, local_id_A] * B[thread_id_B, local_id_B]
)
@T.prim_func(s_tir=True)
def mma_sync_m16n16k16_desc_manual(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (32, 8), "float16", align=64, offset_factor=16, scope="warp")
B = T.match_buffer(b, (32, 8), "float16", align=64, offset_factor=16, scope="warp")
C = T.match_buffer(c, (32, 8), "float16", align=64, offset_factor=16, scope="warp")
with T.sblock("root"):
T.reads(C[0:32, 0:8], A[0:32, 0:8], B[0:32, 0:8])
T.writes(C[0:32, 0:8])
for i, j, k in T.grid(16, 16, 16):
with T.sblock("C"):
i, j, k = T.axis.remap("SSR", [i, j, k])
T.reads(
C[i % 8 * 4 + j % 8 // 2, j // 8 * 4 + i // 8 * 2 + j % 2],
A[i % 8 * 4 + k % 8 // 2, k // 8 * 4 + i // 8 * 2 + k % 2],
B[k % 8 * 4 + j % 8 // 2, j // 8 * 4 + k // 8 * 2 + j % 2],
)
T.writes(C[i % 8 * 4 + j % 8 // 2, j // 8 * 4 + i // 8 * 2 + j % 2])
C[i % 8 * 4 + j % 8 // 2, j // 8 * 4 + i // 8 * 2 + j % 2] = (
C[i % 8 * 4 + j % 8 // 2, j // 8 * 4 + i // 8 * 2 + j % 2]
+ A[i % 8 * 4 + k % 8 // 2, k // 8 * 4 + i // 8 * 2 + k % 2]
* B[k % 8 * 4 + j % 8 // 2, j // 8 * 4 + k // 8 * 2 + j % 2]
)
assert_structural_equal_ignore_global_symbol(
mma_sync_m16n16k16_desc, mma_sync_m16n16k16_desc_manual
)
# The following is an example of an error message from calling an invalid function
# error: Error occurred when invoking the function sqrt:
# loop of ufunc does not support argument 0 of type Var which has no callable sqrt method
# --> test_tvmscript_syntax_sugar.py:334:19
# |
# 334 | ind = sqrt(i)
# | ^^^^^^^
# note: run with `TVM_BACKTRACE=1` environment variable to display a backtrace.
# Uncomment to see the error above.
# def sqrt(x):
# import numpy as np
# return np.sqrt(x)
# @T.prim_func
# def loop(a: T.handle) -> None:
# A = T.match_buffer(a, (128,))
# for i in T.serial(128):
# ind = sqrt(i)
# A[i] = A[ind]
def test_int64_loop():
@T.prim_func(s_tir=True)
def int64_grid(
A: T.Buffer((T.int64(128), T.int64(128)), "float32"),
B: T.Buffer((T.int64(128), T.int64(128)), "float32"),
) -> None:
for i, j in T.grid(T.int64(128), T.int64(128)):
with T.sblock("C"):
vi, vj = T.axis.remap("SS", [i, j])
B[vi, vj] = A[vi, vj] + 1.0
@T.prim_func(s_tir=True)
def int64_grid_expanded(
A: T.Buffer((T.int64(128), T.int64(128)), "float32"),
B: T.Buffer((T.int64(128), T.int64(128)), "float32"),
) -> None:
for i in range(T.int64(0), T.int64(128)):
for j in range(T.int64(0), T.int64(128)):
with T.sblock("C"):
vi = T.axis.spatial(T.int64(128), i)
vj = T.axis.spatial(T.int64(128), j)
B[vi, vj] = A[vi, vj] + 1.0
assert_structural_equal_ignore_global_symbol(int64_grid, int64_grid_expanded)
def test_implicit_evaluate_assume():
@T.prim_func(s_tir=True)
def explicit(A: T.Buffer(1, "int32")):
T.evaluate(T.assume(A[0] == 5))
A[0] = 10
@T.prim_func(s_tir=True)
def implicit(A: T.Buffer(1, "int32")):
T.assume(A[0] == 5)
A[0] = 10
assert_structural_equal_ignore_global_symbol(implicit, explicit)
def test_implicit_evaluate_call_extern():
@T.prim_func(s_tir=True)
def explicit(A: T.Buffer(1, "int32")):
T.evaluate(T.call_extern("extern_func", A.data, dtype="int32"))
@T.prim_func(s_tir=True)
def implicit(A: T.Buffer(1, "int32")):
T.call_extern("extern_func", A.data, dtype="int32")
assert_structural_equal_ignore_global_symbol(implicit, explicit)
def test_preserve_trivial_let_binding():
"""Trivial `T.let[...]` annotations survive the parser as LetStmt and are not inlined.
In fork, bare `j = i` lowers to a local_scalar (AllocBuffer + BufferStore); the
LetStmt form is opt-in via `T.let[T.dtype]`. Both the explicit `T.bind(..., var=j)`
builder API and the `j: T.let[T.dtype]` annotation produce the same LetStmt IR.
"""
@T.prim_func(s_tir=True)
def explicit(i: T.int32):
j = T.int32()
T.bind(i, var=j)
T.evaluate(j)
@T.prim_func(s_tir=True)
def implicit(i: T.int32):
j: T.let[T.int32] = i
T.evaluate(j)
assert_structural_equal_ignore_global_symbol(implicit, explicit)
def test_preserve_trivial_let_binding_of_value():
"""Same as test_preserve_trivial_let_binding but with a constant RHS."""
@T.prim_func(s_tir=True)
def explicit(i: T.int32):
j = T.int32()
T.bind(42, var=j)
T.evaluate(j)
@T.prim_func(s_tir=True)
def implicit(i: T.int32):
j: T.let[T.int32] = 42
T.evaluate(j)
assert_structural_equal_ignore_global_symbol(implicit, explicit)
def test_preserve_parameter_name():
@T.prim_func(s_tir=True)
def func(i: T.int32):
j = i
T.evaluate(j)
param_name = func.params[0].name
assert param_name == "i"
def test_preserve_variable_name():
"""Use variable name when generating tirx::Bind / AllocBuffer"""
@T.prim_func(s_tir=True)
def func():
for i in T.serial(16):
j = i // 4
T.evaluate(j)
# In fork, bare `j = i // 4` lowers to AllocBuffer (local_scalar) in the for-body
# SeqStmt; the variable name lives on the underlying buffer.
var_name = func.body.body.seq[0].buffer.name
assert var_name == "j"
def test_boolean_constant():
"""Python booleans should become T.Bool objects"""
@T.prim_func(s_tir=True)
def explicit():
T.evaluate(T.bool(True))
@T.prim_func(s_tir=True)
def implicit():
T.evaluate(True)
assert_structural_equal_ignore_global_symbol(implicit, explicit)
def test_foldable_boolean_in_assert():
"""Foldable booleans T.Bool objects
The condition of an assert statement should be a boolean
expression. Previously, this test failed because the FFI does not
distinguish between integer primitives and boolean primitives.
"""
@T.prim_func(s_tir=True)
def explicit():
assert T.bool(False), "Message"
T.evaluate(0)
@T.prim_func(s_tir=True)
def implicit():
assert 0 == 1, "Message"
T.evaluate(0)
assert_structural_equal_ignore_global_symbol(implicit, explicit)
def test_return_statement():
"""A python `return` statement uses `T.ret`"""
@T.prim_func(s_tir=True)
def explicit():
T.evaluate(T.ret(5))
@T.prim_func(s_tir=True)
def implicit():
return 5
assert_structural_equal_ignore_global_symbol(implicit, explicit)
def test_loop_jump_statement():
"""`break` and `continue` evaluates to TIR intrinsics"""
@T.prim_func(s_tir=True)
def explicit():
for i in range(16):
if i % 2 == 0:
T.evaluate(T.continue_loop())
if i < 15:
T.evaluate(T.break_loop())
@T.prim_func(s_tir=True)
def implicit():
for i in range(16):
if i % 2 == 0:
continue
if i < 15:
break
assert_structural_equal_ignore_global_symbol(implicit, explicit)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,182 @@
# 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,invalid-name,pointless-string-statement
from tvm.script import tirx as T
"""
This prim func include necessary buffer types that need to be checked
e.g. reads/writes, match_buffer/alloc_buffer, serial/block etc.
"""
@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 = T.axis.S(128, i0)
vj = T.axis.S(128, 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)
"""
This prim func include necessary thread types that need to be checked
e.g. env_thread, launch_thread, thread_binding etc.
"""
@T.prim_func(s_tir=True)
def element_wise_env_thread_x(a: T.handle, b: T.handle, c: T.handle) -> None:
j1_0 = T.env_thread("threadIdx.x")
j0_0 = T.env_thread("threadIdx.x")
i = T.env_thread("blockIdx.x")
A = T.match_buffer(a, [128, 128])
B = T.match_buffer(b, [128, 128])
C = T.match_buffer(c, [128, 128])
T.launch_thread(i, 128)
T.launch_thread(j0_0, 4)
T.launch_thread(j1_0, 4)
for blockIdx_x in T.thread_binding(0, 128, "blockIdx.x"):
for threadIdx_x in T.thread_binding(0, 4, "threadIdx.x"):
for j0_1 in T.serial(0, 32):
with T.sblock(""):
B[blockIdx_x, threadIdx_x * 32 + j0_1] = (
A[blockIdx_x, threadIdx_x * 32 + j0_1] * 2.0
)
for j1_1 in T.serial(0, 32):
with T.sblock(""):
C[blockIdx_x, threadIdx_x * 32 + j1_1] = (
B[blockIdx_x, threadIdx_x * 32 + j1_1] + 1.0
)
"""
This test case is added to test T.grid
"""
@T.prim_func(s_tir=True)
def loop_split(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [128, 128], dtype="float32")
B = T.match_buffer(b, [128], dtype="float32")
for i, ko in T.grid(128, 4):
for ki in T.thread_binding(0, 32, thread="threadIdx.x"):
with T.sblock("B"):
vi = T.axis.S(128, i)
vk = T.axis.R(128, ko * 32 + ki)
T.reads([B[vi], A[vi, vk]])
T.writes([B[vi]])
with T.init():
B[vi] = T.float32(0)
B[vi] = B[vi] + A[vi, vk]
"""
This test case is added to test T.comm_reducer, T.reinterpret, T.tvm_thread_allreduce
"""
@T.prim_func(s_tir=True)
def lowered_loop_split(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [128, 128], dtype="float32")
B = T.match_buffer(b, [128], dtype="float32")
reduce_temp0 = T.sblock_alloc_buffer([1], dtype="float32", strides=[1], scope="local")
normal_reduce_temp0 = T.sblock_alloc_buffer([1], dtype="float32", strides=[1], scope="local")
for i in T.serial(0, 128):
for ki in T.thread_binding(0, 32, thread="threadIdx.x"):
normal_reduce_temp0[0] = T.float32(0)
for ko in T.serial(0, 4):
with T.sblock("B_normal_reduction"):
vi = T.axis.S(128, i)
vk = T.axis.R(128, ko * 32 + ki)
T.reads([A[vi, vk], normal_reduce_temp0[0]])
T.writes([normal_reduce_temp0[0]])
normal_reduce_temp0[0] = normal_reduce_temp0[0] + A[vi, vk]
with T.sblock("B_cross_thread_reduction"):
T.reads([normal_reduce_temp0[0]])
T.writes([reduce_temp0[0]])
T.attr(
T.comm_reducer(lambda x, y: x + y, [T.float32(0)]),
"reduce_scope",
T.int32(0),
)
T.evaluate(
T.tvm_thread_allreduce(
T.uint32(1),
normal_reduce_temp0[0],
True,
reduce_temp0.data,
ki,
dtype="handle",
)
)
with T.sblock("B_write_back"):
vi = T.axis.S(128, i)
T.reads([reduce_temp0[0]])
T.writes([B[vi]])
B[vi] = reduce_temp0[0]
"""
This test case is added to test T.Buffer with slice as argument and T.exp
"""
@T.prim_func(s_tir=True)
def different_access_indices(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, 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("SSR", [i, j, k])
T.reads([B[vi, vj], A[vi, vj, vk]])
T.writes(
[
B[
T.min(vj, vi) : T.min(vj, vi) # type: ignore[misc]
+ (T.max(vj, vi) + 1 - T.min(vj, vi)),
T.min(vi, vj) : T.min(vi, vj) # type: ignore[misc]
+ (T.max(vi, vj) + 1 - T.min(vi, vj)),
]
]
)
with T.init():
B[vj, vi] = T.exp(B[vj, vi], dtype="float32")
B[vi, vj] = B[vi, vj] + A[vi, vj, vk]
# Not running any test as we only want to type-check here
if __name__ == "__main__":
pass