# Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. import numpy as np import pytest import tvm import tvm.testing from tvm.script import tirx as T from tvm.testing import env def gen_2in4_mask(m: int, n: int): assert n % 4 == 0 return np.array( [[np.sort(np.random.choice(4, 2, replace=False)) for _ in range(n // 4)] for _ in range(m)] ).astype("uint8") def get_dense_mat_by_mask(val, mask): m, n_chunks, _ = mask.shape val = val.reshape(m, n_chunks, 2) ret = np.zeros((m, n_chunks, 4)).astype(val.dtype) for i in range(m): for j in range(n_chunks): for k in range(2): ret[i, j, mask[i, j, k]] = val[i, j, k] return ret.reshape(m, n_chunks * 4) @T.prim_func(s_tir=True) def mma_sp_m16n8k16_f16f16f16(a: T.handle, b: T.handle, c: T.handle, _metadata: T.handle): T.func_attr({"global_symbol": "default_function", "tirx.noalias": True}) A = T.match_buffer(a, [16, 8], dtype="float16") B = T.match_buffer(b, [16, 8], dtype="float16") C = T.match_buffer(c, [16, 8], dtype="float16") metadata = T.match_buffer(_metadata, [8], dtype="uint32") brow = T.env_thread("blockIdx.y") bcol = T.env_thread("blockIdx.x") tx = T.env_thread("threadIdx.x") T.launch_thread(brow, 1) T.launch_thread(bcol, 1) T.launch_thread(tx, 32) multi_a = T.decl_buffer([4], "float16", scope="local") multi_b = T.decl_buffer([4], "float16", scope="local") accum = T.decl_buffer([4], "float16", scope="local") meta_local = T.decl_buffer([1], "uint32", scope="local") for i in range(4): accum[i] = T.float16(0) for i in range(4): multi_a[i] = A[tx // 4 + i // 2 * 8, tx % 4 * 2 + i % 2] for i in range(4): multi_b[i] = B[tx % 4 * 2 + i % 2 + i // 2 * 8, tx // 4] meta_local[0] = metadata[tx // 4] T.evaluate( T.ptx.mma.sp( "m16n8k16", "row", "col", "fp16", "fp16", "fp16", multi_a.data, 0, multi_b.data, 0, accum.data, 0, meta_local.data, 0, 0, False, dtype="float16", ) ) for i in range(4): C[i // 2 * 8 + tx // 4, tx % 4 * 2 + i % 2] = accum[i] @T.prim_func(s_tir=True) def mma_sp_m16n8k16_f16f16f32(a: T.handle, b: T.handle, c: T.handle, _metadata: T.handle): T.func_attr({"global_symbol": "default_function", "tirx.noalias": True}) A = T.match_buffer(a, [16, 8], dtype="float16") B = T.match_buffer(b, [16, 8], dtype="float16") C = T.match_buffer(c, [16, 8], dtype="float32") metadata = T.match_buffer(_metadata, [8], dtype="uint32") brow = T.env_thread("blockIdx.y") bcol = T.env_thread("blockIdx.x") tx = T.env_thread("threadIdx.x") T.launch_thread(brow, 1) T.launch_thread(bcol, 1) T.launch_thread(tx, 32) multi_a = T.decl_buffer([4], "float16", scope="local") multi_b = T.decl_buffer([4], "float16", scope="local") accum = T.decl_buffer([4], "float32", scope="local") meta_local = T.decl_buffer([1], "uint32", scope="local") for i in range(4): accum[i] = T.float16(0) for i in range(4): multi_a[i] = A[tx // 4 + i // 2 * 8, tx % 4 * 2 + i % 2] for i in range(4): multi_b[i] = B[tx % 4 * 2 + i % 2 + i // 2 * 8, tx // 4] meta_local[0] = metadata[tx // 4] T.evaluate( T.ptx.mma.sp( "m16n8k16", "row", "col", "fp16", "fp16", "fp32", multi_a.data, 0, multi_b.data, 0, accum.data, 0, meta_local.data, 0, 0, False, dtype="float32", ) ) for i in range(4): C[i // 2 * 8 + tx // 4, tx % 4 * 2 + i % 2] = accum[i] @T.prim_func(s_tir=True) def mma_sp_m16n8k32_f16f16f16(a: T.handle, b: T.handle, c: T.handle, _metadata: T.handle): T.func_attr({"global_symbol": "default_function", "tirx.noalias": True}) A = T.match_buffer(a, [16, 16], dtype="float16") B = T.match_buffer(b, [32, 8], dtype="float16") C = T.match_buffer(c, [16, 8], dtype="float16") metadata = T.match_buffer(_metadata, [16], dtype="uint32") brow = T.env_thread("blockIdx.y") bcol = T.env_thread("blockIdx.x") tx = T.env_thread("threadIdx.x") T.launch_thread(brow, 1) T.launch_thread(bcol, 1) T.launch_thread(tx, 32) multi_a = T.decl_buffer([8], "float16", scope="local") multi_b = T.decl_buffer([8], "float16", scope="local") accum = T.decl_buffer([4], "float16", scope="local") meta_local = T.decl_buffer([1], "uint32", scope="local") for i in range(4): accum[i] = T.float16(0) for i in range(8): multi_a[i] = A[(i % 4) // 2 * 8 + tx // 4, i // 4 * 8 + tx % 4 * 2 + i % 2] for i in range(8): multi_b[i] = B[i // 2 * 8 + tx % 4 * 2 + i % 2, tx // 4] meta_local[0] = metadata[tx // 4 * 2 + tx % 2] T.evaluate( T.ptx.mma.sp( "m16n8k32", "row", "col", "fp16", "fp16", "fp16", multi_a.data, 0, multi_b.data, 0, accum.data, 0, meta_local.data, 0, 0, False, dtype="float16", ) ) for i in range(4): C[i // 2 * 8 + tx // 4, tx % 4 * 2 + i % 2] = accum[i] @T.prim_func(s_tir=True) def mma_sp_m16n8k32_f16f16f32(a: T.handle, b: T.handle, c: T.handle, _metadata: T.handle): T.func_attr({"global_symbol": "default_function", "tirx.noalias": True}) A = T.match_buffer(a, [16, 16], dtype="float16") B = T.match_buffer(b, [32, 8], dtype="float16") C = T.match_buffer(c, [16, 8], dtype="float32") metadata = T.match_buffer(_metadata, [16], dtype="uint32") brow = T.env_thread("blockIdx.y") bcol = T.env_thread("blockIdx.x") tx = T.env_thread("threadIdx.x") T.launch_thread(brow, 1) T.launch_thread(bcol, 1) T.launch_thread(tx, 32) multi_a = T.decl_buffer([8], "float16", scope="local") multi_b = T.decl_buffer([8], "float16", scope="local") accum = T.decl_buffer([4], "float32", scope="local") meta_local = T.decl_buffer([1], "uint32", scope="local") for i in range(4): accum[i] = T.float16(0) for i in range(8): multi_a[i] = A[(i % 4) // 2 * 8 + tx // 4, i // 4 * 8 + tx % 4 * 2 + i % 2] for i in range(8): multi_b[i] = B[i // 2 * 8 + tx % 4 * 2 + i % 2, tx // 4] meta_local[0] = metadata[tx // 4 * 2 + tx % 2] T.evaluate( T.ptx.mma.sp( "m16n8k32", "row", "col", "fp16", "fp16", "fp32", multi_a.data, 0, multi_b.data, 0, accum.data, 0, meta_local.data, 0, 0, False, dtype="float32", ) ) for i in range(4): C[i // 2 * 8 + tx // 4, tx % 4 * 2 + i % 2] = accum[i] @pytest.mark.gpu @pytest.mark.skipif(not env.has_cuda_compute(8), reason="need cuda compute >= 8.0") def test_mma_sp_m16n8k16_f16(): def get_meta_m16n8k16_half(mask): assert mask.shape == (16, 4, 2) mask = mask.reshape(16, 8) ret = np.zeros((8,)).astype("uint32") for i in range(8): base = 1 for blk in range(2): for j in range(8): ret[i] |= int(mask[blk * 8 + i, j]) * base base = base << 2 return ret for out_dtype in ["float16", "float32"]: func = mma_sp_m16n8k16_f16f16f16 if out_dtype == "float16" else mma_sp_m16n8k16_f16f16f32 sch = tvm.s_tir.Schedule(func) cuda_mod = tvm.compile(sch.mod, target="cuda") A_np = np.random.uniform(-1, 1, [16, 8]).astype("float16") B_np = np.random.uniform(-1, 1, [16, 8]).astype("float16") mask = gen_2in4_mask(16, 16) A_dense_np = get_dense_mat_by_mask(A_np, mask) C_np = np.matmul(A_dense_np, B_np).astype(out_dtype) meta = get_meta_m16n8k16_half(mask) def run_and_check(): ctx = tvm.cuda() A_tvm = tvm.runtime.tensor(A_np, ctx) B_tvm = tvm.runtime.tensor(B_np, ctx) C_tvm = tvm.runtime.tensor(np.zeros_like(C_np), ctx) meta_tvm = tvm.runtime.tensor(meta, ctx) cuda_mod(A_tvm, B_tvm, C_tvm, meta_tvm) tvm.testing.assert_allclose(C_tvm.numpy(), C_np, atol=1e-3, rtol=1e-3) tvm.testing.run_with_gpu_lock(run_and_check) @pytest.mark.gpu @pytest.mark.skipif(not env.has_cuda_compute(8), reason="need cuda compute >= 8.0") def test_mma_sp_m16n8k32_f16(): def get_meta_m16n8k32_half(mask): assert mask.shape == (16, 8, 2) mask = mask.reshape(16, 2, 8) ret = np.zeros((8, 2)).astype("uint32") for i in range(8): for k in range(2): base = 1 for blk in range(2): for j in range(8): ret[i, k] |= int(mask[blk * 8 + i, k, j]) * base base = base << 2 return ret.reshape(16) for out_dtype in ["float16", "float32"]: func = mma_sp_m16n8k32_f16f16f16 if out_dtype == "float16" else mma_sp_m16n8k32_f16f16f32 sch = tvm.s_tir.Schedule(func) cuda_mod = tvm.compile(sch.mod, target="cuda") A_np = np.random.uniform(-1, 1, [16, 16]).astype("float16") B_np = np.random.uniform(-1, 1, [32, 8]).astype("float16") mask = gen_2in4_mask(16, 32) A_dense_np = get_dense_mat_by_mask(A_np, mask) C_np = np.matmul(A_dense_np, B_np).astype(out_dtype) meta = get_meta_m16n8k32_half(mask) def run_and_check(): ctx = tvm.cuda() A_tvm = tvm.runtime.tensor(A_np, ctx) B_tvm = tvm.runtime.tensor(B_np, ctx) C_tvm = tvm.runtime.tensor(np.zeros_like(C_np), ctx) meta_tvm = tvm.runtime.tensor(meta, ctx) cuda_mod(A_tvm, B_tvm, C_tvm, meta_tvm) tvm.testing.assert_allclose(C_tvm.numpy(), C_np, atol=1e-3, rtol=1e-3) tvm.testing.run_with_gpu_lock(run_and_check) if __name__ == "__main__": test_mma_sp_m16n8k16_f16() test_mma_sp_m16n8k32_f16()