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

This commit is contained in:
wehub-resource-sync
2026-07-13 13:36:25 +08:00
commit 26446540fa
3151 changed files with 974126 additions and 0 deletions
@@ -0,0 +1,271 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ruff: noqa: E501, F401
"""Tests for AnnotateIrregularLoop"""
import tvm
import tvm.testing
from tvm import s_tir, tirx
from tvm.script import ir as I
from tvm.script import tirx as T
def test_handle_irrgular_unit_loop():
"""Dedicated testcase to check the unitloop with loop jump not simplified"""
@T.prim_func(s_tir=True)
def before(A: T.Buffer((10,), "int32")):
for i in T.serial(1):
if A[i] > 5:
break
A[i] = A[i] + 1
for j in T.serial(1):
if A[j] > 5:
continue
A[j] = A[j] + 1
for k in T.serial(1):
A[k] = A[k] + 1
@T.prim_func(s_tir=True)
def expected(A: T.Buffer((10,), "int32")):
for i in T.serial(1, annotations={"irregular_loop_mark": 1}):
if A[i] > 5:
break
A[i] = A[i] + 1
for j in T.serial(1, annotations={"irregular_loop_mark": 1}):
if A[j] > 5:
continue
A[j] = A[j] + 1
A[0] = A[0] + 1
mod = tvm.IRModule.from_expr(before)
mod = tvm.s_tir.transform.AnnotateIrregularLoop()(mod)
mod = tvm.s_tir.transform.LowerOpaqueBlock()(mod)
tvm.ir.assert_structural_equal(mod["before"].with_attr("global_symbol", "expected"), expected)
def test_annotate_loop_with_break():
"""Test that loops containing break statements are annotated as irregular."""
transform = s_tir.transform.AnnotateIrregularLoop()
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((10,), "int32")):
for i in T.serial(10):
if A[i] > 5:
break
A[i] = A[i] + 1
@I.ir_module
class Expected:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((10,), "int32")):
for i in T.serial(10, annotations={"irregular_loop_mark": 1}):
if A[i] > 5:
break
A[i] = A[i] + 1
After = transform(Before)
tvm.ir.assert_structural_equal(After, Expected)
def test_annotate_loop_with_continue():
"""Test that loops containing continue statements are annotated as irregular."""
transform = s_tir.transform.AnnotateIrregularLoop()
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((10,), "int32")):
for i in T.serial(10):
if A[i] < 0:
continue
A[i] = A[i] * 2
@I.ir_module
class Expected:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((10,), "int32")):
for i in T.serial(10, annotations={"irregular_loop_mark": 1}):
if A[i] < 0:
continue
A[i] = A[i] * 2
After = transform(Before)
tvm.ir.assert_structural_equal(After, Expected)
def test_nested_irregular_both_loops():
"""Test nested loops where both loops have break/continue."""
transform = s_tir.transform.AnnotateIrregularLoop()
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((10, 10), "int32")):
for i in T.serial(10):
if i > 7:
break
for j in T.serial(10):
if A[i, j] < 0:
continue
A[i, j] = A[i, j] + 1
@I.ir_module
class Expected:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((10, 10), "int32")):
for i in T.serial(10, annotations={"irregular_loop_mark": 1}):
if i > 7:
break
for j in T.serial(10, annotations={"irregular_loop_mark": 1}):
if A[i, j] < 0:
continue
A[i, j] = A[i, j] + 1
After = transform(Before)
tvm.ir.assert_structural_equal(After, Expected)
def test_while_loop_with_break():
"""Test that while loops with break/continue are not annotated (while loops don't have annotations)."""
transform = s_tir.transform.AnnotateIrregularLoop()
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((10,), "int32")):
i = T.int32(0)
while i < 10:
if A[i] > 5:
break
A[i] = A[i] + 1
i = i + 1
@I.ir_module
class Expected:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((10,), "int32")):
i = T.int32(0)
while i < 10:
if A[i] > 5:
break
A[i] = A[i] + 1
i = i + 1
After = transform(Before)
tvm.ir.assert_structural_equal(After, Expected)
def test_break_in_nested_conditional():
"""Test break statement deeply nested in conditional blocks."""
transform = s_tir.transform.AnnotateIrregularLoop()
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((10,), "int32"), flag1: T.int32, flag2: T.int32):
for i in T.serial(10):
if flag1 > 0:
if flag2 > 0:
if A[i] > 5:
break
A[i] = A[i] + 1
@I.ir_module
class Expected:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((10,), "int32"), flag1: T.int32, flag2: T.int32):
for i in T.serial(10, annotations={"irregular_loop_mark": 1}):
if flag1 > 0:
if flag2 > 0:
if A[i] > 5:
break
A[i] = A[i] + 1
After = transform(Before)
tvm.ir.assert_structural_equal(After, Expected)
def test_while_loop_with_break_standalone():
"""Test that while loops with break/continue are not annotated (while loops don't have annotations)."""
transform = s_tir.transform.AnnotateIrregularLoop()
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((10,), "int32")):
i = T.int32(0)
while i < 10:
if A[i] > 5:
break
A[i] = A[i] + 1
i = i + 1
@I.ir_module
class Expected:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((10,), "int32")):
i = T.int32(0)
while i < 10:
if A[i] > 5:
break
A[i] = A[i] + 1
i = i + 1
After = transform(Before)
tvm.ir.assert_structural_equal(After, Expected)
def test_nested_irregular_loop_standalone():
"""Test deeply nested loops with irregular control flow only in innermost loop."""
transform = s_tir.transform.AnnotateIrregularLoop()
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((5, 5, 5), "int32")):
for i in T.serial(5):
for j in T.serial(5):
for k in T.serial(5):
if A[i, j, k] > 10:
break
if A[i, j, k] < 0:
continue
A[i, j, k] = A[i, j, k] + 1
@I.ir_module
class Expected:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((5, 5, 5), "int32")):
for i in T.serial(5):
for j in T.serial(5):
for k in T.serial(5, annotations={"irregular_loop_mark": 1}):
if A[i, j, k] > 10:
break
if A[i, j, k] < 0:
continue
A[i, j, k] = A[i, j, k] + 1
After = transform(Before)
tvm.ir.assert_structural_equal(After, Expected)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,90 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ruff: noqa: F401
import pytest
import tvm
from tvm import s_tir, tirx
from tvm.script import tirx as T
def test_canonicalize_loop():
@T.prim_func(s_tir=True)
def before(A: T.Buffer((128,), "float32"), B: T.Buffer((128,), "float32")):
T.func_attr({"global_symbol": "main"})
for i in range(1, 128, 5):
B[i] = A[i] + 1.0
@T.prim_func(s_tir=True)
def expected(A: T.Buffer((128,), "float32"), B: T.Buffer((128,), "float32")):
T.func_attr({"global_symbol": "main"})
for i in T.serial(0, 26):
B[i * 5 + 1] = A[i * 5 + 1] + 1.0
mod = tvm.IRModule.from_expr(before)
mod = s_tir.transform.CanonicalizeLoop()(mod)
tvm.ir.assert_structural_equal(mod["main"], expected)
def test_canonicalize_nested_loop():
@T.prim_func(s_tir=True)
def before(A: T.Buffer((128, 128), "float32"), B: T.Buffer((128, 128), "float32")):
T.func_attr({"global_symbol": "main"})
for i in range(1, 128, 5):
for j in range(2, 128, 3):
B[i, j] = A[i, j] + 1.0
@T.prim_func(s_tir=True)
def expected(A: T.Buffer((128, 128), "float32"), B: T.Buffer((128, 128), "float32")):
T.func_attr({"global_symbol": "main"})
for i in T.serial(0, 26):
for j in T.serial(0, 42):
B[i * 5 + 1, j * 3 + 2] = A[i * 5 + 1, j * 3 + 2] + 1.0
mod = tvm.IRModule.from_expr(before)
mod = s_tir.transform.CanonicalizeLoop()(mod)
tvm.ir.assert_structural_equal(mod["main"], expected)
def test_canonicalize_negative_step():
@T.prim_func(s_tir=True)
def before(A: T.Buffer((128,), "float32"), B: T.Buffer((128,), "float32")):
T.func_attr({"global_symbol": "main"})
for i in T.serial(0, 127, step=-3):
B[i] = A[i] + 1.0
mod = tvm.IRModule.from_expr(before)
with pytest.raises(tvm.error.InternalError):
mod = s_tir.transform.CanonicalizeLoop()(mod)
def test_canonicalize_dynamic_step():
"""Currently we report error for dynamic step since we could not prove it is positive"""
@T.prim_func(s_tir=True)
def before(A: T.Buffer((128,), "float32"), B: T.Buffer((128,), "float32"), step: T.int32):
T.func_attr({"global_symbol": "main"})
for i in T.serial(0, 128, step=step):
B[i] = A[i] + 1.0
mod = tvm.IRModule.from_expr(before)
with pytest.raises(tvm.error.InternalError):
mod = s_tir.transform.CanonicalizeLoop()(mod)
if __name__ == "__main__":
tvm.testing.main()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,97 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ruff: noqa: F401
import pytest
import tvm
import tvm.testing
from tvm import s_tir, te, tirx
from tvm.script import ir as I
from tvm.script import tirx as T
def _check(original, transformed):
func = original
mod = tvm.IRModule.from_expr(func.with_attr("global_symbol", "main"))
mod = tvm.s_tir.transform.ConvertBlocksToOpaque()(mod)
mod = tvm.tirx.transform.StmtSimplify()(mod)
tvm.ir.assert_structural_equal(mod["main"], transformed.with_attr("global_symbol", "main"))
@T.prim_func(s_tir=True)
def elementwise_func(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (16, 16), "float32")
C = T.match_buffer(c, (16, 16), "float32")
for i in range(0, 16):
with T.sblock():
T.reads(A[i, 0:16])
T.writes(C[i, 0:16])
B = T.sblock_alloc_buffer((16, 16), "float32")
for j in range(0, 16):
with T.sblock():
vi = T.axis.S(16, i)
vj = T.axis.S(16, j)
B[vi, vj] = A[vi, vj] + 1.0
for j in range(0, 16):
with T.sblock():
vi = T.axis.S(16, i)
vj = T.axis.S(16, j)
C[vi, vj] = B[vi, vj] * 2.0
@T.prim_func(s_tir=True)
def substituted_elementwise_func(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (16, 16), "float32")
C = T.match_buffer(c, (16, 16), "float32")
for i in range(0, 16):
with T.sblock():
T.reads(A[i, 0:16])
T.writes(C[i, 0:16])
B = T.sblock_alloc_buffer([16, 16], "float32")
for j in range(0, 16):
with T.sblock():
T.reads([A[i, j]])
T.writes([B[i, j]])
B[i, j] = A[i, j] + 1.0
for j in range(0, 16):
with T.sblock():
T.reads([B[i, j]])
T.writes([C[i, j]])
C[i, j] = B[i, j] * 2.0
def test_elementwise():
_check(elementwise_func, substituted_elementwise_func)
def test_error_if_predicate_uses_block_variables():
@I.ir_module(check_well_formed=False, s_tir=True)
class Before:
@T.prim_func(s_tir=True)
def main(A: T.Buffer(8, "int32")):
for i in T.serial(8):
with T.sblock():
vi = T.axis.remap("S", [i])
T.where(vi < 6)
T.evaluate(0)
with pytest.raises(RuntimeError):
tvm.s_tir.transform.ConvertBlocksToOpaque()(Before)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,29 @@
# 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
def test_decorate_device():
x = tvm.tirx.Var("x", "int32")
mod = tvm.IRModule.from_expr(tvm.tirx.PrimFunc([x], tvm.tirx.Evaluate(x)))
stmt = tvm.s_tir.transform.DecorateDeviceScope()(mod)["main"].body
assert stmt.attr_key == "device_scope"
if __name__ == "__main__":
test_decorate_device()
@@ -0,0 +1,605 @@
# 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-function-docstring
# ruff: noqa: E501, F841
import tvm
import tvm.testing
from tvm.s_tir.transform import DefaultGPUSchedule
from tvm.script import tirx as T
def test_broadcast_to_symbolic():
# pylint: disable=no-self-argument,missing-class-docstring,line-too-long
# fmt: off
@tvm.script.ir_module
class Before:
@T.prim_func(s_tir=True)
def broadcast_to(
rxplaceholder: T.Buffer((T.int64(3), T.int64(1)), "float32"),
var_T_broadcast_to: T.handle,
):
T.func_attr({"tirx.noalias": True})
x_0 = T.int64()
x_1 = T.int64()
T_broadcast_to = T.match_buffer(var_T_broadcast_to, (x_0, x_1))
# with T.sblock("root"):
for ax0, ax1 in T.grid(x_0, x_1):
with T.sblock("T_broadcast_to"):
v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
T.reads(rxplaceholder[v_ax0, T.int64(0)])
T.writes(T_broadcast_to[v_ax0, v_ax1])
T_broadcast_to[v_ax0, v_ax1] = rxplaceholder[v_ax0, T.int64(0)]
@tvm.script.ir_module
class Expected:
@T.prim_func(s_tir=True)
def broadcast_to(rxplaceholder: T.Buffer((T.int64(3), T.int64(1)), "float32"), var_T_broadcast_to: T.handle):
T.func_attr({"tirx.is_scheduled": True, "tirx.noalias": True})
x_0, x_1 = T.int64(), T.int64()
T_broadcast_to = T.match_buffer(var_T_broadcast_to, (x_0, x_1))
for ax0_ax1_fused_1 in T.thread_binding(T.int64(256), thread="blockIdx.x"):
for ax0_ax1_fused_2 in T.thread_binding(T.int64(1024), thread="threadIdx.x"):
for ax0_ax1_fused_0 in range((x_0 * x_1 + T.int64(262143)) // T.int64(262144)):
with T.sblock("T_broadcast_to"):
v_ax0 = T.axis.spatial(x_0, (ax0_ax1_fused_0 * T.int64(262144) + ax0_ax1_fused_1 * T.int64(1024) + ax0_ax1_fused_2) // x_1)
v_ax1 = T.axis.spatial(x_1, (ax0_ax1_fused_0 * T.int64(262144) + ax0_ax1_fused_1 * T.int64(1024) + ax0_ax1_fused_2) % x_1)
T.where((ax0_ax1_fused_0 * T.int64(256) + ax0_ax1_fused_1) * T.int64(1024) + ax0_ax1_fused_2 < x_0 * x_1)
T_broadcast_to[v_ax0, v_ax1] = rxplaceholder[v_ax0, T.int64(0)]
# fmt: on
# pylint: enable=no-self-argument,missing-class-docstring,line-too-long
target = tvm.target.Target("nvidia/geforce-rtx-3070")
with target, tvm.transform.PassContext(opt_level=3):
After = DefaultGPUSchedule()(Before)
tvm.ir.assert_structural_equal(After, Expected)
def test_matmul():
# pylint: disable=no-self-argument,missing-class-docstring,line-too-long
# fmt: off
@tvm.script.ir_module
class Before:
@T.prim_func(s_tir=True)
def matmul(
A: T.Buffer((32, 32), "float16"),
B: T.Buffer((32, 32), "float16"),
C: T.Buffer((32, 32), "float16"),
):
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
# with T.sblock("root"):
for i, j, k in T.grid(32, 32, 32):
with T.sblock("C"):
v_i, v_j, v_k = T.axis.remap("SSR", [i, j, k])
T.reads(A[v_i, v_k], B[v_k, v_j])
T.writes(C[v_i, v_j])
with T.init():
C[v_i, v_j] = T.float16(0)
C[v_i, v_j] = C[v_i, v_j] + A[v_i, v_k] * B[v_k, v_j]
@T.prim_func(s_tir=True)
def matmul_gpu(
A: T.Buffer((32, 32), "float16"),
B: T.Buffer((32, 32), "float16"),
C: T.Buffer((32, 32), "float16"),
):
T.func_attr({"global_symbol": "main",
"target": T.target({"arch": "sm_86",
"keys": ["cuda", "gpu"],
"kind": "cuda",
"max_num_threads": 1024,
"tag": "",
"thread_warp_size": 32}),
"tirx.noalias": True})
# with T.sblock("root"):
for i, j, k in T.grid(32, 32, 32):
with T.sblock("C"):
v_i, v_j, v_k = T.axis.remap("SSR", [i, j, k])
T.reads(A[v_i, v_k], B[v_k, v_j])
T.writes(C[v_i, v_j])
with T.init():
C[v_i, v_j] = T.float16(0)
C[v_i, v_j] = C[v_i, v_j] + A[v_i, v_k] * B[v_k, v_j]
@T.prim_func(s_tir=True)
def matmul_cpu(
A: T.Buffer((32, 32), "float16"),
B: T.Buffer((32, 32), "float16"),
C: T.Buffer((32, 32), "float16"),
):
T.func_attr({"global_symbol": "main",
"target": T.target({"keys": ["cpu"], "kind": "llvm", "tag": ""}),
"tirx.noalias": True})
# with T.sblock("root"):
for i, j, k in T.grid(32, 32, 32):
with T.sblock("C"):
v_i, v_j, v_k = T.axis.remap("SSR", [i, j, k])
T.reads(A[v_i, v_k], B[v_k, v_j])
T.writes(C[v_i, v_j])
with T.init():
C[v_i, v_j] = T.float16(0)
C[v_i, v_j] = C[v_i, v_j] + A[v_i, v_k] * B[v_k, v_j]
@tvm.script.ir_module
class Expected:
@T.prim_func(s_tir=True)
def matmul(
A: T.Buffer((32, 32), "float16"),
B: T.Buffer((32, 32), "float16"),
C: T.Buffer((32, 32), "float16"),
):
T.func_attr({"tirx.is_scheduled": True, "global_symbol": "main", "tirx.noalias": True})
# with T.sblock("root"):
for i_j_fused_0 in T.thread_binding(1, thread="blockIdx.x"):
for i_j_fused_1 in T.thread_binding(1024, thread="threadIdx.x"):
for k in range(32):
with T.sblock("C"):
v_i = T.axis.spatial(
32, (i_j_fused_0 * 1024 + i_j_fused_1) // 32
)
v_j = T.axis.spatial(
32, (i_j_fused_0 * 1024 + i_j_fused_1) % 32
)
v_k = T.axis.reduce(32, k)
T.reads(A[v_i, v_k], B[v_k, v_j])
T.writes(C[v_i, v_j])
with T.init():
C[v_i, v_j] = T.float16(0)
C[v_i, v_j] = C[v_i, v_j] + A[v_i, v_k] * B[v_k, v_j]
@T.prim_func(s_tir=True)
def matmul_cpu(A: T.Buffer((32, 32), "float16"), B: T.Buffer((32, 32), "float16"), C: T.Buffer((32, 32), "float16")):
T.func_attr({"global_symbol": "main", "target": T.target({"keys": ["cpu"], "kind": "llvm", "tag": ""}), "tirx.is_scheduled": True, "tirx.noalias": True})
# with T.sblock("root"):
for i, j, k in T.grid(32, 32, 32):
with T.sblock("C"):
v_i, v_j, v_k = T.axis.remap("SSR", [i, j, k])
T.reads(A[v_i, v_k], B[v_k, v_j])
T.writes(C[v_i, v_j])
with T.init():
C[v_i, v_j] = T.float16(0)
C[v_i, v_j] = C[v_i, v_j] + A[v_i, v_k] * B[v_k, v_j]
@T.prim_func(s_tir=True)
def matmul_gpu(A: T.Buffer((32, 32), "float16"), B: T.Buffer((32, 32), "float16"), C: T.Buffer((32, 32), "float16")):
T.func_attr({"global_symbol": "main", "target": T.target({"arch": "sm_86", "keys": ["cuda", "gpu"], "kind": "cuda", "max_num_threads": 1024, "tag": "", "thread_warp_size": 32}), "tirx.is_scheduled": True, "tirx.noalias": True})
# with T.sblock("root"):
for i_j_fused_0 in T.thread_binding(1, thread="blockIdx.x"):
for i_j_fused_1 in T.thread_binding(1024, thread="threadIdx.x"):
for k in range(32):
with T.sblock("C"):
v_i = T.axis.spatial(32, (i_j_fused_0 * 1024 + i_j_fused_1) // 32)
v_j = T.axis.spatial(32, (i_j_fused_0 * 1024 + i_j_fused_1) % 32)
v_k = T.axis.reduce(32, k)
T.reads(A[v_i, v_k], B[v_k, v_j])
T.writes(C[v_i, v_j])
with T.init():
C[v_i, v_j] = T.float16(0)
C[v_i, v_j] = C[v_i, v_j] + A[v_i, v_k] * B[v_k, v_j]
# fmt: on
# pylint: enable=no-self-argument,missing-class-docstring,line-too-long
target = tvm.target.Target("nvidia/geforce-rtx-3070")
with target, tvm.transform.PassContext(opt_level=3):
After = DefaultGPUSchedule()(Before)
tvm.ir.assert_structural_equal(After, Expected)
def test_add():
# pylint: disable=no-self-argument,missing-class-docstring,line-too-long
# fmt: off
@tvm.script.ir_module
class Before:
@T.prim_func(s_tir=True)
def add(rxplaceholder: T.Buffer((T.int64(1), T.int64(2), T.int64(3)), "float32"), rxplaceholder_1: T.Buffer((T.int64(4), T.int64(3), T.int64(2), T.int64(1)), "float32"), T_add: T.Buffer((T.int64(4), T.int64(3), T.int64(2), T.int64(3)), "float32")):
T.func_attr({"tirx.noalias": True})
for i0, i1, i2, i3 in T.grid(T.int64(4), T.int64(3), T.int64(2), T.int64(3)):
with T.sblock("T_add"):
ax0, ax1, ax2, ax3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
T.reads(rxplaceholder[T.int64(0), ax2, ax3], rxplaceholder_1[ax0, ax1, ax2, T.int64(0)])
T.writes(T_add[ax0, ax1, ax2, ax3])
T_add[ax0, ax1, ax2, ax3] = rxplaceholder[T.int64(0), ax2, ax3] + rxplaceholder_1[ax0, ax1, ax2, T.int64(0)]
@tvm.script.ir_module
class Expected:
@T.prim_func(s_tir=True)
def add(
rxplaceholder: T.Buffer((T.int64(1), T.int64(2), T.int64(3)), "float32"),
rxplaceholder_1: T.Buffer(
(T.int64(4), T.int64(3), T.int64(2), T.int64(1)), "float32"
),
T_add: T.Buffer((T.int64(4), T.int64(3), T.int64(2), T.int64(3)), "float32"),
):
T.func_attr({"tirx.is_scheduled": True, "tirx.noalias": True})
# with T.sblock("root"):
for i0_i1_i2_i3_fused_0 in T.thread_binding(T.int64(1), thread="blockIdx.x"):
for i0_i1_i2_i3_fused_1 in T.thread_binding(
T.int64(72), thread="threadIdx.x"
):
with T.sblock("T_add"):
ax0 = T.axis.spatial(
T.int64(4),
(i0_i1_i2_i3_fused_0 * T.int64(72) + i0_i1_i2_i3_fused_1)
// T.int64(18),
)
ax1 = T.axis.spatial(
T.int64(3),
(i0_i1_i2_i3_fused_0 * T.int64(72) + i0_i1_i2_i3_fused_1)
% T.int64(18)
// T.int64(6),
)
ax2 = T.axis.spatial(
T.int64(2),
(i0_i1_i2_i3_fused_0 * T.int64(72) + i0_i1_i2_i3_fused_1)
% T.int64(6)
// T.int64(3),
)
ax3 = T.axis.spatial(
T.int64(3),
(i0_i1_i2_i3_fused_0 * T.int64(72) + i0_i1_i2_i3_fused_1)
% T.int64(3),
)
T.reads(
rxplaceholder[T.int64(0), ax2, ax3],
rxplaceholder_1[ax0, ax1, ax2, T.int64(0)],
)
T.writes(T_add[ax0, ax1, ax2, ax3])
T_add[ax0, ax1, ax2, ax3] = (
rxplaceholder[T.int64(0), ax2, ax3]
+ rxplaceholder_1[ax0, ax1, ax2, T.int64(0)]
)
# fmt: on
# pylint: enable=no-self-argument,missing-class-docstring,line-too-long
target = tvm.target.Target("nvidia/geforce-rtx-3070")
with target, tvm.transform.PassContext(opt_level=3):
After = DefaultGPUSchedule()(Before)
tvm.ir.assert_structural_equal(After, Expected)
def test_full():
# pylint: disable=no-self-argument,missing-class-docstring,line-too-long
# fmt: off
@tvm.script.ir_module
class Before:
@T.prim_func(s_tir=True)
def full(rxplaceholder: T.Buffer((), "int32"), T_full: T.Buffer((T.int64(2), T.int64(3)), "int32")):
T.func_attr({"tirx.noalias": True})
for i0, i1 in T.grid(T.int64(2), T.int64(3)):
with T.sblock("T_full"):
ax0, ax1 = T.axis.remap("SS", [i0, i1])
T.reads(rxplaceholder[()])
T.writes(T_full[ax0, ax1])
T_full[ax0, ax1] = rxplaceholder[()]
@tvm.script.ir_module
class Expected:
@T.prim_func(s_tir=True)
def full(
rxplaceholder: T.Buffer((), "int32"),
T_full: T.Buffer((T.int64(2), T.int64(3)), "int32"),
):
T.func_attr({"tirx.is_scheduled": True, "tirx.noalias": True})
# with T.sblock("root"):
for i0_i1_fused_0 in T.thread_binding(T.int64(1), thread="blockIdx.x"):
for i0_i1_fused_1 in T.thread_binding(T.int64(6), thread="threadIdx.x"):
with T.sblock("T_full"):
ax0 = T.axis.spatial(
T.int64(2),
(i0_i1_fused_0 * T.int64(6) + i0_i1_fused_1) // T.int64(3),
)
ax1 = T.axis.spatial(
T.int64(3),
(i0_i1_fused_0 * T.int64(6) + i0_i1_fused_1) % T.int64(3),
)
T.reads(rxplaceholder[()])
T.writes(T_full[ax0, ax1])
T_full[ax0, ax1] = rxplaceholder[()]
# fmt: on
# pylint: enable=no-self-argument,missing-class-docstring,line-too-long
target = tvm.target.Target("nvidia/geforce-rtx-3070")
with target, tvm.transform.PassContext(opt_level=3):
After = DefaultGPUSchedule()(Before)
tvm.ir.assert_structural_equal(After, Expected)
def test_scheduled():
# pylint: disable=no-self-argument,missing-class-docstring,line-too-long
# fmt: off
@tvm.script.ir_module
class Scheduled:
@T.prim_func(s_tir=True)
def full(
rxplaceholder: T.Buffer((), "int32"),
T_full: T.Buffer((T.int64(2), T.int64(3)), "int32"),
):
T.func_attr({"tirx.is_scheduled": True, "tirx.noalias": True})
# with T.sblock("root"):
for i0_i1_fused_0 in T.thread_binding(T.int64(1), thread="blockIdx.x"):
for i0_i1_fused_1 in T.thread_binding(T.int64(6), thread="threadIdx.x"):
with T.sblock("T_full"):
ax0 = T.axis.spatial(
T.int64(2),
(i0_i1_fused_0 * T.int64(6) + i0_i1_fused_1) // T.int64(3),
)
ax1 = T.axis.spatial(
T.int64(3),
(i0_i1_fused_0 * T.int64(6) + i0_i1_fused_1) % T.int64(3),
)
T.reads(rxplaceholder[()])
T.writes(T_full[ax0, ax1])
T_full[ax0, ax1] = rxplaceholder[()]
# fmt: on
# pylint: enable=no-self-argument,missing-class-docstring,line-too-long
target = tvm.target.Target("nvidia/geforce-rtx-3070")
with target, tvm.transform.PassContext(opt_level=3):
# should do nothing
After = DefaultGPUSchedule()(Scheduled)
tvm.ir.assert_structural_equal(After, Scheduled)
def test_multiple():
# pylint: disable=no-self-argument,missing-class-docstring,line-too-long
# fmt: off
@tvm.script.ir_module
class Before:
@T.prim_func(s_tir=True)
def add(rxplaceholder: T.Buffer((T.int64(1), T.int64(2), T.int64(3)), "float32"), rxplaceholder_1: T.Buffer((T.int64(4), T.int64(3), T.int64(2), T.int64(1)), "float32"), T_add: T.Buffer((T.int64(4), T.int64(3), T.int64(2), T.int64(3)), "float32")):
T.func_attr({"tirx.noalias": True})
for i0, i1, i2, i3 in T.grid(T.int64(4), T.int64(3), T.int64(2), T.int64(3)):
with T.sblock("T_add"):
ax0, ax1, ax2, ax3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
T.reads(rxplaceholder[T.int64(0), ax2, ax3], rxplaceholder_1[ax0, ax1, ax2, T.int64(0)])
T.writes(T_add[ax0, ax1, ax2, ax3])
T_add[ax0, ax1, ax2, ax3] = rxplaceholder[T.int64(0), ax2, ax3] + rxplaceholder_1[ax0, ax1, ax2, T.int64(0)]
@T.prim_func(s_tir=True)
def full(rxplaceholder: T.Buffer((), "int32"), T_full: T.Buffer((T.int64(2), T.int64(3)), "int32")):
T.func_attr({"tirx.noalias": True})
for i0, i1 in T.grid(T.int64(2), T.int64(3)):
with T.sblock("T_full"):
ax0, ax1 = T.axis.remap("SS", [i0, i1])
T.reads(rxplaceholder[()])
T.writes(T_full[ax0, ax1])
T_full[ax0, ax1] = rxplaceholder[()]
@tvm.script.ir_module
class Expected:
@T.prim_func(s_tir=True)
def add(
rxplaceholder: T.Buffer((T.int64(1), T.int64(2), T.int64(3)), "float32"),
rxplaceholder_1: T.Buffer(
(T.int64(4), T.int64(3), T.int64(2), T.int64(1)), "float32"
),
T_add: T.Buffer((T.int64(4), T.int64(3), T.int64(2), T.int64(3)), "float32"),
):
T.func_attr({"tirx.is_scheduled": True, "tirx.noalias": True})
# with T.sblock("root"):
for i0_i1_i2_i3_fused_0 in T.thread_binding(T.int64(1), thread="blockIdx.x"):
for i0_i1_i2_i3_fused_1 in T.thread_binding(
T.int64(72), thread="threadIdx.x"
):
with T.sblock("T_add"):
ax0 = T.axis.spatial(
T.int64(4),
(i0_i1_i2_i3_fused_0 * T.int64(72) + i0_i1_i2_i3_fused_1)
// T.int64(18),
)
ax1 = T.axis.spatial(
T.int64(3),
(i0_i1_i2_i3_fused_0 * T.int64(72) + i0_i1_i2_i3_fused_1)
% T.int64(18)
// T.int64(6),
)
ax2 = T.axis.spatial(
T.int64(2),
(i0_i1_i2_i3_fused_0 * T.int64(72) + i0_i1_i2_i3_fused_1)
% T.int64(6)
// T.int64(3),
)
ax3 = T.axis.spatial(
T.int64(3),
(i0_i1_i2_i3_fused_0 * T.int64(72) + i0_i1_i2_i3_fused_1)
% T.int64(3),
)
T.reads(
rxplaceholder[T.int64(0), ax2, ax3],
rxplaceholder_1[ax0, ax1, ax2, T.int64(0)],
)
T.writes(T_add[ax0, ax1, ax2, ax3])
T_add[ax0, ax1, ax2, ax3] = (
rxplaceholder[T.int64(0), ax2, ax3]
+ rxplaceholder_1[ax0, ax1, ax2, T.int64(0)]
)
@T.prim_func(s_tir=True)
def full(
rxplaceholder: T.Buffer((), "int32"),
T_full: T.Buffer((T.int64(2), T.int64(3)), "int32"),
):
T.func_attr({"tirx.is_scheduled": True, "tirx.noalias": True})
# with T.sblock("root"):
for i0_i1_fused_0 in T.thread_binding(T.int64(1), thread="blockIdx.x"):
for i0_i1_fused_1 in T.thread_binding(T.int64(6), thread="threadIdx.x"):
with T.sblock("T_full"):
ax0 = T.axis.spatial(
T.int64(2),
(i0_i1_fused_0 * T.int64(6) + i0_i1_fused_1) // T.int64(3),
)
ax1 = T.axis.spatial(
T.int64(3),
(i0_i1_fused_0 * T.int64(6) + i0_i1_fused_1) % T.int64(3),
)
T.reads(rxplaceholder[()])
T.writes(T_full[ax0, ax1])
T_full[ax0, ax1] = rxplaceholder[()]
# fmt: on
# pylint: enable=no-self-argument,missing-class-docstring,line-too-long
target = tvm.target.Target("nvidia/geforce-rtx-3070")
with target, tvm.transform.PassContext(opt_level=3):
After = DefaultGPUSchedule()(Before)
tvm.ir.assert_structural_equal(After, Expected)
def test_add_on_metal():
# pylint: disable=no-self-argument,missing-class-docstring,line-too-long
# fmt: off
@tvm.script.ir_module
class Before:
@T.prim_func(s_tir=True)
def add(rxplaceholder: T.Buffer((T.int64(1), T.int64(2), T.int64(3)), "float32"), rxplaceholder_1: T.Buffer((T.int64(4), T.int64(3), T.int64(2), T.int64(1)), "float32"), T_add: T.Buffer((T.int64(4), T.int64(3), T.int64(2), T.int64(3)), "float32")):
T.func_attr({"tirx.noalias": True})
for i0, i1, i2, i3 in T.grid(T.int64(4), T.int64(3), T.int64(2), T.int64(3)):
with T.sblock("T_add"):
ax0, ax1, ax2, ax3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
T.reads(rxplaceholder[T.int64(0), ax2, ax3], rxplaceholder_1[ax0, ax1, ax2, T.int64(0)])
T.writes(T_add[ax0, ax1, ax2, ax3])
T_add[ax0, ax1, ax2, ax3] = rxplaceholder[T.int64(0), ax2, ax3] + rxplaceholder_1[ax0, ax1, ax2, T.int64(0)]
@tvm.script.ir_module
class Expected:
@T.prim_func(s_tir=True)
def add(rxplaceholder: T.Buffer((T.int64(1), T.int64(2), T.int64(3)), "float32"), rxplaceholder_1: T.Buffer((T.int64(4), T.int64(3), T.int64(2), T.int64(1)), "float32"), T_add: T.Buffer((T.int64(4), T.int64(3), T.int64(2), T.int64(3)), "float32")):
T.func_attr({"tirx.is_scheduled": True, "tirx.noalias": True})
for i0_i1_i2_i3_fused_0 in T.thread_binding(T.int64(1), thread="blockIdx.x"):
for i0_i1_i2_i3_fused_1 in T.thread_binding(T.int64(72), thread="threadIdx.x"):
with T.sblock("T_add"):
ax0 = T.axis.spatial(T.int64(4), (i0_i1_i2_i3_fused_0 * T.int64(72) + i0_i1_i2_i3_fused_1) // T.int64(18))
ax1 = T.axis.spatial(T.int64(3), (i0_i1_i2_i3_fused_0 * T.int64(72) + i0_i1_i2_i3_fused_1) % T.int64(18) // T.int64(6))
ax2 = T.axis.spatial(T.int64(2), (i0_i1_i2_i3_fused_0 * T.int64(72) + i0_i1_i2_i3_fused_1) % T.int64(6) // T.int64(3))
ax3 = T.axis.spatial(T.int64(3), (i0_i1_i2_i3_fused_0 * T.int64(72) + i0_i1_i2_i3_fused_1) % T.int64(3))
T.reads(rxplaceholder[T.int64(0), ax2, ax3], rxplaceholder_1[ax0, ax1, ax2, T.int64(0)])
T.writes(T_add[ax0, ax1, ax2, ax3])
T_add[ax0, ax1, ax2, ax3] = rxplaceholder[T.int64(0), ax2, ax3] + rxplaceholder_1[ax0, ax1, ax2, T.int64(0)]
# fmt: on
# pylint: enable=no-self-argument,missing-class-docstring,line-too-long
target = tvm.target.Target("apple/m1-gpu")
with target, tvm.transform.PassContext(opt_level=0):
mod = DefaultGPUSchedule()(Before)
tvm.ir.assert_structural_equal(mod, Expected)
def test_scalar_add():
# pylint: disable=no-self-argument,missing-class-docstring,line-too-long
# fmt: off
@tvm.script.ir_module
class Before:
@T.prim_func(s_tir=True)
def add(rxplaceholder: T.Buffer((), "int64"), T_add: T.Buffer((), "int64")):
T.func_attr({"tirx.noalias": True})
with T.sblock("T_add"):
vi = T.axis.spatial(1, T.int64(0))
T.reads(rxplaceholder[()])
T.writes(T_add[()])
T_add[()] = rxplaceholder[()] + T.int64(1)
@tvm.script.ir_module
class Expected:
@T.prim_func(s_tir=True)
def add(rxplaceholder: T.Buffer((), "int64"), T_add: T.Buffer((), "int64")):
T.func_attr({"tirx.is_scheduled": True, "tirx.noalias": True})
# with T.sblock("root"):
for u_fused_0 in T.thread_binding(1, thread="blockIdx.x"):
for u_fused_1 in T.thread_binding(1, thread="threadIdx.x"):
with T.sblock("T_add"):
vi = T.axis.spatial(1, T.int64(0))
T.reads(rxplaceholder[()])
T.writes(T_add[()])
T_add[()] = rxplaceholder[()] + T.int64(1)
# fmt: on
# pylint: enable=no-self-argument,missing-class-docstring,line-too-long
target = tvm.target.Target("nvidia/geforce-rtx-3070")
with target, tvm.transform.PassContext(opt_level=0):
mod = DefaultGPUSchedule()(Before)
tvm.ir.assert_structural_equal(mod, Expected)
def test_sum():
# sum has two reduction axes and no spatial axis
# pylint: disable=no-self-argument,missing-class-docstring,line-too-long
# fmt: off
@tvm.script.ir_module
class Before:
@T.prim_func(s_tir=True)
def sum(A: T.Buffer((T.int64(2), T.int64(2)), "float64"), A_red: T.Buffer((), "float64")):
for k0, k1 in T.grid(T.int64(2), T.int64(2)):
with T.sblock("A_red"):
v_k0, v_k1 = T.axis.remap("RR", [k0, k1])
with T.init():
A_red[()] = T.float64(0)
A_red[()] = A_red[()] + A[v_k0, v_k1]
@tvm.script.ir_module
class Expected:
@T.prim_func(s_tir=True)
def sum(A: T.Buffer((T.int64(2), T.int64(2)), "float64"), A_red: T.Buffer((), "float64")):
T.func_attr({"tirx.is_scheduled": True})
# with T.sblock("root"):
for u_fused_0 in T.thread_binding(1, thread="blockIdx.x"):
for u_fused_1 in T.thread_binding(1, thread="threadIdx.x"):
for k0, k1 in T.grid(T.int64(2), T.int64(2)):
with T.sblock("A_red"):
v_k0, v_k1 = T.axis.remap("RR", [k0, k1])
T.reads(A[v_k0, v_k1])
T.writes(A_red[()])
with T.init():
A_red[()] = T.float64(0)
A_red[()] = A_red[()] + A[v_k0, v_k1]
# fmt: on
# pylint: enable=no-self-argument,missing-class-docstring,line-too-long
target = tvm.target.Target("nvidia/geforce-rtx-3070")
with target, tvm.transform.PassContext(opt_level=0):
mod = DefaultGPUSchedule()(Before)
tvm.ir.assert_structural_equal(mod, Expected)
def test_scalar_block_no_loops():
# A PrimFunc whose body is a bare SBlockRealize (e.g. a fully-scalar op)
# used to crash DefaultGPUSchedule with "Cannot add loops on top of the
# root block" because the realized block was the function's root sref.
# pylint: disable=no-self-argument,missing-class-docstring,line-too-long
# fmt: off
@tvm.script.ir_module
class Before:
@T.prim_func(s_tir=True)
def scalar_add(a: T.Buffer((), "float32"), b: T.Buffer((), "float32"), c: T.Buffer((), "float32")):
with T.sblock("scalar_add"):
c[()] = a[()] + b[()]
@tvm.script.ir_module
class Expected:
@T.prim_func(s_tir=True)
def scalar_add(a: T.Buffer((), "float32"), b: T.Buffer((), "float32"), c: T.Buffer((), "float32")):
T.func_attr({"tirx.is_scheduled": True})
# with T.sblock("root"):
for u_fused_0 in T.thread_binding(1, thread="blockIdx.x"):
for u_fused_1 in T.thread_binding(1, thread="threadIdx.x"):
with T.sblock("scalar_add"):
vu = T.axis.spatial(1, 0)
T.reads()
T.writes()
c[()] = a[()] + b[()]
# fmt: on
# pylint: enable=no-self-argument,missing-class-docstring,line-too-long
target = tvm.target.Target("nvidia/geforce-rtx-3070")
with target, tvm.transform.PassContext(opt_level=0):
mod = DefaultGPUSchedule()(Before)
tvm.ir.assert_structural_equal(mod, Expected)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,548 @@
# 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 tvm
import tvm.testing
from tvm import s_tir
from tvm.s_tir.transform import HoistedConditionals, HoistedLetBindings
from tvm.script import tirx as T
def _run_transform(before, hoisted_conditionals, hoisted_let_bindings):
"""Run HoistExpression transform with the given configuration."""
before_mod = tvm.IRModule.from_expr(before)
config = {
"s_tir.HoistExpression": {
"hoisted_conditionals": hoisted_conditionals.value,
"hoisted_let_bindings": hoisted_let_bindings.value,
}
}
with tvm.transform.PassContext(config=config):
after_mod = tvm.s_tir.transform.HoistExpression()(before_mod)
return after_mod["main"]
def test_hoist_to_top_if_else_stmt():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((16,), "float32"), n: T.int32):
for i in T.serial(16):
if n != 0:
A[i] = 0.0
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((16,), "float32"), n: T.int32):
if n != 0:
for i in T.serial(16):
A[i] = 0.0
after = _run_transform(before, HoistedConditionals.IfElseStmt, HoistedLetBindings.All)
tvm.ir.assert_structural_equal(after, expected)
def test_hoist_to_top_all():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((16,), "float32"), n: T.int32):
for i in T.serial(16):
if n != 0:
A[i] = 0.0
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((16,), "float32"), n: T.int32):
if n != 0:
for i in T.serial(16):
A[i] = 0.0
after = _run_transform(before, HoistedConditionals.All, HoistedLetBindings.All)
tvm.ir.assert_structural_equal(after, expected)
def test_suppress_hoist_if_else_never():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((16,), "float32"), n: T.int32):
for i in T.serial(16):
if n != 0:
A[i] = 0.0
expected = before
after = _run_transform(before, HoistedConditionals.Never, HoistedLetBindings.All)
tvm.ir.assert_structural_equal(after, expected)
def test_suppress_hoist_if_else_expr_only():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((16,), "float32"), n: T.int32):
for i in T.serial(16):
if n != 0:
A[i] = 0.0
expected = before
after = _run_transform(before, HoistedConditionals.IfElseExpr, HoistedLetBindings.All)
tvm.ir.assert_structural_equal(after, expected)
def test_hoist_block_var():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((128, 16), "float32"), n: T.int32):
i = T.env_thread("threadIdx.x")
T.launch_thread(i, 128)
for j in T.serial(16):
if i < 32:
A[i, j] = 0.0
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((128, 16), "float32"), n: T.int32):
i = T.env_thread("threadIdx.x")
T.launch_thread(i, 128)
if i < 32:
for j in T.serial(16):
A[i, j] = 0.0
after = _run_transform(before, HoistedConditionals.All, HoistedLetBindings.All)
tvm.ir.assert_structural_equal(after, expected)
def test_suppress_hoist_block_var():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((128, 16), "float32"), n: T.int32):
thread_x = T.env_thread("threadIdx.x")
T.launch_thread(thread_x, 128)
for i in T.thread_binding(0, 128, thread="threadIdx.x"):
if i < 32:
for j in T.serial(16):
A[i, j] = 0.0
expected = before
after = _run_transform(
before,
HoistedConditionals.All & ~HoistedConditionals.UsingBlockVar,
HoistedLetBindings.All,
)
tvm.ir.assert_structural_equal(after, expected)
def test_hoist_across_block_var():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((128, 16), "float32"), n: T.int32):
thread_x = T.env_thread("threadIdx.x")
T.launch_thread(thread_x, 128)
for i in T.thread_binding(0, 128, thread="threadIdx.x"):
if n == 0:
for j in T.serial(16):
A[i, j] = 0.0
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((128, 16), "float32"), n: T.int32):
thread_x = T.env_thread("threadIdx.x")
if n == 0:
T.launch_thread(thread_x, 128)
for i in T.thread_binding(0, 128, thread="threadIdx.x"):
for j in T.serial(16):
A[i, j] = 0.0
after = _run_transform(before, HoistedConditionals.All, HoistedLetBindings.All)
tvm.ir.assert_structural_equal(after, expected)
def test_suppress_hoist_across_block_var():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((128, 16), "float32"), n: T.int32):
thread_x = T.env_thread("threadIdx.x")
T.launch_thread(thread_x, 128)
for i in T.thread_binding(0, 128, thread="threadIdx.x"):
for j in T.serial(16):
if n == 0:
A[i, j] = 0.0
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((128, 16), "float32"), n: T.int32):
thread_x = T.env_thread("threadIdx.x")
T.launch_thread(thread_x, 128)
if n == 0:
for i in T.thread_binding(0, 128, thread="threadIdx.x"):
for j in T.serial(16):
A[i, j] = 0.0
after = _run_transform(
before,
HoistedConditionals.All & ~HoistedConditionals.UsingBlockVar,
HoistedLetBindings.All,
)
tvm.ir.assert_structural_equal(after, expected)
def test_hoist_to_middle():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((4, 4), "float32")):
for i in T.serial(4):
for j in T.serial(4):
if i < 3:
A[i, j] = 0.0
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((4, 4), "float32")):
for i in T.serial(4):
if i < 3:
for j in T.serial(4):
A[i, j] = 0.0
after = _run_transform(before, HoistedConditionals.All, HoistedLetBindings.All)
tvm.ir.assert_structural_equal(after, expected)
def test_hoist_with_let():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((4, 4), "float32")):
for i in T.serial(4):
for j in T.serial(4):
condition: T.let[T.bool] = i < 3
if condition:
A[i, j] = 0.0
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((4, 4), "float32")):
for i in T.serial(4):
condition: T.let[T.bool] = i < 3 # noqa: F841
if i < 3:
for j in T.serial(4):
A[i, j] = T.float32(0.0)
after = _run_transform(before, HoistedConditionals.All, HoistedLetBindings.All)
tvm.ir.assert_structural_equal(after, expected)
def test_hoist_disable_let():
"""As test_hoist_with_let, but forbid hoisting of Bind
Because the condition depends on the let binding, it should no
longer be hoisted. With Bind lifecycle management, the condition
var is erased at sequence boundaries, so the if-condition uses
the raw expression.
"""
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((4, 4), "float32")):
for i in T.serial(4):
for j in T.serial(4):
condition: T.let[T.bool] = i < 3
if condition:
A[i, j] = 0.0
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((4, 4), "float32")):
for i, j in T.grid(4, 4):
condition: T.let[T.bool] = i < 3 # noqa: F841
if i < 3:
A[i, j] = T.float32(0.0)
after = _run_transform(before, HoistedConditionals.All, HoistedLetBindings.Never)
tvm.ir.assert_structural_equal(after, expected)
def test_hoist_if_else():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((4, 4), "float32")):
for i in T.serial(4):
for j in T.serial(4):
if i < 3:
A[i, j] = 0.0
else:
A[i, j] = 1.0
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((4, 4), "float32")):
for i in T.serial(4):
if i < 3:
for j in T.serial(4):
A[i, j] = 0.0
else:
for j in T.serial(4):
A[i, j] = 1.0
after = _run_transform(before, HoistedConditionals.All, HoistedLetBindings.All)
tvm.ir.assert_structural_equal(after, expected)
def test_hoist_sequential_assign():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((4, 4), "float32"), B: T.Buffer((4, 4), "float32")):
for i in T.serial(4):
for j in T.serial(4):
if i < 3:
A[i, j] = 0.0
B[i, j] = 0.0
else:
A[i, j] = 1.0
B[i, j] = 1.0
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((4, 4), "float32"), B: T.Buffer((4, 4), "float32")):
for i in T.serial(4):
if i < 3:
for j in T.serial(4):
A[i, j] = 0.0
B[i, j] = 0.0
else:
for j in T.serial(4):
A[i, j] = 1.0
B[i, j] = 1.0
after = _run_transform(before, HoistedConditionals.All, HoistedLetBindings.All)
tvm.ir.assert_structural_equal(after, expected)
def test_hoist_multi_if():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((4, 4), "float32")):
for i in T.serial(4):
for j in T.serial(4):
for k in T.serial(4):
if j < 3:
if i < 2:
A[i, j] = 0.0
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((4, 4), "float32")):
for i in T.serial(4):
if i < 2:
for j in T.serial(4):
if j < 3:
for k in T.serial(4):
A[i, j] = 0.0
after = _run_transform(before, HoistedConditionals.All, HoistedLetBindings.All)
tvm.ir.assert_structural_equal(after, expected)
def test_hoist_complex_conditional():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((4, 4), "float32")):
for i, j, k in T.grid(4, 4, 4):
if j < 3 and i < 2:
A[i, j] = 0.0
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((4, 4), "float32")):
for i in T.serial(4):
if i < 2:
for j in T.serial(4):
if j < 3:
for k in T.serial(4):
A[i, j] = 0.0
after = _run_transform(before, HoistedConditionals.All, HoistedLetBindings.All)
tvm.ir.assert_structural_equal(after, expected)
def test_suppress_splitting_conditional():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((4, 4), "float32")):
for i, j, k in T.grid(4, 4, 4):
if j < 3 and i < 2:
A[i, j] = 0.0
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((4, 4), "float32")):
for i, j in T.grid(4, 4):
if j < 3 and i < 2:
for k in T.serial(4):
A[i, j] = 0.0
after = _run_transform(
before,
HoistedConditionals.All & ~HoistedConditionals.BooleanExpression,
HoistedLetBindings.All,
)
tvm.ir.assert_structural_equal(after, expected)
def test_hoist_multi_if_else():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((4, 4), "float32")):
for i in T.serial(4):
for j in T.serial(4):
for k in T.serial(4):
if j < 3:
if i < 2:
A[i, j] = 0.0
else:
A[i, j] = 1.0
else:
if i < 2:
A[i, j] = 2.0
else:
A[i, j] = 3.0
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((4, 4), "float32")):
for i in T.serial(4):
if i < 2:
for j in T.serial(4):
if j < 3:
for k in T.serial(4):
A[i, j] = 0.0
else:
for k in T.serial(4):
A[i, j] = 2.0
else:
for j in T.serial(4):
if j < 3:
for k in T.serial(4):
A[i, j] = 1.0
else:
for k in T.serial(4):
A[i, j] = 3.0
after = _run_transform(before, HoistedConditionals.All, HoistedLetBindings.All)
tvm.ir.assert_structural_equal(after, expected)
def test_hoist_multi_if_else_different_branches():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((4, 4), "float32")):
for i in T.serial(4):
for j in T.serial(4):
for k in T.serial(4):
if j < 3:
if i < 2:
A[i, j] = 0.0
else:
A[i, j] = 1.0
else:
if i < 1:
A[i, j] = 2.0
else:
A[i, j] = 3.0
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((4, 4), "float32")):
for i in T.serial(4):
if i < 2:
if i < 1:
for j in T.serial(4):
if j < 3:
for k in T.serial(4):
A[i, j] = 0.0
else:
for k in T.serial(4):
A[i, j] = 2.0
else:
for j in T.serial(4):
if j < 3:
for k in T.serial(4):
A[i, j] = 0.0
else:
for k in T.serial(4):
A[i, j] = 3.0
else:
for j in T.serial(4):
if j < 3:
for k in T.serial(4):
A[i, j] = 1.0
else:
for k in T.serial(4):
A[i, j] = 3.0
after = _run_transform(before, HoistedConditionals.All, HoistedLetBindings.All)
tvm.ir.assert_structural_equal(after, expected)
def test_hoist_if_else_expr():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((4, 4), "float32")):
for i, j in T.grid(4, 4):
A[i, j] = T.if_then_else(i < 2, 1.0, 2.0, dtype="float32")
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((4, 4), "float32")):
for i in T.serial(4):
if i < 2:
for j in T.serial(4):
A[i, j] = 1.0
else:
for j in T.serial(4):
A[i, j] = 2.0
after = _run_transform(before, HoistedConditionals.All, HoistedLetBindings.All)
tvm.ir.assert_structural_equal(after, expected)
def test_suppress_hoist_if_else_expr():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((4, 4), "float32")):
for i, j in T.grid(4, 4):
A[i, j] = T.if_then_else(i < 2, 1.0, 2.0, dtype="float32")
expected = before
after = _run_transform(
before,
HoistedConditionals.All & ~HoistedConditionals.IfElseExpr,
HoistedLetBindings.All,
)
tvm.ir.assert_structural_equal(after, expected)
def test_hoist_let_expr():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((4, 4), "float32")):
for i, j in T.grid(4, 4):
x = T.float32()
A[i, j] = T.Let(5.0 * x + T.cast(j, "float32"), where={x: T.cast(i + 1, "float32")})
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((4, 4), "float32")):
for i in T.serial(4):
x: T.let[T.float32] = T.cast(i + 1, "float32") # noqa: F841
for j in T.serial(4):
A[i, j] = T.float32(5.0) * T.cast(i + 1, "float32") + T.cast(j, "float32")
after = _run_transform(before, HoistedConditionals.All, HoistedLetBindings.All)
tvm.ir.assert_structural_equal(after, expected)
def test_suppress_hoist_let_expr():
@T.prim_func(private=True, s_tir=True)
def before(A: T.Buffer((4, 4), "float32")):
for i, j in T.grid(4, 4):
x = T.float32()
A[i, j] = T.Let(5.0 * x + T.cast(j, "float32"), where={x: T.cast(i + 1, "float32")})
expected = before
after = _run_transform(
before,
HoistedConditionals.All,
HoistedLetBindings.All & ~HoistedLetBindings.LetExpr,
)
tvm.ir.assert_structural_equal(after, expected)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,569 @@
# 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
import numpy as np
import pytest
import tvm_ffi
import tvm
from tvm import s_tir
from tvm.script import ir as I
from tvm.script import tirx as T
from tvm.testing import enabled_targets
var_list = []
def verify_structure(stmt, expected_struct):
node_dict = {}
struct = {}
def _extract_vars(op):
global var_list
if isinstance(op, tvm.tirx.Var):
var_list.append(op.name)
def _visit(op):
key = op
if isinstance(op, tvm.tirx.IfThenElse):
global var_list
tvm.tirx.stmt_functor.post_order_visit(op.condition, _extract_vars)
val = [(op.then_case, op.else_case), ("tirx.IfThenElse", tuple(var_list))]
var_list.clear()
elif isinstance(op, tvm.tirx.For):
val = [(op.body,), ("tirx.For", op.loop_var.name)]
elif isinstance(op, tvm.tirx.AttrStmt):
val = [(op.body,), ("tirx.AttrStmt", op.attr_key, int(op.value))]
else:
return
node_dict[key] = val
tvm.tirx.stmt_functor.post_order_visit(stmt, _visit)
for key, val in node_dict.items():
struct[val[1]] = tuple(
node_dict[child][1] if child in node_dict else None for child in val[0]
)
assert struct == expected_struct, (
f"Structure mismatch: expect {expected_struct} but got {struct}"
)
var_list.clear()
def _opaque_eval(var):
return tvm.tirx.Evaluate(tvm.tirx.call_extern("int32", "dummy", var))
def test_hoist_top_for():
@T.prim_func(private=True, s_tir=True)
def func(l: T.int32, m: T.int32, n: T.int32):
for i in T.serial(l):
for j in T.serial(m):
for k in T.serial(n):
if T.likely(i < 2):
T.evaluate(T.call_extern("int32", "dummy", m))
else:
T.evaluate(T.call_extern("int32", "dummy", n))
mod = tvm.IRModule.from_expr(func)
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(mod)["main"].body
expected_struct = {
("tirx.For", "k"): (None,),
("tirx.For", "j"): (("tirx.For", "k"),),
("tirx.IfThenElse", ("i",)): (("tirx.For", "j"), ("tirx.For", "j")),
("tirx.For", "i"): (("tirx.IfThenElse", ("i",)),),
}
verify_structure(new_stmt, expected_struct)
def test_hoist_multi_var_if():
@T.prim_func(private=True, s_tir=True)
def func(l: T.int32, m: T.int32, n: T.int32):
for i in T.serial(l):
for j in T.serial(m):
for k in T.serial(n):
if T.likely(i + j < 2):
T.evaluate(T.call_extern("int32", "dummy", m))
else:
T.evaluate(T.call_extern("int32", "dummy", n))
mod = tvm.IRModule.from_expr(func)
new_mod = tvm.s_tir.transform.HoistIfThenElse()(mod)
new_stmt = new_mod["main"].body
expected_struct = {
("tirx.For", "k"): (None,),
("tirx.IfThenElse", ("i", "j")): (("tirx.For", "k"), ("tirx.For", "k")),
("tirx.For", "j"): (("tirx.IfThenElse", ("i", "j")),),
("tirx.For", "i"): (("tirx.For", "j"),),
}
verify_structure(new_stmt, expected_struct)
def test_hoist_no_match_for():
@T.prim_func(private=True, s_tir=True)
def func(data: T.handle("float32"), l: T.int32, m: T.int32, n: T.int32):
data_ptr = T.decl_buffer(1, "float32", data=data)
for i in T.serial(l):
for j in T.serial(m):
data_ptr[i * 3 + j] = data_ptr[i * 3 + j] + T.float32(0.5)
for k in T.serial(n):
if T.likely(i < 2):
T.evaluate(T.call_extern("int32", "dummy", m))
else:
T.evaluate(T.call_extern("int32", "dummy", n))
mod = tvm.IRModule.from_expr(func)
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(mod)["main"].body
expected_struct = {
("tirx.For", "k"): (None,),
("tirx.IfThenElse", ("i",)): (("tirx.For", "k"), ("tirx.For", "k")),
("tirx.For", "j"): (None,),
("tirx.For", "i"): (("tirx.For", "j"),),
}
verify_structure(new_stmt, expected_struct)
def test_no_else():
@T.prim_func(private=True, s_tir=True)
def func(l: T.int32, m: T.int32, n: T.int32):
for i in T.serial(l):
for j in T.serial(m):
for k in T.serial(n):
if T.likely(i < 2):
T.evaluate(T.call_extern("int32", "dummy", m))
mod = tvm.IRModule.from_expr(func)
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(mod)["main"].body
expected_struct = {
("tirx.For", "k"): (None,),
("tirx.For", "j"): (("tirx.For", "k"),),
("tirx.IfThenElse", ("i",)): (("tirx.For", "j"), None),
("tirx.For", "i"): (("tirx.IfThenElse", ("i",)),),
}
verify_structure(new_stmt, expected_struct)
def test_attr_stmt():
dshape = (32, 64)
@T.prim_func(private=True, s_tir=True)
def func(data: T.handle("float32"), l: T.int32, m: T.int32, n: T.int32):
data_ptr = T.decl_buffer(1, "float32", data=data)
tx = T.launch_thread("threadIdx.x", dshape[0])
bx = T.launch_thread("blockIdx.x", dshape[1])
for i in T.serial(l):
for j in T.serial(m):
for k in T.serial(n):
if i < 4 or j >= 8:
data_ptr[bx * j + tx * j * k] = data_ptr[bx * j + tx * j * k] + T.float32(
0.5
)
else:
data_ptr[bx * j + tx * j * k] = data_ptr[bx * j + tx * j * k] + T.float32(
1.0
)
mod = tvm.IRModule.from_expr(func)
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(mod)["main"].body
expected_struct = {
("tirx.For", "k"): (None,),
("tirx.IfThenElse", ("i", "j")): (("tirx.For", "k"), ("tirx.For", "k")),
("tirx.For", "j"): (("tirx.IfThenElse", ("i", "j")),),
("tirx.For", "i"): (("tirx.For", "j"),),
("tirx.AttrStmt", "thread_extent", 64): (("tirx.For", "i"),),
("tirx.AttrStmt", "thread_extent", 32): (("tirx.AttrStmt", "thread_extent", 64),),
}
verify_structure(new_stmt, expected_struct)
def test_nested_for():
@T.prim_func(private=True, s_tir=True)
def func(data: T.handle("float32")):
data_ptr = T.decl_buffer(1, "float32", data=data)
for i in range(5):
for j in range(10):
if i >= 3:
data_ptr[i * 3 + j] = data_ptr[i * 3 + j] + T.float32(0.5)
for k in range(15):
for l in range(20):
if i < 4 or j >= 8:
data_ptr[i * 3 + j + k + l] = data_ptr[
i * 3 + j + k + l
] * T.float32(2)
else:
data_ptr[i * 3 + j + k + l] = data_ptr[
i * 3 + j + k + l
] * T.float32(1.5)
mod = tvm.IRModule.from_expr(func)
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(mod)["main"].body
expected_struct = {
("tirx.For", "l"): (None,),
("tirx.For", "k"): (("tirx.For", "l"),),
("tirx.IfThenElse", ("i", "j")): (("tirx.For", "k"), ("tirx.For", "k")),
("tirx.For", "j"): (None,),
("tirx.IfThenElse", ("i",)): (("tirx.For", "j"), None),
("tirx.For", "i"): (("tirx.IfThenElse", ("i",)),),
}
verify_structure(new_stmt, expected_struct)
def test_if_block():
# Use different variable names for second loop nest to avoid dict key collision
@I.ir_module
class Module:
@T.prim_func(private=True, s_tir=True)
def main(data: T.Buffer((1,), "float32"), n: T.int32):
# First loop nest: i, j, k, l
for i in T.serial(5):
for j in T.serial(10):
if i >= 3:
data[i * 3 + j] = data[i * 3 + j] + T.float32(0.5)
for k in T.serial(15):
for l in T.serial(20):
if i < 4 or j >= 8:
data[i * 3 + j + k + l] = data[i * 3 + j + k + l] * T.float32(2)
else:
data[i * 3 + j + k + l] = data[i * 3 + j + k + l] * T.float32(
1.5
)
if j < 5:
data[i * 3 + j + k + l] = data[i * 3 + j + k + l] - T.float32(1)
# Second loop nest: i2, j2, k2 (different names)
for i2 in T.serial(5):
for j2 in T.serial(10):
for k2 in T.serial(15):
if n >= 3:
data[i2 * 3 + j2 + k2] = data[i2 * 3 + j2 + k2] + T.float32(0.6)
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(Module)["main"].body
# Updated expected_struct with renamed second nest variables
expected_struct = {
("tirx.IfThenElse", ("i", "j")): (None, None),
("tirx.IfThenElse", ("j",)): (None, None),
("tirx.For", "l"): (None,),
("tirx.For", "k"): (("tirx.For", "l"),),
("tirx.For", "j"): (None,),
("tirx.IfThenElse", ("i",)): (("tirx.For", "j"), None),
("tirx.For", "i"): (("tirx.IfThenElse", ("i",)),),
("tirx.For", "k2"): (None,),
("tirx.For", "j2"): (("tirx.For", "k2"),),
("tirx.For", "i2"): (("tirx.For", "j2"),),
("tirx.IfThenElse", ("n",)): (("tirx.For", "i2"), None),
}
verify_structure(new_stmt, expected_struct)
def test_multi_if():
@T.prim_func(private=True, s_tir=True)
def func(data: T.handle("float32")):
data_ptr = T.decl_buffer(1, "float32", data=data)
for i in range(10):
for j in range(10):
for k in range(10):
if 3 <= i:
if 3 <= j:
data_ptr[i * 100 + j * 10 + k] = data_ptr[
i * 100 + j * 10 + k
] + T.float32(0.5)
mod = tvm.IRModule.from_expr(func)
new_mod = tvm.s_tir.transform.HoistIfThenElse()(mod)
new_stmt = new_mod["main"].body
expected_struct = {
("tirx.For", "k"): (None,),
("tirx.IfThenElse", ("j",)): (("tirx.For", "k"), None),
("tirx.For", "j"): (("tirx.IfThenElse", ("j",)),),
("tirx.IfThenElse", ("i",)): (("tirx.For", "j"), None),
("tirx.For", "i"): (("tirx.IfThenElse", ("i",)),),
}
verify_structure(new_stmt, expected_struct)
def test_no_hoisting_1():
@T.prim_func(private=True, s_tir=True)
def func(data: T.handle("float32")):
data_ptr = T.decl_buffer(1, "float32", data=data)
for i in range(10):
for j in range(10):
for k in range(10):
if k <= 3:
data_ptr[i * 100 + j * 10 + k] = data_ptr[i * 100 + j * 10 + k] + T.float32(
0.5
)
mod = tvm.IRModule.from_expr(func)
stmt = mod["main"].body
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(mod)["main"].body
tvm.ir.assert_structural_equal(new_stmt, stmt)
with tvm.transform.PassContext(
config={"s_tir.HoistIfThenElse": {"support_block_scope_hoisting": True}}
):
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(mod)["main"].body
tvm.ir.assert_structural_equal(new_stmt, stmt)
def test_no_hoisting_2():
@T.prim_func(private=True, s_tir=True)
def func(data: T.handle("float32")):
data_ptr = T.decl_buffer(1, "float32", data=data)
for i in range(10):
for j in range(10):
for k in range(10):
if i <= 3:
data_ptr[i * 100 + j * 10 + k] = data_ptr[i * 100 + j * 10 + k] + T.float32(
0.3
)
data_ptr[i * 100 + j * 10 + k] = data_ptr[i * 100 + j * 10 + k] + T.float32(0.5)
mod = tvm.IRModule.from_expr(func)
stmt = mod["main"].body
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(mod)["main"].body
tvm.ir.assert_structural_equal(new_stmt, stmt)
with tvm.transform.PassContext(
config={"s_tir.HoistIfThenElse": {"support_block_scope_hoisting": True}}
):
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(mod)["main"].body
tvm.ir.assert_structural_equal(new_stmt, stmt)
def test_no_hoisting_4():
dshape = (32, 64)
dshape_inner = (33, 63)
# Create iter_var for tx (used inside loop with T.attr)
tx_var = tvm.tirx.Var("threadIdx.x", "int32")
tx_iter = tvm.tirx.IterVar(
tvm.ir.Range(0, dshape_inner[0]), tx_var, tvm.tirx.IterVar.ThreadIndex, "threadIdx.x"
)
@I.ir_module
class Module:
@T.prim_func(private=True, s_tir=True)
def main(data: T.Buffer((1,), "float32"), l: T.int32, m: T.int32, n: T.int32):
bx = T.launch_thread("blockIdx.x", dshape[1])
for i in T.serial(l):
for j in T.serial(m):
for k in T.serial(n):
T.attr(tx_iter, "thread_extent", dshape_inner[0])
if tx_var < 3:
data[bx * j + tx_var * j * k] = data[
bx * j + tx_var * j * k
] + T.float32(0.3)
else:
data[bx * j + tx_var * j * k] = data[
bx * j + tx_var * j * k
] + T.float32(1.3)
stmt = Module["main"].body
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(Module)["main"].body
tvm.ir.assert_structural_equal(new_stmt, stmt)
with tvm.transform.PassContext(
config={"s_tir.HoistIfThenElse": {"support_block_scope_hoisting": True}}
):
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(Module)["main"].body
tvm.ir.assert_structural_equal(new_stmt, stmt)
def test_no_hoisting_6():
dshape = (32, 64)
@I.ir_module
class Module:
@T.prim_func(private=True, s_tir=True)
def main(data: T.Buffer((1,), "float32"), l: T.int32, m: T.int32, n: T.int32):
tx = T.launch_thread("threadIdx.x", dshape[0])
bx = T.launch_thread("blockIdx.x", dshape[1])
for i in T.serial(l):
for j in T.serial(m):
for k in T.serial(n):
if tx + k < 3:
data[bx * j + tx * j * k] = data[bx * j + tx * j * k] + T.float32(0.3)
else:
data[bx * j + tx * j * k] = data[bx * j + tx * j * k] + T.float32(1.3)
stmt = Module["main"].body
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(Module)["main"].body
tvm.ir.assert_structural_equal(new_stmt, stmt)
with tvm.transform.PassContext(
config={"s_tir.HoistIfThenElse": {"support_block_scope_hoisting": True}}
):
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(Module)["main"].body
tvm.ir.assert_structural_equal(new_stmt, stmt)
def test_no_hoisting_7():
dshape = (32, 64)
@I.ir_module
class Module:
@T.prim_func(private=True, s_tir=True)
def main(data: T.Buffer((1,), "float32"), l: T.int32, m: T.int32, n: T.int32):
tx = T.launch_thread("threadIdx.x", dshape[0])
bx = T.launch_thread("blockIdx.x", dshape[1])
for i in T.serial(l):
for j in T.serial(m):
if tx + j < 9:
for k in T.serial(n):
if tx + k < 3:
data[bx * j + tx * j * k] = data[bx * j + tx * j * k] + T.float32(
0.3
)
stmt = Module["main"].body
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(Module)["main"].body
tvm.ir.assert_structural_equal(new_stmt, stmt)
with tvm.transform.PassContext(
config={"s_tir.HoistIfThenElse": {"support_block_scope_hoisting": True}}
):
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(Module)["main"].body
tvm.ir.assert_structural_equal(new_stmt, stmt)
def test_hoisting_block_scope_2():
dshape = (32, 64)
# Create iter_var for bx (used inside loop with T.attr)
bx_var = tvm.tirx.Var("blockIdx.x", "int32")
bx_iter = tvm.tirx.IterVar(
tvm.ir.Range(0, dshape[1]), bx_var, tvm.tirx.IterVar.ThreadIndex, "blockIdx.x"
)
@I.ir_module
class Module:
@T.prim_func(private=True, s_tir=True)
def main(data: T.Buffer((1,), "float32"), l: T.int32, m: T.int32, n: T.int32):
tx = T.launch_thread("threadIdx.x", dshape[0])
for i in T.serial(l):
for j in T.serial(m):
for k in T.serial(n):
T.attr(bx_iter, "thread_extent", dshape[1])
if tx < 3:
data[bx_var * j + tx * j * k] = data[
bx_var * j + tx * j * k
] + T.float32(0.3)
else:
data[bx_var * j + tx * j * k] = data[
bx_var * j + tx * j * k
] + T.float32(1.3)
mod = Module
mod = tvm.tirx.transform.StmtSimplify()(mod)
mod = tvm.tirx.transform.RemoveNoOp()(mod)
stmt = mod["main"].body
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(mod)["main"].body
tvm.ir.assert_structural_equal(new_stmt, stmt)
with tvm.transform.PassContext(
config={"s_tir.HoistIfThenElse": {"support_block_scope_hoisting": True}}
):
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(mod)["main"].body
assert not tvm_ffi.structural_equal(new_stmt, stmt)
def test_hoisting_block_scope_5():
@I.ir_module
class Module:
@T.prim_func(private=True, s_tir=True)
def main(data: T.Buffer((1,), "float32"), l: T.int32, m: T.int32, n: T.int32, g: T.int32):
for i in T.serial(l):
for j in T.serial(m):
for k in T.serial(n):
if data[g] < T.float32(3):
data[9 * j + 3 * j * k] = data[9 * j + 3 * j * k] + T.float32(0.3)
else:
data[9 * j + 3 * j * k] = data[9 * j + 3 * j * k] + T.float32(1.3)
stmt = Module["main"].body
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(Module)["main"].body
assert not tvm_ffi.structural_equal(new_stmt, stmt)
mod = tvm.IRModule.from_expr(tvm.tirx.PrimFunc([], new_stmt))
stmt = new_stmt
with tvm.transform.PassContext(
config={"s_tir.HoistIfThenElse": {"support_block_scope_hoisting": True}}
):
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(mod)["main"].body
tvm.ir.assert_structural_equal(new_stmt, stmt)
def test_hoisting_block_scope_6():
dshape = (32, 64)
@I.ir_module
class Module:
@T.prim_func(private=True, s_tir=True)
def main(data: T.Buffer((1,), "float32"), l: T.int32, m: T.int32, n: T.int32):
tx = T.launch_thread("threadIdx.x", dshape[0])
bx = T.launch_thread("blockIdx.x", dshape[1])
for i in T.serial(l):
for j in T.serial(m):
for k in T.serial(n):
if tx + n < 3:
data[bx * j + tx * j * k] = data[bx * j + tx * j * k] + T.float32(0.3)
else:
data[bx * j + tx * j * k] = data[bx * j + tx * j * k] + T.float32(1.3)
stmt = Module["main"].body
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(Module)["main"].body
tvm.ir.assert_structural_equal(new_stmt, stmt)
with tvm.transform.PassContext(
config={"s_tir.HoistIfThenElse": {"support_block_scope_hoisting": True}}
):
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(Module)["main"].body
assert not tvm_ffi.structural_equal(new_stmt, stmt)
def test_hoisting_block_scope_7():
dshape = (32, 64)
@I.ir_module
class Module:
@T.prim_func(private=True, s_tir=True)
def main(data: T.Buffer((1,), "float32"), l: T.int32, m: T.int32, n: T.int32):
tx = T.launch_thread("threadIdx.x", dshape[0])
bx = T.launch_thread("blockIdx.x", dshape[1])
for i in T.serial(l):
for j in T.serial(m):
for k in T.serial(n):
if tx + i < 3:
data[bx * j + tx * j * k] = data[bx * j + tx * j * k] + T.float32(0.3)
else:
data[bx * j + tx * j * k] = data[bx * j + tx * j * k] + T.float32(1.3)
stmt = Module["main"].body
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(Module)["main"].body
tvm.ir.assert_structural_equal(new_stmt, stmt)
with tvm.transform.PassContext(
config={"s_tir.HoistIfThenElse": {"support_block_scope_hoisting": True}}
):
new_stmt = tvm.s_tir.transform.HoistIfThenElse()(Module)["main"].body
assert not tvm_ffi.structural_equal(new_stmt, stmt)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,165 @@
# 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 tvm
import tvm.testing
from tvm.script import ir as I
from tvm.script import tirx as T
def test_double_buffer():
n = 100
m = 4
@I.ir_module
class Module:
@T.prim_func(s_tir=True)
def db(A: T.handle("float32"), C: T.handle("float32")):
A_buf = T.decl_buffer((n * m,), "float32", data=A)
C_buf = T.decl_buffer((m,), "float32", data=C)
tx = T.launch_thread("threadIdx.x", 1)
for i in range(n):
B = T.alloc_buffer((m,), "float32", scope="shared")
with T.attr(B.data, "double_buffer_scope", 1):
for j in range(m):
B[j] = A_buf[i * 4 + j]
for j in range(m):
C_buf[j] = B[j] + T.float32(1.0)
mod = Module
opt = tvm.transform.Sequential(
[tvm.s_tir.transform.InjectDoubleBuffer(), tvm.tirx.transform.StmtSimplify()]
)
with tvm.transform.PassContext(config={"s_tir.InjectDoubleBuffer": {"split_loop": 2}}):
mod = opt(mod)
stmt = mod["db"].body
# After transformation, the buffer allocation should be doubled
allocate_node = None
def visitor(op):
nonlocal allocate_node
if isinstance(op, tvm.tirx.AllocBuffer) and "B" in str(op.buffer.data):
allocate_node = op
tvm.tirx.stmt_functor.post_order_visit(stmt, visitor)
assert allocate_node is not None
assert list(allocate_node.buffer.shape) == [m * 2]
f = tvm.s_tir.transform.ThreadSync("shared")(mod)["db"]
count = [0]
def count_sync(op):
if isinstance(op, tvm.ir.Call) and op.op.same_as(tvm.ir.Op.get("tirx.tvm_storage_sync")):
count[0] += 1
tvm.tirx.stmt_functor.post_order_visit(f.body, count_sync)
assert count[0] == 4
def test_double_buffer_transform():
transform = tvm.ir.transform.Sequential(
[
tvm.s_tir.transform.InjectDoubleBuffer(),
tvm.tirx.transform.StmtSimplify(),
]
)
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(A: T.Buffer([16, 32], "float32"), B: T.Buffer(16, "float32")):
for i in range(16):
cache = T.alloc_buffer((32,), "float32")
T.attr(cache.data, "double_buffer_scope", 1)
for j in range(32):
cache[j] = A[i, j]
B[i] = 0.0
for j in range(32):
B[i] = B[i] + cache[j]
After = transform(Before)
# Verify the double buffer transformation: the allocation should be doubled (32 -> 64)
allocate_node = None
def visitor(op):
nonlocal allocate_node
if isinstance(op, tvm.tirx.AllocBuffer):
allocate_node = op
tvm.tirx.stmt_functor.post_order_visit(After["main"].body, visitor)
assert allocate_node is not None
assert list(allocate_node.buffer.shape) == [64]
def test_double_buffer_with_decl_buffer():
"""Like test_double_buffer_transform, but with a declared buffer object"""
transform = tvm.ir.transform.Sequential(
[
tvm.s_tir.transform.InjectDoubleBuffer(),
tvm.tirx.transform.StmtSimplify(),
]
)
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((16, 32), "float32"), B: T.Buffer(16, "float32")):
for i in range(16):
cache = T.decl_buffer(32, "float32")
T.attr(cache.data, "double_buffer_scope", 1)
for j in range(32):
cache[j] = A[i, j]
B[i] = 0.0
for j in range(32):
B[i] = B[i] + cache[j]
@I.ir_module
class Expected:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((16, 32), "float32"), B: T.Buffer(16, "float32")):
cache = T.decl_buffer(64, "float32")
for j in range(32):
cache[j] = A[0, j]
B[0] = T.float32(0.0)
for j in range(32):
B[0] = B[0] + cache[j]
for i_outer in range(15):
T.attr(cache.data, "double_buffer_write", 1)
for j in range(32):
cache[(i_outer + 1) % 2 * 32 + j] = A[i_outer + 1, j]
B[i_outer + 1] = T.float32(0.0)
for j in range(32):
B[i_outer + 1] = B[i_outer + 1] + cache[(i_outer + 1) % 2 * 32 + j]
After = transform(Before)
tvm.ir.assert_structural_equal(After, Expected)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,353 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ruff: noqa: E501, E741
import tvm
import tvm.s_tir
import tvm.testing
from tvm import IRModule
from tvm.script import tirx as T
from tvm.tirx import PrimFunc
def _check_primfunc_transform(before: PrimFunc, expected: PrimFunc):
before_module = IRModule.from_expr(before)
after_module = tvm.s_tir.transform.InjectPermutedLayout()(before_module)
after = after_module["before"].without_attr("global_symbol")
expected = expected.without_attr("global_symbol")
tvm.ir.assert_structural_equal(after, expected)
# This pass is adapted from another previous pass, so we need to ensure backward compatibility here
def test_backward_compatibility_shared_a():
# fmt: off
@T.prim_func(s_tir=True)
def before(X: T.Buffer((4096, 4096), "float16")):
# with T.sblock("root"):
for blockIdx_y in T.thread_binding(256, thread="blockIdx.y"):
for threadIdx_y in T.thread_binding(4, thread="threadIdx.y"):
for threadIdx_x in T.thread_binding(32, thread="threadIdx.x"):
with T.sblock(""):
T.reads(X[blockIdx_y // 8 * 128 + threadIdx_y * 8 + threadIdx_x // 4:blockIdx_y // 8 * 128 + threadIdx_y * 8 + threadIdx_x // 4 + 97, threadIdx_x % 4 * 8:threadIdx_x % 4 * 8 + 4072])
T.writes()
for ax2_0_0 in range(128):
with T.sblock(""):
T.reads(X[blockIdx_y // 8 * 128 + threadIdx_y * 8 + threadIdx_x // 4:blockIdx_y // 8 * 128 + threadIdx_y * 8 + threadIdx_x // 4 + 97, ax2_0_0 * 32 + threadIdx_x % 4 * 8:ax2_0_0 * 32 + threadIdx_x % 4 * 8 + 8])
T.writes()
X_reindex_shared_dyn = T.sblock_alloc_buffer((128, 32), "float16", strides=(32, 1), scope="shared.dyn")
with T.sblock("X_reindex_shared.dyn"):
T.reads(X[blockIdx_y // 8 * 128 + threadIdx_y * 8 + threadIdx_x // 4:blockIdx_y // 8 * 128 + threadIdx_y * 8 + threadIdx_x // 4 + 97, ax2_0_0 * 32 + threadIdx_x % 4 * 8:ax2_0_0 * 32 + threadIdx_x % 4 * 8 + 8])
T.writes(X_reindex_shared_dyn[threadIdx_y * 8 + threadIdx_x // 4:threadIdx_y * 8 + threadIdx_x // 4 + 97, threadIdx_x % 4 * 8:threadIdx_x % 4 * 8 + 8])
T.sblock_attr({"permuted_layout": "g2s_A"})
for ax0_ax1_fused_0 in range(4):
for ax0_ax1_fused_3 in T.vectorized(8):
X_reindex_shared_dyn[ax0_ax1_fused_0 * 32 + threadIdx_y * 8 + threadIdx_x // 4, threadIdx_x % 4 * 8 + ax0_ax1_fused_3] = X[blockIdx_y // 8 * 128 + ax0_ax1_fused_0 * 32 + threadIdx_y * 8 + threadIdx_x // 4, ax2_0_0 * 32 + threadIdx_x % 4 * 8 + ax0_ax1_fused_3]
for ax2_0_1 in range(4):
with T.sblock(""):
T.reads(X_reindex_shared_dyn[threadIdx_y // 2 * 64:threadIdx_y // 2 * 64 + 64, ax2_0_1 * 8:ax2_0_1 * 8 + 8])
T.writes()
X_reindex_shared_dyn_m16n8k8_matrixA = T.sblock_alloc_buffer((64, 8), "float16", scope="m16n8k8.matrixA")
for ax0_0, ax1_0 in T.grid(2, 1):
with T.sblock("X_reindex_shared.dyn_m16n8k8.matrixA_o"):
T.reads(X_reindex_shared_dyn[threadIdx_y // 2 * 64 + ax0_0 * 32:threadIdx_y // 2 * 64 + ax0_0 * 32 + 32, ax2_0_1 * 8:ax2_0_1 * 8 + 8])
T.writes(X_reindex_shared_dyn_m16n8k8_matrixA[ax0_0 * 32:ax0_0 * 32 + 32, 0:8])
T.sblock_attr({"permuted_layout": "s2l_A"})
T.ptx.ldmatrix_legacy("float16", T.bool(False), 4, ".b16", X_reindex_shared_dyn_m16n8k8_matrixA.data, ax0_0 * 8, T.tvm_access_ptr(T.type_annotation("float16"), X_reindex_shared_dyn.data, threadIdx_y // 2 * 2048 + ax0_0 * 1024 + ax2_0_1 * 8, 1024, 1), threadIdx_x * 32)
@T.prim_func(s_tir=True)
def expected(X: T.Buffer((4096, 4096), "float16")):
for blockIdx_y in T.thread_binding(256, thread="blockIdx.y"):
for threadIdx_y in T.thread_binding(4, thread="threadIdx.y"):
for threadIdx_x in T.thread_binding(32, thread="threadIdx.x"):
with T.sblock(""):
for ax2_0_0 in T.serial(128):
with T.sblock(""):
X_reindex_shared_dyn = T.sblock_alloc_buffer((128, 32), "float16", strides=(32, 1), scope="shared.dyn")
with T.sblock("X_reindex_shared.dyn"):
# annotate the reads and writes because they cannot be inferred from tirx.bitwise_xor
T.reads(X[blockIdx_y // 8 * 128 + threadIdx_y * 8 + threadIdx_x // 4:blockIdx_y // 8 * 128 + threadIdx_y * 8 + threadIdx_x // 4 + 97, ax2_0_0 * 32 + threadIdx_x % 4 * 8:ax2_0_0 * 32 + threadIdx_x % 4 * 8 + 8])
T.writes(X_reindex_shared_dyn[threadIdx_y * 8 + threadIdx_x // 4:threadIdx_y * 8 + threadIdx_x // 4 + 97, threadIdx_x % 4 * 8:threadIdx_x % 4 * 8 + 8])
for ax0_ax1_fused_0 in range(4):
for ax0_ax1_fused_3 in T.vectorized(8):
X_reindex_shared_dyn[ax0_ax1_fused_0 * 32 + threadIdx_y * 8 + threadIdx_x // 4, T.bitwise_xor(threadIdx_x % 4, threadIdx_x // 8) * 8 + ax0_ax1_fused_3] = X[blockIdx_y // 8 * 128 + ax0_ax1_fused_0 * 32 + threadIdx_y * 8 + threadIdx_x // 4, ax2_0_0 * 32 + threadIdx_x % 4 * 8 + ax0_ax1_fused_3]
for ax2_0_1 in T.serial(4):
with T.sblock(""):
X_reindex_shared_dyn_m16n8k8_matrixA = T.sblock_alloc_buffer((64, 8), "float16", scope="m16n8k8.matrixA")
for ax0_0, ax1_0 in T.grid(2, 1):
with T.sblock("X_reindex_shared.dyn_m16n8k8.matrixA_o"):
T.reads(X_reindex_shared_dyn[threadIdx_y // 2 * 64 + ax0_0 * 32:threadIdx_y // 2 * 64 + ax0_0 * 32 + 32, ax2_0_1 * 8:ax2_0_1 * 8 + 8])
T.writes(X_reindex_shared_dyn_m16n8k8_matrixA[ax0_0 * 32:ax0_0 * 32 + 32, 0:8])
T.ptx.ldmatrix_legacy("float16", T.bool(False), 4, ".b16", X_reindex_shared_dyn_m16n8k8_matrixA.data, ax0_0 * 8, T.tvm_access_ptr(T.type_annotation("float16"), X_reindex_shared_dyn.data, threadIdx_y // 2 * 2048 + ax0_0 * 1024 + threadIdx_x * 32 + T.bitwise_xor(ax2_0_1, threadIdx_x % 8 // 2) * 8, 1024, 1), 0)
# fmt: on
_check_primfunc_transform(before, expected)
def test_backward_compatibility_shared_a_and_b():
# fmt: off
@T.prim_func(s_tir=True)
def before(X: T.Buffer((4096, 4096), "float16"), Y: T.Buffer((4096, 4096), "float16")):
for blockIdx_x in T.thread_binding(4, thread="blockIdx.x"):
for blockIdx_y in T.thread_binding(256, thread="blockIdx.y"):
for threadIdx_y in T.thread_binding(4, thread="threadIdx.y"):
for threadIdx_x in T.thread_binding(32, thread="threadIdx.x"):
with T.sblock(""):
for ax2_0_0 in T.serial(128):
with T.sblock(""):
X_reindex_shared_dyn = T.sblock_alloc_buffer((128, 32), "float16", strides=(32, 1), scope="shared.dyn")
Y_reindex_shared_dyn = T.sblock_alloc_buffer((32, 128), "float16", strides=(128, 1), scope="shared.dyn")
with T.sblock("X_reindex_shared.dyn"):
T.sblock_attr({"permuted_layout": "g2s_A"})
for ax0_ax1_fused_0 in range(4):
for ax0_ax1_fused_3 in T.vectorized(8):
X_reindex_shared_dyn[ax0_ax1_fused_0 * 32 + threadIdx_y * 8 + threadIdx_x // 4, threadIdx_x % 4 * 8 + ax0_ax1_fused_3] = X[blockIdx_y // 8 * 128 + ax0_ax1_fused_0 * 32 + threadIdx_y * 8 + threadIdx_x // 4, ax2_0_0 * 32 + threadIdx_x % 4 * 8 + ax0_ax1_fused_3]
with T.sblock("Y_reindex_shared.dyn"):
T.sblock_attr({"permuted_layout": "g2s_B"})
for ax0_ax1_fused_0 in range(4):
for ax0_ax1_fused_3 in T.vectorized(8):
Y_reindex_shared_dyn[ax0_ax1_fused_0 * 8 + threadIdx_y * 2 + threadIdx_x // 16, threadIdx_x % 16 * 8 + ax0_ax1_fused_3] = Y[ax2_0_0 * 32 + ax0_ax1_fused_0 * 8 + threadIdx_y * 2 + threadIdx_x // 16, blockIdx_x * 1024 + blockIdx_y % 8 * 128 + threadIdx_x % 16 * 8 + ax0_ax1_fused_3]
for ax2_0_1 in T.serial(4):
with T.sblock(""):
X_reindex_shared_dyn_m16n8k8_matrixA = T.sblock_alloc_buffer((64, 8), "float16", scope="m16n8k8.matrixA")
Y_reindex_shared_dyn_m16n8k8_matrixB = T.sblock_alloc_buffer((8, 64), "float16", scope="m16n8k8.matrixB")
for ax0_0, ax1_0 in T.grid(2, 1):
with T.sblock("X_reindex_shared.dyn_m16n8k8.matrixA_o"):
T.reads(X_reindex_shared_dyn[threadIdx_y // 2 * 64 + ax0_0 * 32:threadIdx_y // 2 * 64 + ax0_0 * 32 + 32, ax2_0_1 * 8:ax2_0_1 * 8 + 8])
T.writes(X_reindex_shared_dyn_m16n8k8_matrixA[ax0_0 * 32:ax0_0 * 32 + 32, 0:8])
T.sblock_attr({"permuted_layout": "s2l_A"})
T.ptx.ldmatrix_legacy("float16", T.bool(False), 4, ".b16", X_reindex_shared_dyn_m16n8k8_matrixA.data, ax0_0 * 8, T.tvm_access_ptr(T.type_annotation("float16"), X_reindex_shared_dyn.data, threadIdx_y // 2 * 2048 + ax0_0 * 1024 + ax2_0_1 * 8, 1024, 1), threadIdx_x * 32)
for ax0_0, ax1_0 in T.grid(1, 2):
with T.sblock("Y_reindex_shared.dyn_m16n8k8.matrixB_o"):
T.reads(Y_reindex_shared_dyn[ax2_0_1 * 8:ax2_0_1 * 8 + 8, threadIdx_y % 2 * 64 + ax1_0 * 32:threadIdx_y % 2 * 64 + ax1_0 * 32 + 32])
T.writes(Y_reindex_shared_dyn_m16n8k8_matrixB[0:8, ax1_0 * 32:ax1_0 * 32 + 32])
T.sblock_attr({"permuted_layout": "s2l_B"})
T.ptx.ldmatrix_legacy("float16", T.bool(True), 4, ".b16", Y_reindex_shared_dyn_m16n8k8_matrixB.data, ax1_0 * 8, T.tvm_access_ptr(T.type_annotation("float16"), Y_reindex_shared_dyn.data, ax2_0_1 * 1024 + threadIdx_y % 2 * 64 + ax1_0 * 32, 1024, 1), threadIdx_x % 8 * 128 + threadIdx_x // 8 * 8)
@T.prim_func(s_tir=True)
def expected(X: T.Buffer((4096, 4096), "float16"), Y: T.Buffer((4096, 4096), "float16")):
for blockIdx_x in T.thread_binding(4, thread="blockIdx.x"):
for blockIdx_y in T.thread_binding(256, thread="blockIdx.y"):
for threadIdx_y in T.thread_binding(4, thread="threadIdx.y"):
for threadIdx_x in T.thread_binding(32, thread="threadIdx.x"):
with T.sblock(""):
T.reads(X[blockIdx_y // 8 * 128 + threadIdx_y * 8 + threadIdx_x // 4:blockIdx_y // 8 * 128 + threadIdx_y * 8 + threadIdx_x // 4 + 97, threadIdx_x % 4 * 8:threadIdx_x % 4 * 8 + 4072], Y[threadIdx_y * 2 + threadIdx_x // 16:threadIdx_y * 2 + threadIdx_x // 16 + 4089, blockIdx_x * 1024 + blockIdx_y % 8 * 128 + threadIdx_x % 16 * 8:blockIdx_x * 1024 + blockIdx_y % 8 * 128 + threadIdx_x % 16 * 8 + 8])
T.writes()
for ax2_0_0 in T.serial(128):
with T.sblock(""):
T.reads(X[blockIdx_y // 8 * 128 + threadIdx_y * 8 + threadIdx_x // 4:blockIdx_y // 8 * 128 + threadIdx_y * 8 + threadIdx_x // 4 + 97, ax2_0_0 * 32 + threadIdx_x % 4 * 8:ax2_0_0 * 32 + threadIdx_x % 4 * 8 + 8], Y[ax2_0_0 * 32 + threadIdx_y * 2 + threadIdx_x // 16:ax2_0_0 * 32 + threadIdx_y * 2 + threadIdx_x // 16 + 25, blockIdx_x * 1024 + blockIdx_y % 8 * 128 + threadIdx_x % 16 * 8:blockIdx_x * 1024 + blockIdx_y % 8 * 128 + threadIdx_x % 16 * 8 + 8])
T.writes()
X_reindex_shared_dyn = T.sblock_alloc_buffer((128, 32), "float16", strides=(32, 1), scope="shared.dyn")
Y_reindex_shared_dyn = T.sblock_alloc_buffer((32, 128), "float16", strides=(128, 1), scope="shared.dyn")
with T.sblock("X_reindex_shared.dyn"):
T.reads(X[blockIdx_y // 8 * 128 + threadIdx_y * 8 + threadIdx_x // 4:blockIdx_y // 8 * 128 + threadIdx_y * 8 + threadIdx_x // 4 + 97, ax2_0_0 * 32 + threadIdx_x % 4 * 8:ax2_0_0 * 32 + threadIdx_x % 4 * 8 + 8])
T.writes(X_reindex_shared_dyn[threadIdx_y * 8 + threadIdx_x // 4:threadIdx_y * 8 + threadIdx_x // 4 + 97, threadIdx_x % 4 * 8:threadIdx_x % 4 * 8 + 8])
for ax0_ax1_fused_0 in range(4):
for ax0_ax1_fused_3 in T.vectorized(8):
X_reindex_shared_dyn[ax0_ax1_fused_0 * 32 + threadIdx_y * 8 + threadIdx_x // 4, T.bitwise_xor(threadIdx_x % 4, threadIdx_x // 8) * 8 + ax0_ax1_fused_3] = X[blockIdx_y // 8 * 128 + ax0_ax1_fused_0 * 32 + threadIdx_y * 8 + threadIdx_x // 4, ax2_0_0 * 32 + threadIdx_x % 4 * 8 + ax0_ax1_fused_3]
with T.sblock("Y_reindex_shared.dyn"):
T.reads(Y[ax2_0_0 * 32 + threadIdx_y * 2 + threadIdx_x // 16:ax2_0_0 * 32 + threadIdx_y * 2 + threadIdx_x // 16 + 25, blockIdx_x * 1024 + blockIdx_y % 8 * 128 + threadIdx_x % 16 * 8:blockIdx_x * 1024 + blockIdx_y % 8 * 128 + threadIdx_x % 16 * 8 + 8])
T.writes(Y_reindex_shared_dyn[threadIdx_y * 2 + threadIdx_x // 16:threadIdx_y * 2 + threadIdx_x // 16 + 25, threadIdx_x % 16 * 8:threadIdx_x % 16 * 8 + 8])
for ax0_ax1_fused_0 in range(4):
for ax0_ax1_fused_3 in T.vectorized(8):
Y_reindex_shared_dyn[ax0_ax1_fused_0 * 8 + threadIdx_y * 2 + threadIdx_x // 16, T.bitwise_xor(threadIdx_x % 16, threadIdx_y * 2 + threadIdx_x // 16) * 8 + ax0_ax1_fused_3] = Y[ax2_0_0 * 32 + ax0_ax1_fused_0 * 8 + threadIdx_y * 2 + threadIdx_x // 16, blockIdx_x * 1024 + blockIdx_y % 8 * 128 + threadIdx_x % 16 * 8 + ax0_ax1_fused_3]
for ax2_0_1 in T.serial(4):
with T.sblock(""):
X_reindex_shared_dyn_m16n8k8_matrixA = T.sblock_alloc_buffer((64, 8), "float16", scope="m16n8k8.matrixA")
Y_reindex_shared_dyn_m16n8k8_matrixB = T.sblock_alloc_buffer((8, 64), "float16", scope="m16n8k8.matrixB")
for ax0_0, ax1_0 in T.grid(2, 1):
with T.sblock("X_reindex_shared.dyn_m16n8k8.matrixA_o"):
T.reads(X_reindex_shared_dyn[threadIdx_y // 2 * 64 + ax0_0 * 32:threadIdx_y // 2 * 64 + ax0_0 * 32 + 32, ax2_0_1 * 8:ax2_0_1 * 8 + 8])
T.writes(X_reindex_shared_dyn_m16n8k8_matrixA[ax0_0 * 32:ax0_0 * 32 + 32, 0:8])
T.ptx.ldmatrix_legacy("float16", T.bool(False), 4, ".b16", X_reindex_shared_dyn_m16n8k8_matrixA.data, ax0_0 * 8, T.tvm_access_ptr(T.type_annotation("float16"), X_reindex_shared_dyn.data, threadIdx_y // 2 * 2048 + ax0_0 * 1024 + threadIdx_x * 32 + T.bitwise_xor(ax2_0_1, threadIdx_x % 8 // 2) * 8, 1024, 1), 0)
for ax0_0, ax1_0 in T.grid(1, 2):
with T.sblock("Y_reindex_shared.dyn_m16n8k8.matrixB_o"):
T.reads(Y_reindex_shared_dyn[ax2_0_1 * 8:ax2_0_1 * 8 + 8, threadIdx_y % 2 * 64 + ax1_0 * 32:threadIdx_y % 2 * 64 + ax1_0 * 32 + 32])
T.writes(Y_reindex_shared_dyn_m16n8k8_matrixB[0:8, ax1_0 * 32:ax1_0 * 32 + 32])
T.ptx.ldmatrix_legacy("float16", T.bool(True), 4, ".b16", Y_reindex_shared_dyn_m16n8k8_matrixB.data, ax1_0 * 8, T.tvm_access_ptr(T.type_annotation("float16"), Y_reindex_shared_dyn.data, ax2_0_1 * 1024 + threadIdx_x % 8 * 128 + T.bitwise_xor(threadIdx_y % 2 * 8 + ax1_0 * 4 + threadIdx_x // 8, threadIdx_x % 8) * 8, 1024, 1), 0)
# fmt: on
_check_primfunc_transform(before, expected)
def test_buffer_a():
# fmt: off
@T.prim_func(s_tir=True)
def before(p_A: T.handle):
A = T.match_buffer(p_A, (T.int64(128), T.int64(32)), "float16")
A_shared_dyn = T.sblock_alloc_buffer((T.int64(128), T.int64(32)), "float16", scope="shared.dyn")
A_warp = T.sblock_alloc_buffer((T.int64(4), T.int64(1), T.int64(32), T.int64(8)), "float16", scope="warp")
for threadIdx_z in T.thread_binding(T.int64(2), thread="threadIdx.z"):
for threadIdx_y in T.thread_binding(T.int64(2), thread="threadIdx.y"):
for threadIdx_x in T.thread_binding(T.int64(32), thread="threadIdx.x"):
for v0 in range(T.int64(4)):
for v1 in T.vectorized(T.int64(8)):
with T.sblock("A_reindex_shared.dyn"):
T.sblock_attr({"permuted_layout": 1})
A_shared_dyn[
v0 * T.int64(32) + threadIdx_z * T.int64(16) + threadIdx_y * T.int64(8) + threadIdx_x // T.int64(4),
threadIdx_x % T.int64(4) * T.int64(8) + v1
] = A[
(v0 * T.int64(32) + threadIdx_z * T.int64(16) + threadIdx_y * T.int64(8) + threadIdx_x // T.int64(4)) % T.int64(32),
threadIdx_x % T.int64(4) * T.int64(8) + v1
]
for v0, v1 in T.grid(T.int64(2), T.int64(4)):
with T.sblock("A_reindex_shared.dyn_warp_o"):
T.sblock_attr({"permuted_layout": 1})
with T.sblock("A_reindex_shared.dyn_warp_o"):
T.reads(A_shared_dyn[threadIdx_z * T.int64(64) + v1 * T.int64(16):threadIdx_z * T.int64(64) + v1 * T.int64(16) + T.int64(16), v0 * T.int64(16):v0 * T.int64(16) + T.int64(16)])
T.writes(A_warp[v1, T.int64(0), T.int64(0):T.int64(32), T.int64(0):T.int64(8)])
T.ptx.ldmatrix_legacy("float16", T.bool(False), 4, ".b16",
A_warp.data,
v1 * T.int64(256) + threadIdx_x * T.int64(8),
T.tvm_access_ptr(T.type_annotation("float16"),
A_shared_dyn.data,
threadIdx_z * T.int64(2048) + v1 * T.int64(512) + v0 * T.int64(16), T.int64(512),
1
),
threadIdx_x % T.int64(16) * T.int64(32) + threadIdx_x // T.int64(16) * T.int64(8)
)
@T.prim_func(s_tir=True)
def expected(A: T.Buffer((T.int64(128), T.int64(32)), "float16")):
A_shared_dyn = T.sblock_alloc_buffer((T.int64(128), T.int64(32)), "float16", scope="shared.dyn")
A_warp = T.sblock_alloc_buffer((T.int64(4), T.int64(1), T.int64(32), T.int64(8)), "float16", scope="warp")
for threadIdx_z in T.thread_binding(T.int64(2), thread="threadIdx.z"):
for threadIdx_y in T.thread_binding(T.int64(2), thread="threadIdx.y"):
for threadIdx_x in T.thread_binding(T.int64(32), thread="threadIdx.x"):
for v0 in range(T.int64(4)):
for v1 in T.vectorized(T.int64(8)):
with T.sblock("A_reindex_shared.dyn"):
T.reads(A[(v0 * T.int64(32) + threadIdx_z * T.int64(16) + threadIdx_y * T.int64(8) + threadIdx_x // T.int64(4)) % T.int64(32), threadIdx_x % T.int64(4) * T.int64(8) + v1])
T.writes(A_shared_dyn[v0 * T.int64(32) + threadIdx_z * T.int64(16) + threadIdx_y * T.int64(8) + threadIdx_x // T.int64(4), threadIdx_x % T.int64(4) * T.int64(8) + v1])
A_shared_dyn[v0 * T.int64(32) + threadIdx_z * T.int64(16) + threadIdx_y * T.int64(8) + threadIdx_x // T.int64(4), T.bitwise_xor(threadIdx_x % T.int64(4), threadIdx_x // T.int64(8)) * T.int64(8) + v1] = A[(v0 * T.int64(32) + threadIdx_z * T.int64(16) + threadIdx_y * T.int64(8) + threadIdx_x // T.int64(4)) % T.int64(32), threadIdx_x % T.int64(4) * T.int64(8) + v1]
for v0, v1 in T.grid(T.int64(2), T.int64(4)):
with T.sblock("A_reindex_shared.dyn_warp_o"):
T.reads(A_shared_dyn[threadIdx_z * T.int64(64) + v1 * T.int64(16):threadIdx_z * T.int64(64) + v1 * T.int64(16) + T.int64(16), v0 * T.int64(16):v0 * T.int64(16) + T.int64(16)])
T.writes(A_warp[v1, T.int64(0), T.int64(0):T.int64(32), T.int64(0):T.int64(8)])
with T.sblock("A_reindex_shared.dyn_warp_o"):
T.reads(A_shared_dyn[threadIdx_z * T.int64(64) + v1 * T.int64(16):threadIdx_z * T.int64(64) + v1 * T.int64(16) + T.int64(16), v0 * T.int64(16):v0 * T.int64(16) + T.int64(16)])
T.writes(A_warp[v1, T.int64(0), T.int64(0):T.int64(32), T.int64(0):T.int64(8)])
T.ptx.ldmatrix_legacy("float16", T.bool(False), 4, ".b16", A_warp.data, v1 * T.int64(256) + threadIdx_x * T.int64(8), T.tvm_access_ptr(T.type_annotation("float16"), A_shared_dyn.data, threadIdx_z * T.int64(2048) + v1 * T.int64(512) + threadIdx_x % T.int64(16) * T.int64(32) + T.bitwise_xor(v0 * T.int64(2) + threadIdx_x // T.int64(16), threadIdx_x % T.int64(8) // T.int64(2)) * T.int64(8), T.int64(512), 1), T.int64(0))
# fmt: on
_check_primfunc_transform(before, expected)
def test_buffer_b():
# fmt: off
@T.prim_func(s_tir=True)
def before(B: T.Buffer((T.int64(128), T.int64(32)), "float16")):
B_shared_dyn = T.sblock_alloc_buffer((T.int64(128), T.int64(32)), "float16", scope="shared.dyn")
for threadIdx_z in T.thread_binding(T.int64(2), thread="threadIdx.z"):
for threadIdx_y in T.thread_binding(T.int64(2), thread="threadIdx.y"):
for threadIdx_x in T.thread_binding(T.int64(32), thread="threadIdx.x"):
for v0 in range(T.int64(4)):
for v1 in T.vectorized(T.int64(8)):
with T.sblock("B_reindex_shared.dyn"):
T.sblock_attr({"permuted_layout": 1})
B_shared_dyn[v0 * T.int64(32) + threadIdx_z * T.int64(16) + threadIdx_y * T.int64(8) + threadIdx_x // T.int64(4), threadIdx_x % T.int64(4) * T.int64(8) + v1] = B[v0 * T.int64(32) + threadIdx_z * T.int64(16) + threadIdx_y * T.int64(8) + threadIdx_x // T.int64(4), threadIdx_x % T.int64(4) * T.int64(8) + v1]
for v0 in range(T.int64(2)):
with T.sblock(""):
B_warp = T.sblock_alloc_buffer((T.int64(4), T.int64(1), T.int64(32), T.int64(8)), "float16", scope="warp")
for v1 in range(T.int64(4)):
with T.sblock("B_reindex_shared.dyn_warp_o"):
T.sblock_attr({"permuted_layout": 1})
with T.sblock("B_reindex_shared.dyn_warp_o"):
T.reads(B_shared_dyn[threadIdx_y * T.int64(64) + v1 * T.int64(16):threadIdx_y * T.int64(64) + v1 * T.int64(16) + T.int64(16), v0 * T.int64(16):v0 * T.int64(16) + T.int64(16)])
T.writes(B_warp[v1, T.int64(0), T.int64(0):T.int64(32), T.int64(0):T.int64(8)])
T.ptx.ldmatrix_legacy("float16", T.bool(False), 4, ".b16", B_warp.data, v1 * T.int64(256) + threadIdx_x * T.int64(8), T.tvm_access_ptr(T.type_annotation("float16"), B_shared_dyn.data, threadIdx_y * T.int64(2048) + v1 * T.int64(512) + v0 * T.int64(16), T.int64(512), 1), threadIdx_x // T.int64(16) * T.int64(256) + threadIdx_x % T.int64(8) * T.int64(32) + threadIdx_x % T.int64(16) // T.int64(8) * T.int64(8))
@T.prim_func(s_tir=True)
def expected(B: T.Buffer((T.int64(128), T.int64(32)), "float16")):
B_shared_dyn = T.sblock_alloc_buffer((T.int64(128), T.int64(32)), "float16", scope="shared.dyn")
for threadIdx_z in T.thread_binding(T.int64(2), thread="threadIdx.z"):
for threadIdx_y in T.thread_binding(T.int64(2), thread="threadIdx.y"):
for threadIdx_x in T.thread_binding(T.int64(32), thread="threadIdx.x"):
for v0 in range(T.int64(4)):
for v1 in T.vectorized(T.int64(8)):
with T.sblock("B_reindex_shared.dyn"):
T.reads(B[v0 * T.int64(32) + threadIdx_z * T.int64(16) + threadIdx_y * T.int64(8) + threadIdx_x // T.int64(4), threadIdx_x % T.int64(4) * T.int64(8) + v1])
T.writes(B_shared_dyn[v0 * T.int64(32) + threadIdx_z * T.int64(16) + threadIdx_y * T.int64(8) + threadIdx_x // T.int64(4), threadIdx_x % T.int64(4) * T.int64(8) + v1])
B_shared_dyn[v0 * T.int64(32) + threadIdx_z * T.int64(16) + threadIdx_y * T.int64(8) + threadIdx_x // T.int64(4), T.bitwise_xor(threadIdx_x % T.int64(4), threadIdx_x // T.int64(8)) * T.int64(8) + v1] = B[v0 * T.int64(32) + threadIdx_z * T.int64(16) + threadIdx_y * T.int64(8) + threadIdx_x // T.int64(4), threadIdx_x % T.int64(4) * T.int64(8) + v1]
for v0 in range(T.int64(2)):
with T.sblock(""):
B_warp = T.sblock_alloc_buffer((T.int64(4), T.int64(1), T.int64(32), T.int64(8)), "float16", scope="warp")
for v1 in range(T.int64(4)):
with T.sblock("B_reindex_shared.dyn_warp_o"):
T.reads(B_shared_dyn[threadIdx_y * T.int64(64) + v1 * T.int64(16):threadIdx_y * T.int64(64) + v1 * T.int64(16) + T.int64(16), v0 * T.int64(16):v0 * T.int64(16) + T.int64(16)])
T.writes(B_warp[v1, T.int64(0), T.int64(0):T.int64(32), T.int64(0):T.int64(8)])
with T.sblock("B_reindex_shared.dyn_warp_o"):
T.reads(B_shared_dyn[threadIdx_y * T.int64(64) + v1 * T.int64(16):threadIdx_y * T.int64(64) + v1 * T.int64(16) + T.int64(16), v0 * T.int64(16):v0 * T.int64(16) + T.int64(16)])
T.writes(B_warp[v1, T.int64(0), T.int64(0):T.int64(32), T.int64(0):T.int64(8)])
T.ptx.ldmatrix_legacy("float16", T.bool(False), 4, ".b16", B_warp.data, v1 * T.int64(256) + threadIdx_x * T.int64(8), T.tvm_access_ptr(T.type_annotation("float16"), B_shared_dyn.data, threadIdx_y * T.int64(2048) + v1 * T.int64(512) + threadIdx_x // T.int64(16) * T.int64(256) + threadIdx_x % T.int64(8) * T.int64(32) + T.bitwise_xor(v0 * T.int64(2) + threadIdx_x % T.int64(16) // T.int64(8), threadIdx_x % T.int64(8) // T.int64(2)) * T.int64(8), T.int64(512), 1), T.int64(0))
# fmt: on
_check_primfunc_transform(before, expected)
def test_buffer_c_fp32():
# fmt: off
@T.prim_func(s_tir=True)
def before(p_O: T.handle):
O = T.match_buffer(p_O, (T.int64(128), T.int64(128)), "float16")
O_shared_dyn = T.sblock_alloc_buffer((T.int64(128), T.int64(128)), scope="shared.dyn")
O_warp = T.sblock_alloc_buffer((T.int64(4), T.int64(4), T.int64(32), T.int64(8)), scope="warp")
for threadIdx_z in T.thread_binding(T.int64(2), thread="threadIdx.z"):
for threadIdx_y in T.thread_binding(T.int64(2), thread="threadIdx.y"):
for threadIdx_x in T.thread_binding(T.int64(32), thread="threadIdx.x"):
for v0, v1 in T.grid(T.int64(4), T.int64(4)):
with T.sblock("O.dyn_warp_o"):
T.sblock_attr({"permuted_layout": 1})
with T.sblock("O.dyn_warp_o"):
for local_id in range(T.int64(8)):
O_shared_dyn[threadIdx_z * T.int64(64) + v0 * T.int64(16) + local_id % T.int64(4) // T.int64(2) * T.int64(8) + threadIdx_x // T.int64(4), threadIdx_y * T.int64(64) + v1 * T.int64(16) + local_id // T.int64(4) * T.int64(8) + threadIdx_x % T.int64(4) * T.int64(2) + local_id % T.int64(2)] = O_warp[v0, v1, threadIdx_x, local_id]
for v0 in range(T.int64(16)):
for v1 in T.vectorized(T.int64(8)):
with T.sblock("O.dyn"):
T.sblock_attr({"permuted_layout": 1})
O[v0 * T.int64(8) + threadIdx_z * T.int64(4) + threadIdx_y * T.int64(2) + threadIdx_x // T.int64(16), threadIdx_x % T.int64(16) * T.int64(8) + v1] = T.Cast("float16", O_shared_dyn[v0 * T.int64(8) + threadIdx_z * T.int64(4) + threadIdx_y * T.int64(2) + threadIdx_x // T.int64(16), threadIdx_x % T.int64(16) * T.int64(8) + v1])
@T.prim_func(s_tir=True)
def expected(O: T.Buffer((T.int64(128), T.int64(128)), "float16")):
# with T.sblock("root"):
O_shared_dyn = T.sblock_alloc_buffer((T.int64(128), T.int64(128)), scope="shared.dyn")
O_warp = T.sblock_alloc_buffer((T.int64(4), T.int64(4), T.int64(32), T.int64(8)), scope="warp")
for threadIdx_z in T.thread_binding(T.int64(2), thread="threadIdx.z"):
for threadIdx_y in T.thread_binding(T.int64(2), thread="threadIdx.y"):
for threadIdx_x in T.thread_binding(T.int64(32), thread="threadIdx.x"):
for v0, v1 in T.grid(T.int64(4), T.int64(4)):
with T.sblock("O.dyn_warp_o"):
T.reads(O_warp[v0, v1, threadIdx_x, T.int64(0):T.int64(8)])
T.writes(O_shared_dyn[threadIdx_z * T.int64(64) + v0 * T.int64(16) + threadIdx_x // T.int64(4):threadIdx_z * T.int64(64) + v0 * T.int64(16) + threadIdx_x // T.int64(4) + T.int64(9), threadIdx_y * T.int64(64) + v1 * T.int64(16) + threadIdx_x % T.int64(4) * T.int64(2):threadIdx_y * T.int64(64) + v1 * T.int64(16) + threadIdx_x % T.int64(4) * T.int64(2) + T.int64(10)])
with T.sblock("O.dyn_warp_o"):
T.reads(O_warp[v0, v1, threadIdx_x, T.int64(0):T.int64(8)])
T.writes(O_shared_dyn[threadIdx_z * T.int64(64) + v0 * T.int64(16) + threadIdx_x // T.int64(4):threadIdx_z * T.int64(64) + v0 * T.int64(16) + threadIdx_x // T.int64(4) + T.int64(9), threadIdx_y * T.int64(64) + v1 * T.int64(16) + threadIdx_x % T.int64(4) * T.int64(2):threadIdx_y * T.int64(64) + v1 * T.int64(16) + threadIdx_x % T.int64(4) * T.int64(2) + T.int64(10)])
for local_id in range(T.int64(8)):
O_shared_dyn[threadIdx_z * T.int64(64) + v0 * T.int64(16) + local_id % T.int64(4) // T.int64(2) * T.int64(8) + threadIdx_x // T.int64(4), T.bitwise_xor(threadIdx_y * T.int64(8) + v1 * T.int64(2) + local_id // T.int64(4), threadIdx_x // T.int64(4)) * T.int64(8) + threadIdx_x % T.int64(4) * T.int64(2) + local_id % T.int64(2)] = O_warp[v0, v1, threadIdx_x, local_id]
for v0 in range(T.int64(16)):
for v1 in T.vectorized(T.int64(8)):
with T.sblock("O.dyn"):
T.reads(O_shared_dyn[v0 * T.int64(8) + threadIdx_z * T.int64(4) + threadIdx_y * T.int64(2) + threadIdx_x // T.int64(16), threadIdx_x % T.int64(16) * T.int64(8) + v1])
T.writes(O[v0 * T.int64(8) + threadIdx_z * T.int64(4) + threadIdx_y * T.int64(2) + threadIdx_x // T.int64(16), threadIdx_x % T.int64(16) * T.int64(8) + v1])
O[v0 * T.int64(8) + threadIdx_z * T.int64(4) + threadIdx_y * T.int64(2) + threadIdx_x // T.int64(16), threadIdx_x % T.int64(16) * T.int64(8) + v1] = T.Cast("float16", O_shared_dyn[v0 * T.int64(8) + threadIdx_z * T.int64(4) + threadIdx_y * T.int64(2) + threadIdx_x // T.int64(16), T.bitwise_xor(threadIdx_x % T.int64(16), threadIdx_z * T.int64(4) + threadIdx_y * T.int64(2) + threadIdx_x // T.int64(16)) * T.int64(8) + v1])
# fmt: on
_check_primfunc_transform(before, expected)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,943 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ruff: noqa: E501, F401, F841
import numpy as np
import pytest
import tvm_ffi
import tvm
import tvm.testing
from tvm import s_tir
from tvm.script import ir as I
from tvm.script import tirx as T
from tvm.testing import env
def test_cp_async_raw_dtype_round_trips():
# The raw cp.async form emitted by InjectPTXAsyncCopy carries the element
# dtype in Call.dtype and must survive a TVMScript print -> parse round-trip
# (it prints dtype-first via tirx.ptx.cp_async_raw). Guards the regression
# where the element dtype was dropped after the flat op was phased out.
@T.prim_func
def f(A: T.Buffer((128,), "float16"), B: T.Buffer((128,), "float16")):
T.func_attr({"global_symbol": "f"})
for i in T.serial(8):
T.ptx.cp_async("float16", B.data, i * 16, A.data, i * 16, 16)
reparsed = tvm.script.from_source(f.script())
tvm.ir.assert_structural_equal(f, reparsed)
def count_cp_async(stmt):
num_alloc = [0]
def verify(n):
if isinstance(n, tvm.ir.Call) and n.op.name == "tirx.ptx.cp_async_raw":
num_alloc[0] += 1
tvm.tirx.stmt_functor.post_order_visit(stmt, verify)
return num_alloc[0]
def generate_global_to_shared_vectorized_copy(dtype, vector_size):
num_iters = 128 // vector_size
vector_size_expr = tvm.runtime.convert(vector_size)
@T.prim_func(s_tir=True)
def ptx_global_to_shared_copy(
A: T.Buffer((32, 128), dtype), B: T.Buffer((32, 128), dtype)
) -> None:
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
bx = T.env_thread("blockIdx.x")
tx = T.env_thread("threadIdx.x")
T.launch_thread(bx, 1)
T.launch_thread(tx, 32)
with T.sblock():
A_shared = T.sblock_alloc_buffer([32, 128], dtype, scope="shared")
T.reads(A[0:32, 0:128])
T.writes(B[0:32, 0:128])
T.attr("default", "async_scope", 1)
for i in T.serial(num_iters):
for j in T.vectorized(vector_size):
A_shared[tx, i * vector_size_expr + j] = A[tx, i * vector_size_expr + j]
T.evaluate(T.ptx.cp_async.commit_group(dtype=""))
T.evaluate(T.ptx.cp_async.wait_group(0, dtype=""))
for i in range(128):
B[tx, i] = A_shared[tx, i]
return ptx_global_to_shared_copy
@T.prim_func(s_tir=True)
def ptx_global_to_shared_copy_fp32x1(
A: T.Buffer((32, 128), "float32"), B: T.Buffer((32, 128), "float32")
) -> None:
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
bx = T.env_thread("blockIdx.x")
tx = T.env_thread("threadIdx.x")
T.launch_thread(bx, 1)
T.launch_thread(tx, 32)
with T.sblock():
A_shared = T.sblock_alloc_buffer([32, 128], "float32", scope="shared")
T.reads(A[0:32, 0:128])
T.writes(B[0:32, 0:128])
T.attr("default", "async_scope", 1)
for i in T.serial(128):
A_shared[tx, i] = A[tx, i]
T.evaluate(T.ptx.cp_async.commit_group(dtype=""))
T.evaluate(T.ptx.cp_async.wait_group(0, dtype=""))
for i in range(128):
B[tx, i] = A_shared[tx, i]
@T.prim_func(s_tir=True)
def ptx_global_to_shared_dyn_copy_fp16x8(
A: T.Buffer((32, 128), "float16"),
B: T.Buffer((32, 128), "float16"),
C: T.Buffer((32, 128), "float16"),
) -> None:
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
bx = T.env_thread("blockIdx.x")
tx = T.env_thread("threadIdx.x")
T.launch_thread(bx, 1)
T.launch_thread(tx, 32)
with T.sblock():
A_shared = T.sblock_alloc_buffer([32, 128], "float16", scope="shared.dyn")
B_shared = T.sblock_alloc_buffer([32, 128], "float16", scope="shared.dyn")
T.reads(A[0:32, 0:128], B[0:32, 0:128])
T.writes(C[0:32, 0:128])
T.attr("default", "async_scope", 1)
for i in T.serial(16):
for j in T.vectorized(8):
A_shared[tx, i * 8 + j] = A[tx, i * 8 + j]
B_shared[tx, i * 8 + j] = B[tx, i * 8 + j]
T.evaluate(T.ptx.cp_async.commit_group(dtype=""))
T.evaluate(T.ptx.cp_async.wait_group(0, dtype=""))
for i in range(128):
C[tx, i] = A_shared[tx, i] + B_shared[tx, i]
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda(), reason="need cuda")
def test_inject_async_copy():
for dtype, vec_size in [("float16", 8), ("float16", 4), ("float32", 4), ("float32", 1)]:
if vec_size == 1:
f = ptx_global_to_shared_copy_fp32x1
else:
f = generate_global_to_shared_vectorized_copy(dtype, vec_size)
mod = tvm.IRModule.from_expr(f)
mod = tvm.s_tir.transform.LowerOpaqueBlock()(mod)
mod = tvm.tirx.transform.FlattenBuffer()(mod)
if vec_size > 1:
mod = tvm.tirx.transform.VectorizeLoop()(mod)
mod = tvm.s_tir.transform.InjectPTXAsyncCopy()(mod)
assert count_cp_async(mod["main"].body) == 1
if not tvm.testing.is_ampere_or_newer():
continue
with tvm.transform.PassContext(config={"tirx.use_async_copy": 1}):
mod = tvm.compile(tvm.IRModule.from_expr(f), target="cuda")
A_np = np.random.rand(32, 128).astype(dtype)
B_np = np.zeros((32, 128)).astype(dtype)
def run_and_check():
dev = tvm.cuda(0)
A_nd = tvm.runtime.tensor(A_np, device=dev)
B_nd = tvm.runtime.tensor(B_np, device=dev)
mod(A_nd, B_nd)
tvm.testing.assert_allclose(B_nd.numpy(), A_np)
tvm.testing.run_with_gpu_lock(run_and_check)
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda(), reason="need cuda")
def test_inject_async_copy_shared_dyn():
f = ptx_global_to_shared_dyn_copy_fp16x8
mod = tvm.IRModule.from_expr(f)
mod = tvm.s_tir.transform.LowerOpaqueBlock()(mod)
mod = tvm.tirx.transform.FlattenBuffer()(mod)
mod = tvm.tirx.transform.VectorizeLoop()(mod)
mod = tvm.s_tir.transform.MergeSharedMemoryAllocations()(mod)
mod = tvm.s_tir.transform.InjectPTXAsyncCopy()(mod)
assert count_cp_async(mod["main"].body) == 2
if not tvm.testing.is_ampere_or_newer():
return
with tvm.transform.PassContext(config={"tirx.use_async_copy": 1}):
mod = tvm.compile(tvm.IRModule.from_expr(f), target="cuda")
A_np = np.random.rand(32, 128).astype("float16")
B_np = np.random.rand(32, 128).astype("float16")
C_np = np.zeros((32, 128)).astype("float16")
def run_and_check():
dev = tvm.cuda(0)
A_nd = tvm.runtime.tensor(A_np, device=dev)
B_nd = tvm.runtime.tensor(B_np, device=dev)
C_nd = tvm.runtime.tensor(C_np, device=dev)
mod(A_nd, B_nd, C_nd)
tvm.testing.assert_allclose(C_nd.numpy(), A_np + B_np)
tvm.testing.run_with_gpu_lock(run_and_check)
# Note: the test_inject_async_copy_barrier case (and its prim_func helper)
# was removed — it relied on the indexed barrier API
# (`create_barriers`, `init_barrier_thread_count`, `arrive_barrier`,
# `wait_barrier`) which fork does not provide; fork uses the
# `ptx_mbarrier_*` family instead.
expected_cuda_script = r"""#include <cuda.h>
#endif
#if (((__CUDACC_VER_MAJOR__ == 11) && (__CUDACC_VER_MINOR__ >= 4)) || \
(__CUDACC_VER_MAJOR__ > 11))
#define TVM_ENABLE_L2_PREFETCH 1
#else
#define TVM_ENABLE_L2_PREFETCH 0
#endif
#ifdef _WIN32
using uint = unsigned int;
using uchar = unsigned char;
using ushort = unsigned short;
using int64_t = long long;
using uint64_t = unsigned long long;
#else
#define uint unsigned int
#define uchar unsigned char
#define ushort unsigned short
#endif
__forceinline__ __device__ void tvm_builtin_ptx_cp_async_wait_group_0() {
asm volatile("cp.async.wait_group 0;");
}
__forceinline__ __device__ void tvm_builtin_ptx_cp_async_wait_group_1() {
asm volatile("cp.async.wait_group 1;");
}
__forceinline__ __device__ void tvm_builtin_ptx_cp_async_wait_group_2() {
asm volatile("cp.async.wait_group 2;");
}
__forceinline__ __device__ void tvm_builtin_ptx_cp_async_wait_group_5() {
asm volatile("cp.async.wait_group 5;");
}
__forceinline__ __device__ void ptx_cp_async_legacy_pred_ca_4_4_4(void* dst, int dst_off, void* src, int src_off, int predicate) {
uint8_t* dst_p = (uint8_t*)dst + dst_off * 4;
uint8_t* src_p = (uint8_t*)src + src_off * 4;
unsigned int dst_addr = __cvta_generic_to_shared(dst_p);
__asm__ __volatile__(
"{\n"
" .reg .pred p;\n"
" setp.eq.u32 p, %3, 1;\n"
" @p cp.async.ca.shared.global [%0], [%1], %2;\n"
" @!p st.shared.u32 [%0], {%4};\n"
"}\n"
:: "r"(dst_addr), "l"(src_p), "n"(4), "r"(predicate), "r"(0)
);
}
__forceinline__ __device__ void ptx_cp_async_legacy_ca_4_4_4(void* dst, int dst_off, void* src, int src_off) {
uint8_t* dst_p = (uint8_t*)dst + dst_off * 4;
uint8_t* src_p = (uint8_t*)src + src_off * 4;
unsigned int dst_addr = __cvta_generic_to_shared(dst_p);
asm volatile("cp.async.ca.shared.global [%0], [%1], %2;"
:: "r"(dst_addr), "l"(src_p), "n"(4));
}
__forceinline__ __device__ void tvm_builtin_ptx_cp_async_commit_group() {
asm volatile("cp.async.commit_group;");
}
extern "C" __global__ void __launch_bounds__(16) main_kernel(float* __restrict__ A_ptr, float* __restrict__ B_ptr, float* __restrict__ C_ptr);
extern "C" __global__ void __launch_bounds__(16) main_kernel(float* __restrict__ A_ptr, float* __restrict__ B_ptr, float* __restrict__ C_ptr) {
__shared__ alignas(64) float A_shared_ptr[64];
__shared__ alignas(64) float B_shared_ptr[64];
A_shared_ptr[((int)threadIdx.x)] = 0x0p+0f/*0.000000e+00*/;
B_shared_ptr[((int)threadIdx.x)] = 0x0p+0f/*0.000000e+00*/;
tvm_builtin_ptx_cp_async_commit_group();
int cse_v1 = (((int)threadIdx.x) * 14);
int cse_v2 = (((int)threadIdx.x) + 16);
ptx_cp_async_legacy_ca_4_4_4(A_shared_ptr, (((int)threadIdx.x) + 16), A_ptr, (((int)threadIdx.x) * 14));
ptx_cp_async_legacy_ca_4_4_4(B_shared_ptr, (((int)threadIdx.x) + 16), B_ptr, (((int)threadIdx.x) * 14));
tvm_builtin_ptx_cp_async_commit_group();
int cse_v3 = (((int)threadIdx.x) + 32);
int cse_v6 = ((((int)threadIdx.x) * 14) + 1);
ptx_cp_async_legacy_ca_4_4_4(A_shared_ptr, (((int)threadIdx.x) + 32), A_ptr, ((((int)threadIdx.x) * 14) + 1));
ptx_cp_async_legacy_ca_4_4_4(B_shared_ptr, (((int)threadIdx.x) + 32), B_ptr, ((((int)threadIdx.x) * 14) + 1));
tvm_builtin_ptx_cp_async_commit_group();
int cse_v4 = (((int)threadIdx.x) * 16);
for (int i = 0; i < 13; ++i) {
int cse_v7 = (((((int)threadIdx.x) * 14) + i) + 2);
int cse_v9 = ((((i + 3) & 3) * 16) + ((int)threadIdx.x));
ptx_cp_async_legacy_pred_ca_4_4_4(A_shared_ptr, ((((i + 3) & 3) * 16) + ((int)threadIdx.x)), A_ptr, (((((int)threadIdx.x) * 14) + i) + 2), (i < 12));
tvm_builtin_ptx_cp_async_commit_group();
tvm_builtin_ptx_cp_async_wait_group_5();
__syncthreads();
int cse_v8 = (((i & 3) * 16) + ((int)threadIdx.x));
C_ptr[((((int)threadIdx.x) * 16) + i)] = (A_shared_ptr[(((i & 3) * 16) + ((int)threadIdx.x))] + B_shared_ptr[(((i & 3) * 16) + ((int)threadIdx.x))]);
__syncthreads();
ptx_cp_async_legacy_pred_ca_4_4_4(B_shared_ptr, ((((i + 3) & 3) * 16) + ((int)threadIdx.x)), B_ptr, (((((int)threadIdx.x) * 14) + i) + 2), (i < 12));
tvm_builtin_ptx_cp_async_commit_group();
}
tvm_builtin_ptx_cp_async_wait_group_2();
__syncthreads();
C_ptr[((((int)threadIdx.x) * 16) + 13)] = (A_shared_ptr[(((int)threadIdx.x) + 16)] + B_shared_ptr[(((int)threadIdx.x) + 16)]);
tvm_builtin_ptx_cp_async_wait_group_1();
__syncthreads();
C_ptr[((((int)threadIdx.x) * 16) + 14)] = (A_shared_ptr[(((int)threadIdx.x) + 32)] + B_shared_ptr[(((int)threadIdx.x) + 32)]);
tvm_builtin_ptx_cp_async_wait_group_0();
__syncthreads();
int cse_v5 = (((int)threadIdx.x) + 48);
C_ptr[((((int)threadIdx.x) * 16) + 15)] = (A_shared_ptr[(((int)threadIdx.x) + 48)] + B_shared_ptr[(((int)threadIdx.x) + 48)]);
}
"""
@pytest.fixture
def postproc_if_missing_async_support():
arch = tvm.support.nvcc.get_target_compute_version()
major, _ = tvm.support.nvcc.parse_compute_version(arch)
support_async = major >= 8
func_name = "tvm_callback_cuda_postproc"
prev_postproc = tvm.get_global_func(func_name, allow_missing=True)
# Store the generated code prior to the post-processing. This
# way, even though the generated code doesn't compile on platforms
# that do not support async, the comparison against an expected
# output can still be performed. We cannot use
# `mod.inspect_source()`, as that contains the source after all
# post-processing.
original_code = None
def get_original_code():
nonlocal original_code
return original_code
@tvm.register_global_func(func_name, override=True)
def tvm_callback_cuda_postproc(code, _):
nonlocal original_code
original_code = code
if support_async:
return code
else:
ret = []
for line in code.split("\n"):
ret.append(line)
ret.append("\n")
if line.startswith('extern "C" __global__') and line.endswith("{"):
break
ret.append("}")
return "".join(ret)
yield get_original_code
# Restore previous postproc func to avoid impacting other tests
if prev_postproc is None:
tvm_ffi.registry.remove_global_func(func_name)
else:
tvm.register_global_func(func_name, prev_postproc, override=True)
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda(), reason="need cuda")
def test_cp_async_in_if_then_else(postproc_if_missing_async_support):
@T.prim_func(s_tir=True)
def simple_compute(
A: T.Buffer((16, 14), "float32"),
B: T.Buffer((16, 14), "float32"),
C: T.Buffer((16, 16), "float32"),
):
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
for tx in T.thread_binding(0, 16, thread="threadIdx.x"):
for i in T.serial(
16,
annotations={
"software_pipeline_stage": [0, 0, 3],
"software_pipeline_order": [0, 2, 1],
"software_pipeline_async_stages": [0],
},
):
with T.sblock("compute"):
T.reads(A[tx, i])
T.writes(C[tx, i])
A_shared = T.sblock_alloc_buffer((16, 1), dtype="float32", scope="shared")
B_shared = T.sblock_alloc_buffer((16, 1), dtype="float32", scope="shared")
with T.sblock():
T.reads(A[tx, i])
T.writes(A_shared[tx, 0])
A_shared[tx, 0] = T.if_then_else(
1 <= i and i < 15, A[tx, i - 1], T.float32(0), dtype="float32"
)
with T.sblock():
T.reads(B[tx, i])
T.writes(B_shared[tx, 0])
B_shared[tx, 0] = T.if_then_else(
1 <= i and i < 15, B[tx, i - 1], T.float32(0), dtype="float32"
)
with T.sblock():
T.reads(A_shared[tx, 0], B_shared[tx, 0])
T.writes(C[tx, i])
C[tx, i] = A_shared[tx, 0] + B_shared[tx, 0]
mod = tvm.IRModule.from_expr(simple_compute)
with tvm.transform.PassContext(config={"tirx.use_async_copy": 1}):
tvm.compile(mod, target="cuda")
generated_code = postproc_if_missing_async_support()
print(generated_code)
# Fork emits an NVRTC-aware preamble (`#ifdef __CUDACC_RTC__ ... #else ...`
# block) before the apache-style `#include <cuda.h>`; the body after that
# block matches the expected snippet, so compare from the kernel-body
# onwards instead of byte-for-byte from the start.
marker = "#include <cuda.h>"
expected_body = expected_cuda_script[expected_cuda_script.index(marker) :]
actual_body = generated_code[generated_code.index(marker) :]
assert actual_body == expected_body
@pytest.mark.skip(
reason="This test fails due to an ordering issue with MergeSharedMemoryAllocations "
"in device_driver_api.cc. However, fixing this causes failures in MLC. "
"This bug should be addressed. See discussion in https://github.com/apache/tvm/pull/16769 "
"and https://github.com/apache/tvm/pull/16569#issuecomment-1992720448"
)
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda(), reason="need cuda")
def test_vectorize_cp_async_in_if_then_else(postproc_if_missing_async_support):
@T.prim_func(s_tir=True)
def complex_compute(
A: T.Buffer((2, 16, 16, 1280), "float16"),
W: T.Buffer((1280, 3, 3, 1280), "float16"),
Conv: T.Buffer((512, 1280), "float16"),
):
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
# with T.sblock("root"):
data_im2col_reindex_shared_dyn = T.sblock_alloc_buffer(
(512, 11520), "float16", scope="shared.dyn"
)
data_im2col_reindex_shared_dyn_wmma_matrix_a = T.sblock_alloc_buffer(
(512, 11520), "float16", scope="wmma.matrix_a"
)
weight_flatten_reindex_shared_dyn = T.sblock_alloc_buffer(
(1280, 11520), "float16", scope="shared.dyn"
)
weight_flatten_reindex_shared_dyn_wmma_matrix_b = T.sblock_alloc_buffer(
(1280, 11520), "float16", scope="wmma.matrix_b"
)
Conv_reindex_wmma_accumulator = T.sblock_alloc_buffer(
(512, 1280), "float16", scope="wmma.accumulator"
)
for x_0_0 in T.thread_binding(8, thread="blockIdx.y"):
for y_0_0 in T.thread_binding(20, thread="blockIdx.x"):
for x_0_1 in T.thread_binding(2, thread="threadIdx.y"):
for y_0_1 in T.thread_binding(2, thread="threadIdx.z"):
for x_0_2_init, y_0_2_init in T.grid(2, 2):
with T.sblock("Conv_init_o"):
v_x_o = T.axis.spatial(32, x_0_0 * 4 + x_0_1 * 2 + x_0_2_init)
v_y_o = T.axis.spatial(80, y_0_0 * 4 + y_0_1 * 2 + y_0_2_init)
T.reads()
T.writes(
Conv_reindex_wmma_accumulator[
v_x_o * 16 : v_x_o * 16 + 16, v_y_o * 16 : v_y_o * 16 + 16
]
)
C_s0 = T.int32()
C_s1 = T.int32()
C = T.match_buffer(
Conv_reindex_wmma_accumulator[
v_x_o * 16 : v_x_o * 16 + 16, v_y_o * 16 : v_y_o * 16 + 16
],
(16, 16),
"float16",
strides=(C_s0, C_s1),
scope="wmma.accumulator",
offset_factor=16,
)
T.tvm_fill_fragment(
C.data,
16,
16,
16,
C.elem_offset // C_s0 // 16 * (C_s0 // 16)
+ C.elem_offset % C_s0 // 16,
T.float32(0),
)
for k_0_0 in T.serial(
180,
annotations={
"software_pipeline_stage": [0, 0, 1],
"software_pipeline_order": [0, 1, 2],
"software_pipeline_async_stages": [0],
},
):
for ax0_ax1_0_fused_0 in range(4):
for ax0_ax1_0_fused_1 in T.thread_binding(2, thread="threadIdx.z"):
for ax0_ax1_0_fused_2 in T.thread_binding(
2, thread="threadIdx.y"
):
for ax0_ax1_0_fused_3 in T.thread_binding(
32, thread="threadIdx.x"
):
with T.sblock("data_im2col_reindex_shared.dyn_o"):
v0 = T.axis.spatial(
512,
x_0_0 * 64
+ (
ax0_ax1_0_fused_0 * 128
+ ax0_ax1_0_fused_1 * 64
+ ax0_ax1_0_fused_2 * 32
+ ax0_ax1_0_fused_3
)
// 8,
)
v1_o = T.axis.spatial(
1440,
k_0_0 * 8
+ (
ax0_ax1_0_fused_0 * 128
+ ax0_ax1_0_fused_1 * 64
+ ax0_ax1_0_fused_2 * 32
+ ax0_ax1_0_fused_3
)
% 8,
)
T.reads(
A[
v0 // 256,
v1_o // 480 + v0 % 256 // 16 - 1,
v1_o % 480 // 160 + v0 % 16 - 1,
v1_o % 160 * 8 : v1_o % 160 * 8 + 8,
]
)
T.writes(
data_im2col_reindex_shared_dyn[
v0, v1_o * 8 : v1_o * 8 + 8
]
)
for ax1_1 in T.vectorized(8):
with T.sblock("data_im2col_reindex_shared.dyn"):
v1_i = T.axis.spatial(8, ax1_1)
T.reads(
A[
v0 // 256,
v1_o // 480 + v0 % 256 // 16 - 1,
v1_o % 480 // 160 + v0 % 16 - 1,
v1_o % 160 * 8 + v1_i,
]
)
T.writes(
data_im2col_reindex_shared_dyn[
v0, v1_o * 8 + v1_i
]
)
T.sblock_attr(
{"buffer_dim_align": [[0, 0, 32, 8]]}
)
data_im2col_reindex_shared_dyn[
v0, v1_o * 8 + v1_i
] = T.if_then_else(
1 <= v1_o // 480 + v0 % 256 // 16
and v1_o // 480 + v0 % 256 // 16 < 17
and 1 <= v1_o % 480 // 160 + v0 % 16
and v1_o % 480 // 160 + v0 % 16 < 17,
A[
v0 // 256,
v1_o // 480 + v0 % 256 // 16 - 1,
v1_o % 480 // 160 + v0 % 16 - 1,
v1_o % 160 * 8 + v1_i,
],
T.float16(0),
)
for ax0_ax1_0_fused_0 in range(4):
for ax0_ax1_0_fused_1 in T.thread_binding(2, thread="threadIdx.z"):
for ax0_ax1_0_fused_2 in T.thread_binding(
2, thread="threadIdx.y"
):
for ax0_ax1_0_fused_3 in T.thread_binding(
32, thread="threadIdx.x"
):
for ax1_1 in T.vectorized(8):
with T.sblock("weight_flatten_reindex_shared.dyn"):
v0 = T.axis.spatial(
1280,
y_0_0 * 64
+ (
ax0_ax1_0_fused_0 * 128
+ ax0_ax1_0_fused_1 * 64
+ ax0_ax1_0_fused_2 * 32
+ ax0_ax1_0_fused_3
)
// 8,
)
v1 = T.axis.spatial(
11520,
k_0_0 * 64
+ (
ax0_ax1_0_fused_0 * 128
+ ax0_ax1_0_fused_1 * 64
+ ax0_ax1_0_fused_2 * 32
+ ax0_ax1_0_fused_3
)
% 8
* 8
+ ax1_1,
)
T.reads(
W[
v0,
v1 // 3840,
v1 % 3840 // 1280,
v1 % 1280,
]
)
T.writes(
weight_flatten_reindex_shared_dyn[v0, v1]
)
T.sblock_attr(
{"buffer_dim_align": [[0, 0, 32, 8]]}
)
weight_flatten_reindex_shared_dyn[v0, v1] = W[
v0,
v1 // 1280 // 3,
v1 // 1280 % 3,
v1 % 1280,
]
for k_0_1 in range(4):
for ax0_0, ax1_0 in T.grid(2, 1):
with T.sblock("data_im2col_reindex_shared.dyn_wmma.matrix_a_o"):
v0_o = T.axis.spatial(32, x_0_0 * 4 + x_0_1 * 2 + ax0_0)
v1_o = T.axis.spatial(720, k_0_0 * 4 + k_0_1 + ax1_0)
T.reads(
data_im2col_reindex_shared_dyn[
v0_o * 16 : v0_o * 16 + 16,
v1_o * 16 : v1_o * 16 + 16,
]
)
T.writes(
data_im2col_reindex_shared_dyn_wmma_matrix_a[
v0_o * 16 : v0_o * 16 + 16,
v1_o * 16 : v1_o * 16 + 16,
]
)
A_s0 = T.int32()
A_s1 = T.int32()
A_1 = T.match_buffer(
data_im2col_reindex_shared_dyn[
v0_o * 16 : v0_o * 16 + 16,
v1_o * 16 : v1_o * 16 + 16,
],
(16, 16),
"float16",
strides=(A_s0, A_s1),
scope="shared.dyn",
offset_factor=16,
)
C_s0 = T.int32()
C_s1 = T.int32()
C = T.match_buffer(
data_im2col_reindex_shared_dyn_wmma_matrix_a[
v0_o * 16 : v0_o * 16 + 16,
v1_o * 16 : v1_o * 16 + 16,
],
(16, 16),
"float16",
strides=(C_s0, C_s1),
scope="wmma.matrix_a",
offset_factor=16,
)
T.tvm_load_matrix_sync(
C.data,
16,
16,
16,
C.elem_offset // C_s0 // 16 * (C_s0 // 16)
+ C.elem_offset % C_s0 // 16,
T.tvm_access_ptr(
T.type_annotation("float16"),
A_1.data,
A_1.elem_offset,
A_s0 * 16,
1,
),
A_s0,
"row_major",
)
for ax0_0, ax1_0 in T.grid(2, 1):
with T.sblock(
"weight_flatten_reindex_shared.dyn_wmma.matrix_b_o"
):
v0_o = T.axis.spatial(80, y_0_0 * 4 + y_0_1 * 2 + ax0_0)
v1_o = T.axis.spatial(720, k_0_0 * 4 + k_0_1 + ax1_0)
T.reads(
weight_flatten_reindex_shared_dyn[
v0_o * 16 : v0_o * 16 + 16,
v1_o * 16 : v1_o * 16 + 16,
]
)
T.writes(
weight_flatten_reindex_shared_dyn_wmma_matrix_b[
v0_o * 16 : v0_o * 16 + 16,
v1_o * 16 : v1_o * 16 + 16,
]
)
A_s0 = T.int32()
A_s1 = T.int32()
A_1 = T.match_buffer(
weight_flatten_reindex_shared_dyn[
v0_o * 16 : v0_o * 16 + 16,
v1_o * 16 : v1_o * 16 + 16,
],
(16, 16),
"float16",
strides=(A_s0, A_s1),
scope="shared.dyn",
offset_factor=16,
)
C_s0 = T.int32()
C_s1 = T.int32()
C = T.match_buffer(
weight_flatten_reindex_shared_dyn_wmma_matrix_b[
v0_o * 16 : v0_o * 16 + 16,
v1_o * 16 : v1_o * 16 + 16,
],
(16, 16),
"float16",
strides=(C_s0, C_s1),
scope="wmma.matrix_b",
offset_factor=16,
)
T.tvm_load_matrix_sync(
C.data,
16,
16,
16,
C.elem_offset // C_s0 // 16 * (C_s0 // 16)
+ C.elem_offset % C_s0 // 16,
T.tvm_access_ptr(
T.type_annotation("float16"),
A_1.data,
A_1.elem_offset,
A_s0 * 16,
1,
),
A_s0,
"col_major",
)
for x_0_2, y_0_2 in T.grid(2, 2):
with T.sblock("Conv_update_o"):
v_x_o = T.axis.spatial(32, x_0_0 * 4 + x_0_1 * 2 + x_0_2)
v_y_o = T.axis.spatial(80, y_0_0 * 4 + y_0_1 * 2 + y_0_2)
v_k_o = T.axis.reduce(720, k_0_0 * 4 + k_0_1)
T.reads(
Conv_reindex_wmma_accumulator[
v_x_o * 16 : v_x_o * 16 + 16,
v_y_o * 16 : v_y_o * 16 + 16,
],
data_im2col_reindex_shared_dyn_wmma_matrix_a[
v_x_o * 16 : v_x_o * 16 + 16,
v_k_o * 16 : v_k_o * 16 + 16,
],
weight_flatten_reindex_shared_dyn_wmma_matrix_b[
v_y_o * 16 : v_y_o * 16 + 16,
v_k_o * 16 : v_k_o * 16 + 16,
],
)
T.writes(
Conv_reindex_wmma_accumulator[
v_x_o * 16 : v_x_o * 16 + 16,
v_y_o * 16 : v_y_o * 16 + 16,
]
)
A_s0 = T.int32()
A_s1 = T.int32()
A_1 = T.match_buffer(
data_im2col_reindex_shared_dyn_wmma_matrix_a[
v_x_o * 16 : v_x_o * 16 + 16,
v_k_o * 16 : v_k_o * 16 + 16,
],
(16, 16),
"float16",
strides=(A_s0, A_s1),
scope="wmma.matrix_a",
offset_factor=16,
)
B_s0 = T.int32()
B_s1 = T.int32()
B = T.match_buffer(
weight_flatten_reindex_shared_dyn_wmma_matrix_b[
v_y_o * 16 : v_y_o * 16 + 16,
v_k_o * 16 : v_k_o * 16 + 16,
],
(16, 16),
"float16",
strides=(B_s0, B_s1),
scope="wmma.matrix_b",
offset_factor=16,
)
C_s0 = T.int32()
C_s1 = T.int32()
C = T.match_buffer(
Conv_reindex_wmma_accumulator[
v_x_o * 16 : v_x_o * 16 + 16,
v_y_o * 16 : v_y_o * 16 + 16,
],
(16, 16),
"float16",
strides=(C_s0, C_s1),
scope="wmma.accumulator",
offset_factor=16,
)
T.tvm_mma_sync(
C.data,
C.elem_offset // C_s0 // 16 * (C_s0 // 16)
+ C.elem_offset % C_s0 // 16,
A_1.data,
A_1.elem_offset // A_s0 // 16 * (A_s0 // 16)
+ A_1.elem_offset % A_s0 // 16,
B.data,
B.elem_offset // B_s0 // 16 * (B_s0 // 16)
+ B.elem_offset % B_s0 // 16,
C.data,
C.elem_offset // C_s0 // 16 * (C_s0 // 16)
+ C.elem_offset % C_s0 // 16,
)
for ax0_0, ax1_0 in T.grid(2, 2):
with T.sblock("Conv_reindex_wmma.accumulator_o"):
v0_o = T.axis.spatial(32, x_0_0 * 4 + x_0_1 * 2 + ax0_0)
v1_o = T.axis.spatial(80, y_0_0 * 4 + y_0_1 * 2 + ax1_0)
T.reads(
Conv_reindex_wmma_accumulator[
v0_o * 16 : v0_o * 16 + 16, v1_o * 16 : v1_o * 16 + 16
]
)
T.writes(
Conv[v0_o * 16 : v0_o * 16 + 16, v1_o * 16 : v1_o * 16 + 16]
)
A_s0 = T.int32()
A_s1 = T.int32()
A_1 = T.match_buffer(
Conv_reindex_wmma_accumulator[
v0_o * 16 : v0_o * 16 + 16, v1_o * 16 : v1_o * 16 + 16
],
(16, 16),
"float16",
strides=(A_s0, A_s1),
scope="wmma.accumulator",
offset_factor=16,
)
C_s0 = T.int32()
C_s1 = T.int32()
C = T.match_buffer(
Conv[v0_o * 16 : v0_o * 16 + 16, v1_o * 16 : v1_o * 16 + 16],
(16, 16),
"float16",
strides=(C_s0, C_s1),
offset_factor=16,
)
T.tvm_store_matrix_sync(
A_1.data,
16,
16,
16,
A_1.elem_offset // A_s0 // 16 * (A_s0 // 16)
+ A_1.elem_offset % A_s0 // 16,
T.tvm_access_ptr(
T.type_annotation("float16"),
C.data,
C.elem_offset,
C_s0 * 16,
2,
),
C_s0,
"row_major",
)
mod = tvm.IRModule.from_expr(complex_compute)
with tvm.transform.PassContext(config={"tirx.use_async_copy": 1}):
tvm.compile(mod, target="cuda")
generated_code = postproc_if_missing_async_support()
# generated_code must contain " setp.ne.b32 p, %0, 0;"
assert "setp.ne.b32" in generated_code
def test_multiplication_nodes_are_inlined():
@I.ir_module(s_tir=True)
class Before:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((32, 128), "float16")):
tx = T.launch_thread("threadIdx.x", T.int64(32))
A_flattened = T.decl_buffer((4096,), "float16", data=A.data)
A_shared = T.decl_buffer([4096], "float16", scope="shared")
T.attr("default", "async_scope", 1)
for i in range(16):
cse_v1: T.int64 = T.Cast("int64", i)
A_shared[T.Ramp(tx * T.int64(128) + cse_v1 * T.int64(8), T.int64(1), 8)] = (
A_flattened[T.Ramp(tx * T.int64(128) + cse_v1 * T.int64(8), T.int64(1), 8)]
)
T.ptx.cp_async.commit_group()
T.ptx.cp_async.wait_group(0)
@I.ir_module(s_tir=True)
class Expected:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((32, 128), "float16")):
tx = T.launch_thread("threadIdx.x", T.int64(32))
A_flattened = T.decl_buffer((4096,), "float16", data=A.data)
A_shared = T.decl_buffer((4096,), "float16", scope="shared")
for i in range(16):
cse_v1: T.int64 = T.Cast("int64", i)
T.ptx.cp_async(
"float16",
A_shared.data,
tx * T.int64(128) + cse_v1 * T.int64(8),
A.data,
tx * T.int64(128) + cse_v1 * T.int64(8),
16,
)
T.ptx.cp_async.commit_group()
T.ptx.cp_async.wait_group(0)
After = tvm.s_tir.transform.InjectPTXAsyncCopy()(Before)
tvm.ir.assert_structural_equal(After, Expected)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,82 @@
# 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 tvm
import tvm.testing
from tvm import s_tir
from tvm.script import tirx as T
def _count_alloc(stmt):
num_alloc = [0]
def visit(n):
if isinstance(n, tvm.tirx.AllocBuffer):
num_alloc[0] += 1
tvm.tirx.stmt_functor.post_order_visit(stmt, visit)
return num_alloc[0]
def _count_ptx_ldg32(stmt):
num_call = [0]
def visit(n):
if isinstance(n, tvm.ir.Call) and n.op.name == "tirx.ptx.ldg32":
num_call[0] += 1
tvm.tirx.stmt_functor.post_order_visit(stmt, visit)
return num_call[0]
@T.prim_func(s_tir=True)
def where_no_alloc(A: T.Buffer((4,), "float32"), C: T.Buffer((4,), "float32")) -> None:
T.func_attr({"global_symbol": "main", "tirx.noalias": True, "target": T.target("cuda")})
for i in range(4):
C[i] = T.if_then_else(A[i] > T.float32(0), A[i], T.float32(0))
@T.prim_func(s_tir=True)
def where_no_alloc_cpu(A: T.Buffer((4,), "float32"), C: T.Buffer((4,), "float32")) -> None:
T.func_attr({"global_symbol": "main", "tirx.noalias": True, "target": T.target("llvm")})
for i in range(4):
C[i] = T.if_then_else(A[i] > T.float32(0), A[i], T.float32(0))
def test_inject_ptx_ldg32_inserts_alloc_for_no_alloc_func():
mod = tvm.IRModule.from_expr(where_no_alloc)
assert _count_alloc(mod["main"].body) == 0
mod = tvm.s_tir.transform.InjectPTXLDG32()(mod)
assert _count_alloc(mod["main"].body) > 0
assert _count_ptx_ldg32(mod["main"].body) == 1
def test_inject_ptx_ldg32_skip_non_cuda_target():
mod = tvm.IRModule.from_expr(where_no_alloc_cpu)
cpu_target = tvm.target.Target("llvm")
mod = tvm.IRModule({"main": mod["main"].with_attr("target", cpu_target)})
assert _count_alloc(mod["main"].body) == 0
mod = tvm.s_tir.transform.InjectPTXLDG32()(mod)
assert _count_alloc(mod["main"].body) == 0
assert _count_ptx_ldg32(mod["main"].body) == 0
if __name__ == "__main__":
tvm.testing.main()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,218 @@
# 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 tvm
import tvm.testing
from tvm.script import ir as I
from tvm.script import tirx as T
def test_vthread():
"""Test virtual thread injection with vthread"""
n = 100
m = 4
nthread = 2
@I.ir_module
class Module:
@T.prim_func(s_tir=True)
def main(A: T.handle("float32"), C: T.handle("float32")):
A_buf = T.decl_buffer((n * nthread,), "float32", data=A)
C_buf = T.decl_buffer((n * nthread,), "float32", data=C)
for i in range(n):
vt_x = T.launch_thread("vthread", nthread)
vt_y = T.launch_thread("vthread", nthread)
B = T.alloc_buffer((m,), scope="shared")
B[i] = A_buf[i * nthread + vt_x]
T.evaluate(
T.call_extern(
"int32",
"Run",
B.access_ptr("r"),
T.call_intrin("int32", "tirx.tvm_context_id"),
)
)
C_buf[i * nthread + vt_x] = B[i] + T.float32(1)
# For vthread, expected allocation is m * nthread
B_expected_alloc = m * nthread
stmt = tvm.s_tir.transform.InjectVirtualThread()(Module)["main"]
# Find allocate nodes
allocates = []
def find_allocates(node):
if isinstance(node, tvm.tirx.AllocBuffer):
allocates.append(node)
tvm.tirx.stmt_functor.post_order_visit(stmt.body, find_allocates)
assert len(allocates) == 1
assert list(allocates[0].buffer.shape) == [B_expected_alloc]
def test_vthread_extern():
"""Test virtual thread injection with extern call"""
n = 100
m = 4
nthread = 2
@I.ir_module
class Module:
@T.prim_func(s_tir=True)
def main():
T.func_attr({"global_symbol": "main"})
for i in range(n):
vt_x = T.launch_thread("vthread", nthread)
vt_y = T.launch_thread("vthread", nthread)
A = T.alloc_buffer((m,), scope="shared")
B = T.alloc_buffer((m,), scope="shared")
C = T.alloc_buffer((m,), scope="shared")
A[vt_x] = T.Cast("float32", vt_x) + T.float32(1)
B[vt_y] = T.Cast("float32", vt_y) + T.float32(1)
T.evaluate(
T.call_extern(
"int32",
"Run",
A.access_ptr("r"),
B.access_ptr("r"),
C.access_ptr("rw"),
)
)
# For vthread:
# A, B expected allocation is m * nthread (used with single vthread each)
A_expected_alloc = m * nthread
# C expected allocation is m * nthread * nthread (used in extern with both vthreads)
C_expected_alloc = m * nthread * nthread
stmt = tvm.s_tir.transform.InjectVirtualThread()(Module)["main"]
# Find allocate nodes
allocates = []
def find_allocates(node):
if isinstance(node, tvm.tirx.AllocBuffer):
allocates.append(node)
tvm.tirx.stmt_functor.post_order_visit(stmt.body, find_allocates)
assert len(allocates) == 3
# Check that we have the expected extents (order may vary)
extents = sorted([int(a.buffer.shape[0]) for a in allocates])
assert extents == sorted([A_expected_alloc, A_expected_alloc, C_expected_alloc])
def test_vthread_if_then_else():
"""Test virtual thread injection with if-then-else"""
nthread = 2
@I.ir_module
class Module:
@T.prim_func(s_tir=True)
def main(A: T.handle("float32")):
T.func_attr({"global_symbol": "main"})
A_buf = T.decl_buffer((100 * nthread,), "float32", data=A)
for i in range(100):
vt = T.launch_thread("vthread", nthread)
B = T.alloc_buffer((128,), scope="shared")
if i == 0:
B[i] = A_buf[i * nthread + vt]
else:
B[i] = A_buf[i * nthread + vt] + T.float32(1)
if i == 0:
B[i] = A_buf[i * nthread + vt] + T.float32(2)
stmt = tvm.s_tir.transform.InjectVirtualThread()(Module)["main"]
# Find IfThenElse nodes
if_nodes = []
def find_ifs(node):
if isinstance(node, tvm.tirx.IfThenElse):
if_nodes.append(node)
tvm.tirx.stmt_functor.post_order_visit(stmt.body, find_ifs)
assert len(if_nodes) == 2
# First if has else_case, second does not
assert if_nodes[0].else_case is not None
assert if_nodes[1].else_case is None
def test_vthread_simplified():
"""Indices resulting from vthread injection should simplified
This ensures that downstream passes that check for Ramp nodes do
not need to each simplify the indices.
"""
@T.prim_func(s_tir=True)
def before_func():
vthread = T.env_thread("vthread")
T.launch_thread(vthread, 4)
B = T.alloc_buffer((4,), "int32", scope="shared")
B[0:4] = T.broadcast(vthread, 4)
@T.prim_func(check_well_formed=False, s_tir=True)
def expected_func():
B = T.alloc_buffer((16,), "int32", scope="shared")
B_1 = T.Buffer([16], "int32", data=B.data, scope="shared")
# The indices for B should each be a single Ramp node, and
# should not be the sum of a Ramp and Broadcast node.
B_1[T.Mul(0, 4) : T.Mul(0, 4) + 4] = T.broadcast(0, 4)
B_1[T.Mul(1, 4) : T.Mul(1, 4) + 4] = T.broadcast(1, 4)
B_1[T.Mul(2, 4) : T.Mul(2, 4) + 4] = T.broadcast(2, 4)
B_1[T.Mul(3, 4) : T.Mul(3, 4) + 4] = T.broadcast(3, 4)
before_mod = tvm.IRModule.from_expr(before_func.with_attr("global_symbol", "main"))
after_mod = tvm.s_tir.transform.InjectVirtualThread()(before_mod)
after_func = after_mod["main"]
tvm.ir.assert_structural_equal(after_func, expected_func.with_attr("global_symbol", "main"))
def test_vthread_vectorized():
"""Use of vthread is compatible with vector allocations"""
@T.prim_func(s_tir=True)
def before_func():
vthread = T.env_thread("vthread")
T.launch_thread(vthread, 4)
B = T.alloc_buffer((4,), "int32", scope="shared")
B[0:4] = T.broadcast(vthread, 4)
before_mod = tvm.IRModule.from_expr(before_func.with_attr("global_symbol", "main"))
intermediate_mod = tvm.s_tir.transform.InjectVirtualThread()(before_mod)
after_mod = tvm.tirx.transform.StorageRewrite()(intermediate_mod)
after_func = after_mod["main"]
# Verify the vectorized allocation has the expected shape and dtype
allocate_node = None
def visitor(op):
nonlocal allocate_node
if isinstance(op, tvm.tirx.AllocBuffer) and "shared" in str(op.buffer.data.ty):
allocate_node = op
tvm.tirx.stmt_functor.post_order_visit(after_func.body, visitor)
assert allocate_node is not None
assert list(allocate_node.buffer.shape) == [4]
assert allocate_node.buffer.dtype == "int32x4"
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,140 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ruff: noqa: E501, F401
import tvm
from tvm import s_tir, tirx
from tvm.script import tirx as T
def test_lift_tx_beyond_local():
# fmt: off
@T.prim_func(s_tir=True)
def before(a: T.handle, b: T.handle, c: T.handle):
n = T.int32()
A = T.match_buffer(a, (32, 1, 128))
B = T.match_buffer(b, (32, n, 128))
C = T.match_buffer(c, (32, 1, n))
for ax0_ax1_fused in T.thread_binding(n * 32, thread="blockIdx.x"):
with T.sblock(""):
T.reads(A[ax0_ax1_fused // n, 0, 0:256], B[ax0_ax1_fused // n, ax0_ax1_fused % n, 0:256])
T.writes(C[ax0_ax1_fused // n, 0, ax0_ax1_fused % n])
D_local = T.sblock_alloc_buffer((32, 1, n), scope="local")
D_rf_local = T.sblock_alloc_buffer((256, 32, 1, n), scope="local")
for ax2_fused_1 in T.thread_binding(256, thread="threadIdx.x"):
with T.sblock("NT_matmul_rf_init"):
T.reads()
T.writes(D_rf_local[ax2_fused_1, ax0_ax1_fused // n, 0, ax0_ax1_fused % n])
D_rf_local[ax2_fused_1, ax0_ax1_fused // n, 0, ax0_ax1_fused % n] = T.float32(0)
for ax2_fused_0 in range(1):
with T.sblock("NT_matmul_rf_update"):
T.where(ax2_fused_0 * 256 + ax2_fused_1 < 128)
T.reads(D_rf_local[ax2_fused_1, ax0_ax1_fused // n, 0, ax0_ax1_fused % n], A[ax0_ax1_fused // n, 0, ax2_fused_0 * 256 + ax2_fused_1], B[ax0_ax1_fused // n, ax0_ax1_fused % n, ax2_fused_0 * 256 + ax2_fused_1])
T.writes(D_rf_local[ax2_fused_1, ax0_ax1_fused // n, 0, ax0_ax1_fused % n])
D_rf_local[ax2_fused_1, ax0_ax1_fused // n, 0, ax0_ax1_fused % n] = D_rf_local[ax2_fused_1, ax0_ax1_fused // n, 0, ax0_ax1_fused % n] + A[ax0_ax1_fused // n, 0, ax2_fused_0 * 256 + ax2_fused_1] * B[ax0_ax1_fused // n, ax0_ax1_fused % n, ax2_fused_0 * 256 + ax2_fused_1]
for ax1_ax2_fused in range(1):
for ax0_fused in T.thread_binding(256, thread="threadIdx.x"):
with T.sblock(""):
T.reads(D_rf_local[ax0_fused, ax0_ax1_fused // n, 0, ax0_ax1_fused % n])
T.writes(D_local[ax0_ax1_fused // n, 0, ax0_ax1_fused % n])
cross_thread_D_local = T.sblock_alloc_buffer((1,), strides=(1,), scope="local")
in_thread_D_local = T.sblock_alloc_buffer((1,), strides=(1,), scope="local")
with T.sblock("NT_matmul_in_thread_init"):
T.reads()
T.writes(in_thread_D_local[0])
in_thread_D_local[0] = T.float32(0)
with T.sblock("NT_matmul_in_thread"):
T.where(0 <= ax0_ax1_fused // n and ax0_ax1_fused // n < 32 and 0 <= ax0_ax1_fused % n and ax0_ax1_fused % n < n)
T.reads(D_rf_local[ax0_fused, ax0_ax1_fused // n, 0, ax0_ax1_fused % n])
T.writes(in_thread_D_local[0])
in_thread_D_local[0] = in_thread_D_local[0] + D_rf_local[ax0_fused, ax0_ax1_fused // n, 0, ax0_ax1_fused % n]
with T.sblock("NT_matmul_cross_thread"):
T.reads(in_thread_D_local[0])
T.writes(cross_thread_D_local[0])
T.attr(T.comm_reducer(lambda x0, y0: x0 + y0, [T.float32(0)]), "reduce_scope", T.int32(0))
T.tvm_thread_allreduce(T.uint32(1), in_thread_D_local[0], T.bool(True), cross_thread_D_local[0], ax0_fused)
with T.sblock("NT_matmul_write_back"):
T.where(ax0_fused == 0)
T.reads(cross_thread_D_local[0])
T.writes(D_local[ax0_ax1_fused // n, 0, ax0_ax1_fused % n])
D_local[ax0_ax1_fused // n, 0, ax0_ax1_fused % n] = cross_thread_D_local[0]
with T.sblock("T_divide"):
T.where(0 <= ax0_ax1_fused // n and ax0_ax1_fused // n < 32 and 0 <= ax0_ax1_fused % n and ax0_ax1_fused % n < n)
T.reads(D_local[ax0_ax1_fused // n, 0, ax0_ax1_fused % n])
T.writes(C[ax0_ax1_fused // n, 0, ax0_ax1_fused % n])
C[ax0_ax1_fused // n, 0, ax0_ax1_fused % n] = D_local[ax0_ax1_fused // n, 0, ax0_ax1_fused % n] * T.float32(0.088397790055248615)
@T.prim_func(s_tir=True)
def expected(A: T.Buffer((32, 1, 128), "float32"), b: T.handle, c: T.handle):
n = T.int32()
B = T.match_buffer(b, (32, n, 128))
C = T.match_buffer(c, (32, 1, n))
# with T.sblock("root"):
for blockIdx_x in T.thread_binding(n * 32, thread="blockIdx.x"):
for threadIdx_x in T.thread_binding(256, thread="threadIdx.x"):
with T.sblock(""):
T.reads(A[blockIdx_x // n, 0, 0:256], B[blockIdx_x // n, blockIdx_x % n, 0:256])
T.writes(C[blockIdx_x // n, 0, blockIdx_x % n])
D_local = T.sblock_alloc_buffer((32, 1, n), scope="local")
D_rf_local = T.sblock_alloc_buffer((256, 32, 1, n), scope="local")
with T.sblock("NT_matmul_rf_init"):
T.reads()
T.writes(D_rf_local[threadIdx_x, blockIdx_x // n, 0, blockIdx_x % n])
D_rf_local[threadIdx_x, blockIdx_x // n, 0, blockIdx_x % n] = T.float32(0)
for ax2_fused_0 in range(1):
with T.sblock("NT_matmul_rf_update"):
T.where(ax2_fused_0 * 256 + threadIdx_x < 128)
T.reads(D_rf_local[threadIdx_x, blockIdx_x // n, 0, blockIdx_x % n], A[blockIdx_x // n, 0, ax2_fused_0 * 256 + threadIdx_x], B[blockIdx_x // n, blockIdx_x % n, ax2_fused_0 * 256 + threadIdx_x])
T.writes(D_rf_local[threadIdx_x, blockIdx_x // n, 0, blockIdx_x % n])
D_rf_local[threadIdx_x, blockIdx_x // n, 0, blockIdx_x % n] = D_rf_local[threadIdx_x, blockIdx_x // n, 0, blockIdx_x % n] + A[blockIdx_x // n, 0, ax2_fused_0 * 256 + threadIdx_x] * B[blockIdx_x // n, blockIdx_x % n, ax2_fused_0 * 256 + threadIdx_x]
for ax1_ax2_fused in range(1):
with T.sblock(""):
T.reads(D_rf_local[threadIdx_x, blockIdx_x // n, 0, blockIdx_x % n])
T.writes(D_local[blockIdx_x // n, 0, blockIdx_x % n])
cross_thread_D_local = T.sblock_alloc_buffer((1,), strides=(1,), scope="local")
in_thread_D_local = T.sblock_alloc_buffer((1,), strides=(1,), scope="local")
with T.sblock("NT_matmul_in_thread_init"):
T.reads()
T.writes(in_thread_D_local[0])
in_thread_D_local[0] = T.float32(0)
with T.sblock("NT_matmul_in_thread"):
T.where(0 <= blockIdx_x // n and blockIdx_x // n < 32 and 0 <= blockIdx_x % n and blockIdx_x % n < n)
T.reads(D_rf_local[threadIdx_x, blockIdx_x // n, 0, blockIdx_x % n])
T.writes(in_thread_D_local[0])
in_thread_D_local[0] = in_thread_D_local[0] + D_rf_local[threadIdx_x, blockIdx_x // n, 0, blockIdx_x % n]
with T.sblock("NT_matmul_cross_thread"):
T.reads(in_thread_D_local[0])
T.writes(cross_thread_D_local[0])
T.attr(T.comm_reducer(lambda x0, y0: x0 + y0, [T.float32(0)]), "reduce_scope", T.int32(0))
T.tvm_thread_allreduce(T.uint32(1), in_thread_D_local[0], T.bool(True), cross_thread_D_local[0], threadIdx_x)
with T.sblock("NT_matmul_write_back"):
T.where(threadIdx_x == 0)
T.reads(cross_thread_D_local[0])
T.writes(D_local[blockIdx_x // n, 0, blockIdx_x % n])
D_local[blockIdx_x // n, 0, blockIdx_x % n] = cross_thread_D_local[0]
with T.sblock("T_divide"):
T.where(0 <= blockIdx_x // n and blockIdx_x // n < 32 and 0 <= blockIdx_x % n and blockIdx_x % n < n)
T.reads(D_local[blockIdx_x // n, 0, blockIdx_x % n])
T.writes(C[blockIdx_x // n, 0, blockIdx_x % n])
C[blockIdx_x // n, 0, blockIdx_x % n] = D_local[blockIdx_x // n, 0, blockIdx_x % n] * T.float32(0.088397790055248615)
# fmt: on
mod = tvm.IRModule({"main": before.with_attr("global_symbol", "main")})
after = s_tir.transform.LiftThreadBinding()(mod)
tvm.ir.assert_structural_equal(expected.with_attr("global_symbol", "main"), after["main"])
if __name__ == "__main__":
test_lift_tx_beyond_local()
@@ -0,0 +1,802 @@
# 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 numpy
import pytest
import tvm
import tvm.testing
from tvm.ir.module import IRModule
from tvm.script import tirx as T
def collect_visit(stmt, f):
ret = []
tvm.tirx.stmt_functor.post_order_visit(stmt, lambda x: ret.append(f(x)))
return ret
def test_multi_loop():
@T.prim_func(s_tir=True)
def func(n: T.int64, m: T.int64):
for i in range(4):
for j in T.serial(n):
for k in T.serial(m):
if T.likely(i * m + j + k < n):
T.evaluate(m)
else:
T.evaluate(n)
mod = tvm.IRModule.from_expr(func.with_attr("global_symbol", "main"))
mod = tvm.s_tir.transform.LoopPartition()(mod)
stmt = tvm.tirx.transform.StmtSimplify()(mod)["main"].body
assert not any(collect_visit(stmt.body[0], lambda x: isinstance(x, tvm.tirx.IfThenElse)))
def test_multi_if():
@T.prim_func(s_tir=True)
def func(n: T.int64, m: T.int64):
for i in range(4):
for j in T.serial(n):
for k in T.serial(m):
if T.likely(i * m + j + k < n):
T.evaluate(m)
else:
T.evaluate(n)
if T.likely(i * m + j - k < n):
T.evaluate(m)
else:
T.evaluate(n)
mod = tvm.IRModule.from_expr(func.with_attr("global_symbol", "main"))
mod = tvm.s_tir.transform.LoopPartition()(mod)
stmt = tvm.tirx.transform.StmtSimplify()(mod)["main"].body
assert not any(collect_visit(stmt.body[0], lambda x: isinstance(x, tvm.tirx.IfThenElse)))
def test_condition():
@T.prim_func(s_tir=True)
def func(m: T.int64, n: T.int64):
for i in T.serial(T.truncdiv(n + 3, 4)):
for j in range(4):
T.evaluate(T.Select(T.likely(i * 4 + j < n), m, n))
mod = tvm.IRModule.from_expr(func.with_attr("global_symbol", "main"))
mod = tvm.s_tir.transform.LoopPartition()(mod)
stmt = tvm.tirx.transform.StmtSimplify()(mod)["main"].body
assert not any(collect_visit(stmt[0], lambda x: isinstance(x, tvm.tirx.Select)))
def test_condition_EQ():
@T.prim_func(s_tir=True)
def func(m: T.int64, n: T.int64):
for i in range(10):
T.evaluate(T.Select(T.likely(i == 5), m, n))
mod = tvm.IRModule.from_expr(func.with_attr("global_symbol", "main"))
with tvm.transform.PassContext(config={"s_tir.LoopPartition": {"partition_const_loop": True}}):
mod = tvm.s_tir.transform.LoopPartition()(mod)
stmt = tvm.tirx.transform.StmtSimplify()(mod)["main"].body
assert not any(collect_visit(stmt[0], lambda x: isinstance(x, tvm.tirx.Select)))
def test_everything_during_deduction():
@T.prim_func(s_tir=True)
def func(m: T.int64, n: T.int64):
for i in T.serial(n):
for j in range(32):
if T.likely(T.truncdiv(i, j) < m):
# this guard will produce everything during deduction
T.evaluate(m)
mod = tvm.IRModule.from_expr(func.with_attr("global_symbol", "main"))
mod = tvm.s_tir.transform.LoopPartition()(mod)
stmt = tvm.tirx.transform.StmtSimplify()(mod)["main"].body
assert isinstance(stmt.body.body, tvm.tirx.IfThenElse)
def test_oneD_pool():
@T.prim_func(s_tir=True)
def func(m: T.int64, data: T.handle("float32"), out: T.handle("float32")):
data_ptr = T.decl_buffer((16,), "float32", data=data)
out_ptr = T.decl_buffer((16,), "float32", data=out)
for ow in range(16):
for kw in range(3):
if T.likely(ow > 0):
if T.likely(ow < 15):
out_ptr[ow] = T.max(out_ptr[ow], data_ptr[ow + kw - 1])
for ow in range(16):
for kw in range(3):
if T.likely(ow < 1):
if T.likely(kw > 0):
out_ptr[ow] = T.max(out_ptr[ow], data_ptr[ow + kw - 1])
for ow in range(16):
for kw in range(3):
if T.likely(ow > 14):
if T.likely(kw < 2):
out_ptr[ow] = T.max(out_ptr[ow], data_ptr[ow + kw - 1])
mod = tvm.IRModule.from_expr(func.with_attr("global_symbol", "main"))
with tvm.transform.PassContext(config={"s_tir.LoopPartition": {"partition_const_loop": True}}):
mod = tvm.s_tir.transform.LoopPartition()(mod)
stmt = tvm.tirx.transform.StmtSimplify()(mod)["main"].body
assert not any(collect_visit(stmt, lambda x: isinstance(x, tvm.tirx.IfThenElse)))
def test_cce_loop_1():
n = 514
m = 514
@T.prim_func(s_tir=True)
def func(A: T.Buffer((n * m,), "float16"), B: T.Buffer((n * m,), "float16")):
for i in range(11):
for j in range(160):
if T.likely(i * 160 + j < 1600):
A[(i + 1) * m + j + 1] = (
B[i * m + j + 1] + B[(i + 1) * m + j + 1] + B[(i + 2) * m + j + 1]
)
mod = tvm.IRModule.from_expr(func.with_attr("global_symbol", "main"))
with tvm.transform.PassContext(config={"s_tir.LoopPartition": {"partition_const_loop": True}}):
mod = tvm.s_tir.transform.LoopPartition()(mod)
stmt = tvm.tirx.transform.StmtSimplify()(mod)["main"].body
assert not any(collect_visit(stmt, lambda x: isinstance(x, tvm.tirx.IfThenElse)))
def test_cce_loop_2():
length = 112
tile = 32
loop = (length + tile - 1) // tile
@T.prim_func(private=True, s_tir=True)
def func():
for i in range(loop):
if T.likely(i * tile + tile > length):
T.evaluate(T.call_extern("float32", "cce_intrisic", i * tile, length))
else:
T.evaluate(T.call_extern("float32", "cce_intrisic", i * tile, i * tile + tile))
mod = tvm.IRModule.from_expr(func.with_attr("global_symbol", "main"))
with tvm.transform.PassContext(config={"s_tir.LoopPartition": {"partition_const_loop": True}}):
mod = tvm.s_tir.transform.LoopPartition()(mod)
stmt = tvm.tirx.transform.StmtSimplify()(mod)["main"].body
assert not any(collect_visit(stmt, lambda x: isinstance(x, tvm.tirx.IfThenElse)))
def test_cce_loop_3():
loop1 = 4
loop2 = 9998
tile = 39991
@T.prim_func(private=True, s_tir=True)
def func():
for i in range(loop2):
for j in range(loop1):
if T.likely(i * loop1 + j < tile):
T.evaluate(T.call_extern("float16", "cce_intrisic", i))
mod = tvm.IRModule.from_expr(func.with_attr("global_symbol", "main"))
with tvm.transform.PassContext(config={"s_tir.LoopPartition": {"partition_const_loop": True}}):
mod = tvm.s_tir.transform.LoopPartition()(mod)
stmt = tvm.tirx.transform.StmtSimplify()(mod)["main"].body
assert not any(collect_visit(stmt, lambda x: isinstance(x, tvm.tirx.IfThenElse)))
@T.prim_func(s_tir=True)
def partitioned_concat(
A: T.Buffer((16,), "float32"), B: T.Buffer((16,), "float32"), C: T.Buffer((32,), "float32")
) -> None:
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
for i in T.serial(0, 16):
C[i] = A[i]
for i in T.serial(0, 16):
C[i + 16] = B[i + 16]
def partition_from_scheduled_tir(prim_func, pass_cfg, do_flatten=True):
with tvm.transform.PassContext(config=pass_cfg):
mod = IRModule.from_expr(prim_func.with_attr("global_symbol", "main"))
mod = tvm.s_tir.transform.LowerOpaqueBlock()(mod)
if do_flatten:
mod = tvm.tirx.transform.FlattenBuffer()(mod)
mod = tvm.s_tir.transform.LoopPartition()(mod)
mod = tvm.tirx.transform.StmtSimplify()(mod)
mod = tvm.tirx.transform.RemoveNoOp()(mod)
return mod
@T.prim_func(s_tir=True)
def partitioned_concat_3(
placeholder: T.Buffer((1, 64, 28, 28), "int8"),
placeholder_1: T.Buffer((1, 32, 28, 28), "int8"),
placeholder_2: T.Buffer((1, 32, 28, 28), "int8"),
T_concat: T.Buffer((1, 128, 28, 28), "int8"),
) -> None:
placeholder_flat = T.decl_buffer([50176], "int8", data=placeholder.data)
placeholder_1_flat = T.decl_buffer([25088], "int8", data=placeholder_1.data)
placeholder_2_flat = T.decl_buffer([25088], "int8", data=placeholder_2.data)
T_concat_flat = T.decl_buffer([100352], "int8", data=T_concat.data)
for i1, i2, i3 in T.grid(64, 28, 28):
T_concat_flat[i1 * 784 + i2 * 28 + i3] = placeholder_flat[i1 * 784 + i2 * 28 + i3]
for i1, i2, i3 in T.grid(32, 28, 28):
T_concat_flat[i1 * 784 + i2 * 28 + i3 + 50176] = placeholder_1_flat[i1 * 784 + i2 * 28 + i3]
for i1, i2, i3 in T.grid(32, 28, 28):
T_concat_flat[i1 * 784 + i2 * 28 + i3 + 75264] = placeholder_2_flat[i1 * 784 + i2 * 28 + i3]
@T.prim_func(s_tir=True)
def concat_func_3(
placeholder: T.Buffer((1, 64, 28, 28), "int8"),
placeholder_1: T.Buffer((1, 32, 28, 28), "int8"),
placeholder_2: T.Buffer((1, 32, 28, 28), "int8"),
T_concat: T.Buffer((1, 128, 28, 28), "int8"),
) -> None:
placeholder_flat = T.decl_buffer([50176], "int8", data=placeholder.data)
placeholder_1_flat = T.decl_buffer([25088], "int8", data=placeholder_1.data)
placeholder_2_flat = T.decl_buffer([25088], "int8", data=placeholder_2.data)
T_concat_flat = T.decl_buffer([100352], "int8", data=T_concat.data)
for i1 in T.serial(128, annotations={"pragma_loop_partition_hint": 1}):
for i2, i3 in T.grid(28, 28):
if 96 <= i1:
T_concat_flat[i1 * 784 + i2 * 28 + i3] = placeholder_2_flat[
i1 * 784 + i2 * 28 + i3 - 75264
]
if 64 <= i1 and i1 < 96:
T_concat_flat[i1 * 784 + i2 * 28 + i3] = placeholder_1_flat[
i1 * 784 + i2 * 28 + i3 - 50176
]
if i1 < 64:
T_concat_flat[i1 * 784 + i2 * 28 + i3] = placeholder_flat[i1 * 784 + i2 * 28 + i3]
def test_condition_mutually_exclusive():
mod = partition_from_scheduled_tir(
concat_func_3, {"s_tir.LoopPartition": {"partition_const_loop": True}}
)
tvm.ir.assert_structural_equal(
mod["main"], partitioned_concat_3.with_attr("global_symbol", "main")
)
def test_loop_partition_unroll_hint():
@T.prim_func(s_tir=True)
def main(
A_arg: T.Buffer((1, 3, 224, 224), "int8"), B_arg: T.Buffer((1, 224, 7, 16), "int8")
) -> None:
A = T.decl_buffer(150528, "int8", data=A_arg.data)
B = T.decl_buffer(25088, "int8", data=B_arg.data)
for ax0 in T.serial(
112,
annotations={"pragma_loop_partition_hint": True},
):
for ax1, ax2, ax3 in T.grid(224, 7, 16):
if 3 <= ax0 * 2 + ax2 and ax0 * 2 + ax2 < 227 and ax3 < 3:
B[ax1 * 112 + ax2 * 16 + ax3] = A[ax3 * 50176 + ax1 * 224 + ax0 * 2 + ax2 - 3]
@T.prim_func(s_tir=True)
def partitioned_main(
A_arg: T.Buffer((1, 3, 224, 224), "int8"), B_arg: T.Buffer((1, 224, 7, 16), "int8")
) -> None:
A = T.decl_buffer(150528, dtype="int8", data=A_arg.data)
B = T.decl_buffer(25088, dtype="int8", data=B_arg.data)
# body
for ax1, ax2, ax3 in T.grid(224, 7, 16):
if 3 <= ax2 and ax3 < 3:
B[ax1 * 112 + ax2 * 16 + ax3] = A[ax3 * 50176 + ax1 * 224 + ax2 - 3]
for ax1, ax2, ax3 in T.grid(224, 7, 16):
if 1 <= ax2 and ax3 < 3:
B[ax1 * 112 + ax2 * 16 + ax3] = A[ax3 * 50176 + ax1 * 224 + ax2 - 1]
for ax0, ax1, ax2, ax3 in T.grid(109, 224, 7, 16):
if ax3 < 3:
B[ax1 * 112 + ax2 * 16 + ax3] = A[ax3 * 50176 + ax1 * 224 + ax0 * 2 + ax2 + 1]
for ax1, ax2, ax3 in T.grid(224, 7, 16):
if ax2 < 5 and ax3 < 3:
B[ax1 * 112 + ax2 * 16 + ax3] = A[ax3 * 50176 + ax1 * 224 + ax2 + 219]
mod = partition_from_scheduled_tir(
main,
{
"s_tir.LoopPartition": {
"partition_const_loop": True,
"unroll_loop_with_partition_hint_no_interval": True,
}
},
)
mod = tvm.tirx.transform.UnrollLoop()(mod)
mod = tvm.tirx.transform.RemoveNoOp()(mod)
mod = tvm.tirx.transform.StmtSimplify()(mod)
tvm.ir.assert_structural_equal(mod["main"], partitioned_main.with_attr("global_symbol", "main"))
def test_loop_partition_recursive_unroll_hint():
@T.prim_func(s_tir=True)
def main():
placeholder_0_dm = T.decl_buffer([1, 32, 32, 16], dtype="int8")
for i3_0 in T.serial(5, annotations={"pragma_loop_partition_hint": 1}):
for i2_0 in T.serial(2, annotations={"pragma_loop_partition_hint": 1}):
pad_temp = T.decl_buffer([1, 16, 16, 16], dtype="int8")
for ax0, ax1, ax2 in T.grid(16, 16, 16):
if (
6 <= i2_0 * 4 + ax0
and i2_0 * 4 + ax0 < 26
and 6 <= i3_0 * 4 + ax1
and i3_0 * 4 + ax1 < 26
):
pad_temp[
0,
i2_0 * 4 + ax0 - 6 + 6 - i2_0 * 4,
i3_0 * 4 + ax1 - 6 + 6 - i3_0 * 4,
ax2,
] = placeholder_0_dm[
0,
i2_0 * 4 + ax0 - 6 - -6,
i3_0 * 4 + ax1 - 6 - -6,
ax2,
]
@T.prim_func(s_tir=True)
def partitioned_main():
placeholder_0_dm = T.decl_buffer((16384,), "int8")
for i3_0 in T.unroll(2):
for i2_0 in T.unroll(2):
pad_temp = T.decl_buffer((4096,), "int8")
for ax0, ax1, ax2 in T.grid(16, 16, 16):
if 6 <= i2_0 * 4 + ax0 and 6 <= i3_0 * 4 + ax1:
pad_temp[ax0 * 256 + ax1 * 16 + ax2] = placeholder_0_dm[
i2_0 * 2048 + ax0 * 512 + i3_0 * 64 + ax1 * 16 + ax2
]
for i2_0 in T.unroll(2):
pad_temp = T.decl_buffer((4096,), "int8")
for ax0, ax1, ax2 in T.grid(16, 16, 16):
if 6 <= i2_0 * 4 + ax0:
pad_temp[ax0 * 256 + ax1 * 16 + ax2] = placeholder_0_dm[
i2_0 * 2048 + ax0 * 512 + ax1 * 16 + ax2 + 128
]
for i3_0 in T.unroll(2):
for i2_0 in T.unroll(2):
pad_temp = T.decl_buffer((4096,), "int8")
for ax0, ax1, ax2 in T.grid(16, 16, 16):
if 6 <= i2_0 * 4 + ax0 and i3_0 * 4 + ax1 < 14:
pad_temp[ax0 * 256 + ax1 * 16 + ax2] = placeholder_0_dm[
i2_0 * 2048 + ax0 * 512 + i3_0 * 64 + ax1 * 16 + ax2 + 192
]
mod = partition_from_scheduled_tir(
main,
{
"s_tir.LoopPartition": {
"partition_const_loop": True,
"unroll_loop_with_partition_hint_no_interval": True,
}
},
)
tvm.ir.assert_structural_equal(mod["main"], partitioned_main.with_attr("global_symbol", "main"))
def test_loop_partition_keep_loop_annotations():
@T.prim_func(s_tir=True)
def before(A: T.Buffer(160, "int32"), B: T.Buffer(160, "int32")) -> None:
for i in T.serial(
160,
annotations={"pragma_loop_partition_hint": True, "key": "value"},
):
if i < 10:
B[i] = A[i] + 1
elif 10 <= i and i < 150:
B[i] = A[i] + 2
else:
B[i] = A[i] + 3
@T.prim_func(s_tir=True)
def after(A: T.Buffer(160, "int32"), B: T.Buffer(160, "int32")) -> None:
A_1 = T.decl_buffer((160,), "int32", data=A.data)
B_1 = T.decl_buffer((160,), "int32", data=B.data)
for i in T.serial(10, annotations={"key": "value"}):
B_1[i] = A_1[i] + 1
for i in T.serial(140, annotations={"key": "value"}):
B_1[i + 10] = A_1[i + 10] + 2
for i in T.serial(10, annotations={"key": "value"}):
B_1[i + 150] = A_1[i + 150] + 3
mod = partition_from_scheduled_tir(
before,
{
"s_tir.LoopPartition": {
"partition_const_loop": True,
}
},
)
tvm.ir.assert_structural_equal(mod["main"], after.with_attr("global_symbol", "main"))
def test_loop_partition_with_unit_loop_in_condition():
@T.prim_func(s_tir=True)
def before(
placeholder: T.Buffer((50176,), "int8"),
placeholder_1: T.Buffer((25088,), "int8"),
placeholder_2: T.Buffer((25088,), "int8"),
T_concat: T.Buffer((100352,), "int8"),
) -> None:
for k in range(1, annotations={"preserve_unit_loop": True}):
for i1 in T.serial(128, annotations={"pragma_loop_partition_hint": 1}):
for i2, i3 in T.grid(28, 28):
if 96 <= k * 128 + i1:
T_concat[k * i1 * 784 + i2 * 28 + i3] = placeholder_2[
i1 * 784 + i2 * 28 + i3 - 75264
]
if 64 <= k * 128 + i1 and k * 128 + i1 < 96:
T_concat[i1 * 784 + i2 * 28 + i3] = placeholder_1[
i1 * 784 + i2 * 28 + i3 - 50176
]
if k * 128 + i1 < 64:
T_concat[i1 * 784 + i2 * 28 + i3] = placeholder[i1 * 784 + i2 * 28 + i3]
@T.prim_func(s_tir=True)
def after(
placeholder: T.Buffer(50176, "int8"),
placeholder_1: T.Buffer(25088, "int8"),
placeholder_2: T.Buffer(25088, "int8"),
T_concat: T.Buffer(100352, "int8"),
) -> None:
placeholder_3 = T.decl_buffer((50176,), "int8", data=placeholder.data)
placeholder_1_1 = T.decl_buffer((25088,), "int8", data=placeholder_1.data)
placeholder_2_1 = T.decl_buffer((25088,), "int8", data=placeholder_2.data)
T_concat_1 = T.decl_buffer((100352,), "int8", data=T_concat.data)
for k in T.serial(1, annotations={"preserve_unit_loop": True}):
for i1, i2, i3 in T.grid(64, 28, 28):
T_concat_1[i1 * 784 + i2 * 28 + i3] = placeholder_3[i1 * 784 + i2 * 28 + i3]
for i1, i2, i3 in T.grid(32, 28, 28):
T_concat_1[i1 * 784 + i2 * 28 + i3 + 50176] = placeholder_1_1[
i1 * 784 + i2 * 28 + i3
]
for i1, i2, i3 in T.grid(32, 28, 28):
T_concat_1[i2 * 28 + i3] = placeholder_2_1[i1 * 784 + i2 * 28 + i3]
mod = partition_from_scheduled_tir(
before,
{
"s_tir.LoopPartition": {
"partition_const_loop": True,
}
},
)
tvm.ir.assert_structural_equal(mod["main"], after.with_attr("global_symbol", "main"))
@T.prim_func(s_tir=True)
def concat_func_single_point(
placeholder: T.Buffer((28, 64), "int8"),
placeholder_1: T.Buffer((28, 1), "int8"),
placeholder_2: T.Buffer((28, 63), "int8"),
T_concat: T.Buffer((28, 128), "int8"),
) -> None:
for i0 in range(28):
for i1 in T.serial(128, annotations={"pragma_loop_partition_hint": 1}):
if i1 > 63:
T_concat[i0, i1] = placeholder[i0, i1 - 64]
elif i1 == 63:
T_concat[i0, i1] = placeholder_1[i0, i1 - 63]
else:
T_concat[i0, i1] = placeholder_2[i0, i1]
@T.prim_func(s_tir=True)
def expected_partitioned_concat_single_point(
placeholder: T.Buffer((28, 64), "int8"),
placeholder_1: T.Buffer((28, 1), "int8"),
placeholder_2: T.Buffer((28, 63), "int8"),
T_concat: T.Buffer((28, 128), "int8"),
):
placeholder_3 = T.decl_buffer((1792,), "int8", data=placeholder.data)
placeholder_1_1 = T.decl_buffer((28,), "int8", data=placeholder_1.data)
placeholder_2_1 = T.decl_buffer((1764,), "int8", data=placeholder_2.data)
T_concat_1 = T.decl_buffer((3584,), "int8", data=T_concat.data)
for i0 in range(28):
for i1 in range(63):
T_concat_1[i0 * 128 + i1] = placeholder_2_1[i0 * 63 + i1]
T_concat_1[i0 * 128 + 63] = placeholder_1_1[i0]
for i1 in range(64):
T_concat_1[i0 * 128 + i1 + 64] = placeholder_3[i0 * 64 + i1]
@T.prim_func(s_tir=True)
def concat_func_start_point_equality(
placeholder: T.Buffer((28, 64), "int8"),
placeholder_1: T.Buffer((28, 1), "int8"),
placeholder_2: T.Buffer((28, 63), "int8"),
T_concat: T.Buffer((28, 128), "int8"),
) -> None:
for i0 in range(28):
for i1 in range(128, annotations={"pragma_loop_partition_hint": 1}):
if i1 == 0:
# Special case for i1 == 0
T_concat[i0, i1] = placeholder_1[i0, 0]
elif i1 < 64:
# Normal case for i1 in [1, 63]
T_concat[i0, i1] = placeholder_2[i0, i1]
else:
# Case for i1 in [64, 127]
T_concat[i0, i1] = placeholder[i0, i1 - 64]
@T.prim_func(s_tir=True)
def concat_func_start_point_equality_expected(
placeholder: T.Buffer((28, 64), "int8"),
placeholder_1: T.Buffer((28, 1), "int8"),
placeholder_2: T.Buffer((28, 63), "int8"),
T_concat: T.Buffer((28, 128), "int8"),
):
placeholder_3 = T.decl_buffer((1792,), "int8", data=placeholder.data)
placeholder_1_1 = T.decl_buffer((28,), "int8", data=placeholder_1.data)
placeholder_2_1 = T.decl_buffer((1764,), "int8", data=placeholder_2.data)
T_concat_1 = T.decl_buffer((3584,), "int8", data=T_concat.data)
for i0 in range(28):
T_concat_1[i0 * 128] = placeholder_1_1[i0]
for i1 in range(63):
T_concat_1[i0 * 128 + i1 + 1] = placeholder_2_1[i0 * 63 + i1 + 1]
for i1 in range(64):
T_concat_1[i0 * 128 + i1 + 64] = placeholder_3[i0 * 64 + i1]
@T.prim_func(s_tir=True)
def concat_func_end_point_equality(
placeholder: T.Buffer((28, 64), "int8"),
placeholder_1: T.Buffer((28, 1), "int8"),
placeholder_2: T.Buffer((28, 63), "int8"),
T_concat: T.Buffer((28, 128), "int8"),
) -> None:
for i0 in range(28):
for i1 in range(128, annotations={"pragma_loop_partition_hint": 1}):
if i1 == 127:
# Explicit equality check for the end point i1 == 127
T_concat[i0, i1] = placeholder_1[i0, 0]
elif i1 >= 64:
# Case for i1 in [64, 126]
T_concat[i0, i1] = placeholder[i0, i1 - 64]
else:
# Case for i1 in [0, 63]
T_concat[i0, i1] = placeholder_2[i0, i1]
@T.prim_func(s_tir=True)
def concat_func_end_point_equality_expected(
placeholder: T.Buffer((28, 64), "int8"),
placeholder_1: T.Buffer((28, 1), "int8"),
placeholder_2: T.Buffer((28, 63), "int8"),
T_concat: T.Buffer((28, 128), "int8"),
):
placeholder_3 = T.decl_buffer((1792,), "int8", data=placeholder.data)
placeholder_1_1 = T.decl_buffer((28,), "int8", data=placeholder_1.data)
placeholder_2_1 = T.decl_buffer((1764,), "int8", data=placeholder_2.data)
T_concat_1 = T.decl_buffer((3584,), "int8", data=T_concat.data)
for i0 in range(28):
for i1 in range(64):
T_concat_1[i0 * 128 + i1] = placeholder_2_1[i0 * 63 + i1]
for i1 in range(63):
T_concat_1[i0 * 128 + i1 + 64] = placeholder_3[i0 * 64 + i1]
T_concat_1[i0 * 128 + 127] = placeholder_1_1[i0]
@T.prim_func(s_tir=True)
def concat_func_edge_equalities(
placeholder: T.Buffer((28, 64), "int8"),
placeholder_1: T.Buffer((28, 1), "int8"),
placeholder_2: T.Buffer((28, 1), "int8"),
T_concat: T.Buffer((28, 66), "int8"),
) -> None:
for i0 in range(28):
for i1 in range(
66, annotations={"pragma_loop_partition_hint": 1}
): # Loop from 0 to 65 inclusive
if i1 == 0:
# Handle equality at the start of the range: i1 == 0
T_concat[i0, i1] = placeholder_2[i0, 0]
elif i1 == 65:
# Handle equality at the end of the range: i1 == 65
T_concat[i0, i1] = placeholder_1[i0, 0]
else:
# Copying from placeholder (from 0 to 63)
T_concat[i0, i1] = placeholder[i0, i1 - 1]
@T.prim_func(s_tir=True)
def concat_func_edge_equalities_expected(
placeholder: T.Buffer((28, 64), "int8"),
placeholder_1: T.Buffer((28, 1), "int8"),
placeholder_2: T.Buffer((28, 1), "int8"),
T_concat: T.Buffer((28, 66), "int8"),
):
placeholder_3 = T.decl_buffer((1792,), "int8", data=placeholder.data)
placeholder_1_1 = T.decl_buffer((28,), "int8", data=placeholder_1.data)
placeholder_2_1 = T.decl_buffer((28,), "int8", data=placeholder_2.data)
T_concat_1 = T.decl_buffer((1848,), "int8", data=T_concat.data)
for i0 in range(28):
T_concat_1[i0 * 66] = placeholder_2_1[i0]
for i1 in range(64):
T_concat_1[i0 * 66 + i1 + 1] = placeholder_3[i0 * 64 + i1]
T_concat_1[i0 * 66 + 65] = placeholder_1_1[i0]
@T.prim_func(s_tir=True)
def concat_five_buffers_with_equalities(
buffer_a: T.Buffer((28, 1), "int8"), # Used for i1 == 0
buffer_b: T.Buffer((28, 63), "int8"), # Fills i1 from 1 to 63
buffer_c: T.Buffer((28, 1), "int8"), # Used for i1 == 64
buffer_d: T.Buffer((28, 63), "int8"), # Fills i1 from 65 to 128
buffer_e: T.Buffer((28, 1), "int8"), # Used for i1 == 129
T_concat: T.Buffer((28, 129), "int8"),
) -> None:
for i0 in range(28):
for i1 in range(130, annotations={"pragma_loop_partition_hint": 1}):
if i1 == 0:
T_concat[i0, i1] = buffer_a[i0, 0]
elif i1 == 64:
T_concat[i0, i1] = buffer_c[i0, 0]
elif i1 == 129:
T_concat[i0, i1] = buffer_e[i0, 0]
elif i1 < 64:
T_concat[i0, i1] = buffer_b[i0, i1 - 1]
else: # i1 > 64 and i1 < 128
T_concat[i0, i1] = buffer_d[i0, i1 - 65]
@T.prim_func(s_tir=True)
def concat_five_buffers_with_equalities_expected(
buffer_a: T.Buffer((28, 1), "int8"), # Used for i1 == 0
buffer_b: T.Buffer((28, 63), "int8"), # Fills i1 from 1 to 63
buffer_c: T.Buffer((28, 1), "int8"), # Used for i1 == 64
buffer_d: T.Buffer((28, 63), "int8"), # Fills i1 from 65 to 128
buffer_e: T.Buffer((28, 1), "int8"), # Used for i1 == 129
T_concat: T.Buffer((28, 129), "int8"),
):
buffer_a_1 = T.decl_buffer((28,), "int8", data=buffer_a.data)
buffer_b_1 = T.decl_buffer((1764,), "int8", data=buffer_b.data)
buffer_c_1 = T.decl_buffer((28,), "int8", data=buffer_c.data)
buffer_d_1 = T.decl_buffer((1764,), "int8", data=buffer_d.data)
buffer_e_1 = T.decl_buffer((28,), "int8", data=buffer_e.data)
T_concat_1 = T.decl_buffer((3612,), "int8", data=T_concat.data)
for i0 in range(28):
T_concat_1[i0 * 129] = buffer_a_1[i0]
for i1 in range(63):
T_concat_1[i0 * 129 + i1 + 1] = buffer_b_1[i0 * 63 + i1]
T_concat_1[i0 * 129 + 64] = buffer_c_1[i0]
for i1 in range(64):
T_concat_1[i0 * 129 + i1 + 65] = buffer_d_1[i0 * 63 + i1]
T_concat_1[i0 * 129 + 129] = buffer_e_1[i0]
@T.prim_func(s_tir=True)
def nested_partition_with_single_points(A: T.Buffer((25,), "int32")):
for i in T.serial(5, annotations={"pragma_loop_partition_hint": 1}):
if i == 1:
for j in T.serial(5, annotations={"pragma_loop_partition_hint": 1}):
if j > 2:
A[i * 5 + j] = i * 5 + j
else:
for j in T.serial(5, annotations={"pragma_loop_partition_hint": 1}):
if j > 2:
A[i * 5 + j] = i * 15 + j
@T.prim_func(s_tir=True)
def nested_partition_with_single_points_expected(A: T.Buffer((25,), "int32")):
A_1 = T.decl_buffer((25,), "int32", data=A.data)
for j in range(2):
A_1[j + 3] = j + 3
for j in range(2):
A_1[j + 8] = j + 8
for i, j in T.grid(3, 2):
A_1[i * 5 + j + 13] = i * 15 + j + 33
@pytest.mark.parametrize(
"origin,expected",
[
(concat_func_single_point, expected_partitioned_concat_single_point),
(concat_func_start_point_equality, concat_func_start_point_equality_expected),
(concat_func_end_point_equality, concat_func_end_point_equality_expected),
(concat_func_edge_equalities, concat_func_edge_equalities_expected),
(concat_five_buffers_with_equalities, concat_five_buffers_with_equalities_expected),
(nested_partition_with_single_points, nested_partition_with_single_points_expected),
],
)
def test_single_point_partition(origin, expected):
origin = origin.with_attr({"global_symbol": "main"})
expected = expected.with_attr({"global_symbol": "main"})
mod = partition_from_scheduled_tir(
origin,
{
"s_tir.LoopPartition": {
"partition_const_loop": True,
"unroll_loop_with_partition_hint_no_interval": True,
}
},
)
tvm.ir.assert_structural_equal(mod["main"], expected)
def test_equation_on_floordiv():
@T.prim_func(s_tir=True)
def before(A: T.Buffer((2, 2, 20), "int32")):
for i in T.serial(5, annotations={"pragma_loop_partition_hint": 1}):
if i == 1:
for vv in T.vectorized(640, annotations={"pragma_loop_partition_hint": 1}):
if i * 2 + vv // 320 == 3:
A[i - 1, i * 2 + vv // 320 - 3, vv % 320 // 16] = 1
@T.prim_func(s_tir=True)
def expected(A: T.Buffer((2, 2, 20), "int32")):
for vv in T.vectorized(320):
A[0, 0, vv // 16] = 1
expected = expected.with_attr({"global_symbol": "main"})
after = partition_from_scheduled_tir(
before.with_attr("global_symbol", "main"), {}, do_flatten=False
)
tvm.ir.assert_structural_equal(after["main"], expected)
def test_ignore_loop_partition_hint():
"""Skip unroll body and prologue for pipeline case"""
@T.prim_func(s_tir=True)
def before(A: T.Buffer((10), "float32"), D: T.Buffer((10), "float32")):
B = T.decl_buffer([2], "float32")
C = T.decl_buffer([2], "float32")
for i in T.serial(12, annotations={"pragma_loop_partition_hint": 1}):
if T.ignore_loop_partition(i < 10):
B[i % 2] = A[i] + 1.0
if T.ignore_loop_partition(1 <= i and i < 11):
C[(i - 1) % 2] = B[(i - 1) % 2] + 2.0
if 2 <= i:
D[i - 2] = C[i % 2] + 3.0
@T.prim_func(s_tir=True)
def expected(A: T.Buffer((10), "float32"), D: T.Buffer((10), "float32")):
B = T.decl_buffer([2], "float32")
C = T.decl_buffer([2], "float32")
for i in range(2):
B[i] = A[i] + 1.0
if i == 1:
C[i - 1] = B[i - 1] + 2.0
for i in T.serial(10):
if i < 8:
B[i % 2] = A[i + 2] + 1.0
if i < 9:
C[(i + 1) % 2] = B[(i + 1) % 2] + 2.0
D[i] = C[i % 2] + 3.0
expected = expected.with_attr({"global_symbol": "main"})
after = partition_from_scheduled_tir(
before.with_attr({"global_symbol": "main"}), {}, do_flatten=False
)
tvm.ir.assert_structural_equal(after["main"], expected)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,112 @@
# 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, F821
import tvm
from tvm import s_tir
from tvm.script import tirx as T
# pylint: disable=no-self-argument
@tvm.script.ir_module
class WithInit:
@T.prim_func(s_tir=True)
def main(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [64, 64, 64])
B = T.match_buffer(b, [64])
for i0, j0 in T.grid(64, 64):
for k0 in T.serial(32, 64):
with T.sblock():
i, j, k = T.axis.remap("SRR", [i0, j0, k0])
with T.init():
B[i] = T.float32(0)
B[i] += A[i, j, k]
@tvm.script.ir_module
class WithBranch:
@T.prim_func(s_tir=True)
def main(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [64, 64, 64])
B = T.match_buffer(b, [64])
for i0, j0 in T.grid(64, 64):
for k0 in T.serial(32, 64):
with T.sblock():
i, j, k = T.axis.remap("SRR", [i0, j0, k0])
T.reads(A[i, j, k])
T.writes(B[i])
if (j == 0) and (k == 32):
B[i] = T.float32(0)
B[i] += A[i, j, k]
@tvm.script.ir_module
class InitWithMatchBuffer:
@T.prim_func(s_tir=True)
def main(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [64, 64, 64])
B = T.match_buffer(b, [64])
for i0, j0 in T.grid(64, 64):
for k0 in T.serial(32, 64):
with T.sblock():
i, j, k = T.axis.remap("SRR", [i0, j0, k0])
BB = T.match_buffer(B[i], ())
AA = T.match_buffer(A[i, 0:64, 0:64], (64, 64))
with T.init():
BB[()] = T.float32(0)
BB[()] += AA[j, k]
@tvm.script.ir_module
class BranchWithMatchBuffer:
@T.prim_func(s_tir=True)
def main(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [64, 64, 64])
B = T.match_buffer(b, [64])
for i0, j0 in T.grid(64, 64):
for k0 in T.serial(32, 64):
with T.sblock():
i, j, k = T.axis.remap("SRR", [i0, j0, k0])
T.reads(A[i, j, k])
T.writes(B[i])
BB = T.match_buffer(B[i], ())
AA = T.match_buffer(A[i, 0:64, 0:64], (64, 64))
if (j == 0) and (k == 32):
BB[()] = T.float32(0)
BB[()] += AA[j, k]
def test_lower_reduction():
origin_mod = WithInit
mod = tvm.s_tir.transform.LowerInitBlock()(origin_mod)
tvm.ir.assert_structural_equal(mod, WithBranch, True)
def test_lower_match_buffer():
origin_mod = InitWithMatchBuffer
mod = tvm.s_tir.transform.LowerInitBlock()(origin_mod)
tvm.ir.assert_structural_equal(mod, BranchWithMatchBuffer, True)
if __name__ == "__main__":
test_lower_reduction()
test_lower_match_buffer()
test_lower_te()
@@ -0,0 +1,571 @@
# 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
import tvm
import tvm.s_tir
import tvm.testing
from tvm.script import tirx as T
def _check(original, transformed):
mod = tvm.IRModule.from_expr(original.with_attr("global_symbol", "main"))
mod = tvm.s_tir.transform.LowerMatchBuffer()(mod)
mod = tvm.tirx.transform.StmtSimplify()(mod)
tvm.ir.assert_structural_equal(mod["main"], transformed.with_attr("global_symbol", "main"))
def _check_fail(original):
mod = tvm.IRModule.from_expr(original)
with pytest.raises(RuntimeError):
mod = tvm.s_tir.transform.LowerMatchBuffer()(mod)
@T.prim_func(s_tir=True)
def buffer_load_store(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (16, 16, 16))
C = T.match_buffer(c, (16, 16))
for i, j, k in T.grid(4, 16, 8):
with T.sblock():
T.reads(C[i * 4 : i * 4 + 4, k * 2 : k * 2 + 2])
T.writes(A[i * 4 : i * 4 + 4, j, k * 2 : k * 2 + 2])
sub_A = T.match_buffer(
A[i * 4 : i * 4 + 4, j, k * 2 : k * 2 + 2], (4, 1, 2), offset_factor=1
)
sub_C = T.match_buffer(C[i * 4 : i * 4 + 4, k * 2 : k * 2 + 2], (4, 2), offset_factor=1)
for ii, kk in T.grid(4, 2):
sub_A[ii, 0, kk] += sub_C[ii, kk]
@T.prim_func(s_tir=True)
def transformed_buffer_load_store(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (16, 16, 16))
C = T.match_buffer(c, (16, 16))
for i, j, k in T.grid(4, 16, 8):
with T.sblock():
T.reads(C[i * 4 : i * 4 + 4, k * 2 : k * 2 + 2])
T.writes(A[i * 4 : i * 4 + 4, j, k * 2 : k * 2 + 2])
for ii, kk in T.grid(4, 2):
A[i * 4 + ii, j, k * 2 + kk] += C[i * 4 + ii, k * 2 + kk]
# Dummy intrinsic whose arguments exercise match_buffer fields. TVMScript
# evaluates the call eagerly (to 0), so it must NOT be registered as an op:
# registering "tirx.intrin_test" only leaves a category-less op in the tirx
# registry, breaking the exactly-one-category invariant for later tests.
def intrin_test(data, elem_offset, stride_0, stride_1, shape_0, shape_1):
return 0
@T.prim_func(s_tir=True)
def opaque_access(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (32, 64, 128))
B = T.match_buffer(b, (64, 64, 64))
for i, j, k in T.grid(2, 64, 8):
with T.sblock():
T.reads([])
T.writes(A[i * 16 : i * 16 + 16, j, k * 16 : k * 16 + 16])
sub_A = T.match_buffer(
A[i * 16 : i * 16 + 16, j, k * 16 : k * 16 + 16],
(16, 1, 16),
strides=[8192, 128, 1],
offset_factor=1,
)
T.evaluate(
intrin_test(
sub_A.data,
sub_A.elem_offset,
sub_A.strides[0],
sub_A.strides[1],
sub_A.shape[0],
sub_A.shape[1],
)
)
for i, j, k in T.grid(64, 2, 8):
with T.sblock():
Bs_0 = T.int32()
Bs_1 = T.int32()
T.reads([])
T.writes(B[i, j * 32 : j * 32 + 32, k * 8 : k * 8 + 8])
sub_B = T.match_buffer(
B[i, j * 32 : j * 32 + 32, k * 8 : k * 8 + 8],
(32, 8),
strides=[Bs_0, Bs_1],
offset_factor=1,
)
T.evaluate(
intrin_test(
sub_B.data,
sub_B.elem_offset,
sub_B.strides[0],
sub_B.strides[1],
sub_B.shape[0],
sub_B.shape[1],
)
)
@T.prim_func(s_tir=True)
def transformed_opaque_access(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (32, 64, 128))
B = T.match_buffer(b, (64, 64, 64))
for i, j, k in T.grid(2, 64, 8):
with T.sblock():
T.reads([])
T.writes(A[i * 16 : i * 16 + 16, j, k * 16 : k * 16 + 16])
T.evaluate(
intrin_test(
A.data,
i * 131072 + j * 128 + k * 16,
8192,
128,
16,
1,
)
)
for i, j, k in T.grid(64, 2, 8):
with T.sblock():
T.reads([])
T.writes(B[i, j * 32 : j * 32 + 32, k * 8 : k * 8 + 8])
T.evaluate(
intrin_test(
B.data,
i * 4096 + j * 2048 + k * 8,
64,
1,
32,
8,
)
)
@T.prim_func(s_tir=True)
def high_dim_opaque_access(a: T.handle) -> None:
A = T.match_buffer(a, (16, 32, 64))
for i, j, k in T.grid(16, 2, 4):
with T.sblock():
As_0 = T.int32()
As_1 = T.int32()
T.reads([])
T.writes(A[i, j * 16 : j * 16 + 16, k * 16 : k * 16 + 16])
sub_A = T.match_buffer(
A[i, j * 16 : j * 16 + 16, k * 16 : k * 16 + 16],
(16, 16),
strides=[As_0, As_1],
offset_factor=1,
)
T.evaluate(
intrin_test(
sub_A.data,
sub_A.elem_offset,
sub_A.strides[0],
sub_A.strides[1],
sub_A.shape[0],
sub_A.shape[1],
)
)
@T.prim_func(s_tir=True)
def transformed_high_dim_opaque_access(a: T.handle) -> None:
A = T.match_buffer(a, (16, 32, 64))
for i, j, k in T.grid(16, 2, 4):
with T.sblock():
T.reads([])
T.writes(A[i, j * 16 : j * 16 + 16, k * 16 : k * 16 + 16])
T.evaluate(
intrin_test(
A.data,
i * 2048 + j * 1024 + k * 16,
64,
1,
16,
16,
)
)
@T.prim_func(s_tir=True)
def high_dim_opaque_access_with_source_strides(a: T.handle) -> None:
A = T.match_buffer(a, (16, 32, 64), strides=[2576, 80, 1])
for i, j, k in T.grid(16, 2, 4):
with T.sblock():
As_0 = T.int32()
As_1 = T.int32()
T.reads([])
T.writes(A[i, j * 16 : j * 16 + 16, k * 16 : k * 16 + 16])
sub_A = T.match_buffer(
A[i, j * 16 : j * 16 + 16, k * 16 : k * 16 + 16],
(16, 16),
strides=[As_0, As_1],
offset_factor=1,
)
T.evaluate(
intrin_test(
sub_A.data,
sub_A.elem_offset,
sub_A.strides[0],
sub_A.strides[1],
sub_A.shape[0],
sub_A.shape[1],
)
)
@T.prim_func(s_tir=True)
def transformed_high_dim_opaque_access_with_source_strides(a: T.handle) -> None:
A = T.match_buffer(a, (16, 32, 64), strides=[2576, 80, 1])
for i, j, k in T.grid(16, 2, 4):
with T.sblock():
T.reads([])
T.writes(A[i, j * 16 : j * 16 + 16, k * 16 : k * 16 + 16])
T.evaluate(
intrin_test(
A.data,
i * 2576 + j * 1280 + k * 16,
80,
1,
16,
16,
)
)
@T.prim_func(s_tir=True)
def recursive_match(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (64, 64, 64))
B = T.match_buffer(b, (64, 64, 64))
for i, j, k in T.grid(64, 4, 4):
with T.sblock():
T.reads([])
T.writes(
[
A[i, j * 16 : j * 16 + 16, k * 16 : k * 16 + 16],
B[i, j * 16 : j * 16 + 16, k * 16 : k * 16 + 16],
]
)
As_0 = T.int32()
As_1 = T.int32()
sub_A = T.match_buffer(
A[i, j * 16 : j * 16 + 16, k * 16 : k * 16 + 16],
(16, 16),
strides=[As_0, As_1],
offset_factor=1,
)
sub_B = T.match_buffer(
B[i, j * 16 : j * 16 + 16, k * 16 : k * 16 + 16],
(16, 16),
offset_factor=1,
)
for jj, kk in T.grid(4, 4):
with T.sblock():
T.reads([])
T.writes(
[
sub_A[jj * 4 : jj * 4 + 4, kk * 4 : kk * 4 + 4],
sub_B[jj * 4 : jj * 4 + 4, kk * 4 : kk * 4 + 4],
]
)
Ass_0 = T.int32()
Ass_1 = T.int32()
sub_sub_A = T.match_buffer(
sub_A[jj * 4 : jj * 4 + 4, kk * 4 : kk * 4 + 4],
(4, 4),
strides=[Ass_0, Ass_1],
offset_factor=1,
)
sub_sub_B = T.match_buffer(
sub_B[jj * 4 : jj * 4 + 4, kk * 4 : kk * 4 + 4],
(4, 4),
offset_factor=1,
)
T.evaluate(
intrin_test(
sub_sub_A.data,
sub_sub_A.elem_offset,
sub_sub_A.strides[0],
sub_sub_A.strides[1],
sub_sub_A.shape[0],
sub_sub_A.shape[1],
)
)
for jjj, kkk in T.grid(4, 4):
sub_sub_B[jjj, kkk] = 1
@T.prim_func(s_tir=True)
def transformed_recursive_match(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (64, 64, 64))
B = T.match_buffer(b, (64, 64, 64))
for i, j, k in T.grid(64, 4, 4):
with T.sblock():
T.reads([])
T.writes(
[
A[i, j * 16 : j * 16 + 16, k * 16 : k * 16 + 16],
B[i, j * 16 : j * 16 + 16, k * 16 : k * 16 + 16],
]
)
for jj, kk in T.grid(4, 4):
with T.sblock():
T.reads([])
T.writes(
[
A[
i,
j * 16 + jj * 4 : j * 16 + jj * 4 + 4,
k * 16 + kk * 4 : k * 16 + kk * 4 + 4,
],
B[
i,
j * 16 + jj * 4 : j * 16 + jj * 4 + 4,
k * 16 + kk * 4 : k * 16 + kk * 4 + 4,
],
]
)
T.evaluate(
intrin_test(
A.data,
i * 4096 + j * 1024 + jj * 256 + k * 16 + kk * 4,
64,
1,
4,
4,
)
)
for jjj, kkk in T.grid(4, 4):
B[i, j * 16 + jj * 4 + jjj, k * 16 + kk * 4 + kkk] = 1
@T.prim_func(s_tir=True)
def symbolic_match(a: T.handle, b: T.handle, n: T.int32, m: T.int32) -> None:
A = T.match_buffer(a, (n * m, m))
B = T.match_buffer(b, (n * 2, m * 4))
for i in range(0, n):
with T.sblock():
T.reads([])
T.writes([A[i * m : i * m + n, 0:m], B[i * n : i * n + 2, 0 : m * 4]])
Bs_0 = T.int32()
Bs_1 = T.int32()
sub_A = T.match_buffer(A[i * m : i * m + m, 0:m], (m, m), offset_factor=1)
sub_B = T.match_buffer(
B[i * n : i * n + 2, 0 : m * 4], (2, m * 4), strides=[Bs_0, Bs_1], offset_factor=1
)
for ii, jj in T.grid(m, m):
sub_A[ii, jj] = 1
for j in range(0, 4):
T.evaluate(
intrin_test(
sub_B.data,
sub_B.elem_offset,
sub_B.strides[0],
sub_B.strides[1],
sub_B.shape[0],
sub_B.shape[1],
)
)
@T.prim_func(s_tir=True)
def transformed_symbolic_match(a: T.handle, b: T.handle, n: T.int32, m: T.int32) -> None:
A = T.match_buffer(a, (n * m, m))
B = T.match_buffer(b, (n * 2, m * 4))
for i in range(0, n):
with T.sblock():
T.reads([])
T.writes([A[i * m : i * m + n, 0:m], B[i * n : i * n + 2, 0 : m * 4]])
for ii, jj in T.grid(m, m):
A[i * m + ii, jj] = 1
for j in range(0, 4):
T.evaluate(
intrin_test(
B.data,
i * n * (m * 4),
m * 4,
1,
2,
m * 4,
)
)
@T.prim_func(s_tir=True)
def rank0_buffer(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (8, 8))
B = T.match_buffer(b, (8, 8))
for i, j in T.grid(8, 8):
with T.sblock():
T.reads([])
T.writes([A[i, j], B[i, j]])
sub_A = T.match_buffer(A[i, j], (), offset_factor=1)
sub_B = T.match_buffer(B[i, j], (), offset_factor=1)
sub_A[()] = 1
T.evaluate(
intrin_test(
sub_B.data,
sub_B.elem_offset,
0,
0,
0,
0,
)
)
@T.prim_func(s_tir=True)
def transformed_rank0_buffer(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, (8, 8))
B = T.match_buffer(b, (8, 8))
for i, j in T.grid(8, 8):
with T.sblock():
T.reads([])
T.writes([A[i, j], B[i, j]])
A[i, j] = 1
T.evaluate(
intrin_test(
B.data,
i * 8 + j,
0,
0,
0,
0,
)
)
@T.prim_func(s_tir=True)
def fail_match_load(a: T.handle) -> None:
A = T.match_buffer(a, (8, 8))
for i, j in T.grid(8, 8):
with T.sblock():
T.reads(A[i, j])
T.writes([])
sub_A = T.match_buffer(A[i, j], (), elem_offset=0)
T.evaluate(sub_A[()])
@T.prim_func(s_tir=True)
def fail_match_store(a: T.handle) -> None:
A = T.match_buffer(a, (8, 8))
for i, j in T.grid(8, 8):
with T.sblock():
T.reads([])
T.writes(A[i, j])
sub_A = T.match_buffer(A[i, j], (), elem_offset=0)
sub_A[()] = 1
# well-formed checker complains about redefinition of a stride variable
@T.prim_func(check_well_formed=False, s_tir=True)
def fail_buffer_bind(a: T.handle) -> None:
A = T.match_buffer(a, (8, 8))
for i, j in T.grid(8, 2):
with T.sblock():
stride = T.int32()
sub_A = T.match_buffer(
A[i, j * 4 : j * 4 + 4], (1, 4), strides=[stride, stride], offset_factor=1
)
for jj in range(0, 4):
sub_A[i, j * 4 + jj] = 1
# well-formed checker complains about redefinition of a stride variable
@T.prim_func(check_well_formed=False, s_tir=True)
def fail_match_func_param(a: T.handle, m: T.int32, n: T.int32) -> None:
A = T.match_buffer(a, (8, 8))
for i, j in T.grid(8, 2):
with T.sblock():
sub_A = T.match_buffer(A[i, j * 4 : j * 4 + 4], (1, 4), strides=[m, n], offset_factor=1)
for jj in range(0, 4):
sub_A[i, j * 4 + jj] = 1
def test_buffer_load_store():
_check(buffer_load_store, transformed_buffer_load_store)
def test_opaque_access():
_check(opaque_access, transformed_opaque_access)
def test_high_dim_opaque_access():
_check(high_dim_opaque_access, transformed_high_dim_opaque_access)
_check(
high_dim_opaque_access_with_source_strides,
transformed_high_dim_opaque_access_with_source_strides,
)
def test_recursive_match():
_check(recursive_match, transformed_recursive_match)
def test_symbolic_match():
_check(symbolic_match, transformed_symbolic_match)
def test_rank0_buffer():
_check(rank0_buffer, transformed_rank0_buffer)
def test_fail_load_store():
_check_fail(fail_match_load)
_check_fail(fail_match_store)
def test_fail_buffer_bind():
_check_fail(fail_buffer_bind)
def test_fail_match_func_param():
_check_fail(fail_match_func_param)
@T.prim_func(s_tir=True)
def scalar_match_buffer_type_coercion(a: T.handle) -> None:
A = T.match_buffer(a, (8, 8))
for i, j in T.grid(8, 8):
with T.sblock(""):
vi = T.axis.spatial(8, i)
vj = T.axis.spatial(8, j)
T.reads()
T.writes(A[vi, vj])
# Create scalar match buffer from single element - this triggers type coercion
scalar_buf = T.match_buffer(A[vi, vj], (), offset_factor=1)
scalar_buf[()] = T.float32(1.0)
@T.prim_func(s_tir=True)
def transformed_scalar_match_buffer_type_coercion(a: T.handle) -> None:
A = T.match_buffer(a, (8, 8))
for i, j in T.grid(8, 8):
with T.sblock(""):
vi = T.axis.spatial(8, i)
vj = T.axis.spatial(8, j)
T.reads()
T.writes(A[vi, vj])
# Scalar match_buffer eliminated, direct assignment
A[vi, vj] = T.float32(1.0)
def test_scalar_match_buffer_type_coercion():
_check(scalar_match_buffer_type_coercion, transformed_scalar_match_buffer_type_coercion)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,429 @@
# 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
import tvm.s_tir
import tvm.testing
from tvm.script import tirx as T
def _check(original, transformed):
func = original
mod = tvm.IRModule.from_expr(func.with_attr("global_symbol", "main"))
mod = tvm.s_tir.transform.LowerOpaqueBlock()(mod)
mod = tvm.tirx.transform.StmtSimplify()(mod)
tvm.ir.assert_structural_equal(
mod["main"], transformed.with_attr("global_symbol", "main"), True
)
@T.prim_func(s_tir=True)
def compacted_elementwise_func(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (16, 16), "float32")
C = T.match_buffer(c, (16, 16), "float32")
for i in range(0, 16):
with T.sblock():
T.reads(A[i, 0:16])
T.writes(C[i, 0:16])
B = T.sblock_alloc_buffer([1, 16], "float32", scope="global")
for j in range(0, 16):
with T.sblock():
T.reads(A[i, j])
T.writes(B[0, j])
B[0, j] = A[i, j] + 1.0
for j in range(0, 16):
with T.sblock():
T.reads(B[0, j])
T.writes(C[i, j])
C[i, j] = B[0, j] * 2.0
@T.prim_func(s_tir=True)
def transformed_elementwise_func(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (16, 16), "float32")
C = T.match_buffer(c, (16, 16), "float32")
for i in T.serial(0, 16):
B_new = T.alloc_buffer(
[1, 16],
"float32",
annotations={"buffer_allocated_addr": [], "buffer_data_alignment": 64},
)
for j in T.serial(0, 16):
B_new[0, j] = A[i, j] + 1.0
for j in T.serial(0, 16):
C[i, j] = B_new[0, j] * 2.0
@T.prim_func(s_tir=True)
def compacted_gpu_func(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (16, 16), "float32")
C = T.match_buffer(c, (16, 16), "float32")
for i0 in T.thread_binding(0, 4, thread="blockIdx.x"):
for i1 in T.thread_binding(0, 2, thread="threadIdx.x"):
for i2 in T.thread_binding(0, 2, thread="vthread"):
with T.sblock():
T.reads(A[i0 * 4 + i1 * 2 + i2, 0:16])
T.writes(C[i0 * 4 + i1 * 2 + i2, 0:16])
B = T.sblock_alloc_buffer([1, 16], "float32", scope="local")
for j in range(0, 16):
with T.sblock():
T.reads(A[i0 * 4 + i1 * 2 + i2, j])
T.writes(B[0, j])
B[0, j] = A[i0 * 4 + i1 * 2 + i2, j] + 1.0
for j in range(0, 16):
with T.sblock():
T.reads(B[0, j])
T.writes(C[i0 * 4 + i1 * 2 + i2, j])
C[i0 * 4 + i1 * 2 + i2, j] = B[0, j] * 2.0
@T.prim_func(s_tir=True)
def transformed_gpu_func(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (16, 16), "float32")
C = T.match_buffer(c, (16, 16), "float32")
i0 = T.env_thread("blockIdx.x")
i1 = T.env_thread("threadIdx.x")
i2 = T.env_thread("vthread")
T.launch_thread(i0, 4)
T.launch_thread(i1, 2)
T.launch_thread(i2, 2)
B = T.alloc_buffer(
[1, 16],
"float32",
scope="local",
annotations={"buffer_allocated_addr": [], "buffer_data_alignment": 64},
)
for j in range(0, 16):
B[0, j] = A[i0 * 4 + i1 * 2 + i2, j] + 1.0
for j in range(0, 16):
C[i0 * 4 + i1 * 2 + i2, j] = B[0, j] * 2.0
@T.prim_func(s_tir=True)
def compacted_symbolic_func(a: T.handle, c: T.handle, n: T.int32, m: T.int32) -> None:
A = T.match_buffer(a, (n, m), "float32")
C = T.match_buffer(c, (n, m), "float32")
for i in range(0, n):
with T.sblock():
T.reads(A[i, m])
T.writes(C[i, m])
B = T.sblock_alloc_buffer((m,), "float32", scope="global")
for j in range(0, m):
with T.sblock():
T.reads(A[i, j])
T.writes(B[j])
B[j] = A[i, j] + 1.0
for j in range(0, m):
with T.sblock():
T.reads(B[j])
T.writes(C[i, j])
C[i, j] = B[j] * 2.0
@T.prim_func(s_tir=True)
def transformed_symbolic_func(a: T.handle, c: T.handle, n: T.int32, m: T.int32) -> None:
A = T.match_buffer(a, (n, m), "float32")
C = T.match_buffer(c, (n, m), "float32")
for i in range(0, n):
B = T.alloc_buffer(
[m],
"float32",
annotations={"buffer_allocated_addr": [], "buffer_data_alignment": 64},
)
for j in range(0, m):
B[j] = A[i, j] + 1.0
for j in range(0, m):
C[i, j] = B[j] * 2.0
@T.prim_func(s_tir=True)
def compacted_predicate_func(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (32), "float32")
C = T.match_buffer(c, (32), "float32")
for i, j in T.grid(5, 7):
with T.sblock():
T.reads(A[i * 7 + j])
T.writes(C[i * 7 + j])
T.where(i * 7 + j < 32)
C[i * 7 + j] = A[i * 7 + j] + 1.0
@T.prim_func(s_tir=True)
def transformed_predicate_func(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (32), "float32")
C = T.match_buffer(c, (32), "float32")
for i, j in T.grid(5, 7):
if i * 7 + j < 32:
C[i * 7 + j] = A[i * 7 + j] + 1.0
@T.prim_func(s_tir=True)
def compacted_unit_loop_func(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (32), "float32")
C = T.match_buffer(c, (32), "float32")
for x, y, z in T.grid(4, 1, 8):
with T.sblock():
T.reads(A[x * 8 + y * 8 + z])
T.writes(C[x * 8 + y * 8 + z])
C[x * 8 + y * 8 + z] = A[x * 8 + y * 8 + z] + 1.0
@T.prim_func(s_tir=True)
def transformed_unit_loop_func(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (32), "float32")
C = T.match_buffer(c, (32), "float32")
for x, z in T.grid(4, 8):
C[x * 8 + z] = A[x * 8 + z] + 1.0
@T.prim_func(s_tir=True)
def compacted_multi_alloc_func(a: T.handle, d: T.handle) -> None:
A = T.match_buffer(a, (32), "float32")
D = T.match_buffer(d, (32), "float32")
for i in range(0, 32):
with T.sblock():
T.reads(A[i])
T.writes(D[i])
B = T.sblock_alloc_buffer((32,), scope="global")
C = T.sblock_alloc_buffer((32,), scope="global")
B[i] = A[i] + 1.0
C[i] = A[i] + B[i]
D[i] = C[i] * 2.0
@T.prim_func(s_tir=True)
def transformed_multi_alloc_func(a: T.handle, d: T.handle) -> None:
A = T.match_buffer(a, (32), "float32")
D = T.match_buffer(d, (32), "float32")
for i in range(0, 32):
B = T.alloc_buffer(
(32,),
"float32",
annotations={"buffer_allocated_addr": [], "buffer_data_alignment": 64},
)
C = T.alloc_buffer(
(32,),
"float32",
annotations={"buffer_allocated_addr": [], "buffer_data_alignment": 64},
)
B[i] = A[i] + 1.0
C[i] = A[i] + B[i]
D[i] = C[i] * 2.0
@T.prim_func(s_tir=True)
def compacted_strided_buffer_func(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (16, 16), "float32")
C = T.match_buffer(c, (16, 16), "float32")
for i0 in range(0, 4):
with T.sblock():
T.reads(A[i0 * 4 : i0 * 4 + 4, 0:16])
T.writes(C[i0 * 4 : i0 * 4 + 4, 0:16])
B = T.sblock_alloc_buffer([4, 16], "float32", strides=[17, 1], scope="global")
for i1 in range(0, 4):
for j in range(0, 16):
with T.sblock():
T.reads(A[i0 * 4 + i1, j])
T.writes(B[i1, j])
B[i1, j] = A[i0 * 4 + i1, j] + 1.0
for i1 in range(0, 4):
for j in range(0, 16):
with T.sblock():
T.reads(B[i1, j])
T.writes(C[i0 * 4 + i1, j])
C[i0 * 4 + i1, j] = B[i1, j] * 2.0
@T.prim_func(s_tir=True)
def transformed_strided_buffer_func(
A: T.Buffer((16, 16), "float32"), C: T.Buffer((16, 16), "float32")
) -> None:
# body
for i0 in T.serial(4):
B = T.alloc_buffer(
[4, 16],
"float32",
strides=[17, 1],
annotations={"buffer_allocated_addr": [], "buffer_data_alignment": 64},
)
for i1, j in T.grid(4, 16):
B[i1, j] = A[i0 * 4 + i1, j] + T.float32(1)
for i1, j in T.grid(4, 16):
C[i0 * 4 + i1, j] = B[i1, j] * T.float32(2)
@T.prim_func(s_tir=True)
def compacted_symbolic_strided_buffer_func(a: T.handle) -> None:
n = T.int32()
A = T.match_buffer(a, (1, n, 10240))
padded_size = T.meta_var(T.min((n + 63) // 64 * 64, 96))
# with T.sblock("root"):
for i, j, k in T.grid(((n + 63) // 64 * 4 + 7) // 8, 2, 160):
with T.sblock(""):
A_pad_shared_dyn = T.sblock_alloc_buffer(
(1, padded_size, 64), strides=(72 * padded_size, 72, 1), scope="shared.dyn"
)
for ax0, ax1 in T.grid(96, 64):
with T.sblock("A_pad_shared.dyn"):
T.where(i * 128 + j * 32 + ax0 < (n + 63) // 64 * 64)
A_pad_shared_dyn[0, ax0, ax1] = T.if_then_else(
i * 128 + j * 32 + ax0 < n,
A[0, i * 128 + j * 32 + ax0, k * 64 + ax1],
T.float32(0),
)
@T.prim_func(s_tir=True)
def transformed_symbolic_strided_buffer_func(a: T.handle):
n = T.int32()
A = T.match_buffer(a, (1, n, 10240))
for i, j, k in T.grid(((n + 63) // 64 * 4 + 7) // 8, 2, 160):
A_pad_shared_dyn = T.alloc_buffer(
(1, T.min((n + 63) // 64 * 64, 96), 64),
strides=(72 * T.min((n + 63) // 64 * 64, 96), 72, 1),
scope="shared.dyn",
annotations={"buffer_allocated_addr": [], "buffer_data_alignment": 64},
)
for ax0, ax1 in T.grid(96, 64):
if i * 128 + j * 32 + ax0 < (n + 63) // 64 * 64:
A_pad_shared_dyn[0, ax0, ax1] = T.if_then_else(
i * 128 + j * 32 + ax0 < n,
A[0, i * 128 + j * 32 + ax0, k * 64 + ax1],
T.float32(0),
)
@T.prim_func(s_tir=True)
def annotated_loops(a: T.handle) -> None:
A = T.match_buffer(a, (16,), "float32")
for i in range(0, 16, annotations={"pragma_1": "str_value", "pragma_2": 1, "pragma_3": 0.0}):
A[i] = 0.0
@T.prim_func(s_tir=True)
def boolean_handling_before(a: T.Buffer(10, "bool"), b: T.Buffer(10, "bool")) -> None:
for i0 in T.serial(10):
with T.sblock("b"):
T.reads(a[i0])
T.writes(b[i0])
b[i0] = a[i0]
@T.prim_func(s_tir=True)
def boolean_handling_after(a: T.Buffer(10, "bool"), b: T.Buffer(10, "bool")) -> None:
# body
for i0 in T.serial(10):
b[i0] = a[i0]
def test_elementwise():
_check(compacted_elementwise_func, transformed_elementwise_func)
def test_gpu_workload():
_check(compacted_gpu_func, transformed_gpu_func)
def test_symbolic_shape():
_check(compacted_symbolic_func, transformed_symbolic_func)
def test_predicate():
_check(compacted_predicate_func, transformed_predicate_func)
def test_unit_loops():
_check(compacted_unit_loop_func, transformed_unit_loop_func)
def test_multi_alloc():
_check(compacted_multi_alloc_func, transformed_multi_alloc_func)
def test_strided_buffer():
_check(compacted_strided_buffer_func, transformed_strided_buffer_func)
def test_symbolic_strided_buffer():
_check(compacted_symbolic_strided_buffer_func, transformed_symbolic_strided_buffer_func)
def test_annotated_loops():
mod = tvm.IRModule.from_expr(annotated_loops.with_attr("global_symbol", "main"))
mod = tvm.s_tir.transform.LowerOpaqueBlock()(mod)
attr1 = mod["main"].body
attr2 = attr1.body
attr3 = attr2.body
assert attr1.attr_key == "pragma_1" and attr1.value == "str_value"
assert attr2.attr_key == "pragma_2"
tvm.ir.assert_structural_equal(attr2.value, tvm.tirx.IntImm("int32", 1))
assert attr3.attr_key == "pragma_3"
tvm.ir.assert_structural_equal(attr3.value, tvm.tirx.FloatImm("float32", 0.0))
def test_annotated_block():
@T.prim_func(s_tir=True)
def annotated_block() -> None:
with T.sblock():
T.sblock_attr({"pragma_1": "str_value", "pragma_2": 1, "pragma_3": 0.0})
T.evaluate(0)
mod = tvm.IRModule.from_expr(annotated_block.with_attr("global_symbol", "main"))
mod = tvm.s_tir.transform.LowerOpaqueBlock()(mod)
attr1 = mod["main"].body
attr2 = attr1.body
attr3 = attr2.body
assert attr1.attr_key == "pragma_1" and attr1.value == "str_value"
assert attr2.attr_key == "pragma_2"
tvm.ir.assert_structural_equal(attr2.value, tvm.tirx.IntImm("int32", 1))
assert attr3.attr_key == "pragma_3"
tvm.ir.assert_structural_equal(attr3.value, tvm.tirx.FloatImm("float32", 0.0))
def test_preserved_annotations():
@T.prim_func(s_tir=True)
def before(A: T.Buffer(8, "float32"), B: T.Buffer(8, "float32")):
for i in T.serial(8, annotations={"k_0": 1, "k_1": [2, 3], "k_2": 3.14}):
with T.sblock("block"):
T.sblock_attr({"k_3": "oops"})
B[i] = A[i] + 1.0
@T.prim_func(s_tir=True)
def after(A: T.Buffer(8, "float32"), B: T.Buffer(8, "float32")):
for i in T.serial(8, annotations={"k_0": 1, "k_1": [2, 3], "k_2": 3.14}):
B[i] = A[i] + 1.0
mod = tvm.IRModule.from_expr(before.with_attr("global_symbol", "main"))
mod = tvm.s_tir.transform.LowerOpaqueBlock()(mod)
tvm.ir.assert_structural_equal(mod["main"], after.with_attr("global_symbol", "main"))
def test_boolean_handling():
_check(boolean_handling_before, boolean_handling_after)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,523 @@
# 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, F841
import tvm
import tvm.testing
from tvm import s_tir
from tvm.script import ir as I
from tvm.script import tirx as T
def _has_volatile_alloc_buffer(mod):
has_volatile_alloc = False
def visit(node):
nonlocal has_volatile_alloc
if isinstance(node, tvm.tirx.AllocBuffer) and "tirx.volatile" in node.annotations:
has_volatile_alloc = has_volatile_alloc or node.annotations["tirx.volatile"] is True
tvm.tirx.stmt_functor.post_order_visit(mod["main"].body, visit)
return has_volatile_alloc
def test_basic():
transform = tvm.s_tir.transform.LowerThreadAllreduce()
@I.ir_module
class Before:
@T.prim_func(private=True, s_tir=True)
def main(A: T.Buffer((128, 32), "float32"), B: T.Buffer(128, "float32")):
T.func_attr({"target": T.target("cuda", host="llvm")})
A_flat = T.decl_buffer(4096, data=A.data)
for i in range(128):
threadIdx_x = T.launch_thread("threadIdx.x", 32)
reduce = T.alloc_buffer((1,), scope="local")
reduce_1 = T.decl_buffer(1, data=reduce.data, scope="local")
with T.attr(
T.comm_reducer(lambda x, y: x + y, [T.float32(0)]),
"reduce_scope",
T.int32(0),
):
T.tvm_thread_allreduce(
T.uint32(1),
A_flat[0],
T.bool(True),
reduce_1[0],
threadIdx_x,
)
if threadIdx_x == 0:
B[i] = reduce_1[0]
After = transform(Before)
# Verify the transform produces valid output (not checking exact structure
# since the output depends on the allreduce lowering implementation)
assert After is not None
# Run script roundtrip to verify it can be printed and reparsed
After_script = After.script()
assert "tvm_warp_shuffle" in After_script
def test_basic_with_decl_buffer():
transform = tvm.s_tir.transform.LowerThreadAllreduce()
@I.ir_module
class Before:
@T.prim_func(private=True, s_tir=True)
def main(A: T.Buffer((128, 32), "float32"), B: T.Buffer(128, "float32")):
T.func_attr({"target": T.target("cuda", host="llvm")})
A_flat = T.decl_buffer(4096, data=A.data)
for i in range(128):
threadIdx_x = T.launch_thread("threadIdx.x", 32)
reduce = T.decl_buffer(1, dtype="float32", scope="local")
with T.attr(
T.comm_reducer(lambda x, y: x + y, [T.float32(0)]),
"reduce_scope",
T.int32(0),
):
T.tvm_thread_allreduce(
T.uint32(1),
A_flat[0],
T.bool(True),
reduce[0],
threadIdx_x,
)
if threadIdx_x == 0:
B[i] = reduce[0]
After = transform(Before)
assert After is not None
After_script = After.script()
assert "tvm_warp_shuffle" in After_script
def test_reduce_summation():
transform = tvm.s_tir.transform.LowerThreadAllreduce()
@I.ir_module
class Before:
@T.prim_func(private=True, s_tir=True)
def main(A: T.Buffer((128, 128), "float32"), B: T.Buffer(128, "float32")):
T.func_attr({"target": T.target("cuda", host="llvm")})
A_flat = T.decl_buffer(16384, data=A.data)
for i in range(128):
threadIdx_x = T.launch_thread("threadIdx.x", 32)
normal_reduce = T.alloc_buffer((1,), scope="local")
normal_reduce_1 = T.decl_buffer(1, data=normal_reduce.data, scope="local")
reduce = T.alloc_buffer((1,), scope="local")
reduce_1 = T.decl_buffer(1, data=reduce.data, scope="local")
normal_reduce_1[0] = T.float32(0)
for ko in range(4):
normal_reduce_1[0] = (
normal_reduce_1[0] + A_flat[i * 128 + ko * 32 + threadIdx_x]
)
with T.attr(
T.comm_reducer(lambda x, y: x + y, [T.float32(0)]),
"reduce_scope",
T.int32(0),
):
T.tvm_thread_allreduce(
T.uint32(1),
normal_reduce_1[0],
T.bool(True),
reduce_1[0],
threadIdx_x,
)
if threadIdx_x == 0:
B[i] = reduce_1[0]
After = transform(Before)
assert After is not None
After_script = After.script()
assert "tvm_warp_shuffle" in After_script
def test_multi_group_reduction():
transform = tvm.s_tir.transform.LowerThreadAllreduce()
@I.ir_module
class Before:
@T.prim_func(private=True, s_tir=True)
def main(A: T.Buffer((32, 32), "float32"), B: T.Buffer((32,), "float32")):
T.func_attr({"target": T.target("cuda", host="llvm")})
threadIdx_y = T.launch_thread("threadIdx.y", 32)
cross_thread_B = T.alloc_buffer((1,), scope="local")
threadIdx_x = T.launch_thread("threadIdx.x", 32)
cross_thread_B_1 = T.decl_buffer((1,), data=cross_thread_B.data, scope="local")
with T.attr(
T.comm_reducer(lambda x0, y0: x0 + y0, [T.float32(0)]),
"reduce_scope",
T.int32(0),
):
A_1 = T.decl_buffer((1024,), data=A.data)
T.tvm_thread_allreduce(
T.uint32(1),
A_1[threadIdx_y * 32 + threadIdx_x],
T.bool(True),
cross_thread_B_1[0],
threadIdx_x,
)
if threadIdx_x == 0:
B_1 = T.decl_buffer((32,), data=B.data)
B_1[threadIdx_y] = cross_thread_B_1[0]
After = transform(Before)
assert After is not None
After_script = After.script()
assert "tvm_warp_shuffle" in After_script
def test_multi_group_mask1():
transform = tvm.s_tir.transform.LowerThreadAllreduce()
@I.ir_module
class Before:
@T.prim_func(private=True, s_tir=True)
def main(A: T.Buffer((32, 8), "float32"), B: T.Buffer((32,), "float32")):
T.func_attr({"target": T.target("cuda", host="llvm")})
threadIdx_y = T.launch_thread("threadIdx.y", 32)
cross_thread_B = T.alloc_buffer((1,), scope="local")
threadIdx_x = T.launch_thread("threadIdx.x", 8)
cross_thread_B_1 = T.decl_buffer((1,), data=cross_thread_B.data, scope="local")
with T.attr(
T.comm_reducer(lambda x0, y0: x0 + y0, [T.float32(0)]),
"reduce_scope",
T.int32(0),
):
A_1 = T.decl_buffer((256,), data=A.data)
T.tvm_thread_allreduce(
T.uint32(1),
A_1[threadIdx_y * 8 + threadIdx_x],
T.bool(True),
cross_thread_B_1[0],
threadIdx_x,
)
if threadIdx_x == 0:
B_1 = T.decl_buffer((32,), data=B.data)
B_1[threadIdx_y] = cross_thread_B_1[0]
After = transform(Before)
assert After is not None
After_script = After.script()
assert "tvm_warp_shuffle" in After_script
def test_multi_warp_reduce1():
transform = tvm.s_tir.transform.LowerThreadAllreduce()
@I.ir_module
class Before:
@T.prim_func(private=True, s_tir=True)
def main(A: T.Buffer((128, 128), "float32"), B: T.Buffer((128,), "float32")):
T.func_attr({"target": T.target("cuda", host="llvm")})
for i in range(128):
threadIdx_x = T.launch_thread("threadIdx.x", 128)
cross_thread_B = T.alloc_buffer((1,), scope="local")
cross_thread_B_1 = T.decl_buffer((1,), data=cross_thread_B.data, scope="local")
with T.attr(
T.comm_reducer(lambda x0, y0: x0 + y0, [T.float32(0)]),
"reduce_scope",
T.int32(0),
):
A_1 = T.decl_buffer((16384,), data=A.data)
T.tvm_thread_allreduce(
T.uint32(1),
A_1[i * 128 + threadIdx_x],
T.bool(True),
cross_thread_B_1[0],
threadIdx_x,
)
if threadIdx_x == 0:
B_1 = T.decl_buffer((128,), data=B.data)
B_1[i] = cross_thread_B_1[0]
After = transform(Before)
assert After is not None
After_script = After.script()
assert "tvm_warp_shuffle" in After_script
assert "tvm_storage_sync" in After_script # multi-warp needs shared sync
def test_multi_warp_reduce2():
transform = tvm.s_tir.transform.LowerThreadAllreduce()
@I.ir_module
class Before:
@T.prim_func(private=True, s_tir=True)
def main(A: T.Buffer((1, 1024), "float32"), B: T.Buffer((1,), "float32")):
T.func_attr({"target": T.target("cuda", host="llvm")})
threadIdx_x = T.launch_thread("threadIdx.x", 1024)
cross_thread_B = T.alloc_buffer((1,), scope="local")
cross_thread_B_1 = T.decl_buffer((1,), data=cross_thread_B.data, scope="local")
with T.attr(
T.comm_reducer(lambda x0, y0: x0 + y0, [T.float32(0)]),
"reduce_scope",
T.int32(0),
):
A_1 = T.decl_buffer((1024,), data=A.data)
T.tvm_thread_allreduce(
T.uint32(1), A_1[threadIdx_x], T.bool(True), cross_thread_B_1[0], threadIdx_x
)
if threadIdx_x == 0:
B_1 = T.decl_buffer((1,), data=B.data)
B_1[0] = cross_thread_B_1[0]
After = transform(Before)
assert After is not None
After_script = After.script()
assert "tvm_warp_shuffle" in After_script
assert "tvm_storage_sync" in After_script
def test_multi_group_multi_warp_reduction():
transform = tvm.s_tir.transform.LowerThreadAllreduce()
@I.ir_module
class Before:
@T.prim_func(private=True, s_tir=True)
def main(A: T.Buffer((4, 128), "float32"), B: T.Buffer((4,), "float32")):
T.func_attr({"target": T.target("cuda", host="llvm")})
threadIdx_y = T.launch_thread("threadIdx.y", 4)
cross_thread_B = T.alloc_buffer((1,), scope="local")
threadIdx_x = T.launch_thread("threadIdx.x", 128)
cross_thread_B_1 = T.decl_buffer((1,), data=cross_thread_B.data, scope="local")
with T.attr(
T.comm_reducer(lambda x0, y0: x0 + y0, [T.float32(0)]),
"reduce_scope",
T.int32(0),
):
A_1 = T.decl_buffer((512,), data=A.data)
T.tvm_thread_allreduce(
T.uint32(1),
A_1[threadIdx_y * 128 + threadIdx_x],
T.bool(True),
cross_thread_B_1[0],
threadIdx_x,
)
if threadIdx_x == 0:
B_1 = T.decl_buffer((4,), data=B.data)
B_1[threadIdx_y] = cross_thread_B_1[0]
After = transform(Before)
assert After is not None
After_script = After.script()
assert "tvm_warp_shuffle" in After_script
assert "tvm_storage_sync" in After_script
def test_multi_group_multi_warp_predicated_reduction():
transform = tvm.s_tir.transform.LowerThreadAllreduce()
@I.ir_module
class Before:
@T.prim_func(private=True, s_tir=True)
def main(A: T.Buffer((2, 70), "float32"), B: T.Buffer((2,), "float32")):
T.func_attr({"target": T.target("cuda", host="llvm")})
threadIdx_y = T.launch_thread("threadIdx.y", 2)
in_thread_B = T.alloc_buffer((1,), scope="local")
cross_thread_B = T.alloc_buffer((1,), scope="local")
threadIdx_x = T.launch_thread("threadIdx.x", 512)
in_thread_B_1 = T.decl_buffer((1,), data=in_thread_B.data, scope="local")
in_thread_B_1[0] = T.float32(0)
if threadIdx_x < 70:
A_1 = T.decl_buffer((140,), data=A.data)
in_thread_B_1[0] = in_thread_B_1[0] + A_1[threadIdx_y * 70 + threadIdx_x]
cross_thread_B_1 = T.decl_buffer((1,), data=cross_thread_B.data, scope="local")
with T.attr(
T.comm_reducer(lambda x0, y0: x0 + y0, [T.float32(0)]),
"reduce_scope",
T.int32(0),
):
T.tvm_thread_allreduce(
T.uint32(1), in_thread_B_1[0], T.bool(True), cross_thread_B_1[0], threadIdx_x
)
if threadIdx_x == 0:
B_1 = T.decl_buffer((2,), data=B.data)
B_1[threadIdx_y] = cross_thread_B_1[0]
After = transform(Before)
assert After is not None
After_script = After.script()
assert "tvm_warp_shuffle" in After_script
assert "tvm_storage_sync" in After_script
def test_metal_no_mask():
transform = tvm.s_tir.transform.LowerThreadAllreduce()
@I.ir_module
class Before:
@T.prim_func(private=True, s_tir=True)
def main(A: T.Buffer((1, 1, 2, 128), "float32"), B: T.Buffer((1, 1, 2), "float32")):
T.func_attr(
{
"target": T.target(
{
"kind": "metal",
"max_threads_per_block": 1024,
"thread_warp_size": 32,
"host": "llvm",
}
),
}
)
blockIdx_x = T.launch_thread("blockIdx.x", 1)
cross_thread_B = T.alloc_buffer((1,), scope="local")
threadIdx_z = T.launch_thread("threadIdx.z", 1)
threadIdx_y = T.launch_thread("threadIdx.y", 2)
threadIdx_x = T.launch_thread("threadIdx.x", 128)
cross_thread_B_1 = T.decl_buffer((1,), data=cross_thread_B.data, scope="local")
with T.attr(
T.comm_reducer(lambda x0, y0: x0 + y0, [T.float32(0)]),
"reduce_scope",
T.int32(0),
):
A_1 = T.decl_buffer((256,), data=A.data)
T.tvm_thread_allreduce(
T.uint32(1),
A_1[threadIdx_y * 128 + threadIdx_x],
T.bool(True),
cross_thread_B_1[0],
threadIdx_x,
)
if threadIdx_x == 0:
B_1 = T.decl_buffer((2,), data=B.data)
B_1[threadIdx_y] = cross_thread_B_1[0]
After = transform(Before)
assert After is not None
After_script = After.script()
# Metal does not use warp masks
assert "tvm_warp_shuffle_down" in After_script
assert "tvm_storage_sync" in After_script
def test_webgpu_warp_reduce():
transform = tvm.s_tir.transform.LowerThreadAllreduce()
@I.ir_module
class Before:
@T.prim_func(private=True, s_tir=True)
def main(A: T.Buffer((128, 32), "float32"), B: T.Buffer(128, "float32")):
T.func_attr(
{
"target": T.target(
{
"kind": "webgpu",
"supports_subgroups": True,
"host": "llvm",
}
),
}
)
A_flat = T.decl_buffer(4096, data=A.data)
for i in range(128):
threadIdx_x = T.launch_thread("threadIdx.x", 32)
reduce_data = T.alloc_buffer((1,), "float32", scope="local")
reduce = T.decl_buffer(1, data=reduce_data.data, scope="local")
with T.attr(
T.comm_reducer(lambda x, y: x + y, [T.float32(0)]),
"reduce_scope",
T.int32(0),
):
T.tvm_thread_allreduce(
T.uint32(1),
A_flat[0],
T.bool(True),
reduce[0],
threadIdx_x,
)
if threadIdx_x == 0:
B[i] = reduce[0]
After = transform(Before)
assert After is not None
After_script = After.script()
assert "tvm_warp_shuffle_down" in After_script
assert "tvm_warp_shuffle(" in After_script
assert "tvm_storage_sync" not in After_script
assert "T.uint32(" not in After_script
def test_webgpu_multi_warp_reduce():
transform = tvm.s_tir.transform.LowerThreadAllreduce()
@I.ir_module
class Before:
@T.prim_func(private=True, s_tir=True)
def main(A: T.Buffer((1, 1, 2, 128), "float32"), B: T.Buffer((1, 1, 2), "float32")):
T.func_attr(
{
"target": T.target(
{
"kind": "webgpu",
"max_num_threads": 1024,
"supports_subgroups": True,
"host": "llvm",
}
),
}
)
blockIdx_x = T.launch_thread("blockIdx.x", 1)
cross_thread_B = T.alloc_buffer((1,), "float32", scope="local")
threadIdx_z = T.launch_thread("threadIdx.z", 1)
threadIdx_y = T.launch_thread("threadIdx.y", 2)
threadIdx_x = T.launch_thread("threadIdx.x", 128)
cross_thread_B_1 = T.decl_buffer((1,), data=cross_thread_B.data, scope="local")
with T.attr(
T.comm_reducer(lambda x0, y0: x0 + y0, [T.float32(0)]),
"reduce_scope",
T.int32(0),
):
A_1 = T.decl_buffer((256,), data=A.data)
T.tvm_thread_allreduce(
T.uint32(1),
A_1[threadIdx_y * 128 + threadIdx_x],
T.bool(True),
cross_thread_B_1[0],
threadIdx_x,
)
if threadIdx_x == 0:
B_1 = T.decl_buffer((2,), data=B.data)
B_1[threadIdx_y] = cross_thread_B_1[0]
After = transform(Before)
assert After is not None
After_script = After.script()
assert "tvm_warp_shuffle_down" in After_script
assert "tvm_storage_sync" in After_script
assert _has_volatile_alloc_buffer(After)
assert "T.uint32(" not in After_script
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,135 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ruff: noqa: E501, F401
import tvm
import tvm.testing
from tvm import s_tir
from tvm.script import tirx as T
# fmt: off
# pylint: disable=no-member,invalid-name,unused-variable,line-too-long,redefined-outer-name,unexpected-keyword-arg,too-many-nested-blocks
@tvm.script.ir_module
class MatmulBefore:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((1024, 1024), "float32"), B: T.Buffer((1024, 1024), "float32"), C: T.Buffer((1024, 1024), "float32")) -> None:
# function attr dict
T.func_attr({"global_symbol": "default_function", "tirx.noalias": True})
# body
# with T.sblock("root")
for blockIdx_y in T.thread_binding(32, thread="blockIdx.y"):
for blockIdx_x in T.thread_binding(32, thread="blockIdx.x"):
for threadIdx_y in T.thread_binding(2, thread="threadIdx.y"):
for threadIdx_x in T.thread_binding(2, thread="threadIdx.x"):
for k_0 in T.serial(32):
with T.sblock():
T.reads(A[blockIdx_y * 32 : blockIdx_y * 32 + 32, k_0 * 32 : k_0 * 32 + 32], B[k_0 * 32 : k_0 * 32 + 32, blockIdx_x * 32 : blockIdx_x * 32 + 32])
T.writes(C[blockIdx_y * 32 : blockIdx_y * 32 + 32, blockIdx_x * 32 : blockIdx_x * 32 + 32])
A_shared = T.sblock_alloc_buffer([1024, 1024], dtype="float32", scope="shared")
B_shared = T.sblock_alloc_buffer([1024, 1024], dtype="float32", scope="shared")
for ax0_ax1_fused_0 in T.serial(64):
for ax0_ax1_fused_3 in T.vectorized(4):
with T.sblock("A_shared"):
T.reads(A[blockIdx_y * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) // 32, k_0 * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) % 32])
T.writes(A_shared[blockIdx_y * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) // 32, k_0 * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) % 32])
T.sblock_attr({"tirx.manifest_shared_memory_local_stage":1})
A_shared[blockIdx_y * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) // 32, k_0 * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) % 32] = A[blockIdx_y * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) // 32, k_0 * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) % 32]
for ax0_ax1_fused_0 in T.serial(64):
for ax0_ax1_fused_3 in T.vectorized(4):
with T.sblock("B_shared"):
T.reads(B[k_0 * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) // 32, blockIdx_x * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) % 32])
T.writes(B_shared[k_0 * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) // 32, blockIdx_x * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) % 32])
T.sblock_attr({"tirx.manifest_shared_memory_local_stage":1})
B_shared[k_0 * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) // 32, blockIdx_x * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) % 32] = B[k_0 * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) // 32, blockIdx_x * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) % 32]
for k_1, i_2, j_2, k_2 in T.grid(2, 16, 16, 16):
with T.sblock("C"):
T.reads(A_shared[blockIdx_y * 32 + threadIdx_y * 16 + i_2, k_0 * 32 + k_1 * 16 + k_2], B_shared[k_0 * 32 + k_1 * 16 + k_2, blockIdx_x * 32 + threadIdx_x * 16 + j_2])
T.writes(C[blockIdx_y * 32 + threadIdx_y * 16 + i_2, blockIdx_x * 32 + threadIdx_x * 16 + j_2])
if k_0 * 32 + k_1 * 16 + k_2 == 0:
C[blockIdx_y * 32 + threadIdx_y * 16 + i_2, blockIdx_x * 32 + threadIdx_x * 16 + j_2] = T.float32(0)
C[blockIdx_y * 32 + threadIdx_y * 16 + i_2, blockIdx_x * 32 + threadIdx_x * 16 + j_2] = C[blockIdx_y * 32 + threadIdx_y * 16 + i_2, blockIdx_x * 32 + threadIdx_x * 16 + j_2] + A_shared[blockIdx_y * 32 + threadIdx_y * 16 + i_2, k_0 * 32 + k_1 * 16 + k_2] * B_shared[k_0 * 32 + k_1 * 16 + k_2, blockIdx_x * 32 + threadIdx_x * 16 + j_2]
@tvm.script.ir_module
class MatmulAfter:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((1024, 1024), "float32"), B: T.Buffer((1024, 1024), "float32"), C: T.Buffer((1024, 1024), "float32")) -> None:
# function attr dict
T.func_attr({"global_symbol": "default_function", "tirx.noalias": True})
# body
# with T.sblock("root")
for blockIdx_y in T.thread_binding(32, thread="blockIdx.y"):
for blockIdx_x in T.thread_binding(32, thread="blockIdx.x"):
for threadIdx_y in T.thread_binding(2, thread="threadIdx.y"):
for threadIdx_x in T.thread_binding(2, thread="threadIdx.x"):
for k_0 in T.serial(32):
with T.sblock():
T.reads(A[blockIdx_y * 32 : blockIdx_y * 32 + 32, k_0 * 32 : k_0 * 32 + 32], B[k_0 * 32 : k_0 * 32 + 32, blockIdx_x * 32 : blockIdx_x * 32 + 32])
T.writes(C[blockIdx_y * 32 : blockIdx_y * 32 + 32, blockIdx_x * 32 : blockIdx_x * 32 + 32])
A_shared = T.sblock_alloc_buffer([1024, 1024], dtype="float32", scope="shared")
B_shared = T.sblock_alloc_buffer([1024, 1024], dtype="float32", scope="shared")
A_shared_local = T.sblock_alloc_buffer([64, 4], dtype="float32", scope="local")
B_shared_local = T.sblock_alloc_buffer([64, 4], dtype="float32", scope="local")
for ax0_ax1_fused_0 in T.serial(64):
for ax0_ax1_fused_3 in T.vectorized(4):
with T.sblock():
T.reads(A[blockIdx_y * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) // 32, k_0 * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) % 32])
T.writes(A_shared_local[ax0_ax1_fused_0, ax0_ax1_fused_3])
A_shared_local[ax0_ax1_fused_0, ax0_ax1_fused_3] = A[blockIdx_y * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) // 32, k_0 * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) % 32]
for ax0_ax1_fused_0 in T.serial(64):
for ax0_ax1_fused_3 in T.vectorized(4):
with T.sblock("A_shared"):
T.reads(A[blockIdx_y * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) // 32, k_0 * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) % 32])
T.writes(A_shared[blockIdx_y * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) // 32, k_0 * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) % 32])
A_shared[blockIdx_y * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) // 32, k_0 * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) % 32] = A_shared_local[ax0_ax1_fused_0, ax0_ax1_fused_3]
for ax0_ax1_fused_0 in T.serial(64):
for ax0_ax1_fused_3 in T.vectorized(4):
with T.sblock():
T.reads(B[k_0 * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) // 32, blockIdx_x * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) % 32])
T.writes(B_shared_local[ax0_ax1_fused_0, ax0_ax1_fused_3])
B_shared_local[ax0_ax1_fused_0, ax0_ax1_fused_3] = B[k_0 * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) // 32, blockIdx_x * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) % 32]
for ax0_ax1_fused_0 in T.serial(64):
for ax0_ax1_fused_3 in T.vectorized(4):
with T.sblock("B_shared"):
T.reads(B[k_0 * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) // 32, blockIdx_x * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) % 32])
T.writes(B_shared[k_0 * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) // 32, blockIdx_x * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) % 32])
B_shared[k_0 * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) // 32, blockIdx_x * 32 + (ax0_ax1_fused_0 * 16 + threadIdx_y * 8 + threadIdx_x * 4 + ax0_ax1_fused_3) % 32] = B_shared_local[ax0_ax1_fused_0, ax0_ax1_fused_3]
for k_1, i_2, j_2, k_2 in T.grid(2, 16, 16, 16):
with T.sblock("C"):
T.reads(A_shared[blockIdx_y * 32 + threadIdx_y * 16 + i_2, k_0 * 32 + k_1 * 16 + k_2], B_shared[k_0 * 32 + k_1 * 16 + k_2, blockIdx_x * 32 + threadIdx_x * 16 + j_2])
T.writes(C[blockIdx_y * 32 + threadIdx_y * 16 + i_2, blockIdx_x * 32 + threadIdx_x * 16 + j_2])
if k_0 * 32 + k_1 * 16 + k_2 == 0:
C[blockIdx_y * 32 + threadIdx_y * 16 + i_2, blockIdx_x * 32 + threadIdx_x * 16 + j_2] = T.float32(0)
C[blockIdx_y * 32 + threadIdx_y * 16 + i_2, blockIdx_x * 32 + threadIdx_x * 16 + j_2] = C[blockIdx_y * 32 + threadIdx_y * 16 + i_2, blockIdx_x * 32 + threadIdx_x * 16 + j_2] + A_shared[blockIdx_y * 32 + threadIdx_y * 16 + i_2, k_0 * 32 + k_1 * 16 + k_2] * B_shared[k_0 * 32 + k_1 * 16 + k_2, blockIdx_x * 32 + threadIdx_x * 16 + j_2]
# fmt: on
# pylint: enable=no-member,invalid-name,unused-variable,line-too-long,redefined-outer-name,unexpected-keyword-arg,too-many-nested-blocks
def _check(before, expected):
after = tvm.s_tir.transform.ManifestSharedMemoryLocalStage()(before)
tvm.ir.assert_structural_equal(after, expected)
def test_transform_matmul():
_check(MatmulBefore, MatmulAfter)
if __name__ == "__main__":
tvm.testing.main()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,349 @@
# 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, F841
import numpy as np
import tvm
import tvm.testing
from tvm import s_tir
from tvm.script import ir as I
from tvm.script import tirx as T
from tvm.topi.math import cast
def test_matmul_t_buffer():
"""Shared allocations should be merged, preserving DeclBuffer if present
This test uses a matmul PrimFunc adapted from
test_matmul_dyn_shared, using `T.Buffer` (Allocate without
DeclBuffer) for the replaced allocations.
"""
transform = tvm.s_tir.transform.MergeSharedMemoryAllocations()
buffer_func = T.Buffer
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(
A: T.Buffer((1024, 1024), "float16"),
B: T.Buffer((1024, 1024), "float16"),
matmul: T.Buffer((1024, 1024), "float32"),
):
A_flat = T.decl_buffer(1048576, "float16", data=A.data)
B_flat = T.decl_buffer(1048576, "float16", data=B.data)
matmul_flat = T.decl_buffer(1048576, data=matmul.data)
threadIdx_x = T.launch_thread("threadIdx.x", 16)
C_local = T.alloc_buffer((1,), "float32", scope="local")
A_sh = T.alloc_buffer((256,), "float16", scope="shared.dyn")
B_sh = T.alloc_buffer((256,), "float16", scope="shared.dyn")
C_sh = T.alloc_buffer((256,), "float32", scope="shared.dyn")
threadIdx_y = T.launch_thread("threadIdx.y", 16)
blockIdx_x = T.launch_thread("blockIdx.x", 64)
blockIdx_y = T.launch_thread("blockIdx.y", 64)
C_local[0] = T.float32(0)
for i in range(64):
A_sh[threadIdx_y * 16 + threadIdx_x] = A_flat[
blockIdx_y * 16384 + threadIdx_y * 1024 + i * 16 + threadIdx_x
]
B_sh[threadIdx_y * 16 + threadIdx_x] = B_flat[
i * 16384 + threadIdx_y * 1024 + blockIdx_x * 16 + threadIdx_x
]
T.tvm_storage_sync("shared")
for k in range(16):
C_local[0] = C_local[0] + T.Cast(
"float32",
A_sh[threadIdx_y * 16 + k] * B_sh[k * 16 + threadIdx_x],
)
T.tvm_storage_sync("shared")
C_sh[threadIdx_y * 16 + threadIdx_x] = C_local[0]
T.tvm_storage_sync("shared.dyn")
matmul_flat[blockIdx_y * 16384 + threadIdx_y * 1024 + blockIdx_x * 16 + threadIdx_x] = (
C_sh[threadIdx_y * 16 + threadIdx_x]
)
@I.ir_module
class Expected:
@T.prim_func(s_tir=True)
def main(
A: T.Buffer((1024, 1024), "float16"),
B: T.Buffer((1024, 1024), "float16"),
matmul: T.Buffer((1024, 1024), "float32"),
):
A_flat = T.decl_buffer(1048576, "float16", data=A.data)
B_flat = T.decl_buffer(1048576, "float16", data=B.data)
matmul_flat = T.decl_buffer(1048576, data=matmul.data)
threadIdx_x = T.launch_thread("threadIdx.x", 16)
buf_dyn_shmem = T.alloc_buffer((1024,), "uint8", scope="shared.dyn")
C_local = T.alloc_buffer((1,), "float32", scope="local")
A_sh = T.decl_buffer(256, "float16", data=buf_dyn_shmem.data, scope="shared.dyn")
B_sh = T.decl_buffer(256, "float16", data=buf_dyn_shmem.data, scope="shared.dyn")
C_sh = T.decl_buffer(256, "float32", data=buf_dyn_shmem.data, scope="shared.dyn")
threadIdx_y = T.launch_thread("threadIdx.y", 16)
blockIdx_x = T.launch_thread("blockIdx.x", 64)
blockIdx_y = T.launch_thread("blockIdx.y", 64)
C_local[0] = T.float32(0)
for i in range(64):
A_sh[threadIdx_y * 16 + threadIdx_x + 256] = A_flat[
blockIdx_y * 16384 + threadIdx_y * 1024 + i * 16 + threadIdx_x
]
B_sh[threadIdx_y * 16 + threadIdx_x] = B_flat[
i * 16384 + threadIdx_y * 1024 + blockIdx_x * 16 + threadIdx_x
]
T.tvm_storage_sync("shared")
for k in range(16):
C_local[0] = C_local[0] + T.Cast(
"float32",
A_sh[threadIdx_y * 16 + k + 256] * B_sh[k * 16 + threadIdx_x],
)
T.tvm_storage_sync("shared")
C_sh[threadIdx_y * 16 + threadIdx_x] = C_local[0]
T.tvm_storage_sync("shared.dyn")
After = transform(Before)
script = After["main"].script()
# Verify merged allocation: one shared.dyn buffer of 1024 bytes (256*2 float16 + 256 float32)
assert "alloc_buffer((1024,)" in script
assert '"uint8"' in script
assert '"shared.dyn"' in script
# Verify storage sync calls preserved
assert "tvm_storage_sync" in script
# Verify offset indexing (shared memory merged)
assert "+ 256" in script
def test_matmul_decl_buffer():
"""Shared allocations should be merged, preserving DeclBuffer if present
This test uses a matmul PrimFunc adapted from
test_matmul_dyn_shared, using `T.decl_buffer` (Allocate followed by DeclBuffer)
for the replaced allocations.
"""
transform = tvm.s_tir.transform.MergeSharedMemoryAllocations()
buffer_func = T.decl_buffer
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(
A: T.Buffer((1024, 1024), "float16"),
B: T.Buffer((1024, 1024), "float16"),
matmul: T.Buffer((1024, 1024), "float32"),
):
A_flat = T.decl_buffer(1048576, "float16", data=A.data)
B_flat = T.decl_buffer(1048576, "float16", data=B.data)
matmul_flat = T.decl_buffer(1048576, data=matmul.data)
threadIdx_x = T.launch_thread("threadIdx.x", 16)
C_local = T.alloc_buffer((1,), "float32", scope="local")
A_sh = T.alloc_buffer((256,), "float16", scope="shared.dyn")
B_sh = T.alloc_buffer((256,), "float16", scope="shared.dyn")
C_sh = T.alloc_buffer((256,), "float32", scope="shared.dyn")
threadIdx_y = T.launch_thread("threadIdx.y", 16)
blockIdx_x = T.launch_thread("blockIdx.x", 64)
blockIdx_y = T.launch_thread("blockIdx.y", 64)
C_local[0] = T.float32(0)
for i in range(64):
A_sh[threadIdx_y * 16 + threadIdx_x] = A_flat[
blockIdx_y * 16384 + threadIdx_y * 1024 + i * 16 + threadIdx_x
]
B_sh[threadIdx_y * 16 + threadIdx_x] = B_flat[
i * 16384 + threadIdx_y * 1024 + blockIdx_x * 16 + threadIdx_x
]
T.tvm_storage_sync("shared")
for k in range(16):
C_local[0] = C_local[0] + T.Cast(
"float32",
A_sh[threadIdx_y * 16 + k] * B_sh[k * 16 + threadIdx_x],
)
T.tvm_storage_sync("shared")
C_sh[threadIdx_y * 16 + threadIdx_x] = C_local[0]
T.tvm_storage_sync("shared.dyn")
matmul_flat[blockIdx_y * 16384 + threadIdx_y * 1024 + blockIdx_x * 16 + threadIdx_x] = (
C_sh[threadIdx_y * 16 + threadIdx_x]
)
After = transform(Before)
script = After["main"].script()
# Verify merged allocation: one shared.dyn buffer of 1024 bytes
assert "alloc_buffer((1024,)" in script
assert '"uint8"' in script
assert '"shared.dyn"' in script
assert "tvm_storage_sync" in script
assert "+ 256" in script
def test_simple_alloc_no_reuse():
"""Test alloc and free within the same scope."""
transform = tvm.s_tir.transform.MergeSharedMemoryAllocations()
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main():
threadIdx_x = T.launch_thread("threadIdx.x", 128)
A_sh = T.alloc_buffer((128,), "float32", scope="shared.dyn")
B_sh = T.alloc_buffer((128,), "float32", scope="shared.dyn")
B_sh[threadIdx_x] = A_sh[threadIdx_x]
After = transform(Before)
script = After["main"].script()
# Verify merged allocation: 1024 bytes (128*4 + 128*4)
assert "alloc_buffer((1024,)" in script
assert '"uint8"' in script
assert '"shared.dyn"' in script
# Verify offset indexing
assert "+ 128" in script
def test_simple_alloc_reuse():
"""Test alloc and free within the same scope with a reuse chance."""
transform = tvm.s_tir.transform.MergeSharedMemoryAllocations()
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main():
threadIdx_x = T.launch_thread("threadIdx.x", 128)
A_sh = T.alloc_buffer((128,), "float32", scope="shared.dyn")
B_sh = T.alloc_buffer((128,), "float32", scope="shared.dyn")
A_sh[threadIdx_x] = 0
B_sh[threadIdx_x] = 0
After = transform(Before)
script = After["main"].script()
# Verify merged allocation: 512 bytes (128*4, reusable)
assert "alloc_buffer((512,)" in script
assert '"uint8"' in script
assert '"shared.dyn"' in script
def test_async_copy():
"""Test async copy in shared memory."""
transform = tvm.s_tir.transform.MergeSharedMemoryAllocations()
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(A: T.Buffer((128,), "float32"), B: T.Buffer((128,), "float32")):
threadIdx_x = T.launch_thread("threadIdx.x", 128)
A_sh = T.alloc_buffer((128,), "float32", scope="shared.dyn")
B_sh = T.alloc_buffer((128,), "float32", scope="shared.dyn")
T.ptx.cp_async("float32", A_sh.data, threadIdx_x, A.data, threadIdx_x, 512)
T.ptx.cp_async("float32", B_sh.data, threadIdx_x, B.data, threadIdx_x, 512)
After = transform(Before)
# The pass merges shared.dyn allocations. A_sh and B_sh are accessed
# sequentially inside the thread_extent with non-overlapping lifetimes,
# so the liveness analysis allows reuse — both fit in 512 bytes
# (= 128 elements * 4 bytes).
script = After["main"].script()
# Verify merged allocation (512 bytes - A_sh and B_sh can be reused)
assert '"uint8"' in script and '"shared.dyn"' in script and "(512,)" in script
# Verify cp_async uses the merged buffer
assert "buf_dyn_shmem" in script
assert "threadIdx_x * 4" in script
def test_multi_thread_extent_blocks():
"""Each thread_extent block must get its own merged buffer.
Reproduces the scoping bug from PR #19605: a single PrimFunc
with two sibling thread_extent regions, each containing its
own shared.dyn allocations. The merged buffer must be allocated
inside each kernel body — not just the first.
"""
transform = tvm.s_tir.transform.MergeSharedMemoryAllocations()
@I.ir_module(check_well_formed=False)
class Before:
@T.prim_func(s_tir=True, check_well_formed=False)
def main(
X: T.Buffer((128,), "float32"),
Y: T.Buffer((128,), "float32"),
):
X_flat = T.decl_buffer(128, data=X.data)
Y_flat = T.decl_buffer(128, data=Y.data)
# First kernel launch
tx0 = T.env_thread("threadIdx.x")
with T.attr(tx0, "thread_extent", 128):
A_sh = T.alloc_buffer((128,), "float32", scope="shared.dyn")
B_sh = T.alloc_buffer((128,), "float32", scope="shared.dyn")
A_sh[tx0] = X_flat[tx0]
B_sh[tx0] = A_sh[tx0]
X_flat[tx0] = B_sh[tx0]
# Second kernel launch — must NOT see kernel #0's merged buffer.
tx1 = T.env_thread("threadIdx.x")
with T.attr(tx1, "thread_extent", 128):
C_sh = T.alloc_buffer((128,), "float32", scope="shared.dyn")
D_sh = T.alloc_buffer((128,), "float32", scope="shared.dyn")
C_sh[tx1] = Y_flat[tx1]
D_sh[tx1] = C_sh[tx1]
Y_flat[tx1] = D_sh[tx1]
After = transform(Before)
script = After["main"].script()
# Two merged allocations — one per thread_extent body.
# Each of the four original 128-float32 buffers (A_sh, B_sh, C_sh, D_sh)
# gets merged within its own kernel scope.
assert script.count("shared.dyn") >= 2, (
"Expected at least two shared.dyn allocations (one per kernel)"
)
assert script.count("alloc_buffer") >= 2, (
"Expected at least two alloc_buffer nodes (one merged buf per kernel)"
)
# Both thread_extent blocks must contain their own merged buffer —
# they must NOT share the same buf_dyn_shmem variable.
# Structurally verify that the first kernel's body accesses are
# not rewritten to the second kernel's buf_dyn_shmem (and vice versa).
first_block = script.split("with T.attr(tx1")[0]
second_block = script.split("with T.attr(tx1")[1] if "tx1" in script else ""
assert "buf_dyn_shmem" in first_block, "Kernel 1 must have a merged buffer"
if second_block:
assert "buf_dyn_shmem" in second_block, "Kernel 2 must have a merged buffer"
# End-to-end: post-merge IR must remain well-formed through
# the host/device split — this is the exact ordering from
# PR #19605 that triggers the scoping bug.
target = tvm.target.Target("llvm")
mod_with_target = tvm.IRModule({"main": After["main"].with_attr({"target": target})})
split = tvm.tirx.transform.SplitHostDevice()
# If kernel #1 referenced an undefined buf_dyn_shmem, this
# would raise during well-formedness checking inside SplitHostDevice.
split(mod_with_target)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,460 @@
# 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 numpy as np
import tvm
import tvm.testing
from tvm import s_tir, tirx
from tvm.s_tir.tensor_intrin.hexagon import VRMPY_u8u8i32_INTRIN
from tvm.script import tirx as T
def _check(original, transformed):
func = original
mod = tvm.IRModule.from_expr(func.with_attr("global_symbol", "main"))
mod = tvm.s_tir.transform.PlanAndUpdateBufferAllocationLocation()(mod)
tvm.ir.assert_structural_equal(mod["main"], transformed.with_attr("global_symbol", "main"))
@T.prim_func(s_tir=True)
def element_func(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (16, 16))
C = T.match_buffer(c, (16, 16))
B = T.sblock_alloc_buffer((16, 16))
for i0 in range(0, 16):
for j0 in range(0, 16):
with T.sblock():
i, j = T.axis.remap("SS", [i0, j0])
B[i, j] = A[i, j] + 1.0
for j0 in range(0, 16):
with T.sblock():
i, j = T.axis.remap("SS", [i0, j0])
C[i, j] = B[i, j] * 2.0
@T.prim_func(s_tir=True)
def transformed_element_func(a: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [16, 16])
C = T.match_buffer(c, [16, 16])
for i_0 in range(0, 16):
with T.sblock():
T.reads([A[i_0, 0:16]])
T.writes([C[i_0, 0:16]])
B = T.sblock_alloc_buffer([16, 16])
for j_0 in T.serial(0, 16):
with T.sblock():
i, j = T.axis.remap("SS", [i_0, j_0])
B[i, j] = A[i, j] + 1.0
for j_0 in T.serial(0, 16):
with T.sblock():
i, j = T.axis.remap("SS", [i_0, j_0])
C[i, j] = B[i, j] * 2.0
@T.prim_func(s_tir=True)
def original_func() -> None:
A = T.sblock_alloc_buffer((128, 128), "float32")
for i0, j0 in T.grid(128, 128):
with T.sblock():
i, j = T.axis.remap("SS", [i0, j0])
A[i, j] = T.float32(0)
for i0, j0, k0 in T.grid(32, 32, 32):
with T.sblock():
i, j, k = T.axis.remap("SSR", [i0, j0, k0])
B = T.sblock_alloc_buffer((128, 128), "float32")
C = T.sblock_alloc_buffer((128, 128), "float32")
D = T.sblock_alloc_buffer((128, 128), "float32")
if k == 0:
for ii, jj in T.grid(4, 4):
B[i * 4 + ii, j * 4 + jj] = A[i * 4 + ii, j * 4 + jj]
for ii, jj in T.grid(4, 4):
for kk in range(0, 4):
B[i * 4 + ii, j * 4 + jj] += C[i * 4 + ii, k * 4 + kk]
for kk in range(0, 4):
B[i * 4 + ii, j * 4 + jj] += (
D[j * 4 + jj, k * 4 + kk] * C[i * 4 + ii, k * 4 + kk]
)
@T.prim_func(s_tir=True)
def transformed_func() -> None:
A = T.sblock_alloc_buffer([128, 128])
for i0, j0 in T.grid(128, 128):
with T.sblock():
i, j = T.axis.remap("SS", [i0, j0])
A[i, j] = T.float32(0)
for i0, j0, k0 in T.grid(32, 32, 32):
with T.sblock():
i, j, k = T.axis.remap("SSR", [i0, j0, k0])
B = T.sblock_alloc_buffer([128, 128])
if k == 0:
for ii, jj in T.grid(4, 4):
B[i * 4 + ii, j * 4 + jj] = A[i * 4 + ii, j * 4 + jj]
for ii, jj in T.grid(4, 4):
with T.sblock(""):
T.reads([B[((i * 4) + ii), ((j * 4) + jj)]])
T.writes([B[((i * 4) + ii), ((j * 4) + jj)]])
C = T.sblock_alloc_buffer([128, 128])
for kk in T.serial(0, 4):
B[((i * 4) + ii), ((j * 4) + jj)] = (
B[((i * 4) + ii), ((j * 4) + jj)] + C[((i * 4) + ii), ((k * 4) + kk)]
)
for kk in T.serial(0, 4):
with T.sblock(""):
T.reads(
[
B[((i * 4) + ii), ((j * 4) + jj)],
C[((i * 4) + ii), ((k * 4) + kk)],
]
)
T.writes([B[((i * 4) + ii), ((j * 4) + jj)]])
D = T.sblock_alloc_buffer([128, 128])
B[((i * 4) + ii), ((j * 4) + jj)] = B[
((i * 4) + ii), ((j * 4) + jj)
] + (
D[((j * 4) + jj), ((k * 4) + kk)]
* C[((i * 4) + ii), ((k * 4) + kk)]
)
@T.prim_func(s_tir=True)
def match_buffer_func() -> None:
C = T.sblock_alloc_buffer((128, 128))
for i in range(128):
with T.sblock():
vi = T.axis.S(128, i)
C0 = T.match_buffer(C[vi, 0:128], (128))
for j in range(128):
with T.sblock():
jj = T.axis.S(128, j)
C1 = T.match_buffer(C0[jj], ())
C1[()] = 0
@T.prim_func(s_tir=True)
def transformed_match_buffer_func() -> None:
for i in range(0, 128):
with T.sblock():
vi = T.axis.S(128, i)
C = T.sblock_alloc_buffer((128, 128))
C0 = T.match_buffer(C[vi, 0:128], (128))
for j in range(128):
with T.sblock():
jj = T.axis.S(128, j)
C1 = T.match_buffer(C0[jj], ())
C1[()] = 0
@T.prim_func(s_tir=True)
def opaque_access(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [1024])
B = T.match_buffer(b, [1024])
A_cache = T.sblock_alloc_buffer([1024])
for i in T.serial(0, 8):
with T.sblock():
vi = T.axis.S(8, i)
with T.sblock():
v = T.axis.S(8, vi)
T.reads([A[(v * 128) : ((v * 128) + 128)]])
T.writes([A_cache[(v * 128) : ((v * 128) + 128)]])
T.evaluate(
T.call_extern(
"test",
A_cache.data,
(v * 128),
128,
A.data,
(v * 128),
128,
dtype="float32",
)
)
for j in T.serial(0, 128):
with T.sblock():
v = T.axis.S(1024, vi * 128 + j)
T.reads([A_cache[v]])
T.writes([B[v]])
B[v] = A_cache[v]
@T.prim_func(s_tir=True)
def transformed_opaque_access(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [1024])
B = T.match_buffer(b, [1024])
for i in T.serial(0, 8):
with T.sblock():
vi = T.axis.S(8, i)
T.reads(A[vi * 128 : vi * 128 + 128])
T.writes(B[vi * 128 : vi * 128 + 128])
A_cache = T.sblock_alloc_buffer([1024])
with T.sblock():
v = T.axis.S(8, vi)
T.reads([A[v * 128 : v * 128 + 128]])
T.writes([A_cache[v * 128 : v * 128 + 128]])
T.evaluate(
T.call_extern(
"test", A_cache.data, v * 128, 128, A.data, v * 128, 128, dtype="float32"
)
)
for j in T.serial(0, 128):
with T.sblock():
v = T.axis.S(1024, vi * 128 + j)
T.reads([A_cache[v]])
T.writes([B[v]])
B[v] = A_cache[v]
def test_elementwise():
_check(element_func, transformed_element_func)
def test_locate_buffer_allocation():
_check(original_func, transformed_func)
def test_match_buffer_allocation():
_check(match_buffer_func, transformed_match_buffer_func)
def test_opaque_access():
_check(opaque_access, transformed_opaque_access)
def test_loop_carried_dependency():
"""The buffer allocation should be above opaque iter var's loop scopes
such that buffer accesses with loop carried dependencies are covered,
and the allocate buffer should keep the order."""
@T.prim_func(s_tir=True)
def before(A: T.Buffer((8, 8, 8), "int32"), B: T.Buffer((8, 8, 8), "int32")):
C = T.sblock_alloc_buffer([8, 8, 8], dtype="int32")
D = T.sblock_alloc_buffer([8, 8, 8], dtype="int32")
for i in T.serial(8):
for j in T.serial(8):
for k in T.serial(8):
with T.sblock("b0"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
C[vi, vj, vk] = A[vi, vj, vk] + 1
for k in T.serial(8):
with T.sblock("b1"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
D[vi, vj, vk] = A[vi, vj, vk] + 2
for k in T.serial(8):
with T.sblock("b2"):
vi, vk = T.axis.remap("SS", [i, k])
vj = T.axis.opaque(8, j)
B[vi, vj, vk] = (
C[vi, vj, vk]
+ T.if_then_else(0 < vj, C[vi, vj - 1, vk], 0, dtype="int32")
+ D[vi, vj, vk]
)
@T.prim_func(s_tir=True)
def after(A: T.Buffer((8, 8, 8), "int32"), B: T.Buffer((8, 8, 8), "int32")) -> None:
for i in T.serial(8):
with T.sblock():
T.reads(A[i, 0:8, 0:8])
T.writes(B[i, 0:8, 0:8])
C = T.sblock_alloc_buffer([8, 8, 8], dtype="int32")
D = T.sblock_alloc_buffer([8, 8, 8], dtype="int32")
for j in T.serial(8):
for k in T.serial(8):
with T.sblock("b0"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
C[vi, vj, vk] = A[vi, vj, vk] + 1
for k in T.serial(8):
with T.sblock("b1"):
vi, vj, vk = T.axis.remap("SSS", [i, j, k])
D[vi, vj, vk] = A[vi, vj, vk] + 2
for k in T.serial(8):
with T.sblock("b2"):
vi, vk = T.axis.remap("SS", [i, k])
vj = T.axis.opaque(8, j)
B[vi, vj, vk] = (
C[vi, vj, vk]
+ T.if_then_else(0 < vj, C[vi, vj - 1, vk], 0, dtype="int32")
+ D[vi, vj, vk]
)
_check(before, after)
def test_1D_cascade_op_rolling_buffer():
"""The intermediate buffer must be allocated above rolling buffer's rolling loop,
which is marked as opaque in consumer block's iter mappings."""
@T.prim_func(s_tir=True)
def before(A: T.Buffer((4, 16), "int32"), C: T.Buffer((4, 8), "int32")):
B = T.sblock_alloc_buffer((4, 6), "int32")
for c in T.serial(4):
for i in T.serial(0, 2):
for j in T.serial(0, 6):
for k in T.serial(3):
with T.sblock("P1"):
T.where(i < 1 or j >= 2)
cc, vi, vj, vk = T.axis.remap("SSSR", [c, i, j, k])
if vk == 0:
B[cc, T.floormod(vi * 4 + vj, 6)] = 0
B[cc, T.floormod(vi * 4 + vj, 6)] = (
B[cc, T.floormod(vi * 4 + vj, 6)] + A[cc, vi * 4 + vj + vk]
)
for j in T.serial(0, 4):
for k in T.serial(3):
with T.sblock("P2"):
vi = T.axis.opaque(2, i)
cc, vj, vk = T.axis.remap("SSR", [c, j, k])
if vk == 0:
C[cc, vi * 4 + vj] = 0
C[cc, vi * 4 + vj] = (
C[cc, vi * 4 + vj] + B[cc, T.floormod(vi * 4 + vj + vk, 6)]
)
@T.prim_func(s_tir=True)
def after(A: T.Buffer((4, 16), "int32"), C: T.Buffer((4, 8), "int32")):
for c in T.serial(4):
with T.sblock():
T.reads(A[c, 0:12], C[c, 0:8])
T.writes(C[c, 0:8])
B = T.sblock_alloc_buffer([4, 6], dtype="int32")
for i in T.serial(2):
for j, k in T.grid(6, 3):
with T.sblock("P1"):
T.where(i < 1 or j >= 2)
cc, vi, vj, vk = T.axis.remap("SSSR", [c, i, j, k])
if vk == 0:
B[cc, (vi * 4 + vj) % 6] = 0
B[cc, (vi * 4 + vj) % 6] = (
B[cc, (vi * 4 + vj) % 6] + A[cc, vi * 4 + vj + vk]
)
for j, k in T.grid(4, 3):
with T.sblock("P2"):
vi = T.axis.opaque(2, i)
cc, vj, vk = T.axis.remap("SSR", [c, j, k])
if vk == 0:
C[cc, vi * 4 + vj] = 0
C[cc, vi * 4 + vj] = C[cc, vi * 4 + vj] + B[cc, (vi * 4 + vj + vk) % 6]
_check(before, after)
def test_buffer_conditional_lowering():
"""Buffers passed as pointer arguments are unmodified
Confirm that the `tirx.PlanAndUpdateBufferAllocationLocation` pass
leaves (Buffer nodes corresponding to pointer-typed PrimFunc arguments)
unchanged, rather than lowering them to `reads`, `writes`, and `alloc_buffer` nodes.
"""
@T.prim_func(s_tir=True)
def before(A: T.handle("float32")):
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
for i in range(1):
A_1 = T.decl_buffer((1,), data=A)
A_1[i] = 0
after = before
_check(before, after)
def test_dltensor_buffer_is_unlowered():
"""Buffers allocated with a Bind are unmodified
Confirm that the `tirx.PlanAndUpdateBufferAllocationLocation` pass
leaves (Buffer nodes corresponding to PrimFunc DLTensor arguments)
unchanged, rather than lowering them to `reads`, `writes`, and
`alloc_buffer` nodes.
"""
@T.prim_func(s_tir=True)
def before(dlpack_handle: T.handle, axis: T.int64) -> T.int64:
ndim: T.int32 = T.tvm_struct_get(dlpack_handle, 0, 5, "int32")
stride_ptr: T.let[T.handle("int64")] = T.tvm_struct_get(
dlpack_handle, 0, 4, dtype=T.handle("int64").ty
)
if T.isnullptr(stride_ptr):
shape_ptr: T.let[T.handle("int64")] = T.tvm_struct_get(
dlpack_handle, 0, 3, dtype=T.handle("int64").ty
)
shape = T.decl_buffer(ndim, "int64", data=shape_ptr)
product = T.decl_buffer([], "int64")
product[()] = 1
for dim in range(axis + 1, ndim):
product[()] = product[()] * shape[dim]
return product[()]
else:
strides = T.decl_buffer(ndim, "int64", data=stride_ptr)
stride: T.int64 = strides[axis]
return stride
after = before
_check(before, after)
def test_reduce_buffer_dominate_reduce_loops():
"""Reduction write buffer allocation should dominate all reduce loops"""
@T.prim_func(s_tir=True)
def before(x: T.Buffer((256, 256, 256), "float32"), x_red: T.Buffer((256, 256), "float32")):
x_red_ = T.sblock_alloc_buffer((256, 256))
for ax0_0, k1_0, ax1_0 in T.grid(4, 4, 4):
for ax0_1, k1_1, ax1_1 in T.grid(64, 64, 64):
with T.sblock("x_red"):
v_ax0 = T.axis.spatial(256, ax0_0 * 64 + ax0_1)
v_ax1 = T.axis.spatial(256, ax1_0 * 64 + ax1_1)
v_k1 = T.axis.reduce(256, k1_0 * 64 + k1_1)
if v_k1 == 0:
x_red_[v_ax0, v_ax1] = T.float32(0.0)
x_red_[v_ax0, v_ax1] = x_red_[v_ax0, v_ax1] + x[v_ax0, v_k1, v_ax1]
for ax0, ax1 in T.grid(64, 64):
with T.sblock("x_red_"):
v0 = T.axis.spatial(256, ax0_0 * 64 + ax0)
v1 = T.axis.spatial(256, ax1_0 * 64 + ax1)
x_red[v0, v1] = x_red_[v0, v1]
@T.prim_func(s_tir=True)
def after(x: T.Buffer((256, 256, 256), "float32"), x_red: T.Buffer((256, 256), "float32")):
for ax0_0 in range(4):
with T.sblock(""):
T.reads(x[ax0_0 * 64 : ax0_0 * 64 + 64, 0:256, 0:256])
T.writes(x_red[ax0_0 * 64 : ax0_0 * 64 + 64, 0:256])
x_red_ = T.sblock_alloc_buffer((256, 256))
for k1_0, ax1_0 in T.grid(4, 4):
for ax0_1, k1_1, ax1_1 in T.grid(64, 64, 64):
with T.sblock("x_red"):
v_ax0 = T.axis.spatial(256, ax0_0 * 64 + ax0_1)
v_ax1 = T.axis.spatial(256, ax1_0 * 64 + ax1_1)
v_k1 = T.axis.reduce(256, k1_0 * 64 + k1_1)
T.reads(x_red_[v_ax0, v_ax1], x[v_ax0, v_k1, v_ax1])
T.writes(x_red_[v_ax0, v_ax1])
if v_k1 == 0:
x_red_[v_ax0, v_ax1] = T.float32(0.0)
x_red_[v_ax0, v_ax1] = x_red_[v_ax0, v_ax1] + x[v_ax0, v_k1, v_ax1]
for ax0, ax1 in T.grid(64, 64):
with T.sblock("x_red_"):
v0 = T.axis.spatial(256, ax0_0 * 64 + ax0)
v1 = T.axis.spatial(256, ax1_0 * 64 + ax1)
T.reads(x_red_[v0, v1])
T.writes(x_red[v0, v1])
x_red[v0, v1] = x_red_[v0, v1]
_check(before, after)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,354 @@
# 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
import numpy
import tvm
import tvm.testing
from tvm import s_tir
from tvm.ir.module import IRModule
from tvm.script import tirx as T
default_lwp_test_config = {
"tirx.instrument_lwp": True,
"s_tir.lwp_disable_func_prof": True,
"s_tir.reset_start_id": True,
}
@T.prim_func(s_tir=True)
def input1(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (8, 8, 128), dtype="int32")
B = T.match_buffer(b, (8, 8, 128), dtype="int32")
C = T.match_buffer(c, (8, 8, 128), dtype="int32")
for i, j in T.grid(8, 8):
for k, l in T.grid(8, 16):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
B[vi, vj, vk * 16 + vl] = A[vi, vj, vk * 16 + vl] * 2
for k, l in T.grid(8, 16):
with T.sblock("C"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
C[vi, vj, vk * 16 + vl] = B[vi, vj, vk * 16 + vl] * 2
@T.prim_func(s_tir=True)
def input2(a: T.handle, b: T.handle, c: T.handle, d: T.handle) -> None:
A = T.match_buffer(a, (8, 8, 128), dtype="int32")
B = T.match_buffer(b, (8, 8, 128), dtype="int32")
C = T.match_buffer(c, (8, 8, 128), dtype="int32")
D = T.match_buffer(d, (8, 8, 128), dtype="int32")
for i in T.serial(0, 8):
for j in T.serial(0, 8):
for k, l in T.grid(8, 16):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
B[vi, vj, vk * 16 + vl] = A[vi, vj, vk * 16 + vl] * 2
for k, l in T.grid(8, 16):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
B[vi, vj, vk * 16 + vl] = B[vi, vj, vk * 16 + vl] * D[vi, vj, vk * 16 + vl]
for j in T.serial(0, 8):
for k, l in T.grid(8, 16):
with T.sblock("C"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
C[vi, vj, vk * 16 + vl] = B[vi, vj, vk * 16 + vl] + 2
for k, l in T.grid(8, 16):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
C[vi, vj, vk * 16 + vl] = C[vi, vj, vk * 16 + vl] * D[vi, vj, vk * 16 + vl]
@T.prim_func(s_tir=True)
def input3(a: T.handle, b: T.handle, c: T.handle, d: T.handle) -> None:
A = T.match_buffer(a, (8, 8, 128), dtype="int32")
B = T.match_buffer(b, (8, 8, 128), dtype="int32")
C = T.match_buffer(c, (8, 8, 128), dtype="int32")
D = T.match_buffer(d, (8, 8, 128), dtype="int32")
for i in T.serial(0, 8):
for j in T.parallel(0, 8):
for k in T.serial(0, 8):
for l in T.serial(0, 16):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
B[vi, vj, vk * 16 + vl] = A[vi, vj, vk * 16 + vl] * 2
for k in T.serial(0, 8):
for l in T.serial(0, 16):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
B[vi, vj, vk * 16 + vl] = B[vi, vj, vk * 16 + vl] * D[vi, vj, vk * 16 + vl]
for j in T.serial(0, 8):
for k in T.parallel(0, 8):
for l in T.serial(0, 16):
with T.sblock("C"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
C[vi, vj, vk * 16 + vl] = B[vi, vj, vk * 16 + vl] + 2
for k in T.parallel(0, 8):
for l in T.serial(0, 16):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
C[vi, vj, vk * 16 + vl] = C[vi, vj, vk * 16 + vl] * D[vi, vj, vk * 16 + vl]
@T.prim_func(s_tir=True)
def test1_expected_output(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (8, 8, 128), dtype="int32")
B = T.match_buffer(b, (8, 8, 128), dtype="int32")
C = T.match_buffer(c, (8, 8, 128), dtype="int32")
for i, j in T.grid(8, 8):
T.evaluate(T.start_profile_intrinsic(3, dtype="handle"))
for k, l in T.grid(8, 16):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
B[vi, vj, vk * 16 + vl] = A[vi, vj, vk * 16 + vl] * 2
T.evaluate(T.end_profile_intrinsic(3, dtype="handle"))
T.evaluate(T.start_profile_intrinsic(5, dtype="handle"))
for k, l in T.grid(8, 16):
with T.sblock("C"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
C[vi, vj, vk * 16 + vl] = B[vi, vj, vk * 16 + vl] * 2
T.evaluate(T.end_profile_intrinsic(5, dtype="handle"))
@T.prim_func(s_tir=True)
def test2_expected_output(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (8, 8, 128), dtype="int32")
B = T.match_buffer(b, (8, 8, 128), dtype="int32")
C = T.match_buffer(c, (8, 8, 128), dtype="int32")
T.evaluate(T.start_profile_intrinsic(1, dtype="handle"))
for i in T.serial(0, 8):
T.evaluate(T.start_profile_intrinsic(2, dtype="handle"))
for j in T.serial(0, 8):
for k in T.serial(0, 8):
for l in T.serial(0, 16):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
B[vi, vj, vk * 16 + vl] = A[vi, vj, vk * 16 + vl] * 2
for k in T.serial(0, 8):
for l in T.serial(0, 16):
with T.sblock("C"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
C[vi, vj, vk * 16 + vl] = B[vi, vj, vk * 16 + vl] * 2
T.evaluate(T.end_profile_intrinsic(2, dtype="handle"))
T.evaluate(T.end_profile_intrinsic(1, dtype="handle"))
@T.prim_func(s_tir=True)
def test3_expected_output(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (8, 8, 128), dtype="int32")
B = T.match_buffer(b, (8, 8, 128), dtype="int32")
C = T.match_buffer(c, (8, 8, 128), dtype="int32")
T.evaluate(T.start_profile_intrinsic(1, dtype="handle"))
for i in T.serial(0, 8):
T.evaluate(T.start_profile_intrinsic(2, dtype="handle"))
for j in T.serial(0, 8):
T.evaluate(T.start_profile_intrinsic(3, dtype="handle"))
for k in T.serial(0, 8):
for l in T.serial(0, 16):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
B[vi, vj, vk * 16 + vl] = A[vi, vj, vk * 16 + vl] * 2
T.evaluate(T.end_profile_intrinsic(3, dtype="handle"))
T.evaluate(T.start_profile_intrinsic(5, dtype="handle"))
for k in T.serial(0, 8):
for l in T.serial(0, 16):
with T.sblock("C"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
C[vi, vj, vk * 16 + vl] = B[vi, vj, vk * 16 + vl] * 2
T.evaluate(T.end_profile_intrinsic(5, dtype="handle"))
T.evaluate(T.end_profile_intrinsic(2, dtype="handle"))
T.evaluate(T.end_profile_intrinsic(1, dtype="handle"))
@T.prim_func(s_tir=True)
def test4_expected_output(a: T.handle, b: T.handle, c: T.handle, d: T.handle) -> None:
A = T.match_buffer(a, (8, 8, 128), dtype="int32")
B = T.match_buffer(b, (8, 8, 128), dtype="int32")
C = T.match_buffer(c, (8, 8, 128), dtype="int32")
D = T.match_buffer(d, (8, 8, 128), dtype="int32")
for i in T.serial(0, 8):
T.evaluate(T.start_profile_intrinsic(2, dtype="handle"))
for j in T.serial(0, 8):
T.evaluate(T.start_profile_intrinsic(3, dtype="handle"))
for k, l in T.grid(8, 16):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
B[vi, vj, vk * 16 + vl] = A[vi, vj, vk * 16 + vl] * 2
T.evaluate(T.end_profile_intrinsic(3, dtype="handle"))
T.evaluate(T.start_profile_intrinsic(5, dtype="handle"))
for k, l in T.grid(8, 16):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
B[vi, vj, vk * 16 + vl] = B[vi, vj, vk * 16 + vl] * D[vi, vj, vk * 16 + vl]
T.evaluate(T.end_profile_intrinsic(5, dtype="handle"))
T.evaluate(T.end_profile_intrinsic(2, dtype="handle"))
T.evaluate(T.start_profile_intrinsic(7, dtype="handle"))
for j in T.serial(0, 8):
T.evaluate(T.start_profile_intrinsic(8, dtype="handle"))
for k, l in T.grid(8, 16):
with T.sblock("C"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
C[vi, vj, vk * 16 + vl] = B[vi, vj, vk * 16 + vl] + 2
T.evaluate(T.end_profile_intrinsic(8, dtype="handle"))
T.evaluate(T.start_profile_intrinsic(10, dtype="handle"))
for k, l in T.grid(8, 16):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
C[vi, vj, vk * 16 + vl] = C[vi, vj, vk * 16 + vl] * D[vi, vj, vk * 16 + vl]
T.evaluate(T.end_profile_intrinsic(10, dtype="handle"))
T.evaluate(T.end_profile_intrinsic(7, dtype="handle"))
@T.prim_func(s_tir=True)
def test5_expected_output(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, (8, 8, 128), dtype="int32")
B = T.match_buffer(b, (8, 8, 128), dtype="int32")
C = T.match_buffer(c, (8, 8, 128), dtype="int32")
T.evaluate(T.start_profile_intrinsic(1, dtype="handle"))
for i in T.serial(0, 8):
T.evaluate(T.start_profile_intrinsic(2, dtype="handle"))
for j in T.serial(0, 8):
for k in T.serial(0, 8):
for l in T.serial(0, 16):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
B[vi, vj, vk * 16 + vl] = A[vi, vj, vk * 16 + vl] * 2
for k in T.serial(0, 8):
for l in T.serial(0, 16):
with T.sblock("C"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
C[vi, vj, vk * 16 + vl] = B[vi, vj, vk * 16 + vl] * 2
T.evaluate(T.end_profile_intrinsic(2, dtype="handle"))
T.evaluate(T.end_profile_intrinsic(1, dtype="handle"))
@T.prim_func(s_tir=True)
def test6_expected_output(a: T.handle, b: T.handle, c: T.handle, d: T.handle) -> None:
A = T.match_buffer(a, (8, 8, 128), dtype="int32")
B = T.match_buffer(b, (8, 8, 128), dtype="int32")
C = T.match_buffer(c, (8, 8, 128), dtype="int32")
D = T.match_buffer(d, (8, 8, 128), dtype="int32")
for i in T.serial(0, 8):
T.evaluate(T.start_profile_intrinsic(2, dtype="handle"))
for j in T.parallel(0, 8):
for k in T.serial(0, 8):
for l in T.serial(0, 16):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
B[vi, vj, vk * 16 + vl] = A[vi, vj, vk * 16 + vl] * 2
for k in T.serial(0, 8):
for l in T.serial(0, 16):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
B[vi, vj, vk * 16 + vl] = B[vi, vj, vk * 16 + vl] * D[vi, vj, vk * 16 + vl]
T.evaluate(T.end_profile_intrinsic(2, dtype="handle"))
T.evaluate(T.start_profile_intrinsic(7, dtype="handle"))
for j in T.serial(0, 8):
T.evaluate(T.start_profile_intrinsic(8, dtype="handle"))
for k in T.parallel(0, 8):
for l in T.serial(0, 16):
with T.sblock("C"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
C[vi, vj, vk * 16 + vl] = B[vi, vj, vk * 16 + vl] + 2
T.evaluate(T.end_profile_intrinsic(8, dtype="handle"))
T.evaluate(T.start_profile_intrinsic(10, dtype="handle"))
for k in T.parallel(0, 8):
for l in T.serial(0, 16):
with T.sblock("B"):
vi, vj, vk, vl = T.axis.remap("SSSS", [i, j, k, l])
C[vi, vj, vk * 16 + vl] = C[vi, vj, vk * 16 + vl] * D[vi, vj, vk * 16 + vl]
T.evaluate(T.end_profile_intrinsic(10, dtype="handle"))
T.evaluate(T.end_profile_intrinsic(7, dtype="handle"))
# By default, only loops with siblings are instrumented.
def test1():
with tvm.transform.PassContext(config=default_lwp_test_config):
mod = tvm.IRModule.from_expr(input1.with_attr("global_symbol", "main"))
mod = tvm.s_tir.transform.InstrumentProfileIntrinsics()(mod)
tvm.ir.assert_structural_equal(
mod["main"], test1_expected_output.with_attr("global_symbol", "main")
)
# By default, only loops with siblings are instrumented. Here, 'lwp_max_depth'
# doesn't have any effect unless 'instr_siblings' is set to False (ex: test3).
def test2():
test2_config = default_lwp_test_config.copy()
test2_config.update({"s_tir.lwp_max_depth": 3})
with tvm.transform.PassContext(config=test2_config):
mod = tvm.IRModule.from_expr(input1.with_attr("global_symbol", "main"))
mod = tvm.s_tir.transform.InstrumentProfileIntrinsics()(mod)
tvm.ir.assert_structural_equal(
mod["main"], test1_expected_output.with_attr("global_symbol", "main")
)
# test3: Use 'lwp_max_depth' to instrument loops upto a certain depth. This flag
# is effective only when 'instr_siblings' is disabled. Also, note that inner-most
# loops are always excluded from instrumentation unless overwritten using
# 'lwp_min_height' (ex: test5)
def test3():
test3_config = default_lwp_test_config.copy()
test3_config.update({"s_tir.lwp_max_depth": 3, "s_tir.instr_siblings": False})
with tvm.transform.PassContext(config=test3_config):
mod = tvm.IRModule.from_expr(input1.with_attr("global_symbol", "main"))
mod = tvm.s_tir.transform.InstrumentProfileIntrinsics()(mod)
tvm.ir.assert_structural_equal(
mod["main"], test3_expected_output.with_attr("global_symbol", "main")
)
# test4: Use 'lwp_min_height' to exclude inner loops upto a certain height from
# instrumentation.
def test4():
with tvm.transform.PassContext(config=default_lwp_test_config):
mod = tvm.IRModule.from_expr(input2.with_attr("global_symbol", "main"))
mod = tvm.s_tir.transform.InstrumentProfileIntrinsics()(mod)
tvm.ir.assert_structural_equal(
mod["main"], test4_expected_output.with_attr("global_symbol", "main")
)
# test5: Use both 'lwp_min_height' and 'lwp_max_depth'.
# instrumentation.
def test5():
test5_config = default_lwp_test_config.copy()
test5_config.update(
{"s_tir.lwp_max_depth": 3, "s_tir.instr_siblings": False, "s_tir.lwp_min_height": 2}
)
with tvm.transform.PassContext(config=test5_config):
mod = tvm.IRModule.from_expr(input1.with_attr("global_symbol", "main"))
mod = tvm.s_tir.transform.InstrumentProfileIntrinsics()(mod)
tvm.ir.assert_structural_equal(
mod["main"], test5_expected_output.with_attr("global_symbol", "main")
)
# test6: Tests instrumentation for the parallel loops
def test6():
with tvm.transform.PassContext(config=default_lwp_test_config):
mod = tvm.IRModule.from_expr(input3.with_attr("global_symbol", "main"))
mod = tvm.s_tir.transform.InstrumentProfileIntrinsics()(mod)
tvm.ir.assert_structural_equal(
mod["main"], test6_expected_output.with_attr("global_symbol", "main")
)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,131 @@
# 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
import tvm
import tvm.testing
from tvm.script import ir as I
from tvm.script import tirx as T
def test_remove_store_undef():
"""Remove a store whose value is T.undef()"""
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(A: T.Buffer(1, "int32")):
A[0] = T.undef(dtype="int32")
@I.ir_module
class Expected:
@T.prim_func(s_tir=True)
def main(A: T.Buffer(1, "int32")):
T.evaluate(0)
After = tvm.s_tir.transform.RemoveStoreUndef()(Before)
tvm.ir.assert_structural_equal(After, Expected)
def test_remove_store_undef_expression():
"""Expressions containing T.undef() are removed"""
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(A: T.Buffer(1, "int32")):
A[0] = 1 + T.undef(dtype="int32")
@I.ir_module
class Expected:
@T.prim_func(s_tir=True)
def main(A: T.Buffer(1, "int32")):
T.evaluate(0)
After = tvm.s_tir.transform.RemoveStoreUndef()(Before)
tvm.ir.assert_structural_equal(After, Expected)
def test_keep_other_call_nodes():
"""Expressions containing other CallNodes are not removed"""
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(A: T.Buffer(1, "int32"), n: T.int32):
A[0] = T.shift_left(n, 1, dtype="int32")
Expected = Before
After = tvm.s_tir.transform.RemoveStoreUndef()(Before)
tvm.ir.assert_structural_equal(After, Expected)
def test_remove_let_undef():
"""Remove a store whose value is bound to T.undef()"""
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(A: T.Buffer(1, "int32")):
val: T.let[T.int32] = T.undef(dtype="int32")
A[0] = val
@I.ir_module
class Expected:
@T.prim_func(s_tir=True)
def main(A: T.Buffer(1, "int32")):
T.evaluate(0)
After = tvm.s_tir.transform.RemoveStoreUndef()(Before)
tvm.ir.assert_structural_equal(After, Expected)
def test_raise_error_for_undef_as_store_indices():
"""Use of T.undef() as buffer indices is an error"""
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(A: T.Buffer(1, "int32")):
val: T.let[T.int32] = T.undef(dtype="int32")
A[val] = 5
with pytest.raises(RuntimeError):
tvm.s_tir.transform.RemoveStoreUndef()(Before)
def test_raise_error_for_undef_as_load_indices():
"""Use of T.undef() as buffer indices is an error
Even though this occurs as part of the BufferStore's value, the
T.undef() may not appear in a buffer's indices.
"""
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(A: T.Buffer(1, "int32"), B: T.Buffer(1, "int32")):
B[0] = A[T.undef(dtype="int32")]
with pytest.raises(RuntimeError):
tvm.s_tir.transform.RemoveStoreUndef()(Before)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,92 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ruff: noqa: F401
import sys
import tvm
from tvm.ir.module import IRModule
from tvm.script import tirx as T
from tvm.tirx.function import PrimFunc
def _check(before, expect):
if isinstance(before, PrimFunc):
before = IRModule({"main": before.with_attr("global_symbol", "main")})
if isinstance(expect, PrimFunc):
expect = IRModule({"main": expect.with_attr("global_symbol", "main")})
mod = tvm.s_tir.transform.RemoveWeightLayoutRewriteBlock()(before)
tvm.ir.assert_structural_equal(mod, expect)
def test_matmul():
@T.prim_func(s_tir=True)
def before(
A: T.Buffer((16, 16), "float32"),
B: T.Buffer((16, 16), "float32"),
C: T.Buffer((16, 16), "float32"),
) -> None:
T.func_attr({"layout_free_buffers": [1]})
B_ = T.sblock_alloc_buffer([16, 4, 4], dtype="float32")
for i0_o, i1_o in T.grid(16, 16):
with T.sblock("layout_rewrite"):
i0, i1 = T.axis.remap("SS", [i0_o, i1_o])
T.reads(B[i0, i1])
T.writes(B_[i1, i0 // 4, i0 % 4])
T.sblock_attr({"meta_schedule.layout_rewrite_preproc": True})
B_[i1, i0 // 4, i0 % 4] = B[i0, i1]
for i0, j, k0, i1, k1 in T.grid(4, 16, 4, 4, 4):
with T.sblock("matmul"):
vi = T.axis.spatial(16, i0 * 4 + i1)
vj = T.axis.spatial(16, j)
vk = T.axis.reduce(16, k0 * 4 + k1)
T.reads(A[vi, vk], B_[vj, vk // 4, vk % 4])
T.writes(C[vi, vj])
with T.init():
C[vi, vj] = T.float32(0)
C[vi, vj] = C[vi, vj] + A[vi, vk] * B_[vj, vk // 4, vk % 4]
@T.prim_func(s_tir=True)
def after(
A: T.Buffer((16, 16), "float32"),
B: T.Buffer((16, 4, 4), "float32"),
C: T.Buffer((16, 16), "float32"),
) -> None:
T.func_attr({"layout_free_buffers": [1]})
for i0_o, i1_o in T.grid(16, 16):
with T.sblock("layout_rewrite"):
i0, i1 = T.axis.remap("SS", [i0_o, i1_o])
T.reads()
T.writes()
T.sblock_attr({"meta_schedule.layout_rewrite_preproc": True})
T.evaluate(0)
for i0, j, k0, i1, k1 in T.grid(4, 16, 4, 4, 4):
with T.sblock("matmul"):
vi = T.axis.spatial(16, i0 * 4 + i1)
vj = T.axis.spatial(16, j)
vk = T.axis.reduce(16, k0 * 4 + k1)
T.reads(A[vi, vk], B[vj, vk // 4, vk % 4])
T.writes(C[vi, vj])
with T.init():
C[vi, vj] = T.float32(0)
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk // 4, vk % 4]
_check(before, after)
if __name__ == "__main__":
test_matmul()
@@ -0,0 +1,181 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ruff: noqa: E501, F401
import tvm
import tvm.testing
from tvm import s_tir
from tvm.script import tirx as T
# fmt: off
# pylint: disable=no-member,invalid-name,unused-variable,line-too-long,redefined-outer-name,redundant-keyword-arg
@tvm.script.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(inputs: T.Buffer((1, 4, 4, 512), "float32"), weight: T.Buffer((4, 4, 512, 256), "float32"), conv2d_transpose_nhwc: T.Buffer((1, 8, 8, 256), "float32")) -> None:
# function attr dict
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
inputs_flat = T.decl_buffer([8192], dtype="float32", data=inputs.data)
weight_flat = T.decl_buffer([2097152], dtype="float32", data=weight.data)
conv2d_transpose_nhwc_flat = T.decl_buffer([16384], dtype="float32", data=conv2d_transpose_nhwc.data)
# var definition
threadIdx_x = T.env_thread("threadIdx.x")
blockIdx_x = T.env_thread("blockIdx.x")
# body
T.launch_thread(blockIdx_x, 64)
conv2d_transpose_nhwc_local = T.decl_buffer([8], "float32", scope="local")
PadInput_shared = T.decl_buffer([768], "float32", scope="shared")
weight_shared = T.decl_buffer([4096], "float32", scope="shared")
T.launch_thread(threadIdx_x, 32)
for i2_3_init, i1_4_init, i2_4_init in T.grid(2, 2, 2):
conv2d_transpose_nhwc_local[i1_4_init * 4 + i2_3_init * 2 + i2_4_init] = T.float32(0)
for i6_0 in T.serial(16):
for ax0_ax1_ax2_ax3_fused_0 in T.serial(24):
PadInput_shared[ax0_ax1_ax2_ax3_fused_0 * 32 + threadIdx_x] = T.if_then_else(128 <= ax0_ax1_ax2_ax3_fused_0 * 32 + threadIdx_x and ax0_ax1_ax2_ax3_fused_0 * 32 + threadIdx_x < 640 and 1 <= blockIdx_x // 32 * 2 + (ax0_ax1_ax2_ax3_fused_0 * 32 + threadIdx_x) % 128 // 32 and blockIdx_x // 32 * 2 + (ax0_ax1_ax2_ax3_fused_0 * 32 + threadIdx_x) % 128 // 32 < 5, inputs_flat[blockIdx_x // 32 * 1024 + ax0_ax1_ax2_ax3_fused_0 * 512 + i6_0 * 32 + threadIdx_x - 2560], T.float32(0), dtype="float32")
for ax0_ax1_ax2_ax3_fused_0 in T.serial(32):
weight_shared[T.ramp(ax0_ax1_ax2_ax3_fused_0 * 128 + threadIdx_x * 4, 1, 4)] = weight_flat[T.ramp((ax0_ax1_ax2_ax3_fused_0 * 128 + threadIdx_x * 4) // 256 * 131072 + i6_0 * 8192 + (ax0_ax1_ax2_ax3_fused_0 * 128 + threadIdx_x * 4) % 256 // 8 * 256 + blockIdx_x % 32 * 8 + threadIdx_x % 2 * 4, 1, 4)]
for i6_1, i2_3, i4_2, i5_2, i6_2, i1_4, i2_4 in T.grid(4, 2, 4, 4, 8, 2, 2):
conv2d_transpose_nhwc_local[i1_4 * 4 + i2_3 * 2 + i2_4] = conv2d_transpose_nhwc_local[i1_4 * 4 + i2_3 * 2 + i2_4] + T.if_then_else((i1_4 + i4_2) % 2 == 0 and (i2_4 + i5_2) % 2 == 0, PadInput_shared[threadIdx_x // 8 * 128 + (i1_4 + i4_2) // 2 * 128 + (i2_4 + i5_2) // 2 * 32 + i2_3 * 32 + i6_1 * 8 + i6_2], T.float32(0), dtype="float32") * weight_shared[i6_1 * 64 + i6_2 * 8 + threadIdx_x % 8 + 3840 - i5_2 * 256 - i4_2 * 1024]
for ax1, ax2 in T.grid(2, 4):
conv2d_transpose_nhwc_flat[threadIdx_x // 8 * 4096 + ax1 * 2048 + blockIdx_x // 32 * 1024 + ax2 * 256 + blockIdx_x % 32 * 8 + threadIdx_x % 8] = conv2d_transpose_nhwc_local[ax1 * 4 + ax2]
@tvm.script.ir_module
class After:
@T.prim_func(s_tir=True)
def main(inputs: T.Buffer((1, 4, 4, 512), "float32"), weight: T.Buffer((4, 4, 512, 256), "float32"), conv2d_transpose_nhwc: T.Buffer((1, 8, 8, 256), "float32")) -> None:
# function attr dict
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
inputs_flat = T.decl_buffer([8192], dtype="float32", data=inputs.data)
weight_flat = T.decl_buffer([2097152], dtype="float32", data=weight.data)
conv2d_transpose_nhwc_flat = T.decl_buffer([16384], dtype="float32", data=conv2d_transpose_nhwc.data)
# var definition
threadIdx_x = T.env_thread("threadIdx.x")
blockIdx_x = T.env_thread("blockIdx.x")
# body
T.launch_thread(blockIdx_x, 64)
conv2d_transpose_nhwc_local = T.decl_buffer([8], "float32", scope="local")
PadInput_shared = T.decl_buffer([768], "float32", scope="shared")
weight_shared = T.decl_buffer([4096], "float32", scope="shared")
T.launch_thread(threadIdx_x, 32)
for i2_3_init, i1_4_init, i2_4_init in T.grid(2, 2, 2):
conv2d_transpose_nhwc_local[i1_4_init * 4 + i2_3_init * 2 + i2_4_init] = T.float32(0)
for i6_0 in T.serial(16):
for ax0_ax1_ax2_ax3_fused_0 in T.serial(24):
PadInput_shared[ax0_ax1_ax2_ax3_fused_0 * 32 + threadIdx_x] = T.if_then_else(1 <= (ax0_ax1_ax2_ax3_fused_0 + threadIdx_x // 32) // 4 and (ax0_ax1_ax2_ax3_fused_0 + threadIdx_x // 32) // 20 < 1 and 1 <= blockIdx_x // 32 * 2 + (ax0_ax1_ax2_ax3_fused_0 + threadIdx_x // 32) % 4 and (blockIdx_x // 32 * 2 + (ax0_ax1_ax2_ax3_fused_0 + threadIdx_x // 32) % 4) // 5 < 1, inputs_flat[blockIdx_x // 32 * 1024 + ax0_ax1_ax2_ax3_fused_0 * 512 + i6_0 * 32 + threadIdx_x - 2560], T.float32(0), dtype="float32")
for ax0_ax1_ax2_ax3_fused_0 in T.serial(32):
weight_shared[T.ramp(ax0_ax1_ax2_ax3_fused_0 * 128 + threadIdx_x * 4, 1, 4)] = weight_flat[T.ramp((ax0_ax1_ax2_ax3_fused_0 + threadIdx_x * 4 // 128) // 2 * 131072 + i6_0 * 8192 + (ax0_ax1_ax2_ax3_fused_0 * 16 + threadIdx_x * 4 // 8) % 32 * 256 + blockIdx_x % 32 * 8 + threadIdx_x % 2 * 4, 1, 4)]
for i6_1, i2_3, i4_2, i5_2, i6_2, i1_4, i2_4 in T.grid(4, 2, 4, 4, 8, 2, 2):
conv2d_transpose_nhwc_local[i1_4 * 4 + i2_3 * 2 + i2_4] = conv2d_transpose_nhwc_local[i1_4 * 4 + i2_3 * 2 + i2_4] + T.if_then_else((i1_4 + i4_2) % 2 == 0 and (i2_4 + i5_2) % 2 == 0, PadInput_shared[threadIdx_x // 8 * 128 + (i1_4 + i4_2) // 2 * 128 + (i2_4 + i5_2) // 2 * 32 + i2_3 * 32 + i6_1 * 8 + i6_2], T.float32(0), dtype="float32") * weight_shared[i6_1 * 64 + i6_2 * 8 + threadIdx_x % 8 + 3840 - i5_2 * 256 - i4_2 * 1024]
for ax1, ax2 in T.grid(2, 4):
conv2d_transpose_nhwc_flat[threadIdx_x // 8 * 4096 + ax1 * 2048 + blockIdx_x // 32 * 1024 + ax2 * 256 + blockIdx_x % 32 * 8 + threadIdx_x % 8] = conv2d_transpose_nhwc_local[ax1 * 4 + ax2]
@tvm.script.ir_module
class After_simplified:
@T.prim_func(s_tir=True)
def main(inputs: T.Buffer((1, 4, 4, 512), "float32"), weight: T.Buffer((4, 4, 512, 256), "float32"), conv2d_transpose_nhwc: T.Buffer((1, 8, 8, 256), "float32")) -> None:
# function attr dict
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
# var definition
threadIdx_x = T.env_thread("threadIdx.x")
blockIdx_x = T.env_thread("blockIdx.x")
inputs_flat = T.decl_buffer([8192], dtype="float32", data=inputs.data)
weight_flat = T.decl_buffer([2097152], dtype="float32", data=weight.data)
conv2d_transpose_nhwc_flat = T.decl_buffer([16384], dtype="float32", data=conv2d_transpose_nhwc.data)
# body
T.launch_thread(blockIdx_x, 64)
conv2d_transpose_nhwc_local = T.decl_buffer([8], "float32", scope="local")
PadInput_shared = T.decl_buffer([768], "float32", scope="shared")
weight_shared = T.decl_buffer([4096], "float32", scope="shared")
T.launch_thread(threadIdx_x, 32)
for i2_3_init, i1_4_init, i2_4_init in T.grid(2, 2, 2):
conv2d_transpose_nhwc_local[i1_4_init * 4 + i2_3_init * 2 + i2_4_init] = T.float32(0)
for i6_0 in T.serial(16):
for ax0_ax1_ax2_ax3_fused_0 in T.serial(24):
PadInput_shared[ax0_ax1_ax2_ax3_fused_0 * 32 + threadIdx_x] = T.if_then_else(4 <= ax0_ax1_ax2_ax3_fused_0 and ax0_ax1_ax2_ax3_fused_0 < 20 and 1 <= blockIdx_x // 32 * 2 + ax0_ax1_ax2_ax3_fused_0 % 4 and blockIdx_x // 32 * 2 + ax0_ax1_ax2_ax3_fused_0 % 4 < 5, inputs_flat[blockIdx_x // 32 * 1024 + ax0_ax1_ax2_ax3_fused_0 * 512 + i6_0 * 32 + threadIdx_x - 2560], T.float32(0), dtype="float32")
for ax0_ax1_ax2_ax3_fused_0 in T.serial(32):
weight_shared[T.ramp(ax0_ax1_ax2_ax3_fused_0 * 128 + threadIdx_x * 4, 1, 4)] = weight_flat[T.ramp(ax0_ax1_ax2_ax3_fused_0 // 2 * 131072 + i6_0 * 8192 + ax0_ax1_ax2_ax3_fused_0 % 2 * 4096 + threadIdx_x // 2 * 256 + blockIdx_x % 32 * 8 + threadIdx_x % 2 * 4, 1, 4)]
for i6_1, i2_3, i4_2, i5_2, i6_2, i1_4, i2_4 in T.grid(4, 2, 4, 4, 8, 2, 2):
conv2d_transpose_nhwc_local[i1_4 * 4 + i2_3 * 2 + i2_4] = conv2d_transpose_nhwc_local[i1_4 * 4 + i2_3 * 2 + i2_4] + T.if_then_else((i1_4 + i4_2) % 2 == 0 and (i2_4 + i5_2) % 2 == 0, PadInput_shared[threadIdx_x // 8 * 128 + (i1_4 + i4_2) // 2 * 128 + (i2_4 + i5_2) // 2 * 32 + i2_3 * 32 + i6_1 * 8 + i6_2], T.float32(0), dtype="float32") * weight_shared[i6_1 * 64 + i6_2 * 8 + threadIdx_x % 8 + 3840 - i5_2 * 256 - i4_2 * 1024]
for ax1, ax2 in T.grid(2, 4):
conv2d_transpose_nhwc_flat[threadIdx_x // 8 * 4096 + ax1 * 2048 + blockIdx_x // 32 * 1024 + ax2 * 256 + blockIdx_x % 32 * 8 + threadIdx_x % 8] = conv2d_transpose_nhwc_local[ax1 * 4 + ax2]
# pylint: enable=no-member,invalid-name,unused-variable,line-too-long,redefined-outer-name,redundant-keyword-arg
# fmt: on
def test_renormalize_split_pattern():
after = tvm.s_tir.transform.RenormalizeSplitPattern()(Before)
tvm.ir.assert_structural_equal(after, After)
after = tvm.tirx.transform.StmtSimplify()(after)
tvm.ir.assert_structural_equal(after, After_simplified)
@T.prim_func(s_tir=True)
def impossible_equality(n: T.int32):
# Prior to bugfix, this conditional defined the expression "2" as
# equal to zero within the then_case. [min_value=2, max_value=0]
if 2 == 0:
# Then this expression evaluates n/2, using the min/max values
# of "2", which is caught as a divide by zero error.
if n // 2 >= 16:
T.evaluate(0)
@T.prim_func(s_tir=True)
def impossible_inequality(n: T.int32):
# Prior to bugfix, this conditional set up a range of possible
# values for the expression "-2" as [0, kPosInf].
if -1 < -2:
if n // (-2) >= 16:
T.evaluate(0)
integer_condition = tvm.testing.parameter(
impossible_equality,
impossible_inequality,
)
def test_analyze_inside_integer_conditional(integer_condition):
"""Avoid crash occurring in ConstIntBoundAnalyzer.
Crash occurred when simplifying some expressions with provably
false integer expressions. If the expressions were renormalized
before calling Simplify, conditional statements could assign a
range of possible values to integers, as if they were variables.
This would result in divide by zero throwing an exception,
followed by a second exception during stack unwinding causing the
program to crash.
"""
# Similar issue would occur in most transformations that subclass
# IRMutatorWithAnalyzer. tirx.transform.StmtSimplify() is an
# exception, as it rewrites the integer conditionals first. These
# tests are written using RenormalizeSplitPattern as it is the
# first case identified.
transform = tvm.s_tir.transform.RenormalizeSplitPattern()
# Issue would result in an error through while applying the transformation.
mod = tvm.IRModule.from_expr(integer_condition)
transform(mod)
if __name__ == "__main__":
tvm.testing.main()
@@ -0,0 +1,92 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ruff: noqa: E501, F401, F841
import tvm
from tvm import s_tir
from tvm.script import ir as I
from tvm.script import tirx as T
def test_rewrite_Select():
@I.ir_module
class ModuleY:
@T.prim_func(s_tir=True)
def main(i: T.int32):
A = T.alloc_buffer((100,))
T.evaluate(T.Select(i > 1, A[i - 1], T.float32(1.0)))
yy = tvm.s_tir.transform.RewriteUnsafeSelect()(ModuleY)["main"].body.seq[-1].value
@I.ir_module
class ModuleZ:
@T.prim_func(s_tir=True)
def main(i: T.int32):
A = T.alloc_buffer((100,))
T.evaluate(
T.Select(
T.Select(i > 1, A[i - 1], T.float32(1.0)) > T.float32(0.0), A[i], T.float32(0.1)
)
)
zz = tvm.s_tir.transform.RewriteUnsafeSelect()(ModuleZ)["main"].body.seq[-1].value
@I.ir_module
class ModuleA:
@T.prim_func(s_tir=True)
def main(i: T.int32):
A = T.alloc_buffer((100,))
# Inline y and z to avoid Let bindings - outer Select condition is safe (no buffer access)
T.evaluate(
T.Select(
T.floordiv(i, 4) > 10,
T.Select(i > 1, A[i - 1], T.float32(1.0)),
T.Select(
T.Select(i > 1, A[i - 1], T.float32(1.0)) > T.float32(0.0),
A[i],
T.float32(0.1),
),
)
)
aa = tvm.s_tir.transform.RewriteUnsafeSelect()(ModuleA)["main"].body.seq[-1].value
builtin_if_then_else = tvm.ir.Op.get("tirx.if_then_else")
assert yy.op.same_as(builtin_if_then_else)
assert yy.op.same_as(builtin_if_then_else)
assert isinstance(aa, tvm.tirx.Select)
def test_scalar_address_survives_generic_transforms():
@I.ir_module
class Before:
@T.prim_func(s_tir=True)
def main(value: T.uint32, condition: T.bool):
T.evaluate(T.Select(condition, T.isnullptr(T.address_of(value)), T.bool(False)))
transforms = [
tvm.s_tir.transform.RewriteUnsafeSelect(),
tvm.s_tir.transform.ThreadSync("shared"),
tvm.s_tir.transform.MergeSharedMemoryAllocations(),
tvm.tirx.transform.StorageRewrite(),
]
for transform in transforms:
transformed = transform(Before)
assert transformed["main"] is not None
if __name__ == "__main__":
test_rewrite_Select()
@@ -0,0 +1,189 @@
# 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, F821, F841
import pytest
import tvm
import tvm.testing
from tvm import s_tir
from tvm.script import tirx as T
from tvm.testing import env
def run_passes(func: tvm.tirx.PrimFunc):
mod = tvm.IRModule.from_expr(func)
cuda_target = tvm.target.Target("cuda", host="llvm")
mod = tvm.tirx.transform.Apply(
lambda f: f.with_attr({"global_symbol": "test", "target": cuda_target})
)(mod)
mod = tvm.tirx.transform.SplitHostDevice()(mod)
return tvm.s_tir.transform.ThreadSync("shared")(mod)
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda(), reason="need cuda")
def test_sync_read_thread_id_independent_location():
@T.prim_func(check_well_formed=False, s_tir=True)
def func(p0_arg: T.Buffer((1, 2, 1, 1), "float32"), p1: T.Buffer(2, "float32")) -> None:
threadIdx_x = T.env_thread("threadIdx.x")
blockIdx_x = T.env_thread("blockIdx.x")
p0 = T.Buffer([2], dtype="float32", data=p0_arg.data)
result_local = T.sblock_alloc_buffer([1], dtype="float32", scope="local")
temp_shared = T.sblock_alloc_buffer([1], dtype="float32", scope="shared")
T.launch_thread(blockIdx_x, 8)
T.launch_thread(threadIdx_x, 4)
result_local[0] = T.float32(0)
if threadIdx_x < 1:
temp_shared[0] = p0[0]
result_local[0] = result_local[0] + temp_shared[0] * p1[0]
if threadIdx_x < 1:
temp_shared[0] = p0[1]
result_local[0] = result_local[0] + temp_shared[0] * p1[1]
mod = run_passes(func)
assert "T.tvm_storage_sync" in str(mod)
def test_sync_shared_dyn():
@T.prim_func(private=True, s_tir=True)
def func(A: T.Buffer((4, 4), "float32"), E: T.Buffer((4, 4), "float32")):
blockIdx_x = T.launch_thread("blockIdx.x", 1)
B = T.alloc_buffer((24,), "float32", scope="shared.dyn")
C = T.alloc_buffer((1,), "float32", scope="local")
D = T.alloc_buffer((16,), "float32", scope="shared.dyn")
threadIdx_x = T.launch_thread("threadIdx.x", 16)
B_1 = T.decl_buffer((24,), data=B.data, scope="shared.dyn")
A_1 = T.decl_buffer((16,), data=A.data)
B_1[threadIdx_x // 4 * 6 + threadIdx_x % 4] = A_1[threadIdx_x]
C_1 = T.decl_buffer((1,), data=C.data, scope="local")
C_1[0] = B_1[threadIdx_x // 4 * 6 + threadIdx_x % 4]
D_1 = T.decl_buffer((16,), data=D.data, scope="shared.dyn")
D_1[threadIdx_x] = C_1[0]
E_1 = T.decl_buffer((16,), data=E.data)
E_1[threadIdx_x] = D_1[threadIdx_x]
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((4, 4), "float32"), E: T.Buffer((4, 4), "float32")):
blockIdx_x = T.launch_thread("blockIdx.x", 1)
B_1 = T.alloc_buffer((24,), "float32", scope="shared.dyn")
C_1 = T.alloc_buffer((1,), "float32", scope="local")
D_1 = T.alloc_buffer((16,), "float32", scope="shared.dyn")
threadIdx_x = T.launch_thread("threadIdx.x", 16)
B_1_1 = T.decl_buffer((24,), data=B_1.data, scope="shared.dyn")
A_1 = T.decl_buffer((16,), data=A.data)
B_1_1[threadIdx_x // 4 * 6 + threadIdx_x % 4] = A_1[threadIdx_x]
C_1_1 = T.decl_buffer((1,), data=C_1.data, scope="local")
C_1_1[0] = B_1_1[threadIdx_x // 4 * 6 + threadIdx_x % 4]
D_1_1 = T.decl_buffer((16,), data=D_1.data, scope="shared.dyn")
T.evaluate(T.call_intrin("int32", "tirx.tvm_storage_sync", "shared.dyn"))
D_1_1[threadIdx_x] = C_1_1[0]
E_1 = T.decl_buffer((16,), data=E.data)
E_1[threadIdx_x] = D_1_1[threadIdx_x]
mod = tvm.IRModule({"main": func})
mod = tvm.s_tir.transform.ThreadSync("shared.dyn")(mod)
tvm.ir.assert_structural_equal(mod["main"], expected)
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda(), reason="need cuda")
def test_sync_bind():
@T.prim_func(private=True, s_tir=True)
def func(A: T.Buffer((16 * 512), "float32")):
blockIdx_x = T.launch_thread("blockIdx.x", 16)
A_shared = T.alloc_buffer((512,), "float32", scope="shared")
in_thread_A_temp = T.alloc_buffer((1,), "float32", scope="local")
cross_thread_A_temp = T.alloc_buffer((1,), "float32", scope="local")
threadIdx_x = T.launch_thread("threadIdx.x", 128)
A_shared_1 = T.decl_buffer((512,), data=A_shared.data, scope="shared")
for ax0 in range(512):
A_shared_1[ax0] = A[blockIdx_x * 512 + ax0]
in_thread_A_temp_1 = T.decl_buffer((1,), data=in_thread_A_temp.data, scope="local")
in_thread_A_temp_1[0] = T.float32(0)
A_temp_1 = T.bind(in_thread_A_temp_1[0] + A_shared_1[threadIdx_x])
in_thread_A_temp_1[0] = A_temp_1
A_temp_2 = T.bind(in_thread_A_temp_1[0] + A_shared_1[threadIdx_x + 128])
in_thread_A_temp_1[0] = A_temp_2
A_temp_3 = T.bind(in_thread_A_temp_1[0] + A_shared_1[threadIdx_x + 256])
in_thread_A_temp_1[0] = A_temp_3
A_temp_4 = T.bind(in_thread_A_temp_1[0] + A_shared_1[threadIdx_x + 384])
in_thread_A_temp_1[0] = A_temp_4
cross_thread_A_temp_1 = T.decl_buffer((1,), data=cross_thread_A_temp.data, scope="local")
with T.attr(
T.comm_reducer(lambda x0, y0: x0 + y0, [T.float32(0)]),
"reduce_scope",
T.int32(0),
):
T.tvm_thread_allreduce(
T.uint32(1),
in_thread_A_temp_1[0],
T.bool(True),
cross_thread_A_temp_1[0],
threadIdx_x,
)
@T.prim_func(private=True, s_tir=True)
def expected(A: T.Buffer((8192,), "float32")):
blockIdx_x = T.launch_thread("blockIdx.x", 16)
A_shared_1 = T.alloc_buffer((512,), "float32", scope="shared")
in_thread_A_temp_1 = T.alloc_buffer((1,), "float32", scope="local")
cross_thread_A_temp_1 = T.alloc_buffer((1,), "float32", scope="local")
threadIdx_x = T.launch_thread("threadIdx.x", 128)
A_shared_1_1 = T.decl_buffer((512,), data=A_shared_1.data, scope="shared")
for ax0 in range(512):
A_shared_1_1[ax0] = A[blockIdx_x * 512 + ax0]
in_thread_A_temp_1_1 = T.decl_buffer((1,), data=in_thread_A_temp_1.data, scope="local")
in_thread_A_temp_1_1[0] = T.float32(0)
T.evaluate(T.call_intrin("int32", "tirx.tvm_storage_sync", "shared"))
A_temp_1 = T.bind(in_thread_A_temp_1_1[0] + A_shared_1_1[threadIdx_x])
in_thread_A_temp_1_1[0] = A_temp_1
A_temp_2 = T.bind(in_thread_A_temp_1_1[0] + A_shared_1_1[threadIdx_x + 128])
in_thread_A_temp_1_1[0] = A_temp_2
A_temp_3 = T.bind(in_thread_A_temp_1_1[0] + A_shared_1_1[threadIdx_x + 256])
in_thread_A_temp_1_1[0] = A_temp_3
A_temp_4 = T.bind(in_thread_A_temp_1_1[0] + A_shared_1_1[threadIdx_x + 384])
in_thread_A_temp_1_1[0] = A_temp_4
cross_thread_A_temp_1_1 = T.decl_buffer(
(1,), data=cross_thread_A_temp_1.data, scope="local"
)
T.attr(
T.comm_reducer(lambda x0, y0: x0 + y0, [T.float32(0)]),
"reduce_scope",
T.int32(0),
)
T.tvm_thread_allreduce(
T.uint32(1),
in_thread_A_temp_1_1[0],
T.bool(True),
cross_thread_A_temp_1_1[0],
threadIdx_x,
)
mod = tvm.IRModule({"main": func})
mod = tvm.s_tir.transform.ThreadSync("shared")(mod)
tvm.ir.assert_structural_equal(mod["main"], expected)
if __name__ == "__main__":
test_thread_storage_sync()
test_sync_else_branch()
test_sync_read_thread_id_independent_location()
test_sync_shared_dyn()
test_sync_bind()
@@ -0,0 +1,319 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ruff: noqa: F401
import sys
import pytest
import tvm
import tvm.testing
from tvm import s_tir
from tvm.script import tirx as T
def _check(original, transformed):
mod = tvm.IRModule.from_expr(original.with_attr("global_symbol", "main"))
mod = tvm.s_tir.transform.UnifyThreadBinding()(mod)
mod = tvm.tirx.transform.StmtSimplify()(mod)
tvm.ir.assert_structural_equal(
mod["main"], transformed.with_attr("global_symbol", "main"), True
)
def _check_fail(original):
mod = tvm.IRModule.from_expr(original)
with pytest.raises(ValueError):
tvm.s_tir.transform.UnifyThreadBinding()(mod)
@T.prim_func(s_tir=True)
def element_wise_thread_x(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 in T.thread_binding(0, 128, "blockIdx.x"):
for j0_0 in T.thread_binding(0, 4, "threadIdx.x"):
for j0_1 in T.serial(0, 32):
with T.sblock(""):
B[i, j0_0 * 32 + j0_1] = A[i, j0_0 * 32 + j0_1] * 2.0
for j1_0 in T.thread_binding(0, 4, "threadIdx.x"):
for j1_1 in T.serial(0, 32):
with T.sblock(""):
C[i, j1_0 * 32 + j1_1] = B[i, j1_0 * 32 + j1_1] + 1.0
@T.prim_func(s_tir=True)
def unified_element_wise_thread_x(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 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
)
@T.prim_func(s_tir=True)
def element_wise_thread_x_different_dtype(
A: T.Buffer((128, 128), "float32"),
B: T.Buffer((128, 128), "float32"),
C: T.Buffer((128, 128), "float32"),
) -> None:
for i in T.thread_binding(128, "blockIdx.x"):
for j0_0 in T.thread_binding(4, "threadIdx.x"):
for j0_1 in T.serial(0, 32):
with T.sblock(""):
B[i, j0_0 * 32 + j0_1] = A[i, j0_0 * 32 + j0_1] * 2.0
for j1_0 in T.thread_binding(T.int64(4), "threadIdx.x"):
for j1_1 in T.serial(T.int64(32)):
with T.sblock(""):
C[i, j1_0 * T.int64(32) + j1_1] = B[i, j1_0 * T.int64(32) + j1_1] + 1.0
@T.prim_func(s_tir=True)
def unified_element_wise_thread_x_different_dtype(
A: T.Buffer((128, 128), "float32"),
B: T.Buffer((128, 128), "float32"),
C: T.Buffer((128, 128), "float32"),
) -> None:
for blockIdx_x in T.thread_binding(128, "blockIdx.x"):
for threadIdx_x in T.thread_binding(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(T.int64(32)):
with T.sblock(""):
C[blockIdx_x, T.cast(threadIdx_x, "int64") * T.int64(32) + j1_1] = (
B[blockIdx_x, T.cast(threadIdx_x, "int64") * T.int64(32) + j1_1] + 1.0
)
@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 j0_1 in T.serial(0, 32):
with T.sblock(""):
B[i, j0_0 * 32 + j0_1] = A[i, j0_0 * 32 + j0_1] * 2.0
for j1_1 in T.serial(0, 32):
with T.sblock(""):
C[i, j1_0 * 32 + j1_1] = B[i, j1_0 * 32 + j1_1] + 1.0
@T.prim_func(s_tir=True)
def unified_element_wise_env_thread_x(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 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
)
@T.prim_func(s_tir=True)
def element_wise_vthread_x(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
B = T.match_buffer(b, [128, 128])
for i_0 in T.thread_binding(0, 2, "vthread.x"):
for i_1 in T.thread_binding(0, 64, "threadIdx.x"):
for j_0 in T.thread_binding(0, 2, "vthread.x"):
for j_1 in T.serial(0, 64):
with T.sblock(""):
B[i_0 * 64 + i_1, j_0 * 64 + j_1] = A[i_0 * 64 + i_1, j_0 * 64 + j_1] * 2.0
@T.prim_func(s_tir=True)
def unified_element_wise_vthread_x(a: T.handle, b: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
B = T.match_buffer(b, [128, 128])
for vthread_x in T.thread_binding(0, 2, "vthread.x"):
for threadIdx_x in T.thread_binding(0, 64, "threadIdx.x"):
for j_1 in T.serial(0, 64):
with T.sblock(""):
B[vthread_x * 64 + threadIdx_x, vthread_x * 64 + j_1] = (
A[vthread_x * 64 + threadIdx_x, vthread_x * 64 + j_1] * 2.0
)
@T.prim_func(s_tir=True)
def element_wise_two_thread_x_in_same_kernel_not_equal(
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, 64])
for i in T.thread_binding(0, 128, "blockIdx.x"):
for j0 in T.thread_binding(0, 128, "threadIdx.x"):
B[i, j0] = A[i, j0] * 2.0
for j1 in T.thread_binding(0, 64, "threadIdx.x"):
C[i, j1] = A[i, j1] + 1.0
@T.prim_func(s_tir=True)
def element_wise_kernels_with_different_size(
a: T.handle, b: T.handle, c: T.handle, d: T.handle
) -> None:
A = T.match_buffer(a, [128, 128])
B = T.match_buffer(b, [128, 128])
C = T.match_buffer(c, [256, 256])
D = T.match_buffer(d, [256, 256])
for i0 in T.thread_binding(0, 128, "blockIdx.x"):
for j0 in T.thread_binding(0, 128, "threadIdx.x"):
B[i0, j0] = A[i0, j0] * 2.0
for i1 in T.thread_binding(0, 256, "blockIdx.x"):
for j1 in T.thread_binding(0, 256, "threadIdx.x"):
D[i1, j1] = C[i1, j1] + 1.0
@T.prim_func(s_tir=True)
def unified_element_wise_kernels_with_different_size(
a: T.handle, b: T.handle, c: T.handle, d: T.handle
) -> None:
A = T.match_buffer(a, [128, 128])
B = T.match_buffer(b, [128, 128])
C = T.match_buffer(c, [256, 256])
D = T.match_buffer(d, [256, 256])
for blockIdx_x in T.thread_binding(0, 128, "blockIdx.x"):
for threadIdx_x in T.thread_binding(0, 128, "threadIdx.x"):
B[blockIdx_x, threadIdx_x] = A[blockIdx_x, threadIdx_x] * 2.0
for blockIdx_x in T.thread_binding(0, 256, "blockIdx.x"):
for threadIdx_x in T.thread_binding(0, 256, "threadIdx.x"):
D[blockIdx_x, threadIdx_x] = C[blockIdx_x, threadIdx_x] + 1.0
@T.prim_func(s_tir=True)
def element_wise_implicit_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])
for i in T.thread_binding(0, 128, "threadIdx.y"):
for j0_0 in T.thread_binding(0, 4, "threadIdx.x"):
for j0_1 in T.serial(0, 32):
with T.sblock(""):
B[i, j0_0 * 32 + j0_1] = A[i, j0_0 * 32 + j0_1] * 2.0
for j1_0 in T.thread_binding(0, 4, "threadIdx.x"):
for j1_1 in T.serial(0, 32):
with T.sblock(""):
C[i, j1_0 * 32 + j1_1] = B[i, j1_0 * 32 + j1_1] + 1.0
@T.prim_func(s_tir=True)
def unified_element_wise_implicit_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])
for blockIdx_x in T.thread_binding(0, 128, "threadIdx.y"):
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
)
def test_thread_x():
_check(element_wise_thread_x, unified_element_wise_thread_x)
def test_thread_x_different_dtype():
_check(element_wise_thread_x_different_dtype, unified_element_wise_thread_x_different_dtype)
def test_env_thread_x():
_check(element_wise_env_thread_x, unified_element_wise_env_thread_x)
def test_vthread_x():
_check(element_wise_vthread_x, unified_element_wise_vthread_x)
def test_two_thread_x_in_same_kernel_not_equal():
_check_fail(element_wise_two_thread_x_in_same_kernel_not_equal)
def test_kernels_with_different_size():
_check(
element_wise_kernels_with_different_size, unified_element_wise_kernels_with_different_size
)
def test_implicit_block():
_check(element_wise_implicit_block, unified_element_wise_implicit_block)
def test_inner_binding_with_annotation():
@T.prim_func(s_tir=True)
def inner_binding_with_annotation(A: T.Buffer((64,), "float32"), B: T.Buffer((64,), "float32")):
for bx in T.thread_binding(32, "blockIdx.x"):
for tx in T.thread_binding(2, "threadIdx.x", annotations={"my_annotation": 1}):
with T.sblock("block"):
v = T.axis.spatial(64, bx * 2 + tx)
B[v] = A[v]
@T.prim_func(s_tir=True)
def unified_inner_binding_with_annotation(
A: T.Buffer((64,), "float32"), B: T.Buffer((64,), "float32")
):
for blockIdx_x in T.thread_binding(32, thread="blockIdx.x"):
for threadIdx_x in T.thread_binding(2, thread="threadIdx.x"):
for var in T.serial(1, annotations={"my_annotation": 1}):
with T.sblock("block"):
v = T.axis.spatial(64, blockIdx_x * 2 + threadIdx_x)
T.reads(A[v])
T.writes(B[v])
B[v] = A[v]
_check(inner_binding_with_annotation, unified_inner_binding_with_annotation)
if __name__ == "__main__":
tvm.testing.main()