# 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


@T.prim_func(s_tir=True)
def gemm_mma_m8n8k4_row_col_fp64pf64fp64(a: T.handle, b: T.handle, c: T.handle):
    T.func_attr({"global_symbol": "default_function", "tirx.noalias": True})
    A = T.match_buffer(a, [8, 4], dtype="float64")
    B = T.match_buffer(b, [8, 4], dtype="float64")
    C = T.match_buffer(c, [8, 8], dtype="float64")
    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)
    MultiA = T.decl_buffer([1], "float64", scope="local")
    MultiB = T.decl_buffer([1], "float64", scope="local")
    Accum = T.decl_buffer([2], "float64", scope="local")
    for i in range(2):
        Accum[i] = T.float64(0)

    MultiA[0] = A[(tx % 32) // 4, (tx % 32) % 4]
    MultiB[0] = B[(tx % 32) // 4, (tx % 32) % 4]
    T.evaluate(
        T.ptx.mma.legacy(
            "m8n8k4",
            "row",
            "col",
            "float64",
            "float64",
            "float64",
            MultiA.data,
            0,
            MultiB.data,
            0,
            Accum.data,
            0,
            False,
            dtype="float64",
        )
    )
    for mma_accum_c_id in range(2):
        C[(tx % 32) // 4, (tx % 32) % 4 * 2 + mma_accum_c_id] = Accum[mma_accum_c_id]


@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(8), reason="need cuda compute >= 8.0")
def test_gemm_mma_m8n8k4_row_col_fp64pf64fp64():
    sch = tvm.s_tir.Schedule(gemm_mma_m8n8k4_row_col_fp64pf64fp64)
    cuda_mod = tvm.compile(sch.mod, target="cuda")

    A_np = np.random.uniform(-1, 1, [8, 4]).astype("float64")
    B_np = np.random.uniform(-1, 1, [8, 4]).astype("float64")
    C_np = np.zeros([8, 8]).astype("float64")

    golden = np.matmul(A_np.astype("float64"), B_np.astype("float64").T)

    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(C_np, ctx)
        cuda_mod(A_tvm, B_tvm, C_tvm)
        C_numpy = C_tvm.numpy()
        tvm.testing.assert_allclose(golden, C_numpy, atol=1e-3, rtol=1e-3)

    tvm.testing.run_with_gpu_lock(run_and_check)


@T.prim_func(s_tir=True)
def gemm_mma_m8n8k4_row_row_fp16fp16fp16(a: T.handle, b: T.handle, c: T.handle):
    T.func_attr({"global_symbol": "default_function", "tirx.noalias": True})
    A = T.match_buffer(a, [16, 4], dtype="float16")
    B = T.match_buffer(b, [4, 16], dtype="float16")
    C = T.match_buffer(c, [16, 16], dtype="float16")
    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)
    MultiA = T.decl_buffer([4], "float16", scope="local")
    MultiB = T.decl_buffer([4], "float16", scope="local")
    Accum = T.decl_buffer([8], "float16", scope="local")
    for i in range(8):
        Accum[i] = T.float32(0)

    for mma_multi_a_col in T.vectorized(4):
        MultiA[mma_multi_a_col] = A[
            ((tx % 32) % 4) + (4 * (((tx % 32) // 16 + (tx % 32) % 16 // 4 * 2) % 4)),
            mma_multi_a_col,
        ]
    for mma_multi_b_col in T.vectorized(4):
        MultiB[mma_multi_b_col] = B[
            (tx % 32) % 4,
            mma_multi_b_col + (4 * ((tx % 32) // 8)),
        ]
    T.evaluate(
        T.ptx.mma.legacy(
            "m8n8k4",
            "row",
            "row",
            "float16",
            "float16",
            "float16",
            MultiA.data,
            0,
            MultiB.data,
            0,
            Accum.data,
            0,
            False,
            dtype="float16",
        )
    )
    for mma_accum_c_id in range(8):
        C[
            ((tx % 32) % 4) + (4 * (((tx % 32) // 16 + (tx % 32) % 16 // 4 * 2) % 4)),
            mma_accum_c_id % 4 + (4 * ((tx % 32) % 16 // 8)) + mma_accum_c_id // 4 * 8,
        ] = Accum[mma_accum_c_id]


@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(7), reason="need cuda compute >= 7.0")
def test_gemm_mma_m8n8k4_row_row_fp16fp16fp16():
    sch = tvm.s_tir.Schedule(gemm_mma_m8n8k4_row_row_fp16fp16fp16)
    cuda_mod = tvm.compile(sch.mod, target="cuda")

    A_np = np.random.uniform(-1, 1, [16, 4]).astype("float16")
    B_np = np.random.uniform(-1, 1, [4, 16]).astype("float16")
    C_np = np.zeros([16, 16]).astype("float16")

    golden = np.matmul(A_np.astype("float16"), B_np.astype("float16"))

    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(C_np, ctx)
        cuda_mod(A_tvm, B_tvm, C_tvm)
        C_numpy = C_tvm.numpy()
        tvm.testing.assert_allclose(golden, C_numpy, atol=1e-3, rtol=1e-3)

    tvm.testing.run_with_gpu_lock(run_and_check)


@T.prim_func(s_tir=True)
def gemm_mma_m8n8k4_row_row_fp16fp16fp32(a: T.handle, b: T.handle, c: T.handle):
    T.func_attr({"global_symbol": "default_function", "tirx.noalias": True})
    A = T.match_buffer(a, [16, 4], dtype="float16")
    B = T.match_buffer(b, [4, 16], dtype="float16")
    C = T.match_buffer(c, [16, 16], dtype="float32")
    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)
    MultiA = T.decl_buffer([4], "float16", scope="local")
    MultiB = T.decl_buffer([4], "float16", scope="local")
    Accum = T.decl_buffer([8], "float32", scope="local")

    for i in range(8):
        Accum[i] = T.float32(0)

    for mma_multi_a_col in T.vectorized(4):
        MultiA[mma_multi_a_col] = A[
            ((tx % 32) % 4) + (4 * (((tx % 32) // 16 + (tx % 32) % 16 // 4 * 2) % 4)),
            mma_multi_a_col,
        ]
    for mma_multi_b_col in T.vectorized(4):
        MultiB[mma_multi_b_col] = B[
            (tx % 32) % 4,
            mma_multi_b_col + (4 * ((tx % 32) // 8)),
        ]
    T.evaluate(
        T.ptx.mma.legacy(
            "m8n8k4",
            "row",
            "row",
            "float16",
            "float16",
            "float32",
            MultiA.data,
            0,
            MultiB.data,
            0,
            Accum.data,
            0,
            False,
            dtype="float32",
        )
    )
    for mma_accum_c_id in range(8):
        C[
            ((tx % 32) % 2)
            + ((mma_accum_c_id // 2 % 2) * 2)
            + 4 * ((tx % 32) // 16)
            + ((tx % 32) % 16 // 4) % 2 * 8,
            (tx % 32) % 4 // 2 * 2
            + (tx % 32) % 16 // 8 * 4
            + mma_accum_c_id % 2
            + mma_accum_c_id // 4 * 8,
        ] = Accum[mma_accum_c_id]


@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(7), reason="need cuda compute >= 7.0")
def test_gemm_mma_m8n8k4_row_row_fp16fp16fp32():
    sch = tvm.s_tir.Schedule(gemm_mma_m8n8k4_row_row_fp16fp16fp32)
    cuda_mod = tvm.compile(sch.mod, target="cuda")

    A_np = np.random.uniform(-1, 1, [16, 4]).astype("float16")
    B_np = np.random.uniform(-1, 1, [4, 16]).astype("float16")
    C_np = np.zeros([16, 16]).astype("float32")

    golden = np.matmul(A_np.astype("float32"), B_np.astype("float32"))

    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(C_np, ctx)
        cuda_mod(A_tvm, B_tvm, C_tvm)
        C_numpy = C_tvm.numpy()
        tvm.testing.assert_allclose(golden, C_numpy, atol=1e-3, rtol=1e-3)

    tvm.testing.run_with_gpu_lock(run_and_check)


@T.prim_func(s_tir=True)
def gemm_mma_m8n8k16_row_col_s8s8s32(a: T.handle, b: T.handle, c: T.handle):
    T.func_attr({"global_symbol": "default_function", "tirx.noalias": True})
    A = T.match_buffer(a, [8, 16], dtype="int8")
    B = T.match_buffer(b, [8, 16], dtype="int8")
    C = T.match_buffer(c, [8, 8], dtype="int32")
    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)
    MultiA = T.decl_buffer([4], "int8", scope="local")
    MultiB = T.decl_buffer([4], "int8", scope="local")
    Accum = T.decl_buffer([2], "int32", scope="local")
    for i in range(2):
        Accum[i] = T.int32(0)

    for mma_multi_a_col in T.vectorized(4):
        MultiA[mma_multi_a_col] = A[(tx % 32) // 4, mma_multi_a_col + (tx % 32) % 4 * 4]
    for mma_multi_b_col in T.vectorized(4):
        MultiB[mma_multi_b_col] = B[(tx % 32) // 4, mma_multi_b_col + (tx % 32) % 4 * 4]
    T.evaluate(
        T.ptx.mma.legacy(
            "m8n8k16",
            "row",
            "col",
            "int8",
            "int8",
            "int32",
            MultiA.data,
            0,
            MultiB.data,
            0,
            Accum.data,
            0,
            False,
            dtype="int32",
        )
    )
    for mma_accum_c_id in range(2):
        C[(tx % 32) // 4, (tx % 32) % 4 * 2 + mma_accum_c_id] = Accum[mma_accum_c_id]


# This test uses mma instructions that are not available on NVCC 10.1.
# Failure occurs during the external call to nvcc, when attempting to
# generate the .fatbin file.
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_nvcc_version(11), reason="need nvcc >= 11")
@pytest.mark.skipif(not env.has_cuda_compute(7, 5), reason="need cuda compute >= 7.5")
def test_gemm_mma_m8n8k16_row_col_s8s8s32():
    sch = tvm.s_tir.Schedule(gemm_mma_m8n8k16_row_col_s8s8s32)
    cuda_mod = tvm.compile(sch.mod, target="cuda")

    A_np = np.random.uniform(-10, 10, [8, 16]).astype("int8")
    B_np = np.random.uniform(-10, 10, [8, 16]).astype("int8")
    C_np = np.zeros([8, 8]).astype("int32")

    golden = np.matmul(A_np.astype("int32"), B_np.astype("int32").T)

    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(C_np, ctx)
        cuda_mod(A_tvm, B_tvm, C_tvm)
        C_numpy = C_tvm.numpy()
        tvm.testing.assert_allclose(golden, C_numpy, atol=1e-3, rtol=1e-3)

    tvm.testing.run_with_gpu_lock(run_and_check)


@T.prim_func(s_tir=True)
def gemm_mma_m8n8k16_row_col_s8u8s32(a: T.handle, b: T.handle, c: T.handle):
    T.func_attr({"global_symbol": "default_function", "tirx.noalias": True})
    A = T.match_buffer(a, [8, 16], dtype="int8")
    B = T.match_buffer(b, [8, 16], dtype="uint8")
    C = T.match_buffer(c, [8, 8], dtype="int32")
    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)
    MultiA = T.decl_buffer([4], "int8", scope="local")
    MultiB = T.decl_buffer([4], "uint8", scope="local")
    Accum = T.decl_buffer([2], "int32", scope="local")
    for i in range(2):
        Accum[i] = T.int32(0)

    for mma_multi_a_col in T.vectorized(4):
        MultiA[mma_multi_a_col] = A[(tx % 32) // 4, mma_multi_a_col + (tx % 32) % 4 * 4]
    for mma_multi_b_col in T.vectorized(4):
        MultiB[mma_multi_b_col] = B[(tx % 32) // 4, mma_multi_b_col + (tx % 32) % 4 * 4]
    T.evaluate(
        T.ptx.mma.legacy(
            "m8n8k16",
            "row",
            "col",
            "int8",
            "uint8",
            "int32",
            MultiA.data,
            0,
            MultiB.data,
            0,
            Accum.data,
            0,
            False,
            dtype="int32",
        )
    )
    for mma_accum_c_id in range(2):
        C[(tx % 32) // 4, (tx % 32) % 4 * 2 + mma_accum_c_id] = Accum[mma_accum_c_id]


# This test uses mma instructions that are not available on NVCC 10.1.
# Failure occurs during the external call to nvcc, when attempting to
# generate the .fatbin file.
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_nvcc_version(11), reason="need nvcc >= 11")
@pytest.mark.skipif(not env.has_cuda_compute(7, 5), reason="need cuda compute >= 7.5")
def test_gemm_mma_m8n8k16_row_col_s8u8s32():
    sch = tvm.s_tir.Schedule(gemm_mma_m8n8k16_row_col_s8u8s32)
    cuda_mod = tvm.compile(sch.mod, target="cuda")

    A_np = np.random.uniform(-10, 10, [8, 16]).astype("int8")
    B_np = np.random.uniform(-10, 10, [8, 16]).astype("uint8")
    C_np = np.zeros([8, 8]).astype("int32")

    golden = np.matmul(A_np.astype("int32"), B_np.astype("int32").T)

    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(C_np, ctx)
        cuda_mod(A_tvm, B_tvm, C_tvm)
        C_numpy = C_tvm.numpy()
        tvm.testing.assert_allclose(golden, C_numpy, atol=1e-3, rtol=1e-3)

    tvm.testing.run_with_gpu_lock(run_and_check)


@T.prim_func(s_tir=True)
def gemm_mma_m8n8k32_row_col_s4s4s32(a: T.handle, b: T.handle, c: T.handle):
    T.func_attr({"global_symbol": "default_function", "tirx.noalias": True})
    A = T.match_buffer(a, [8, 32], dtype="int4")
    B = T.match_buffer(b, [8, 32], dtype="int4")
    C = T.match_buffer(c, [8, 8], dtype="int32")
    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)
    MultiA = T.decl_buffer([8], "int4", scope="local")
    MultiB = T.decl_buffer([8], "int4", scope="local")
    Accum = T.decl_buffer([2], "int32", scope="local")
    for i in range(2):
        Accum[i] = T.int32(0)

    for mma_multi_a_col in T.vectorized(8):
        MultiA[mma_multi_a_col] = A[(tx % 32) // 4, mma_multi_a_col + (tx % 32) % 4 * 8]
    for mma_multi_b_col in T.vectorized(8):
        MultiB[mma_multi_b_col] = B[(tx % 32) // 4, mma_multi_b_col + (tx % 32) % 4 * 8]
    T.evaluate(
        T.ptx.mma.legacy(
            "m8n8k32",
            "row",
            "col",
            "int4",
            "int4",
            "int32",
            MultiA.data,
            0,
            MultiB.data,
            0,
            Accum.data,
            0,
            False,
            dtype="int32",
        )
    )
    for mma_accum_c_id in range(2):
        C[(tx % 32) // 4, (tx % 32) % 4 * 2 + mma_accum_c_id] = Accum[mma_accum_c_id]


# This test uses mma instructions that are not available on NVCC 10.1.
# Failure occurs during the external call to nvcc, when attempting to
# generate the .fatbin file.
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_nvcc_version(11), reason="need nvcc >= 11")
@pytest.mark.skipif(not env.has_cuda_compute(7, 5), reason="need cuda compute >= 7.5")
def test_gemm_mma_m8n8k32_row_col_s4s4s32():
    sch = tvm.s_tir.Schedule(gemm_mma_m8n8k32_row_col_s4s4s32)
    cuda_mod = tvm.compile(sch.mod, target="cuda")

    def run_and_check():
        ctx = tvm.cuda()
        A_tvm = tvm.runtime.empty([8, 32], "int4", ctx)
        B_tvm = tvm.runtime.empty([8, 32], "int4", ctx)
        C_tvm = tvm.runtime.empty([8, 8], "int32", ctx)
        cuda_mod(A_tvm, B_tvm, C_tvm)

    tvm.testing.run_with_gpu_lock(run_and_check)
    # Currently the correctness is not checked.
    # TODO: add correctness checking here.


@T.prim_func(s_tir=True)
def gemm_mma_m8n8k32_row_col_s4u4s32(a: T.handle, b: T.handle, c: T.handle):
    T.func_attr({"global_symbol": "default_function", "tirx.noalias": True})
    A = T.match_buffer(a, [8, 32], dtype="int4")
    B = T.match_buffer(b, [8, 32], dtype="uint4")
    C = T.match_buffer(c, [8, 8], dtype="int32")
    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)
    MultiA = T.decl_buffer([8], "int4", scope="local")
    MultiB = T.decl_buffer([8], "uint4", scope="local")
    Accum = T.decl_buffer([2], "int32", scope="local")
    for i in range(2):
        Accum[i] = T.int32(0)

    for mma_multi_a_col in T.vectorized(8):
        MultiA[mma_multi_a_col] = A[(tx % 32) // 4, mma_multi_a_col + (tx % 32) % 4 * 8]
    for mma_multi_b_col in T.vectorized(8):
        MultiB[mma_multi_b_col] = B[(tx % 32) // 4, mma_multi_b_col + (tx % 32) % 4 * 8]
    T.evaluate(
        T.ptx.mma.legacy(
            "m8n8k32",
            "row",
            "col",
            "int4",
            "uint4",
            "int32",
            MultiA.data,
            0,
            MultiB.data,
            0,
            Accum.data,
            0,
            False,
            dtype="int32",
        )
    )
    for mma_accum_c_id in range(2):
        C[(tx % 32) // 4, (tx % 32) % 4 * 2 + mma_accum_c_id] = Accum[mma_accum_c_id]


# This test uses mma instructions that are not available on NVCC 10.1.
# Failure occurs during the external call to nvcc, when attempting to
# generate the .fatbin file.
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_nvcc_version(11), reason="need nvcc >= 11")
@pytest.mark.skipif(not env.has_cuda_compute(7, 5), reason="need cuda compute >= 7.5")
def test_gemm_mma_m8n8k32_row_col_s4u4s32():
    sch = tvm.s_tir.Schedule(gemm_mma_m8n8k32_row_col_s4u4s32)
    cuda_mod = tvm.compile(sch.mod, target="cuda")

    def run_and_check():
        ctx = tvm.cuda()
        A_tvm = tvm.runtime.empty([8, 32], "int4", ctx)
        B_tvm = tvm.runtime.empty([8, 32], "uint4", ctx)
        C_tvm = tvm.runtime.empty([8, 8], "int32", ctx)
        cuda_mod(A_tvm, B_tvm, C_tvm)

    tvm.testing.run_with_gpu_lock(run_and_check)
    # Currently the correctness is not checked.
    # TODO: add correctness checking here.


@T.prim_func(s_tir=True)
def gemm_mma_m16n8k8_row_col_fp16fp16fp32(a: T.handle, b: T.handle, c: 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, [8, 8], dtype="float16")
    C = T.match_buffer(c, [16, 8], dtype="float32")
    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)
    MultiA = T.decl_buffer([4], "float16", scope="local")
    MultiB = T.decl_buffer([2], "float16", scope="local")
    Accum = T.decl_buffer([4], "float32", scope="local")
    for i in range(4):
        Accum[i] = T.float32(0)

    for mma_multi_a_col in T.vectorized(4):
        MultiA[mma_multi_a_col] = A[
            (tx % 32) // 4 + mma_multi_a_col // 2 * 8, (tx % 32) % 4 * 2 + mma_multi_a_col % 2
        ]
    for mma_multi_b_col in T.vectorized(4):
        MultiB[mma_multi_b_col] = B[
            (tx % 32) // 4 + mma_multi_b_col // 2 * 8, (tx % 32) % 4 * 2 + mma_multi_b_col % 2
        ]
    T.evaluate(
        T.ptx.mma.legacy(
            "m16n8k8",
            "row",
            "col",
            "float16",
            "float16",
            "float32",
            MultiA.data,
            0,
            MultiB.data,
            0,
            Accum.data,
            0,
            False,
            dtype="float32",
        )
    )
    for mma_accum_c_id in range(4):
        C[(tx % 32) // 4 + mma_accum_c_id // 2 * 8, (tx % 32) % 4 * 2 + mma_accum_c_id % 2] = Accum[
            mma_accum_c_id
        ]


@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(8), reason="need cuda compute >= 8.0")
def test_gemm_mma_m16n8k8_row_col_fp16fp16fp32():
    sch = tvm.s_tir.Schedule(gemm_mma_m16n8k8_row_col_fp16fp16fp32)
    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, [8, 8]).astype("float16")
    C_np = np.zeros([16, 8]).astype("float32")

    golden = np.matmul(A_np.astype("float32"), B_np.astype("float32").T)

    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(C_np, ctx)
        cuda_mod(A_tvm, B_tvm, C_tvm)
        C_numpy = C_tvm.numpy()
        tvm.testing.assert_allclose(golden, C_numpy, atol=1e-3, rtol=1e-3)

    tvm.testing.run_with_gpu_lock(run_and_check)


@T.prim_func(s_tir=True)
def gemm_mma_m16n8k16_row_col_fp16fp16fp16(a: T.handle, b: T.handle, c: 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, [8, 16], dtype="float16")
    C = T.match_buffer(c, [16, 8], dtype="float16")
    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)
    MultiA = T.decl_buffer([8], "float16", scope="local")
    MultiB = T.decl_buffer([4], "float16", scope="local")
    Accum = T.decl_buffer([4], "float16", scope="local")
    for i in range(4):
        Accum[i] = T.float32(0)

    for mma_multi_a_col in range(8):
        MultiA[mma_multi_a_col] = A[
            (tx % 32) // 4 + mma_multi_a_col % 4 // 2 * 8,
            (tx % 32) % 4 * 2 + mma_multi_a_col % 2 + mma_multi_a_col // 4 * 8,
        ]
    for mma_multi_b_col in T.vectorized(4):
        MultiB[mma_multi_b_col] = B[
            (tx % 32) // 4,
            (tx % 32) % 4 * 2 + mma_multi_b_col % 2 + mma_multi_b_col // 2 * 8,
        ]
    T.evaluate(
        T.ptx.mma.legacy(
            "m16n8k16",
            "row",
            "col",
            "float16",
            "float16",
            "float16",
            MultiA.data,
            0,
            MultiB.data,
            0,
            Accum.data,
            0,
            False,
            dtype="float16",
        )
    )
    for mma_accum_c_id in range(4):
        C[
            (tx % 32) // 4 + mma_accum_c_id // 2 * 8,
            (tx % 32) % 4 * 2 + mma_accum_c_id % 2,
        ] = Accum[mma_accum_c_id]


@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(8), reason="need cuda compute >= 8.0")
def test_gemm_mma_m16n8k16_row_col_fp16fp16fp16():
    sch = tvm.s_tir.Schedule(gemm_mma_m16n8k16_row_col_fp16fp16fp16)
    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, [8, 16]).astype("float16")
    C_np = np.zeros([16, 8]).astype("float16")

    golden = np.matmul(A_np.astype("float16"), B_np.astype("float16").T)

    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(C_np, ctx)
        cuda_mod(A_tvm, B_tvm, C_tvm)
        C_numpy = C_tvm.numpy()
        tvm.testing.assert_allclose(golden, C_numpy, atol=1e-3, rtol=1e-3)

    tvm.testing.run_with_gpu_lock(run_and_check)


@T.prim_func(s_tir=True)
def gemm_mma_m16n8k16_row_col_fp16fp16fp32(a: T.handle, b: T.handle, c: 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, [8, 16], dtype="float16")
    C = T.match_buffer(c, [16, 8], dtype="float32")
    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)
    MultiA = T.decl_buffer([8], "float16", scope="local")
    MultiB = T.decl_buffer([4], "float16", scope="local")
    Accum = T.decl_buffer([4], "float32", scope="local")
    for i in range(4):
        Accum[i] = T.float32(0)

    for mma_multi_a_col in range(8):
        MultiA[mma_multi_a_col] = A[
            (tx % 32) // 4 + mma_multi_a_col % 4 // 2 * 8,
            (tx % 32) % 4 * 2 + mma_multi_a_col % 2 + mma_multi_a_col // 4 * 8,
        ]
    for mma_multi_b_col in T.vectorized(4):
        MultiB[mma_multi_b_col] = B[
            (tx % 32) // 4,
            (tx % 32) % 4 * 2 + mma_multi_b_col % 2 + mma_multi_b_col // 2 * 8,
        ]
    T.evaluate(
        T.ptx.mma.legacy(
            "m16n8k16",
            "row",
            "col",
            "float16",
            "float16",
            "float32",
            MultiA.data,
            0,
            MultiB.data,
            0,
            Accum.data,
            0,
            False,
            dtype="float32",
        )
    )
    for mma_accum_c_id in range(4):
        C[
            (tx % 32) // 4 + mma_accum_c_id // 2 * 8,
            (tx % 32) % 4 * 2 + mma_accum_c_id % 2,
        ] = Accum[mma_accum_c_id]


@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(8), reason="need cuda compute >= 8.0")
def test_gemm_mma_m16n8k16_row_col_fp16fp16fp32():
    sch = tvm.s_tir.Schedule(gemm_mma_m16n8k16_row_col_fp16fp16fp32)
    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, [8, 16]).astype("float16")
    C_np = np.zeros([16, 8]).astype("float32")

    golden = np.matmul(A_np.astype("float32"), B_np.astype("float32").T)

    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(C_np, ctx)
        cuda_mod(A_tvm, B_tvm, C_tvm)
        C_numpy = C_tvm.numpy()
        tvm.testing.assert_allclose(golden, C_numpy, atol=1e-3, rtol=1e-3)

    tvm.testing.run_with_gpu_lock(run_and_check)


@T.prim_func(s_tir=True)
def gemm_mma_m16n8k16_row_col_s8s8s32(a: T.handle, b: T.handle, c: T.handle):
    T.func_attr({"global_symbol": "default_function", "tirx.noalias": True})
    A = T.match_buffer(a, [16, 16], dtype="int8")
    B = T.match_buffer(b, [8, 16], dtype="int8")
    C = T.match_buffer(c, [16, 8], dtype="int32")
    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)
    MultiA = T.decl_buffer([8], "int8", scope="local")
    MultiB = T.decl_buffer([4], "int8", scope="local")
    Accum = T.decl_buffer([4], "int32", scope="local")
    for i in range(4):
        Accum[i] = T.int32(0)

    for mma_multi_a_col in range(8):
        MultiA[mma_multi_a_col] = A[
            (tx % 32) // 4 + mma_multi_a_col // 4 * 8,
            (tx % 32) % 4 * 4 + mma_multi_a_col % 4,
        ]
    for mma_multi_b_col in T.vectorized(4):
        MultiB[mma_multi_b_col] = B[
            (tx % 32) // 4,
            (tx % 32) % 4 * 4 + mma_multi_b_col,
        ]
    T.evaluate(
        T.ptx.mma.legacy(
            "m16n8k16",
            "row",
            "col",
            "int8",
            "int8",
            "int32",
            MultiA.data,
            0,
            MultiB.data,
            0,
            Accum.data,
            0,
            False,
            dtype="int32",
        )
    )
    for mma_accum_c_id in range(4):
        C[
            (tx % 32) // 4 + mma_accum_c_id // 2 * 8,
            (tx % 32) % 4 * 2 + mma_accum_c_id % 2,
        ] = Accum[mma_accum_c_id]


@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(8), reason="need cuda compute >= 8.0")
def test_gemm_mma_m16n8k16_row_col_s8s8s32():
    sch = tvm.s_tir.Schedule(gemm_mma_m16n8k16_row_col_s8s8s32)
    cuda_mod = tvm.compile(sch.mod, target="cuda")

    A_np = np.random.uniform(-10, 10, [16, 16]).astype("int8")
    B_np = np.random.uniform(-10, 10, [8, 16]).astype("int8")
    C_np = np.zeros([16, 8]).astype("int32")

    golden = np.matmul(A_np.astype("int32"), B_np.astype("int32").T)

    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(C_np, ctx)
        cuda_mod(A_tvm, B_tvm, C_tvm)
        C_numpy = C_tvm.numpy()
        tvm.testing.assert_allclose(golden, C_numpy, atol=1e-3, rtol=1e-3)

    tvm.testing.run_with_gpu_lock(run_and_check)


@T.prim_func(s_tir=True)
def gemm_mma_m16n8k16_row_col_s8u8s32(a: T.handle, b: T.handle, c: T.handle):
    T.func_attr({"global_symbol": "default_function", "tirx.noalias": True})
    A = T.match_buffer(a, [16, 16], dtype="int8")
    B = T.match_buffer(b, [8, 16], dtype="uint8")
    C = T.match_buffer(c, [16, 8], dtype="int32")
    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)
    MultiA = T.decl_buffer([8], "int8", scope="local")
    MultiB = T.decl_buffer([4], "uint8", scope="local")
    Accum = T.decl_buffer([4], "int32", scope="local")
    for i in range(4):
        Accum[i] = T.int32(0)

    for mma_multi_a_col in range(8):
        MultiA[mma_multi_a_col] = A[
            (tx % 32) // 4 + mma_multi_a_col // 4 * 8,
            (tx % 32) % 4 * 4 + mma_multi_a_col % 4,
        ]
    for mma_multi_b_col in T.vectorized(4):
        MultiB[mma_multi_b_col] = B[
            (tx % 32) // 4,
            (tx % 32) % 4 * 4 + mma_multi_b_col,
        ]
    T.evaluate(
        T.ptx.mma.legacy(
            "m16n8k16",
            "row",
            "col",
            "int8",
            "uint8",
            "int32",
            MultiA.data,
            0,
            MultiB.data,
            0,
            Accum.data,
            0,
            False,
            dtype="int32",
        )
    )
    for mma_accum_c_id in range(4):
        C[
            (tx % 32) // 4 + mma_accum_c_id // 2 * 8,
            (tx % 32) % 4 * 2 + mma_accum_c_id % 2,
        ] = Accum[mma_accum_c_id]


@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(8), reason="need cuda compute >= 8.0")
def test_gemm_mma_m16n8k16_row_col_s8u8s32():
    sch = tvm.s_tir.Schedule(gemm_mma_m16n8k16_row_col_s8u8s32)
    cuda_mod = tvm.compile(sch.mod, target="cuda")

    A_np = np.random.uniform(-10, 10, [16, 16]).astype("int8")
    B_np = np.random.uniform(-10, 10, [8, 16]).astype("uint8")
    C_np = np.zeros([16, 8]).astype("int32")

    golden = np.matmul(A_np.astype("int32"), B_np.astype("int32").T)

    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(C_np, ctx)
        cuda_mod(A_tvm, B_tvm, C_tvm)
        C_numpy = C_tvm.numpy()
        tvm.testing.assert_allclose(golden, C_numpy, atol=1e-3, rtol=1e-3)

    tvm.testing.run_with_gpu_lock(run_and_check)


@T.prim_func(s_tir=True)
def gemm_mma_m16n8k32_row_col_s8s8s32(a: T.handle, b: T.handle, c: T.handle):
    T.func_attr({"global_symbol": "default_function", "tirx.noalias": True})
    A = T.match_buffer(a, [16, 32], dtype="int8")
    B = T.match_buffer(b, [8, 32], dtype="int8")
    C = T.match_buffer(c, [16, 8], dtype="int32")
    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)
    MultiA = T.decl_buffer([16], "int8", scope="local")
    MultiB = T.decl_buffer([8], "int8", scope="local")
    Accum = T.decl_buffer([4], "int32", scope="local")
    for i in range(4):
        Accum[i] = T.int32(0)

    for mma_multi_a_col in range(16):
        MultiA[mma_multi_a_col] = A[
            (tx % 32) // 4 + mma_multi_a_col % 8 // 4 * 8,
            (tx % 32) % 4 * 4 + mma_multi_a_col % 4 + mma_multi_a_col // 8 * 16,
        ]
    for mma_multi_b_col in range(8):
        MultiB[mma_multi_b_col] = B[
            (tx % 32) // 4,
            (tx % 32) % 4 * 4 + mma_multi_b_col % 4 + mma_multi_b_col // 4 * 16,
        ]
    T.evaluate(
        T.ptx.mma.legacy(
            "m16n8k32",
            "row",
            "col",
            "int8",
            "int8",
            "int32",
            MultiA.data,
            0,
            MultiB.data,
            0,
            Accum.data,
            0,
            False,
            dtype="int32",
        )
    )
    for mma_accum_c_id in range(4):
        C[
            (tx % 32) // 4 + mma_accum_c_id // 2 * 8,
            (tx % 32) % 4 * 2 + mma_accum_c_id % 2,
        ] = Accum[mma_accum_c_id]


@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(8), reason="need cuda compute >= 8.0")
def test_gemm_mma_m16n8k32_row_col_s8s8s32():
    sch = tvm.s_tir.Schedule(gemm_mma_m16n8k32_row_col_s8s8s32)
    cuda_mod = tvm.compile(sch.mod, target="cuda")

    A_np = np.random.uniform(-10, 10, [16, 32]).astype("int8")
    B_np = np.random.uniform(-10, 10, [8, 32]).astype("int8")
    C_np = np.zeros([16, 8]).astype("int32")

    golden = np.matmul(A_np.astype("int32"), B_np.astype("int32").T)

    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(C_np, ctx)
        cuda_mod(A_tvm, B_tvm, C_tvm)
        C_numpy = C_tvm.numpy()
        tvm.testing.assert_allclose(golden, C_numpy, atol=1e-3, rtol=1e-3)

    tvm.testing.run_with_gpu_lock(run_and_check)


@T.prim_func(s_tir=True)
def gemm_mma_m16n8k32_row_col_s8u8s32(a: T.handle, b: T.handle, c: T.handle):
    T.func_attr({"global_symbol": "default_function", "tirx.noalias": True})
    A = T.match_buffer(a, [16, 32], dtype="int8")
    B = T.match_buffer(b, [8, 32], dtype="uint8")
    C = T.match_buffer(c, [16, 8], dtype="int32")
    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)
    MultiA = T.decl_buffer([16], "int8", scope="local")
    MultiB = T.decl_buffer([8], "uint8", scope="local")
    Accum = T.decl_buffer([4], "int32", scope="local")
    for i in range(4):
        Accum[i] = T.int32(0)

    for mma_multi_a_col in range(16):
        MultiA[mma_multi_a_col] = A[
            (tx % 32) // 4 + mma_multi_a_col % 8 // 4 * 8,
            (tx % 32) % 4 * 4 + mma_multi_a_col % 4 + mma_multi_a_col // 8 * 16,
        ]
    for mma_multi_b_col in range(8):
        MultiB[mma_multi_b_col] = B[
            (tx % 32) // 4,
            (tx % 32) % 4 * 4 + mma_multi_b_col % 4 + mma_multi_b_col // 4 * 16,
        ]
    T.evaluate(
        T.ptx.mma.legacy(
            "m16n8k32",
            "row",
            "col",
            "int8",
            "uint8",
            "int32",
            MultiA.data,
            0,
            MultiB.data,
            0,
            Accum.data,
            0,
            False,
            dtype="int32",
        )
    )
    for mma_accum_c_id in range(4):
        C[
            (tx % 32) // 4 + mma_accum_c_id // 2 * 8,
            (tx % 32) % 4 * 2 + mma_accum_c_id % 2,
        ] = Accum[mma_accum_c_id]


@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(8), reason="need cuda compute >= 8.0")
def test_gemm_mma_m16n8k32_row_col_s8u8s32():
    sch = tvm.s_tir.Schedule(gemm_mma_m16n8k32_row_col_s8u8s32)
    cuda_mod = tvm.compile(sch.mod, target="cuda")

    A_np = np.random.uniform(-10, 10, [16, 32]).astype("int8")
    B_np = np.random.uniform(-10, 10, [8, 32]).astype("uint8")
    C_np = np.zeros([16, 8]).astype("int32")

    golden = np.matmul(A_np.astype("int32"), B_np.astype("int32").T)

    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(C_np, ctx)
        cuda_mod(A_tvm, B_tvm, C_tvm)
        C_numpy = C_tvm.numpy()
        tvm.testing.assert_allclose(golden, C_numpy, atol=1e-3, rtol=1e-3)

    tvm.testing.run_with_gpu_lock(run_and_check)


@T.prim_func(s_tir=True)
def gemm_mma_m16n8k64_row_col_s4s4s32(a: T.handle, b: T.handle, c: T.handle):
    T.func_attr({"global_symbol": "default_function", "tirx.noalias": True})
    A = T.match_buffer(a, [16, 64], dtype="int4")
    B = T.match_buffer(b, [8, 64], dtype="int4")
    C = T.match_buffer(c, [16, 8], dtype="int32")
    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)
    MultiA = T.decl_buffer([32], "int4", scope="local")
    MultiB = T.decl_buffer([16], "int4", scope="local")
    Accum = T.decl_buffer([4], "int32", scope="local")
    for i in range(4):
        Accum[i] = T.int32(0)

    for mma_multi_a_col in range(32):
        MultiA[mma_multi_a_col] = A[
            (tx % 32) // 4 + mma_multi_a_col % 16 // 8 * 8,
            (tx % 32) % 4 * 8 + mma_multi_a_col % 8 + mma_multi_a_col // 16 * 32,
        ]
    for mma_multi_b_col in range(16):
        MultiB[mma_multi_b_col] = B[
            (tx % 32) // 4,
            (tx % 32) % 4 * 8 + mma_multi_b_col % 8 + mma_multi_b_col // 8 * 32,
        ]
    T.evaluate(
        T.ptx.mma.legacy(
            "m8n8k32",
            "row",
            "col",
            "int4",
            "int4",
            "int32",
            MultiA.data,
            0,
            MultiB.data,
            0,
            Accum.data,
            0,
            False,
            dtype="int32",
        )
    )
    for mma_accum_c_id in range(4):
        C[
            (tx % 32) // 4 + mma_accum_c_id // 2 * 8,
            (tx % 32) % 4 * 2 + mma_accum_c_id % 2,
        ] = Accum[mma_accum_c_id]


@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(8), reason="need cuda compute >= 8.0")
def test_gemm_mma_m16n8k64_row_col_s4s4s32():
    sch = tvm.s_tir.Schedule(gemm_mma_m16n8k64_row_col_s4s4s32)
    cuda_mod = tvm.compile(sch.mod, target="cuda")

    def run_and_check():
        ctx = tvm.cuda()
        A_tvm = tvm.runtime.empty([16, 64], "int4", ctx)
        B_tvm = tvm.runtime.empty([8, 64], "int4", ctx)
        C_tvm = tvm.runtime.empty([16, 8], "int32", ctx)
        cuda_mod(A_tvm, B_tvm, C_tvm)

    tvm.testing.run_with_gpu_lock(run_and_check)
    # Currently the correctness is not checked.
    # TODO: add correctness checking here.


@T.prim_func(s_tir=True)
def gemm_mma_m16n8k64_row_col_s4u4s32(a: T.handle, b: T.handle, c: T.handle):
    T.func_attr({"global_symbol": "default_function", "tirx.noalias": True})
    A = T.match_buffer(a, [16, 64], dtype="int4")
    B = T.match_buffer(b, [8, 64], dtype="uint4")
    C = T.match_buffer(c, [16, 8], dtype="int32")
    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)
    MultiA = T.decl_buffer([32], "int4", scope="local")
    MultiB = T.decl_buffer([16], "uint4", scope="local")
    Accum = T.decl_buffer([4], "int32", scope="local")
    for i in range(4):
        Accum[i] = T.int32(0)

    for mma_multi_a_col in range(32):
        MultiA[mma_multi_a_col] = A[
            (tx % 32) // 4 + mma_multi_a_col % 16 // 8 * 8,
            (tx % 32) % 4 * 8 + mma_multi_a_col % 8 + mma_multi_a_col // 16 * 32,
        ]
    for mma_multi_b_col in range(16):
        MultiB[mma_multi_b_col] = B[
            (tx % 32) // 4,
            (tx % 32) % 4 * 8 + mma_multi_b_col % 8 + mma_multi_b_col // 8 * 32,
        ]
    T.evaluate(
        T.ptx.mma.legacy(
            "m8n8k32",
            "row",
            "col",
            "int4",
            "uint4",
            "int32",
            MultiA.data,
            0,
            MultiB.data,
            0,
            Accum.data,
            0,
            False,
            dtype="int32",
        )
    )
    for mma_accum_c_id in range(4):
        C[
            (tx % 32) // 4 + mma_accum_c_id // 2 * 8,
            (tx % 32) % 4 * 2 + mma_accum_c_id % 2,
        ] = Accum[mma_accum_c_id]


@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(8), reason="need cuda compute >= 8.0")
def test_gemm_mma_m16n8k64_row_col_s4u4s32():
    sch = tvm.s_tir.Schedule(gemm_mma_m16n8k64_row_col_s4u4s32)
    cuda_mod = tvm.compile(sch.mod, target="cuda")

    def run_and_check():
        ctx = tvm.cuda()
        A_tvm = tvm.runtime.empty([16, 64], "int4", ctx)
        B_tvm = tvm.runtime.empty([8, 64], "uint4", ctx)
        C_tvm = tvm.runtime.empty([16, 8], "int32", ctx)
        cuda_mod(A_tvm, B_tvm, C_tvm)

    tvm.testing.run_with_gpu_lock(run_and_check)
    # Currently the correctness is not checked.
    # TODO: add correctness checking here.


