# Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # pylint: disable=missing-function-docstring import numpy as np import pytest import tvm import tvm.testing from tvm.script import tirx as T from tvm.testing import env def _get_source(func: tvm.tirx.PrimFunc) -> str: target = tvm.target.Target("cuda") mod = tvm.IRModule({"main": func}) mod = tvm.compile(mod, target=target, tir_pipeline="tirx") src = mod.mod.imports[0].inspect_source() return src, mod def _helper_source(src: str, helper_name: str) -> str: start = src.index(helper_name) next_helper = src.find("__device__", start + len(helper_name)) if next_helper == -1: return src[start:] return src[start:next_helper] def test_tirx_launch_bounds_omits_min_blocks_without_persistent_schedule(): @T.prim_func def main(A: T.Buffer((4,), "int32")): T.device_entry() bx = T.cta_id([4]) tx = T.thread_id([128]) if tx == 0: A[bx] = A[bx] + 1 src, _ = _get_source(main) assert 'extern "C" __global__ void __launch_bounds__(128) main_kernel' in src assert "__launch_bounds__(128, 1)" not in src def test_tirx_launch_bounds_min_blocks_attr_sets_one_block_per_sm(): @T.prim_func def main(A: T.Buffer((4,), "int32")): T.device_entry() T.attr({"tirx.launch_bounds_min_blocks_per_sm": 1}) bx = T.cta_id([4]) tx = T.thread_id([128]) if tx == 0: A[bx] = A[bx] + 1 src, _ = _get_source(main) assert 'extern "C" __global__ void __launch_bounds__(128, 1) main_kernel' in src assert "tirx.launch_bounds_min_blocks_per_sm" not in src def test_serial_pragma_unroll_codegen(): @T.prim_func def main(A: T.Buffer((4,), "int32")): T.device_entry() tx = T.thread_id([32]) if tx == 0: for i in T.serial(4, unroll=True): if i == 2: break A[i] = A[i] + 1 src, _ = _get_source(main) assert "#pragma unroll\n" in src assert "for (" in src assert "break;" in src def test_cluster_cta_id_codegen_uses_coordinate_sregs(): @T.prim_func def main(A: T.Buffer((1,), "int32")): T.device_entry() cbx, cby = T.cta_id_in_cluster([2, 2]) tx = T.thread_id([32]) if tx == 0: A[0] = cbx + cby src, _ = _get_source(main) assert "%cluster_ctaid.x" in src assert "%cluster_ctaid.y" in src assert "%cluster_ctarank" not in src assert "cooperative_groups::cluster_group::block_index" not in src def test_cuda_handle_uint64_reinterpret_codegen(): @T.prim_func def main(A: T.Buffer((1,), "uint64")): T.device_entry() tx = T.thread_id([32]) if tx == 0: ptr = T.reinterpret("handle", A[0]) A[0] = T.reinterpret("uint64", ptr) src, _ = _get_source(main) assert "reinterpret_cast" in src assert "reinterpret_cast" in src assert "*(void* *)" not in src @pytest.mark.gpu @pytest.mark.skipif(not env.has_cuda(), reason="need cuda") def test_cuda_atomic_add(): @T.prim_func def main(A: T.Buffer((1,), "int32"), B: T.Buffer((1,), "float32")): T.device_entry() cta_id = T.cta_id([1]) tx = T.thread_id([32]) if tx == 0: T.cuda.atomic_add(A.data, T.int32(1)) T.cuda.atomic_add(B.data, T.float32(1.0)) src, mod = _get_source(main) assert "tvm_builtin_cuda_atomic_add" in src A_np = np.zeros(1, dtype="int32") B_np = np.zeros(1, dtype="float32") def run_and_check(): dev = tvm.device("cuda") A_tvm = tvm.runtime.tensor(A_np, device=dev) B_tvm = tvm.runtime.tensor(B_np, device=dev) mod["main"](A_tvm, B_tvm) np.testing.assert_allclose(A_tvm.numpy(), 1) np.testing.assert_allclose(B_tvm.numpy(), 1.0) tvm.testing.run_with_gpu_lock(run_and_check) def test_ptx_ld_acquire_and_volatile_codegen(): @T.prim_func def main(A: T.Buffer((1,), "uint64"), B: T.Buffer((1,), "int32"), C: T.Buffer((1,), "uint32")): T.device_entry() tx = T.thread_id([32]) if tx == 0: A[0] = T.ptx.ld_acquire(A.data, "uint64", "u64", scope="gpu", space="global") B[0] = T.ptx.ld_acquire(B.data, "int32", "s32", scope="sys", space="global") C[0] = T.ptx.ld_acquire(C.data, "uint32", "b32", scope="gpu", space="global") T.ptx.ld_global_acquire(B[0], B.data) A[0] = T.ptx.ld_volatile(A.data, "uint64", "u64", space="global") src, _ = _get_source(main) assert "ld.acquire.gpu.global.u64" in src assert "ld.acquire.sys.global.s32" in src assert "ld.acquire.gpu.global.b32" in src assert "ptx_ld_global_acquire_int32" in src assert "ptx_ld_global_acquire_b32" not in src assert "ld.volatile.global.u64" in src def test_megamoe_extracted_intrinsics_codegen(): @T.prim_func def main( U32: T.Buffer((4,), "uint32"), I32: T.Buffer((1,), "int32"), U64: T.Buffer((1,), "uint64"), F32: T.Buffer((4,), "float32"), ): T.device_entry() tx = T.thread_id([32]) if tx == 0: T.ptx.red_scalar( U64.data, U64[0], sem="release", scope="gpu", space="global", op="or", ptx_type="b64", ) T.ptx.red_scalar( I32.data, I32[0], sem="release", scope="sys", space="global", op="add", ptx_type="s32", ) U32[0] = T.ptx.atom_scalar( U32.data, U32[0], sem="release", scope="gpu", space="global", op="add", ptx_type="u32", ) U64[0] = T.ptx.atom_scalar( U64.data, U64[0], scope="sys", space="global", op="add", ptx_type="u64" ) T.ptx.red_scalar( U32.data, U32[0], scope="gpu", space="global", op="add", ptx_type="u32" ) T.ptx.st(U32.data, U32[0], space="shared", ptx_type="u32") T.ptx.st( U32.data, U32[0], U32[1], U32[2], U32[3], space="shared", vec="v4", ptx_type="b32", ) T.ptx.st_bulk(U32.data, T.uint32(16), weak=True, space="shared::cta") U32[0] = T.ptx.fns_b32(U32[0], U32[1], I32[0]) T.ptx.stmatrix( True, # trans 1, # num ".b8", # dtype U32.data, # smem_ptr U32.data, # src0 shape="m16n8", space="shared", ) F32[1] = T.cuda.uint_as_float(U32[0]) F32[2] = T.ptx.ld(F32.data, "float32", "f32", space="global") U32[3] = T.cuda.float_as_uint(F32[1]) F32[0] = T.ptx.add_rn_f32_bf16(F32[0], T.cast(U32[0], "uint16")) U64[0] = T.reinterpret("uint64", U32.data) U32[0] = T.cuda.ballot_sync(T.uint32(0xFFFFFFFF), I32[0]) I32[0] = T.cuda.ffs_u32(U32[0]) U32[0] = T.cuda.reduce_add_sync_u32(T.uint32(0xFFFFFFFF), U32[0]) U32[0] = T.cuda.reduce_min_sync_u32(T.uint32(0xFFFFFFFF), U32[0]) U64[0] = T.cuda.clock64() U32[0] = T.cuda.float22bfloat162_rn(F32[0], F32[1]) src, _ = _get_source(main) for snippet in [ "red.release.gpu.global.or.b64", "red.release.sys.global.add.s32", "atom.release.gpu.global.add.u32", "atom.sys.global.add.u64", "red.gpu.global.add.u32", "st.shared.u32", "st.shared.v4.b32", "st.bulk.weak.shared::cta", "fns.b32", "stmatrix.sync.aligned.m16n8.x1.trans.shared.b8", "ld.global.f32", "add.rn.f32.bf16", "__uint_as_float", "__float_as_uint", "__ballot_sync", "__ffs", "__reduce_add_sync", "__reduce_min_sync", "clock64()", "__float22bfloat162_rn", ]: assert snippet in src def test_ptx_cp_async_bulk_non_tma_form_codegen(): @T.prim_func def main( A: T.Buffer((128,), "float32"), B: T.Buffer((128,), "float32"), C: T.Buffer((1,), "uint64"), ): T.device_entry() tx = T.thread_id([32]) if tx == 0: smem = T.alloc_shared([128], "float32") T.ptx.cp_async_bulk_g2s_cta( smem.ptr_to([0]), A.data, T.uint32(64), smem.ptr_to([0]), cache_policy=C[0] ) T.ptx.cp_async_bulk_g2s_cluster( smem.ptr_to([0]), A.data, T.uint32(64), smem.ptr_to([0]), cache_policy=C[0] ) T.ptx.cp_async_bulk_s2g(B.data, smem.ptr_to([0]), T.uint32(64), cache_policy=C[0]) src, _ = _get_source(main) assert "cp.async.bulk.shared::cta.global.mbarrier::complete_tx::bytes.L2::cache_hint" in src assert "cp.async.bulk.shared::cluster.global.mbarrier::complete_tx::bytes.L2::cache_hint" in src assert "cp.async.bulk.global.shared::cta.bulk_group.L2::cache_hint" in src assert "unsigned long long cache_policy" in src def test_tensor_map_param_codegen(): @T.prim_func def main(A_map: T.TensorMap()): T.device_entry() tx = T.thread_id([32]) if tx == 0: T.evaluate(T.address_of(A_map)) src, _ = _get_source(main) assert "const __grid_constant__ CUtensorMap A_map" in src assert "((unsigned long long)(&(A_map)))" in src def test_tma_cache_policy_operand_codegen(): @T.prim_func def main(Cache: T.Buffer((1,), "uint64")): A_map: T.let[T.handle("tensormap")] = T.tvm_stack_alloca("tensormap", 1) B_map: T.let[T.handle("tensormap")] = T.tvm_stack_alloca("tensormap", 1) T.device_entry() tx = T.thread_id([32]) if tx == 0: smem = T.alloc_buffer((128,), "float32", scope="shared", align=128) bar = T.shared_scalar("uint64") T.ptx.cp_async.bulk.tensor.g2c( 2, smem.data, T.address_of(bar), T.address_of(A_map), 1, 2, "", 0, 0, cache_policy=Cache[0], ) T.ptx.cp_async.bulk.tensor.g2c( 2, smem.data, T.address_of(bar), T.address_of(A_map), 3, 2, "", 0, 0, cache_policy=Cache[0], ) T.ptx.cp_async.bulk.tensor.s2g( 2, smem.data, T.address_of(A_map), "", 0, 0, cache_policy=Cache[0] ) masked_bar = T.cuda.sm100_tma_2sm_mbarrier_addr(T.address_of(bar)) T.ptx.cp_async.bulk.tensor.g2c_bar_addr( 2, smem.data, masked_bar, T.address_of(A_map), 1, 2, "", 0, 0, cache_policy=Cache[0], ) if tx == 0: T.ptx.cp_async.bulk.tensor.g2c_bar_addr( 2, smem.data, masked_bar, T.address_of(A_map), 1, 2, "", 0, 0, cache_policy=Cache[0], ) else: T.ptx.cp_async.bulk.tensor.g2c_bar_addr( 2, smem.data, masked_bar, T.address_of(B_map), 1, 2, "", 0, 0, cache_policy=Cache[0], ) src, _ = _get_source(main) assert "ptx_cp_async_bulk_tensor_g2cluster_tile_2d_cache_hint" in src assert "ptx_cp_async_bulk_tensor_g2cluster_tile_2d_multicast_cache_hint" in src assert "g2cluster_unicast" not in src assert "ptx_cp_async_bulk_tensor_g2cta" not in src assert ( "cp.async.bulk.tensor.2d.shared::cluster.global" ".mbarrier::complete_tx::bytes.cta_group::2.L2::cache_hint" ) in src assert ( "cp.async.bulk.tensor.2d.shared::cluster.global" ".mbarrier::complete_tx::bytes.multicast::cluster" ".cta_group::2.L2::cache_hint" ) in src assert "cp.async.bulk.tensor.2d.global.shared::cta.tile.bulk_group.L2::cache_hint" in src assert "tvm_builtin_cp_async_bulk_tensor_2d_g2c_cta_group2" not in src assert "tvm_builtin_cuda_cvta_generic_to_shared((&(bar_ptr[0]))) & (uint)4278190079" in src assert "ptx_cp_async_bulk_tensor_g2cluster_tile_2d_cache_hint_bar_addr" in src assert "unsigned long long cache_policy" in src def test_cuda_thread_fence(): @T.prim_func def main(A: T.Buffer((16, 16), "int32")): T.device_entry() cta_id = T.cta_id([1]) tx = T.thread_id([32]) if tx == 0: T.cuda.thread_fence() src, mod = _get_source(main) assert "tvm_builtin_cuda_thread_fence" in src def test_cuda_nano_sleep(): @T.prim_func def main(A: T.Buffer((16, 16), "int32")): T.device_entry() cta_id = T.cta_id([1]) tx = T.thread_id([32]) if tx == 0: T.cuda.nano_sleep(1) src, mod = _get_source(main) assert "tvm_builtin_cuda_nano_sleep" in src def test_cuda_atomic_cas(): @T.prim_func def main(A: T.Buffer((16, 16), "int32")): T.device_entry() cta_id = T.cta_id([1]) tx = T.thread_id([32]) if tx == 0: T.cuda.atomic_cas(A.data, T.int32(1), T.int32(2)) src, mod = _get_source(main) assert "tvm_builtin_cuda_atomic_cas" in src @pytest.mark.gpu @pytest.mark.skipif(not env.has_cuda(), reason="need cuda") def test_cuda_func_call(): def test_add_one(): add_one = """ __device__ int32_t add_one(int32_t a) { return a + 1; } """ @T.prim_func def main(a: T.Buffer((16, 16), "int32"), b: T.Buffer((16, 16), "int32")): T.device_entry() cta_id = T.cta_id([1]) tx = T.thread_id([32]) if tx == 0: for i, j in T.grid(16, 16): b[i, j] = T.cuda.func_call( "add_one", a[i, j], source_code=add_one, return_type="int32" ) src, mod = _get_source(main) A = np.random.randint(0, 10, (16, 16)).astype("int32") B = np.zeros((16, 16), dtype="int32") def run_and_check(): dev = tvm.device("cuda") A_tvm = tvm.runtime.tensor(A, device=dev) B_tvm = tvm.runtime.tensor(B, device=dev) mod["main"](A_tvm, B_tvm) np.testing.assert_allclose(B_tvm.numpy(), A + 1) tvm.testing.run_with_gpu_lock(run_and_check) print(src) test_add_one() def test_print(): print_func = """ __device__ void print(int32_t a) { printf("%d\\n", a); } """ @T.prim_func def main(a: T.Buffer((16, 16), "int32")): T.device_entry() cta_id = T.cta_id([1]) tx = T.thread_id([32]) if tx == 0: for i, j in T.grid(16, 16): T.cuda.func_call("print", a[i, j], source_code=print_func) src, mod = _get_source(main) A = np.random.randint(0, 10, (16, 16)).astype("int32") def run_and_check(): dev = tvm.device("cuda") A_tvm = tvm.runtime.tensor(A, device=dev) mod["main"](A_tvm) dev.sync() tvm.testing.run_with_gpu_lock(run_and_check) print(src) test_print() @pytest.mark.gpu @pytest.mark.skipif(not env.has_cuda(), reason="need cuda") def test_warp_shuffle_xor_sync(): # fmt: off @T.prim_func def func(A_ptr: T.handle): A = T.match_buffer(A_ptr, (32,), dtype="float32", align=16) T.device_entry() cta_id = T.cta_id([1]) warp_id = T.warp_id([1]) lane_id = T.lane_id([32]) A_local = T.alloc_buffer([1], "float32", scope="local") i = T.alloc_buffer([1], "int32", scope="local") A_local[0] = T.float32(31 - lane_id) i[0] = 16 while i[0] >= 1: A_local[0] += T.tvm_warp_shuffle_xor(0xFFFFFFFF, A_local[0], i[0], 32, 32) i[0] = i[0] // 2 A[lane_id] = A_local[0] # fmt: on target = tvm.target.Target("cuda") mod = tvm.IRModule({"main": func}) mod = tvm.compile(mod, target=target, tir_pipeline="tirx") A_np = np.zeros(32, dtype="float32") assert "__shfl_xor_sync" in mod.mod.imports[0].inspect_source() A_ref = np.ones(32, dtype="float32") * 496 def run_and_check(): dev = tvm.cuda(0) A = tvm.runtime.tensor(A_np, device=dev) mod(A) np.testing.assert_allclose(A.numpy(), A_ref) tvm.testing.run_with_gpu_lock(run_and_check) @pytest.mark.gpu @pytest.mark.skipif(not env.has_cuda(), reason="need cuda") @pytest.mark.parametrize("cp_size", [4, 8, 16]) @pytest.mark.parametrize("cache_hint", ["", "evict_last"]) @pytest.mark.parametrize("prefetch_size", [-1, 64, 128, 256]) @pytest.mark.parametrize("predicate", [-1, T.int32(0), T.int32(1)]) @pytest.mark.parametrize("fill_mode", ["", "zero"]) def test_ptx_cp_async(cp_size, cache_hint, prefetch_size, predicate, fill_mode): if fill_mode != "" and predicate == -1: return N = cp_size // 2 # fmt: off @T.prim_func def main(A: T.Buffer((N), "float16")): T.device_entry() cta_id = T.cta_id([1]) tid = T.thread_id([32]) A_shared = T.alloc_shared([N], "float16") for i in T.vectorized(N): A_shared[i] = 5.0 T.ptx.fence.proxy_async("shared::cta") T.ptx.cp_async(A_shared.ptr_to([0]), A.ptr_to([0]), cp_size, cache_hint=cache_hint, prefetch_size=prefetch_size, predicate=predicate, fill_mode=fill_mode) # noqa: E501 T.ptx.cp_async.commit_group() T.ptx.cp_async.wait_group(0) for i in T.serial(N): A[i] = A_shared[i] + 1.0 # fmt: on src, mod = _get_source(main) A_np = np.ones(N, dtype="float16") A_ref = np.ones(N, dtype="float16") * 2 if int(predicate) == 0: if fill_mode == "zero": A_ref = np.ones(N, dtype="float16") else: A_ref = np.ones(N, dtype="float16") * 6 def run_and_check(): dev = tvm.device("cuda") A = tvm.runtime.tensor(A_np, device=dev) mod(A) np.testing.assert_allclose(A.numpy(), A_ref) tvm.testing.run_with_gpu_lock(run_and_check) print(src) @pytest.mark.gpu @pytest.mark.skipif(not env.has_cuda(), reason="need cuda") @pytest.mark.parametrize("trans", [False, True]) @pytest.mark.parametrize("num", [1, 2, 4]) def test_ptx_ldmatrix(trans, num): dtype = ".b16" # fmt: off @T.prim_func def main(A: T.Buffer((16, 16), "float16"), B: T.Buffer((16, 16), "float16")): T.device_entry() cta_id = T.cta_id([1]) tx = T.thread_id([32]) A_shared = T.alloc_shared([16, 16], "float16") if tx == 0: for i, j in T.grid(16, 16): A_shared[i, j] = A[i, j] T.cuda.cta_sync() A_local = T.alloc_local([8], "float16") A_local[0] = -1.0 # ldmatrix .x{num}.b16 writes `num` 32-bit registers; A_local # is a contiguous fp16[8] buffer, so consecutive register # destinations land 2 fp16 elements apart. if num == 1: T.ptx.ldmatrix( trans, num, dtype, A_shared.ptr_to([tx % 16, tx // 16 * 8]), T.address_of(A_local[0]), ) elif num == 2: T.ptx.ldmatrix( trans, num, dtype, A_shared.ptr_to([tx % 16, tx // 16 * 8]), T.address_of(A_local[0]), T.address_of(A_local[2]), ) else: T.ptx.ldmatrix( trans, num, dtype, A_shared.ptr_to([tx % 16, tx // 16 * 8]), T.address_of(A_local[0]), T.address_of(A_local[2]), T.address_of(A_local[4]), T.address_of(A_local[6]), ) for i in range(8): row: T.let = (i // 2) % 2 * 8 col: T.let = (i // 4) * 8 B[row + tx // 4, col + tx % 4 * 2 + i % 2] = A_local[i] # fmt: on src, mod = _get_source(main) A_np = np.arange(16 * 16, dtype="float16").reshape((16, 16)) B_np = np.zeros((16, 16), dtype="float16") B_ref = np.zeros((16, 16), dtype="float16") if num == 1: B_ref[0:8, 0:8] = A_np[0:8, 0:8] if not trans else A_np[0:8, 0:8].T elif num == 2: B_ref[0:8, 0:8] = A_np[0:8, 0:8] if not trans else A_np[0:8, 0:8].T B_ref[8:16, 0:8] = A_np[8:16, 0:8] if not trans else A_np[8:16, 0:8].T elif num == 4: B_ref[0:8, 0:8] = A_np[0:8, 0:8] if not trans else A_np[0:8, 0:8].T B_ref[0:8, 8:16] = A_np[0:8, 8:16] if not trans else A_np[0:8, 8:16].T B_ref[8:16, 0:8] = A_np[8:16, 0:8] if not trans else A_np[8:16, 0:8].T B_ref[8:16, 8:16] = A_np[8:16, 8:16] if not trans else A_np[8:16, 8:16].T def run_and_check(): dev = tvm.device("cuda") A = tvm.runtime.tensor(A_np, device=dev) B = tvm.runtime.tensor(B_np, device=dev) mod(A, B) np.testing.assert_allclose(B.numpy(), B_ref) tvm.testing.run_with_gpu_lock(run_and_check) if __name__ == "__main__": tvm.testing.main()