# 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.script.tirx import tile as Tx 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 @pytest.mark.gpu @pytest.mark.skipif(not env.has_cuda_compute(10), reason="need cuda compute >= 10.0") def test_tmem_alloc_dealloc_relinquish(): N_COLS = 512 cta_group = 1 # fmt: off @T.prim_func def test_tmem(A: T.Buffer((16, 16), "float16")): T.device_entry() cta_id = T.cta_id([1]) warp_id = T.warp_id([4]) lane_id = T.lane_id([32]) tid = T.thread_id([128]) # tmem_addr = T.alloc_buffer((1,), "uint32", scope="shared", align=8) tmem_addr = T.shared_scalar("uint32") # alloc TMEM if warp_id == 0: T.ptx.tcgen05.alloc(T.address_of(tmem_addr), n_cols=N_COLS, cta_group=cta_group) T.cuda.cta_sync() # dealloc TMEM if warp_id == 0: T.ptx.tcgen05.relinquish_alloc_permit(cta_group=cta_group) T.ptx.tcgen05.dealloc(tmem_addr, n_cols=N_COLS, cta_group=cta_group) # fmt: on target = tvm.target.Target("cuda") with target: src, _ = _get_source(test_tmem) assert f"tcgen05.alloc.cta_group::{cta_group}.sync.aligned.shared::cta.b32" in src assert f"tcgen05.dealloc.cta_group::{cta_group}.sync.aligned.b32" in src assert f"tcgen05.relinquish_alloc_permit.cta_group::{cta_group}.sync.aligned" in src @pytest.mark.gpu @pytest.mark.skipif(not env.has_cuda_compute(10), reason="need cuda compute >= 10.0") def test_mbarrier_try_wait_once_codegen(): # fmt: off @T.prim_func def test_try_wait_once(A: T.Buffer((16, 16), "float16")): T.device_entry() T.cta_id([1]) T.thread_id([128]) bar = T.shared_scalar("uint64") T.evaluate(T.ptx.mbarrier.try_wait_once(T.address_of(bar), 0, 0)) # fmt: on target = tvm.target.Target("cuda") with target: src, _ = _get_source(test_try_wait_once) assert "mbarrier.try_wait.parity.shared::cta.b64" in src assert "selp.u32" in src @pytest.mark.gpu @pytest.mark.skipif(not env.has_cuda_compute(10), reason="need cuda compute >= 10.0") def test_fence_before_after_thread_sync(): # fmt: off @T.prim_func def test_fence(A: T.Buffer((16, 16), "float16")): T.device_entry() cta_id = T.cta_id([1]) warp_id = T.warp_id([4]) lane_id = T.lane_id([32]) tid = T.thread_id([128]) T.ptx.tcgen05.fence.before_thread_sync() T.ptx.bar.sync(0, 32) T.ptx.tcgen05.fence.after_thread_sync() # fmt: on target = tvm.target.Target("cuda") with target: src, _ = _get_source(test_fence) assert "tcgen05.fence::after_thread_sync" in src assert "tcgen05.fence::before_thread_sync" in src @pytest.mark.gpu @pytest.mark.skipif(not env.has_cuda_compute(10), reason="need cuda compute >= 10.0") def test_tcgen05_ld_st_roundtrip(): HEIGHT = 128 WIDTH = 256 N_COLS = 512 REPEAT_NUM = 1 cta_group = 1 # fmt: off @T.prim_func def test_ld_st(A: T.Buffer((HEIGHT, WIDTH), "float32"), B: T.Buffer((HEIGHT, WIDTH), "float32")): # noqa: E501 T.device_entry() cta_id = T.cta_id([1]) warp_id = T.warp_id([4]) lane_id = T.lane_id([32]) tx = T.thread_id([128]) reg = T.alloc_buffer((WIDTH,), "float32", scope="local") # tmem_addr = T.alloc_buffer((1,), "uint32", scope="shared", align=8) tmem_addr = T.shared_scalar("uint32") # alloc TMEM if warp_id == 0: T.ptx.tcgen05.alloc(T.address_of(tmem_addr), n_cols=N_COLS, cta_group=cta_group) T.cuda.cta_sync() # GMEM -> RF for i in range(WIDTH): reg[i] = A[tx, i] # RF -> TMEM for i in range(WIDTH): T.ptx.tcgen05.st(tmem_addr, reg[i], shape="32x32b", num=REPEAT_NUM, row=warp_id * 32, col=i) # noqa: E501 T.ptx.tcgen05.wait.st() T.cuda.cta_sync() # reset RF for i in range(WIDTH): reg[i] = 0.0 T.cuda.cta_sync() # TMEM -> RF T.ptx.tcgen05.fence.after_thread_sync() for i in range(WIDTH): T.ptx.tcgen05.ld(tmem_addr, reg[i], shape="32x32b", num=REPEAT_NUM, row=warp_id * 32, col=i) # noqa: E501 T.ptx.tcgen05.wait.ld() # RF -> GMEM for i in range(WIDTH): B[tx, i] = reg[i] # dealloc TMEM if warp_id == 0: T.ptx.tcgen05.relinquish_alloc_permit(cta_group=cta_group) T.ptx.tcgen05.dealloc(tmem_addr, n_cols=N_COLS, cta_group=cta_group) # fmt: on target = tvm.target.Target("cuda") with target: src, mod = _get_source(test_ld_st) assert "tcgen05.ld.sync.aligned.32x32b.x1.b32" in src assert "tcgen05.st.sync.aligned.32x32b.x1.b32" in src def run_and_check(): dev = tvm.cuda(0) A_np = np.random.randn(HEIGHT, WIDTH).astype("float32") B_np = np.zeros((HEIGHT, WIDTH), dtype="float32") A = tvm.runtime.tensor(A_np, device=dev) B = tvm.runtime.tensor(B_np, device=dev) mod(A, B) np.testing.assert_allclose(A.numpy(), B.numpy()) tvm.testing.run_with_gpu_lock(run_and_check) @pytest.mark.gpu @pytest.mark.skipif(not env.has_cuda_compute(10), reason="need cuda compute >= 10.0") def test_tcgen05_cp_ld_roundtrip(): dtype = "float32" dtype_bits = tvm.DataType(dtype).bits HEIGHT = 128 WIDTH = 64 N_COLS = 512 REPEAT_NUM = 1 SWIZZLE = 0 A_layout = T.TileLayout(T.S[(HEIGHT, WIDTH // 4, 4) : (4, HEIGHT * 4, 1)]) ldo, sdo = 128, 8 cta_group = 1 # fmt: off @T.prim_func def test_cp_ld(A: T.Buffer((HEIGHT, WIDTH), dtype, layout=T.TileLayout(T.S[(HEIGHT, WIDTH // 4, 4) : (4, HEIGHT * 4, 1)])), # noqa: E501 B: T.Buffer((HEIGHT, WIDTH), dtype, layout=T.TileLayout(T.S[(HEIGHT, WIDTH // 4, 4) : (4, HEIGHT * 4, 1)]))): # noqa: E501 T.device_entry() cta_id = T.cta_id([1]) warp_id = T.warp_id([4]) lane_id = T.lane_id([32]) tx = T.thread_id([128]) A_smem = T.alloc_buffer((HEIGHT, WIDTH), dtype, scope="shared", layout=A_layout) reg = T.alloc_buffer((WIDTH,), dtype, scope="local") # tmem_addr = T.alloc_buffer((1,), "uint32", scope="shared", align=8) tmem_addr = T.shared_scalar("uint32") descA = T.alloc_buffer((1,), "uint64", scope="local") bar = T.alloc_buffer((1,), "uint64", scope="shared", align=8) phase = T.alloc_buffer((1,), "int32", scope="local") # alloc TMEM if warp_id == 0: T.ptx.tcgen05.alloc(T.address_of(tmem_addr), n_cols=N_COLS, cta_group=cta_group) T.cuda.cta_sync() Tx.cta.copy(A_smem[:, :], A[:, :]) T.ptx.fence.proxy_async("shared::cta") T.cuda.cta_sync() # reset RF for i in range(WIDTH): reg[i] = 0.0 # SMEM -> TMEM (cp) phase[0] = 0 if tx == 0: T.ptx.mbarrier.init(bar.data, 1) for k in range(dtype_bits * WIDTH // 256): T.ptx.tcgen05.encode_matrix_descriptor(descA.data, A_smem.access_ptr("r", offset=A_smem.elem_offset_of([0, k * 8])), ldo=ldo, sdo=sdo, swizzle=SWIZZLE) # noqa: E501 T.ptx.tcgen05.cp(tmem_addr, descA[0], shape="128x256b", cta_group=cta_group, col=k * 256 // 32) # noqa: E501 T.ptx.tcgen05.commit(bar.data, cta_group) T.ptx.mbarrier.try_wait(bar.data, phase[0]) phase[0] = phase[0] ^ 1 T.cuda.cta_sync() # TMEM -> RF (ld) T.ptx.tcgen05.fence.after_thread_sync() for i in range(WIDTH): T.ptx.tcgen05.ld(tmem_addr, reg[i], shape="32x32b", num=REPEAT_NUM, row=warp_id * 32, col=i) # noqa: E501 T.ptx.tcgen05.wait.ld() # RF -> GMEM for i in range(WIDTH): B[tx, i] = reg[i] # dealloc TMEM if warp_id == 0: T.ptx.tcgen05.relinquish_alloc_permit(cta_group=cta_group) T.ptx.tcgen05.dealloc(tmem_addr, n_cols=N_COLS, cta_group=cta_group) # fmt: on target = tvm.target.Target("cuda") with target: src, mod = _get_source(test_cp_ld) assert "tcgen05.cp.cta_group::1.128x256b" in src assert "tcgen05.ld.sync.aligned.32x32b.x1.b32" in src def run_and_check(): dev = tvm.cuda(0) A_np = np.random.randn(HEIGHT, WIDTH).astype(dtype) B_np = np.zeros((HEIGHT, WIDTH), dtype=dtype) A = tvm.runtime.tensor(A_np, device=dev) B = tvm.runtime.tensor(B_np, device=dev) mod(A, B) np.testing.assert_allclose(A.numpy(), B.numpy()) tvm.testing.run_with_gpu_lock(run_and_check) @pytest.mark.parametrize("swizzle", [0, 1, 2, 3]) @pytest.mark.gpu @pytest.mark.skipif(not env.has_cuda_compute(10), reason="need cuda compute >= 10.0") def test_tcgen05_mma_ss_no_tma(swizzle): d_type, a_type, b_type = "float32", "float16", "float16" M, N, K = 128, 128, 64 MMA_K = 16 N_COLS = 512 REPEAT_NUM = 1 SWIZZLE = swizzle cta_group = 1 if SWIZZLE == 0: A_layout = T.TileLayout(T.S[(M, K // 8, 8) : (8, M * 8, 1)]) B_layout = T.TileLayout(T.S[(N, K // 8, 8) : (8, N * 8, 1)]) ldo, sdo = 128, 8 elif SWIZZLE == 1: A_layout = T.ComposeLayout( T.SwizzleLayout(3, 1, 3, swizzle_inner=True), T.TileLayout(T.S[(M, K // 16, 16) : (16, M * 16, 1)]), ) B_layout = T.ComposeLayout( T.SwizzleLayout(3, 1, 3, swizzle_inner=True), T.TileLayout(T.S[(N, K // 16, 16) : (16, N * 16, 1)]), ) ldo, sdo = 256, 16 elif SWIZZLE == 2: A_layout = T.ComposeLayout( T.SwizzleLayout(3, 2, 3, swizzle_inner=True), T.TileLayout(T.S[(M, K // 32, 32) : (32, M * 32, 1)]), ) B_layout = T.ComposeLayout( T.SwizzleLayout(3, 2, 3, swizzle_inner=True), T.TileLayout(T.S[(N, K // 32, 32) : (32, N * 32, 1)]), ) ldo, sdo = 512, 32 elif SWIZZLE == 3: A_layout = T.ComposeLayout( T.SwizzleLayout(3, 3, 3, swizzle_inner=True), T.TileLayout(T.S[(M, 1, 64) : (64, M * 64, 1)]), ) B_layout = T.ComposeLayout( T.SwizzleLayout(3, 3, 3, swizzle_inner=True), T.TileLayout(T.S[(N, 1, 64) : (64, N * 64, 1)]), ) ldo, sdo = 1, 64 else: raise ValueError(f"Invalid swizzle: {SWIZZLE}") dyn_smem_bytes = 1024 + (M * K + N * K) * 2 # fmt: off @T.prim_func def test_mma_ss_no_tma(A: T.Buffer((M, K), a_type, layout=T.TileLayout(T.S[M, K])), B: T.Buffer((N, K), b_type, layout=T.TileLayout(T.S[N, K])), C: T.Buffer((M, N), d_type)): T.device_entry() cta_id = T.cta_id([1]) warp_id = T.warp_id([4]) lane_id = T.lane_id([32]) tx = T.thread_id([128]) dyn = T.alloc_buffer((dyn_smem_bytes,), "uint8", scope="shared") tmem_addr = T.decl_scalar("uint32", dyn.data, scope="shared", elem_offset=0) A_smem = T.decl_buffer((M, K), a_type, dyn.data, elem_offset=256, layout=A_layout) B_smem = T.decl_buffer((N, K), b_type, dyn.data, elem_offset=256 + M*K, layout=B_layout) bar = T.decl_buffer((1,), "uint64", dyn.data, scope="shared", elem_offset=8) reg = T.alloc_buffer((N,), d_type, scope="local") descA = T.alloc_buffer((1,), "uint64", scope="local") descB = T.alloc_buffer((1,), "uint64", scope="local") descI = T.alloc_buffer((1,), "uint32", scope="local") phase = T.alloc_buffer((1,), "int32", scope="local") # alloc TMEM if warp_id == 0: T.ptx.tcgen05.alloc(T.address_of(tmem_addr), n_cols=N_COLS, cta_group=cta_group) T.cuda.cta_sync() for i in range(N): reg[i] = 0.0 Tx.cta.copy(A_smem[:, :], A[:, :]) Tx.cta.copy(B_smem[:, :], B[:, :]) T.ptx.fence.proxy_async("shared::cta") T.cuda.cta_sync() # MMA phase[0] = 0 if tx == 0: T.ptx.mbarrier.init(bar.data, 1) T.ptx.tcgen05.encode_instr_descriptor(descI.data, d_dtype=d_type, a_dtype=a_type, b_dtype=b_type, M=M, N=N, K=MMA_K, trans_a=False, trans_b=False, n_cta_groups=cta_group) # noqa: E501 for k in range(K // MMA_K): T.ptx.tcgen05.encode_matrix_descriptor(descA.data, A_smem.access_ptr("r", offset=A_smem.elem_offset_of([0, k * MMA_K])), ldo=ldo, sdo=sdo, swizzle=SWIZZLE) # noqa: E501 T.ptx.tcgen05.encode_matrix_descriptor(descB.data, B_smem.access_ptr("r", offset=B_smem.elem_offset_of([0, k * MMA_K])), ldo=ldo, sdo=sdo, swizzle=SWIZZLE) # noqa: E501 if k == 0: T.ptx.tcgen05.mma(tmem_addr, descA[0], descB[0], descI[0], d_dtype=d_type, a_dtype=a_type, b_dtype=b_type, use_a_tmem=False, cta_group=cta_group, enable_input_d=0) # noqa: E501 else: T.ptx.tcgen05.mma(tmem_addr, descA[0], descB[0], descI[0], d_dtype=d_type, a_dtype=a_type, b_dtype=b_type, use_a_tmem=False, cta_group=cta_group, enable_input_d=1) # noqa: E501 T.ptx.tcgen05.commit(bar.data, cta_group) T.ptx.mbarrier.try_wait(bar.data, phase[0]) phase[0] = phase[0] ^ 1 T.cuda.cta_sync() # TMEM -> RF T.ptx.tcgen05.fence.after_thread_sync() for i in range(N): T.ptx.tcgen05.ld(tmem_addr, reg[i], shape="32x32b", num=REPEAT_NUM, row=warp_id * 32, col=i) # noqa: E501 T.ptx.tcgen05.wait.ld() # RF -> GMEM for i in range(N): C[tx, i] = reg[i] # dealloc TMEM if warp_id == 0: T.ptx.tcgen05.relinquish_alloc_permit(cta_group=cta_group) T.ptx.tcgen05.dealloc(tmem_addr, n_cols=N_COLS, cta_group=cta_group) # fmt: on import torch torch.manual_seed(42) target = tvm.target.Target("cuda") with target: src, mod = _get_source(test_mma_ss_no_tma) print(src) assert "tcgen05.mma.cta_group::1.kind::f16" in src assert "tcgen05.commit.cta_group::1.mbarrier::arrive::one.shared::cluster.b64" in src assert "tcgen05.ld.sync.aligned.32x32b.x1.b32" in src assert "tcgen05.wait::ld.sync.aligned" in src def run_and_check(): dev = tvm.cuda(0) A_torch = torch.rand((M, K), dtype=torch.float16) B_torch = torch.rand((N, K), dtype=torch.float16) C_torch = torch.zeros((M, N), dtype=torch.float32) A = tvm.runtime.tensor(A_torch, device=dev) B = tvm.runtime.tensor(B_torch, device=dev) C = tvm.runtime.tensor(C_torch, device=dev) mod(A, B, C) ref = torch.matmul(A_torch, B_torch.T) np.testing.assert_allclose(C.numpy(), ref.numpy(), rtol=1e-3, atol=1e-2) tvm.testing.run_with_gpu_lock(run_and_check) if __name__ == "__main__": tvm.testing.main()