@T.prim_func(s_tir=True)
def gemm_mma_m16n8k256_row_col_b1b1s32(a: T.handle, b: T.handle, c: T.handle):
    T.func_attr({"global_symbol": "default_function", "tirx.noalias": True})
    A = T.match_buffer(a, [16, 256], dtype="int1")
    B = T.match_buffer(b, [8, 256], dtype="int1")
    C = T.match_buffer(c, [16, 8], dtype="int32")
    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)
    MultiA = T.decl_buffer([128], "int1", scope="local")
    MultiB = T.decl_buffer([64], "int1", scope="local")
    Accum = T.decl_buffer([4], "int32", scope="local")
    for i in range(4):
        Accum[i] = T.int32(0)

    for mma_multi_a_col in range(128):
        MultiA[mma_multi_a_col] = A[
            (tx % 32) // 4 + mma_multi_a_col % 64 // 32 * 8,
            (tx % 32) % 4 * 32 + mma_multi_a_col % 32 + mma_multi_a_col // 64 * 128,
        ]
    for mma_multi_b_col in range(16):
        MultiB[mma_multi_b_col] = B[
            (tx % 32) // 4,
            (tx % 32) % 4 * 32 + mma_multi_b_col % 32 + mma_multi_b_col // 32 * 128,
        ]
    T.evaluate(
        T.ptx.mma.legacy(
            "m16n8k256",
            "row",
            "col",
            "int1",
            "int1",
            "int32",
            MultiA.data,
            0,
            MultiB.data,
            0,
            Accum.data,
            0,
            False,
            "xor",
            dtype="int32",
        )
    )
    for mma_accum_c_id in range(4):
        C[
            (tx % 32) // 4 + mma_accum_c_id // 2 * 8,
            (tx % 32) % 4 * 2 + mma_accum_c_id % 2,
        ] = Accum[mma_accum_c_id]


@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(8), reason="need cuda compute >= 8.0")
def test_gemm_mma_m16n8k256_row_col_b1b1s32():
    sch = tvm.s_tir.Schedule(gemm_mma_m16n8k256_row_col_b1b1s32)
    cuda_mod = tvm.compile(sch.mod, target="cuda")

    def run_and_check():
        ctx = tvm.cuda()
        A_tvm = tvm.runtime.empty([16, 256], "int1", ctx)
        B_tvm = tvm.runtime.empty([8, 256], "int1", ctx)
        C_tvm = tvm.runtime.empty([16, 8], "int32", ctx)
        cuda_mod(A_tvm, B_tvm, C_tvm)

    tvm.testing.run_with_gpu_lock(run_and_check)
    # Currently the correctness is not checked.
    # TODO: add correctness checking here.


if __name__ == "__main__":
    tvm.testing.main()
