# Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # pylint: disable=missing-docstring # ruff: noqa: E501, F841 import tempfile import pytest pytest.importorskip("cloudpickle") # tvm.s_tir.dlight.benchmark imports cloudpickle import tvm.testing from tvm.s_tir import meta_schedule as ms from tvm.s_tir.dlight.benchmark import ( benchmark, benchmark_prim_func, benchmark_relax_func, extract_from_relax, extract_func_info_from_prim_func, extract_prim_func, ) from tvm.s_tir.meta_schedule.testing.local_rpc import LocalRPC from tvm.script import ir as I from tvm.script import relax as R from tvm.script import tirx as T # The test function uses an undefined symbolic var in Relax. # In principle, this should be attached to an argument. # pylint: disable=no-self-argument,invalid-name,line-too-long,no-method-argument # fmt: off @I.ir_module(check_well_formed=False, s_tir=True) class Module: @T.prim_func(s_tir=True) def full1(var_T_full: T.handle): T.func_attr({"op_pattern": 0, "tirx.noalias": True}) n = T.int64() T_full = T.match_buffer(var_T_full, (T.int64(1), T.int64(32), T.int64(1), n), "float16") # with T.sblock("root"): for ax0, ax1, ax2, ax3 in T.grid(T.int64(1), T.int64(32), T.int64(1), n): with T.sblock("T_full"): v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3]) T.reads() T.writes(T_full[v_ax0, v_ax1, v_ax2, v_ax3]) T_full[v_ax0, v_ax1, v_ax2, v_ax3] = T.float16(1.0) @T.prim_func(s_tir=True) def full2(var_T_full: T.handle): T.func_attr({"op_pattern": 0, "tirx.noalias": True}) n = T.int64() T_full = T.match_buffer(var_T_full, (T.int64(1), T.int64(32), n, T.int64(128)), "float16") # with T.sblock("root"): for ax0, ax1, ax2, ax3 in T.grid(T.int64(1), T.int64(32), n, T.int64(128)): with T.sblock("T_full"): v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3]) T.reads() T.writes(T_full[v_ax0, v_ax1, v_ax2, v_ax3]) T_full[v_ax0, v_ax1, v_ax2, v_ax3] = T.float16(1.0) @T.prim_func(s_tir=True) def matmul1(var_A: T.handle, var_B: T.handle, matmul: T.Buffer((T.int64(1), T.int64(32), T.int64(1), T.int64(128)), "float16")): T.func_attr({"op_pattern": 4, "tirx.noalias": True}) n = T.int64() A = T.match_buffer(var_A, (T.int64(1), T.int64(32), T.int64(1), n), "float16") B = T.match_buffer(var_B, (T.int64(1), T.int64(32), n, T.int64(128)), "float16") # with T.sblock("root"): for i0, i1, i2, i3, k in T.grid(T.int64(1), T.int64(32), T.int64(1), T.int64(128), n): with T.sblock("matmul"): v_i0, v_i1, v_i2, v_i3, v_k = T.axis.remap("SSSSR", [i0, i1, i2, i3, k]) T.reads(A[v_i0, v_i1, v_i2, v_k], B[v_i0, v_i1, v_k, v_i3]) T.writes(matmul[v_i0, v_i1, v_i2, v_i3]) with T.init(): matmul[v_i0, v_i1, v_i2, v_i3] = T.float16(0) matmul[v_i0, v_i1, v_i2, v_i3] = matmul[v_i0, v_i1, v_i2, v_i3] + A[v_i0, v_i1, v_i2, v_k] * B[v_i0, v_i1, v_k, v_i3] @R.function def test(): n = T.int64() R.func_attr({"tir_var_upper_bound": {"n": 2048}}) cls = Module with R.dataflow(): lv1 = R.call_tir(cls.full1,(), out_ty=R.Tensor((1, 32, 1, n), dtype="float16")) lv1_1 = R.call_tir(cls.full1,(), out_ty=R.Tensor((1, 32, 1, n), dtype="float16")) lv1_2 = R.call_tir(cls.full1,(), out_ty=R.Tensor((1, 32, 1, n), dtype="float16")) lv2 = R.call_tir(cls.full2,(), out_ty=R.Tensor((1, 32, n, 128), dtype="float16")) lv2_1 = R.call_tir(cls.full2,(), out_ty=R.Tensor((1, 32, n, 128), dtype="float16")) lv3 = R.call_tir(cls.matmul1, (lv1, lv2), out_ty=R.Tensor((1, 32, 1, 128), dtype="float16")) R.output(lv3) return lv3 @T.prim_func(s_tir=True) def cuda_workload(var_inp0: T.handle, inp1: T.Buffer((T.int64(4096), T.int64(4096)), "float32"), var_matmul: T.handle): T.func_attr({"tirx.is_scheduled": True}) m = T.int64() inp0 = T.match_buffer(var_inp0, (T.int64(1), m, T.int64(4096))) matmul = T.match_buffer(var_matmul, (T.int64(1), m, T.int64(4096))) # with T.sblock("root"): matmul_reindex_pad_local = T.sblock_alloc_buffer((T.int64(1), (m + T.int64(31)) // T.int64(32) * T.int64(32), T.int64(4096)), scope="local") inp0_reindex_pad_shared = T.sblock_alloc_buffer((T.int64(1), (m + T.int64(31)) // T.int64(32) * T.int64(32), T.int64(4096)), scope="shared") inp1_reindex_shared = T.sblock_alloc_buffer((T.int64(1), T.int64(4096), T.int64(4096)), scope="shared") for ax0 in T.thread_binding(T.int64(1), thread="blockIdx.z"): for ax1_0 in T.thread_binding((m + T.int64(31)) // T.int64(32), thread="blockIdx.x"): for ax2_0 in T.thread_binding(T.int64(64), thread="blockIdx.y"): for ax2_1 in T.thread_binding(T.int64(1), thread="vthread.y"): for ax1_1 in T.thread_binding(T.int64(1), thread="vthread.x"): for ax2_2 in T.thread_binding(T.int64(16), thread="threadIdx.y"): for ax1_2 in T.thread_binding(T.int64(8), thread="threadIdx.x", annotations={"pragma_auto_unroll_max_step": 256, "pragma_unroll_explicit": 1}): for ax2_3_init, ax1_3_init in T.grid(T.int64(4), T.int64(4)): with T.sblock("matmul_init"): v0 = T.axis.spatial(T.int64(1), ax0) v1 = T.axis.spatial((m + T.int64(31)) // T.int64(32) * T.int64(32), ax1_0 * T.int64(32) + ax1_1 * T.int64(32) + ax1_2 * T.int64(4) + ax1_3_init) v2 = T.axis.spatial(T.int64(4096), ax2_0 * T.int64(64) + ax2_1 * T.int64(64) + ax2_2 * T.int64(4) + ax2_3_init) T.reads() T.writes(matmul_reindex_pad_local[T.int64(0), v1, v2]) matmul_reindex_pad_local[T.int64(0), v1, v2] = T.float32(0) for ax3_0 in range(T.int64(256)): for ax0_ax1_ax2_fused_0 in T.thread_binding(T.int64(16), thread="threadIdx.y"): for ax0_ax1_ax2_fused_1 in T.thread_binding(T.int64(8), thread="threadIdx.x"): for ax0_ax1_ax2_fused_2 in range(T.int64(2)): for ax0_ax1_ax2_fused_3 in T.vectorized(T.int64(2)): with T.sblock("inp0_reindex_pad_shared"): v0 = T.axis.spatial(T.int64(1), T.int64(0)) v1 = T.axis.spatial((m + T.int64(31)) // T.int64(32) * T.int64(32), ax1_0 * T.int64(32) + (ax0_ax1_ax2_fused_0 * T.int64(32) + ax0_ax1_ax2_fused_1 * T.int64(4) + ax0_ax1_ax2_fused_2 * T.int64(2) + ax0_ax1_ax2_fused_3) // T.int64(16)) v2 = T.axis.spatial(T.int64(4096), ax3_0 * T.int64(16) + (ax0_ax1_ax2_fused_0 * T.int64(32) + ax0_ax1_ax2_fused_1 * T.int64(4) + ax0_ax1_ax2_fused_2 * T.int64(2) + ax0_ax1_ax2_fused_3) % T.int64(16)) T.reads(inp0[v0, v1, v2]) T.writes(inp0_reindex_pad_shared[v0, v1, v2]) T.sblock_attr({"buffer_dim_align": [[0, 1, 8, 2]]}) inp0_reindex_pad_shared[v0, v1, v2] = T.if_then_else(v1 < m, inp0[v0, v1, v2], T.float32(0)) for ax0_ax1_ax2_fused_0 in T.thread_binding(T.int64(16), thread="threadIdx.y"): for ax0_ax1_ax2_fused_1 in T.thread_binding(T.int64(8), thread="threadIdx.x"): for ax0_ax1_ax2_fused_2 in range(T.int64(4)): for ax0_ax1_ax2_fused_3 in T.vectorized(T.int64(2)): with T.sblock("inp1_reindex_shared"): v0 = T.axis.spatial(T.int64(1), T.int64(0)) v1 = T.axis.spatial(T.int64(4096), ax2_0 * T.int64(64) + (ax0_ax1_ax2_fused_0 * T.int64(64) + ax0_ax1_ax2_fused_1 * T.int64(8) + ax0_ax1_ax2_fused_2 * T.int64(2) + ax0_ax1_ax2_fused_3) // T.int64(16)) v2 = T.axis.spatial(T.int64(4096), ax3_0 * T.int64(16) + (ax0_ax1_ax2_fused_0 * T.int64(64) + ax0_ax1_ax2_fused_1 * T.int64(8) + ax0_ax1_ax2_fused_2 * T.int64(2) + ax0_ax1_ax2_fused_3) % T.int64(16)) T.reads(inp1[v2, v1]) T.writes(inp1_reindex_shared[v0, v1, v2]) T.sblock_attr({"buffer_dim_align": [[0, 1, 8, 2]]}) inp1_reindex_shared[v0, v1, v2] = inp1[v2, v1] for ax3_1, ax2_3, ax1_3 in T.grid(T.int64(16), T.int64(4), T.int64(4)): with T.sblock("matmul_update"): v0 = T.axis.spatial(T.int64(1), ax0) v1 = T.axis.spatial((m + T.int64(31)) // T.int64(32) * T.int64(32), ax1_0 * T.int64(32) + ax1_1 * T.int64(32) + ax1_2 * T.int64(4) + ax1_3) v2 = T.axis.spatial(T.int64(4096), ax2_0 * T.int64(64) + ax2_1 * T.int64(64) + ax2_2 * T.int64(4) + ax2_3) v3 = T.axis.reduce(T.int64(4096), ax3_0 * T.int64(16) + ax3_1) T.reads(matmul_reindex_pad_local[T.int64(0), v1, v2], inp0_reindex_pad_shared[T.int64(0), v1, v3], inp1_reindex_shared[T.int64(0), v2, v3]) T.writes(matmul_reindex_pad_local[T.int64(0), v1, v2]) matmul_reindex_pad_local[T.int64(0), v1, v2] = matmul_reindex_pad_local[T.int64(0), v1, v2] + inp0_reindex_pad_shared[T.int64(0), v1, v3] * inp1_reindex_shared[T.int64(0), v2, v3] for ax0_1, ax1, ax2_0_1 in T.grid(T.int64(1), T.int64(4), T.int64(2)): for ax2_1_1 in T.vectorized(T.int64(2)): with T.sblock("matmul_reindex_pad_local"): v0 = T.axis.spatial(T.int64(1), ax0_1) v1 = T.axis.spatial((m + T.int64(31)) // T.int64(32) * T.int64(32), ax1_0 * T.int64(32) + ax1_2 * T.int64(4) + ax1) v2 = T.axis.spatial(T.int64(4096), ax2_0 * T.int64(64) + ax2_2 * T.int64(4) + ax2_0_1 * T.int64(2) + ax2_1_1) T.reads(matmul_reindex_pad_local[v0, v1, v2]) T.writes(matmul[T.int64(0), v1, v2]) if v1 < m: matmul[T.int64(0), v1, v2] = matmul_reindex_pad_local[v0, v1, v2] # fmt: on # pylint: enable=no-self-argument,invalid-name,line-too-long,no-method-argument @pytest.mark.skip("requires CUDA") def test_benchmark_prim_func_rpc(): with LocalRPC() as rpc: rpc_config = ms.runner.RPCConfig( tracker_host=rpc.tracker_host, tracker_port=rpc.tracker_port, tracker_key=rpc.tracker_key, session_priority=1, session_timeout_sec=100, ) input_infos, _, _ = benchmark( cuda_workload, args=[ ((1, "m", 4096), "float32"), ((4096, 4096), "float32"), ((1, "m", 4096), "float32"), ], dym_var_sample={"m": 128}, target="nvidia/geforce-rtx-3070", rpc_config=rpc_config, ) assert input_infos == [ ((1, 128, 4096), "float32"), ((4096, 4096), "float32"), ((1, 128, 4096), "float32"), ] @pytest.mark.skip("requires CUDA") def test_benchmark_prim_func_local(): input_infos, _, _ = benchmark( cuda_workload, args=[ ((1, "m", 4096), "float32"), ((4096, 4096), "float32"), ((1, "m", 4096), "float32"), ], dym_var_sample={"m": 128}, target="nvidia/geforce-rtx-3070", ) assert input_infos == [ ((1, 128, 4096), "float32"), ((4096, 4096), "float32"), ((1, 128, 4096), "float32"), ] @pytest.mark.skip("requires CUDA") def test_benchmark_prim_func_full_local(): with tvm.target.Target("nvidia/geforce-rtx-3070"): benchmark_prim_func( cuda_workload, ) @pytest.mark.skip("requires CUDA") def test_benchmark_prim_func_full_rpc(): with LocalRPC() as rpc: rpc_config = ms.runner.RPCConfig( tracker_host=rpc.tracker_host, tracker_port=rpc.tracker_port, tracker_key=rpc.tracker_key, session_priority=1, session_timeout_sec=100, ) benchmark_prim_func( cuda_workload, target="nvidia/geforce-rtx-3070", rpc_config=rpc_config, evaluator_config=ms.runner.EvaluatorConfig( number=10, repeat=10, min_repeat_ms=0, enable_cpu_cache_flush=False, ), ) def test_benchmark_relax_func(): with tvm.target.Target({"kind": "llvm", "num-cores": 4}): benchmark_relax_func(Module, "test") def test_extract_prim_func_full1(): print( extract_prim_func( model_name="TEST", relax_func_name="test", prim_func_name="full1", func=Module["full1"], # type: ignore func_args=[((1, 32, 1, "n"), "float16")], dym_var_dict={"n": "int32"}, weight=2, sample_number=10, target={"kind": "llvm", "num-cores": 4}, ) ) def test_extract_prim_func_matmul1(): print( extract_prim_func( model_name="TEST", relax_func_name="test", prim_func_name="matmul1", func=Module["matmul1"], # type: ignore weight=2, sample_number=10, target={"kind": "llvm", "num-cores": 4}, ) ) def test_extract_from_relax(): with tvm.target.Target({"kind": "llvm", "num-cores": 4}): with tempfile.TemporaryDirectory() as filepath: extract_from_relax( Module, "TEST", file_path=filepath, ) def test_extract_func_info_from_prim_func(): assert ( str(extract_func_info_from_prim_func(cuda_workload)) == "([((1, m, 4096), 'float32'), ((4096, 4096), 'float32'), ((1, m, 4096), 'float32')], {'m': 'int64'})" ) assert ( str(extract_func_info_from_prim_func(Module["full1"])) == "([((1, 32, 1, n), 'float16')], {'n': 'int64'})" ) assert ( str(extract_func_info_from_prim_func(Module["matmul1"])) == "([((1, 32, 1, n), 'float16'), ((1, 32, n, 128), 'float16'), ((1, 32, 1, 128), 'float16')], {'n': 'int64'})" ) assert ( str(extract_func_info_from_prim_func(Module["full2"])) == "([((1, 32, n, 128), 'float16')], {'n': 'int64'})" ) if __name__ == "__main__": tvm.testing.main()