# 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.
# ruff: noqa: E501, F821, F841


import pytest

import tvm
import tvm.testing
from tvm.relax.transform import LegalizeOps
from tvm.script import ir as I
from tvm.script import relax as R
from tvm.script import tirx as T

##################### Neural network #####################


def test_conv1d():
    # fmt: off
    @tvm.script.ir_module
    class Conv1d:
        @R.function
        def main(x: R.Tensor((2, 128, 28), "float32"), w: R.Tensor((64, 16, 3), "float32")) -> R.Tensor((2, 64, 13), "float32"):
            gv: R.Tensor((2, 64, 13), "float32") = R.nn.conv1d(x, w, strides=(2,), padding=(1,), dilation=(2,), groups=8)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 128, 28), dtype="float32"), w: R.Tensor((64, 16, 3), dtype="float32")) -> R.Tensor((2, 64, 13), dtype="float32"):
            gv = R.call_tir(Expected.conv1d, (x, w), out_ty=R.Tensor((2, 64, 13), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def conv1d(A: T.Buffer((T.int64(2), T.int64(128), T.int64(28)), "float32"), B: T.Buffer((T.int64(64), T.int64(16), T.int64(3)), "float32"), group_conv1d_ncw: T.Buffer((T.int64(2), T.int64(64), T.int64(13)), "float32")):
            T.func_attr({"tirx.noalias": True})
            pad_temp = T.sblock_alloc_buffer((T.int64(2), T.int64(128), T.int64(30)))
            for i0, i1, i2 in T.grid(T.int64(2), T.int64(128), T.int64(30)):
                with T.sblock("pad_temp"):
                    v_i0, v_i1, v_i2 = T.axis.remap("SSS", [i0, i1, i2])
                    T.reads(A[v_i0, v_i1, v_i2 - T.int64(1)])
                    T.writes(pad_temp[v_i0, v_i1, v_i2])
                    pad_temp[v_i0, v_i1, v_i2] = T.if_then_else(T.int64(1) <= v_i2 and v_i2 < T.int64(29), A[v_i0, v_i1, v_i2 - T.int64(1)], T.float32(0))
            for nn, ff, yy, rc, ry in T.grid(T.int64(2), T.int64(64), T.int64(13), T.int64(16), T.int64(3)):
                with T.sblock("group_conv1d_ncw"):
                    v_nn, v_ff, v_yy, v_rc, v_ry = T.axis.remap("SSSRR", [nn, ff, yy, rc, ry])
                    T.reads(pad_temp[v_nn, v_ff // T.int64(8) * T.int64(16) + v_rc, v_yy * T.int64(2) + v_ry * T.int64(2)], B[v_ff, v_rc, v_ry])
                    T.writes(group_conv1d_ncw[v_nn, v_ff, v_yy])
                    with T.init():
                        group_conv1d_ncw[v_nn, v_ff, v_yy] = T.float32(0)
                    group_conv1d_ncw[v_nn, v_ff, v_yy] = group_conv1d_ncw[v_nn, v_ff, v_yy] + pad_temp[v_nn, v_ff // T.int64(8) * T.int64(16) + v_rc, v_yy * T.int64(2) + v_ry * T.int64(2)] * B[v_ff, v_rc, v_ry]
    # fmt: on

    mod = LegalizeOps()(Conv1d)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_conv1d_with_out_dtype():
    # fmt: off
    @tvm.script.ir_module
    class Conv1d:
        @R.function
        def main(x: R.Tensor((2, 3, 28), "float32"), w: R.Tensor((4, 3, 3), "float32")) -> R.Tensor((2, 4, 26), "float16"):
            gv: R.Tensor((2, 4, 26), "float16") = R.nn.conv1d(x, w, out_dtype="float16")
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 3, 28), dtype="float32"), w: R.Tensor((4, 3, 3), dtype="float32")) -> R.Tensor((2, 4, 26), dtype="float16"):
            gv = R.call_tir(Expected.conv1d, (x, w), out_ty=R.Tensor((2, 4, 26), dtype="float16"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def conv1d(rxplaceholder: T.Buffer((T.int64(2), T.int64(3), T.int64(28)), "float32"), rxplaceholder_1: T.Buffer((T.int64(4), T.int64(3), T.int64(3)), "float32"), conv1d_ncw: T.Buffer((T.int64(2), T.int64(4), T.int64(26)), "float16")):
            T.func_attr({"tirx.noalias": True})
            # with T.sblock("root"):
            pad_temp = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(28)))
            for i0, i1, i2 in T.grid(T.int64(2), T.int64(3), T.int64(28)):
                with T.sblock("pad_temp"):
                    v_i0, v_i1, v_i2 = T.axis.remap("SSS", [i0, i1, i2])
                    T.reads(rxplaceholder[v_i0, v_i1, v_i2])
                    T.writes(pad_temp[v_i0, v_i1, v_i2])
                    pad_temp[v_i0, v_i1, v_i2] = rxplaceholder[v_i0, v_i1, v_i2]
            for nn, ff, yy, rc, ry in T.grid(T.int64(2), T.int64(4), T.int64(26), T.int64(3), T.int64(3)):
                with T.sblock("conv1d_ncw"):
                    v_nn, v_ff, v_yy, v_rc, v_ry = T.axis.remap("SSSRR", [nn, ff, yy, rc, ry])
                    T.reads(pad_temp[v_nn, v_rc, v_yy + v_ry], rxplaceholder_1[v_ff, v_rc, v_ry])
                    T.writes(conv1d_ncw[v_nn, v_ff, v_yy])
                    with T.init():
                        conv1d_ncw[v_nn, v_ff, v_yy] = T.float16(0)
                    conv1d_ncw[v_nn, v_ff, v_yy] = conv1d_ncw[v_nn, v_ff, v_yy] + T.Cast("float16", pad_temp[v_nn, v_rc, v_yy + v_ry]) * T.Cast("float16", rxplaceholder_1[v_ff, v_rc, v_ry])
    # fmt: on

    mod = LegalizeOps()(Conv1d)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_conv1d_nwc():
    # fmt: off
    @tvm.script.ir_module
    class Conv1d:
        @R.function
        def main(x: R.Tensor((2, 28, 128), "float32"), w: R.Tensor((64, 128, 3), "float32")) -> R.Tensor((2, 26, 64), "float32"):
            gv: R.Tensor((2, 26, 64), "float32") = R.nn.conv1d(x, w, data_layout="NWC")
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 28, 128), dtype="float32"), w: R.Tensor((64, 128, 3), dtype="float32")) -> R.Tensor((2, 26, 64), dtype="float32"):
            gv = R.call_tir(Expected.conv1d, (x, w), out_ty=R.Tensor((2, 26, 64), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def conv1d(rxplaceholder: T.Buffer((T.int64(2), T.int64(28), T.int64(128)), "float32"), rxplaceholder_1: T.Buffer((T.int64(64), T.int64(128), T.int64(3)), "float32"), conv1d_nwc: T.Buffer((T.int64(2), T.int64(26), T.int64(64)), "float32")):
            T.func_attr({"tirx.noalias": True})
            # with T.sblock("root"):
            pad_temp = T.sblock_alloc_buffer((T.int64(2), T.int64(28), T.int64(128)))
            for i0, i1, i2 in T.grid(T.int64(2), T.int64(28), T.int64(128)):
                with T.sblock("pad_temp"):
                    v_i0, v_i1, v_i2 = T.axis.remap("SSS", [i0, i1, i2])
                    T.reads(rxplaceholder[v_i0, v_i1, v_i2])
                    T.writes(pad_temp[v_i0, v_i1, v_i2])
                    pad_temp[v_i0, v_i1, v_i2] = rxplaceholder[v_i0, v_i1, v_i2]
            for nn, yy, ff, ry, rc in T.grid(T.int64(2), T.int64(26), T.int64(64), T.int64(3), T.int64(128)):
                with T.sblock("conv1d_nwc"):
                    v_nn, v_yy, v_ff, v_ry, v_rc = T.axis.remap("SSSRR", [nn, yy, ff, ry, rc])
                    T.reads(pad_temp[v_nn, v_yy + v_ry, v_rc], rxplaceholder_1[v_ff, v_rc, v_ry])
                    T.writes(conv1d_nwc[v_nn, v_yy, v_ff])
                    with T.init():
                        conv1d_nwc[v_nn, v_yy, v_ff] = T.float32(0)
                    conv1d_nwc[v_nn, v_yy, v_ff] = conv1d_nwc[v_nn, v_yy, v_ff] + pad_temp[v_nn, v_yy + v_ry, v_rc] * rxplaceholder_1[v_ff, v_rc, v_ry]
    # fmt: on

    mod = LegalizeOps()(Conv1d)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_conv1d_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class Conv1d:
        @R.function
        def main(x: R.Tensor(("n", "c", "w"), "float32"), kernel: R.Tensor(("f", "c", "kw"), "float32")) -> R.Tensor(("n", "f", "w - kw + 1"), "float32"):
            n = T.int64()
            w = T.int64()
            f = T.int64()
            kw = T.int64()
            gv: R.Tensor((n, f, w - kw + 1), "float32") = R.nn.conv1d(x, kernel)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor(("n", "c", "w"), dtype="float32"), kernel: R.Tensor(("f", "c", "kw"), dtype="float32")) -> R.Tensor(("n", "f", "w - kw + 1"), dtype="float32"):
            n = T.int64()
            f = T.int64()
            w = T.int64()
            kw = T.int64()
            c = T.int64()
            gv = R.call_tir(Expected.conv1d, (x, kernel), out_ty=R.Tensor((n, f, w + 1 - kw), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def conv1d(var_rxplaceholder: T.handle, var_rxplaceholder_1: T.handle, var_conv1d_ncw: T.handle):
            T.func_attr({"tirx.noalias": True})
            n, c, w = T.int64(), T.int64(), T.int64()
            rxplaceholder = T.match_buffer(var_rxplaceholder, (n, c, w))
            f, kw = T.int64(), T.int64()
            rxplaceholder_1 = T.match_buffer(var_rxplaceholder_1, (f, c, kw))
            conv1d_ncw = T.match_buffer(var_conv1d_ncw, (n, f, w + T.int64(1) - kw))
            # with T.sblock("root"):
            pad_temp = T.sblock_alloc_buffer((n, c, w))
            for i0, i1, i2 in T.grid(n, c, w):
                with T.sblock("pad_temp"):
                    v_i0, v_i1, v_i2 = T.axis.remap("SSS", [i0, i1, i2])
                    T.reads(rxplaceholder[v_i0, v_i1, v_i2])
                    T.writes(pad_temp[v_i0, v_i1, v_i2])
                    pad_temp[v_i0, v_i1, v_i2] = rxplaceholder[v_i0, v_i1, v_i2]
            for nn, ff, yy, rc, ry in T.grid(n, f, w + T.int64(1) - kw, c, kw):
                with T.sblock("conv1d_ncw"):
                    v_nn, v_ff, v_yy, v_rc, v_ry = T.axis.remap("SSSRR", [nn, ff, yy, rc, ry])
                    T.reads(pad_temp[v_nn, v_rc, v_yy + v_ry], rxplaceholder_1[v_ff, v_rc, v_ry])
                    T.writes(conv1d_ncw[v_nn, v_ff, v_yy])
                    with T.init():
                        conv1d_ncw[v_nn, v_ff, v_yy] = T.float32(0)
                    conv1d_ncw[v_nn, v_ff, v_yy] = conv1d_ncw[v_nn, v_ff, v_yy] + pad_temp[v_nn, v_rc, v_yy + v_ry] * rxplaceholder_1[v_ff, v_rc, v_ry]
    # fmt: on

    mod = LegalizeOps()(Conv1d)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_conv1d_transpose():
    # fmt: off
    @I.ir_module(s_tir=True)
    class Conv1dTranspose:
        @R.function
        def main(x: R.Tensor((2, 128, 28), "float32"), w: R.Tensor((128, 16, 3), "float32")):
            gv = R.nn.conv1d_transpose(x, w, strides=2, padding=1, dilation=1, output_padding=1, groups=8)
            return gv

    @I.ir_module(s_tir=True)
    class Expected:
        @T.prim_func(private=True, s_tir=True)
        def conv1d_transpose(x: T.Buffer((T.int64(2), T.int64(128), T.int64(28)), "float32"), w: T.Buffer((T.int64(128), T.int64(16), T.int64(3)), "float32"), compute: T.Buffer((T.int64(2), T.int64(128), T.int64(56)), "float32")):
            T.func_attr({"tirx.noalias": True})
            data_dilate = T.sblock_alloc_buffer((T.int64(2), T.int64(128), T.int64(55)))
            data_pad = T.sblock_alloc_buffer((T.int64(2), T.int64(128), T.int64(58)))
            kernel = T.sblock_alloc_buffer((T.int64(16), T.int64(128), T.int64(3)))
            for i0, i1, i2 in T.grid(T.int64(2), T.int64(128), T.int64(55)):
                with T.sblock("data_dilate"):
                    v_i0, v_i1, v_i2 = T.axis.remap("SSS", [i0, i1, i2])
                    data_dilate[v_i0, v_i1, v_i2] = T.if_then_else(v_i2 % T.int64(2) == T.int64(0), x[v_i0, v_i1, v_i2 // T.int64(2)], T.float32(0.0))
            for i0, i1, i2 in T.grid(T.int64(2), T.int64(128), T.int64(58)):
                with T.sblock("data_pad"):
                    v_i0, v_i1, v_i2 = T.axis.remap("SSS", [i0, i1, i2])
                    data_pad[v_i0, v_i1, v_i2] = T.if_then_else(T.int64(1) <= v_i2 and v_i2 < T.int64(56), data_dilate[v_i0, v_i1, v_i2 - T.int64(1)], T.float32(0.0))
            for o, i, w_1 in T.grid(T.int64(16), T.int64(128), T.int64(3)):
                with T.sblock("kernel"):
                    v_o, v_i, v_w = T.axis.remap("SSS", [o, i, w_1])
                    kernel[v_o, v_i, v_w] = w[v_i, v_o, T.int64(2) - v_w]
            for b, c, w_1, dc, dw in T.grid(T.int64(2), T.int64(128), T.int64(56), T.int64(16), T.int64(3)):
                with T.sblock("compute"):
                    v_b, v_c, v_w, v_dc, v_dw = T.axis.remap("SSSRR", [b, c, w_1, dc, dw])
                    with T.init():
                        compute[v_b, v_c, v_w] = T.float32(0.0)
                    compute[v_b, v_c, v_w] = compute[v_b, v_c, v_w] + data_pad[v_b, v_c // T.int64(16) * T.int64(16) + v_dc, v_w + v_dw] * kernel[v_c % T.int64(16), v_c // T.int64(16) * T.int64(16) + v_dc, v_dw]

        @R.function
        def main(x: R.Tensor((2, 128, 28), dtype="float32"), w: R.Tensor((128, 16, 3), dtype="float32")) -> R.Tensor((2, 128, 56), dtype="float32"):
            cls = Expected
            gv = R.call_tir(cls.conv1d_transpose, (x, w), out_ty=R.Tensor((2, 128, 56), dtype="float32"))
            return gv
    # fmt: on

    mod = LegalizeOps()(Conv1dTranspose)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_conv2d():
    # fmt: off
    @tvm.script.ir_module
    class Conv2d:
        @R.function
        def main(x: R.Tensor((2, 128, 28, 28), "float32"), w: R.Tensor((64, 16, 3, 3), "float32")) -> R.Tensor((2, 64, 13, 13), "float32"):
            gv: R.Tensor((2, 64, 13, 13), "float32") = R.nn.conv2d(x, w, strides=(2, 2), padding=(1, 1), dilation=(2, 2), groups=8)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 128, 28, 28), "float32"), w: R.Tensor((64, 16, 3, 3), "float32")) -> R.Tensor((2, 64, 13, 13), "float32"):
            gv = R.call_tir(Expected.conv2d, (x, w), R.Tensor((2, 64, 13, 13), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def conv2d(rxplaceholder: T.Buffer((T.int64(2), T.int64(128), T.int64(28), T.int64(28)), "float32"), rxplaceholder_1: T.Buffer((T.int64(64), T.int64(16), T.int64(3), T.int64(3)), "float32"), group_conv2d_nchw: T.Buffer((T.int64(2), T.int64(64), T.int64(13), T.int64(13)), "float32")):
            T.func_attr({"tirx.noalias": True})
            pad_temp = T.sblock_alloc_buffer([T.int64(2), T.int64(128), T.int64(30), T.int64(30)], dtype="float32")
            for i0, i1, i2, i3 in T.grid(T.int64(2), T.int64(128), T.int64(30), T.int64(30)):
                with T.sblock("pad_temp"):
                    i0_1, i1_1, i2_1, i3_1 = T.axis.remap("SSSS", [i0, i1, i2, i3])
                    T.reads(rxplaceholder[i0_1, i1_1, i2_1 - T.int64(1), i3_1 - T.int64(1)])
                    T.writes(pad_temp[i0_1, i1_1, i2_1, i3_1])
                    pad_temp[i0_1, i1_1, i2_1, i3_1] = T.if_then_else(T.int64(1) <= i2_1 and i2_1 < T.int64(29) and T.int64(1) <= i3_1 and i3_1 < T.int64(29), rxplaceholder[i0_1, i1_1, i2_1 - T.int64(1), i3_1 - T.int64(1)], T.float32(0), dtype="float32")
            for i0, i1, i2, i3, i4, i5, i6 in T.grid(T.int64(2), T.int64(64), T.int64(13), T.int64(13), T.int64(16), T.int64(3), T.int64(3)):
                with T.sblock("group_conv2d_nchw"):
                    nn, ff, yy, xx, rc, ry, rx = T.axis.remap("SSSSRRR", [i0, i1, i2, i3, i4, i5, i6])
                    T.reads(pad_temp[nn, ff // T.int64(8) * T.int64(16) + rc, yy * T.int64(2) + ry * T.int64(2), xx * T.int64(2) + rx * T.int64(2)], rxplaceholder_1[ff, rc, ry, rx])
                    T.writes(group_conv2d_nchw[nn, ff, yy, xx])
                    with T.init():
                        group_conv2d_nchw[nn, ff, yy, xx] = T.float32(0)
                    group_conv2d_nchw[nn, ff, yy, xx] = group_conv2d_nchw[nn, ff, yy, xx] + pad_temp[nn, ff // T.int64(8) * T.int64(16) + rc, yy * T.int64(2) + ry * T.int64(2), xx * T.int64(2) + rx * T.int64(2)] * rxplaceholder_1[ff, rc, ry, rx]
    # fmt: on

    mod = LegalizeOps()(Conv2d)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_conv2d_with_out_dtype():
    # fmt: off
    @tvm.script.ir_module
    class Conv2d:
        @R.function
        def main(x: R.Tensor((2, 3, 28, 28), "float32"), w: R.Tensor((4, 3, 3, 3), "float32")) -> R.Tensor((2, 4, 26, 26), "float16"):
            gv: R.Tensor((2, 4, 26, 26), "float16") = R.nn.conv2d(x, w, out_dtype="float16")
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 3, 28, 28), "float32"), w: R.Tensor((4, 3, 3, 3), "float32")) -> R.Tensor((2, 4, 26, 26), "float16"):
            gv = R.call_tir(Expected.conv2d, (x, w), R.Tensor((2, 4, 26, 26), dtype="float16"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def conv2d(rxplaceholder: T.Buffer((T.int64(2), T.int64(3), T.int64(28), T.int64(28)), "float32"), rxplaceholder_1: T.Buffer((T.int64(4), T.int64(3), T.int64(3), T.int64(3)), "float32"), conv2d_nchw: T.Buffer((T.int64(2), T.int64(4), T.int64(26), T.int64(26)), "float16")):
            T.func_attr({"tirx.noalias": True})
            pad_temp = T.sblock_alloc_buffer([T.int64(2), T.int64(3), T.int64(28), T.int64(28)], dtype="float32")
            for i0, i1, i2, i3 in T.grid(T.int64(2), T.int64(3), T.int64(28), T.int64(28)):
                with T.sblock("pad_temp"):
                    i0_1, i1_1, i2_1, i3_1 = T.axis.remap("SSSS", [i0, i1, i2, i3])
                    T.reads(rxplaceholder[i0_1, i1_1, i2_1, i3_1])
                    T.writes(pad_temp[i0_1, i1_1, i2_1, i3_1])
                    pad_temp[i0_1, i1_1, i2_1, i3_1] = rxplaceholder[i0_1, i1_1, i2_1, i3_1]
            for i0, i1, i2, i3, i4, i5, i6 in T.grid(T.int64(2), T.int64(4), T.int64(26), T.int64(26), T.int64(3), T.int64(3), T.int64(3)):
                with T.sblock("conv2d_nchw"):
                    nn, ff, yy, xx, rc, ry, rx = T.axis.remap("SSSSRRR", [i0, i1, i2, i3, i4, i5, i6])
                    T.reads(pad_temp[nn, rc, yy + ry, xx + rx], rxplaceholder_1[ff, rc, ry, rx])
                    T.writes(conv2d_nchw[nn, ff, yy, xx])
                    with T.init():
                        conv2d_nchw[nn, ff, yy, xx] = T.float16(0)
                    conv2d_nchw[nn, ff, yy, xx] = conv2d_nchw[nn, ff, yy, xx] + T.Cast("float16", pad_temp[nn, rc, yy + ry, xx + rx]) * T.Cast("float16", rxplaceholder_1[ff, rc, ry, rx])
    # fmt: on

    mod = LegalizeOps()(Conv2d)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_conv2d_nhwc():
    # fmt: off
    @tvm.script.ir_module
    class Conv2d:
        @R.function
        def main(x: R.Tensor((2, 28, 28, 128), "float32"), w: R.Tensor((64, 128, 3, 3), "float32")) -> R.Tensor((2, 26, 26, 64), "float32"):
            gv: R.Tensor((2, 26, 26, 64), "float32") = R.nn.conv2d(x, w, data_layout="NHWC")
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 28, 28, 128), "float32"), w: R.Tensor((64, 128, 3, 3), "float32")) -> R.Tensor((2, 26, 26, 64), "float32"):
            gv = R.call_tir(Expected.conv2d, (x, w), R.Tensor((2, 26, 26, 64), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def conv2d(rxplaceholder: T.Buffer((T.int64(2), T.int64(28), T.int64(28), T.int64(128)), "float32"), rxplaceholder_1: T.Buffer((T.int64(64), T.int64(128), T.int64(3), T.int64(3)), "float32"), conv2d_nhwc: T.Buffer((T.int64(2), T.int64(26), T.int64(26), T.int64(64)), "float32")):
            T.func_attr({"tirx.noalias": True})
            pad_temp = T.sblock_alloc_buffer([T.int64(2), T.int64(28), T.int64(28), T.int64(128)], dtype="float32")
            for i0, i1, i2, i3 in T.grid(T.int64(2), T.int64(28), T.int64(28), T.int64(128)):
                with T.sblock("pad_temp"):
                    i0_1, i1_1, i2_1, i3_1 = T.axis.remap("SSSS", [i0, i1, i2, i3])
                    T.reads(rxplaceholder[i0_1, i1_1, i2_1, i3_1])
                    T.writes(pad_temp[i0_1, i1_1, i2_1, i3_1])
                    pad_temp[i0_1, i1_1, i2_1, i3_1] = rxplaceholder[i0_1, i1_1, i2_1, i3_1]
            for i0, i1, i2, i3, i4, i5, i6 in T.grid(T.int64(2), T.int64(26), T.int64(26), T.int64(64), T.int64(3), T.int64(3), T.int64(128)):
                with T.sblock("conv2d_nhwc"):
                    nn, yy, xx, ff, ry, rx, rc = T.axis.remap("SSSSRRR", [i0, i1, i2, i3, i4, i5, i6])
                    T.reads(pad_temp[nn, yy + ry, xx + rx, rc], rxplaceholder_1[ff, rc, ry, rx])
                    T.writes(conv2d_nhwc[nn, yy, xx, ff])
                    with T.init():
                        conv2d_nhwc[nn, yy, xx, ff] = T.float32(0)
                    conv2d_nhwc[nn, yy, xx, ff] = conv2d_nhwc[nn, yy, xx, ff] + pad_temp[nn, yy + ry, xx + rx, rc] * rxplaceholder_1[ff, rc, ry, rx]
    # fmt: on

    mod = LegalizeOps()(Conv2d)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_conv2d_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class Conv2d:
        @R.function
        def main(x: R.Tensor(("n", "c", "h", "w"), "float32"), kernel: R.Tensor(("f", "c", "kh", "kw"), "float32")) -> R.Tensor(("n", "f", "h - kh + 1", "w - kw + 1"), "float32"):
            n = T.int64()
            h = T.int64()
            w = T.int64()
            f = T.int64()
            kh = T.int64()
            kw = T.int64()
            gv: R.Tensor((n, f, h - kh + 1, w - kw + 1), "float32") = R.nn.conv2d(x, kernel)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor(("n", "c", "h", "w"), "float32"), kernel: R.Tensor(("f", "c", "kh", "kw"), "float32")) -> R.Tensor(("n", "f", "h - kh + 1", "w - kw + 1"), "float32"):
            n = T.int64()
            f = T.int64()
            h = T.int64()
            kh = T.int64()
            w = T.int64()
            kw = T.int64()
            gv = R.call_tir(Expected.conv2d, (x, kernel), R.Tensor((n, f, h + 1 - kh, w + 1 - kw), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def conv2d(var_rxplaceholder: T.handle, var_rxplaceholder_1: T.handle, var_conv2d_nchw: T.handle):
            T.func_attr({"tirx.noalias": True})
            c = T.int64()
            f = T.int64()
            h = T.int64()
            kh = T.int64()
            kw = T.int64()
            n = T.int64()
            w = T.int64()
            rxplaceholder = T.match_buffer(var_rxplaceholder, [n, c, h, w], dtype="float32")
            rxplaceholder_1 = T.match_buffer(var_rxplaceholder_1, [f, c, kh, kw], dtype="float32")
            conv2d_nchw = T.match_buffer(var_conv2d_nchw, [n, f, h + T.int64(1) - kh, w + T.int64(1) - kw], dtype="float32")
            pad_temp = T.sblock_alloc_buffer([n, c, h, w], dtype="float32")
            for i0, i1, i2, i3 in T.grid(n, c, h, w):
                with T.sblock("pad_temp"):
                    i0_1, i1_1, i2_1, i3_1 = T.axis.remap("SSSS", [i0, i1, i2, i3])
                    T.reads(rxplaceholder[i0_1, i1_1, i2_1, i3_1])
                    T.writes(pad_temp[i0_1, i1_1, i2_1, i3_1])
                    pad_temp[i0_1, i1_1, i2_1, i3_1] = rxplaceholder[i0_1, i1_1, i2_1, i3_1]
            for i0, i1, i2, i3, i4, i5, i6 in T.grid(n, f, h + T.int64(1) - kh, w + T.int64(1) - kw, c, kh, kw):
                with T.sblock("conv2d_nchw"):
                    nn, ff, yy, xx, rc, ry, rx = T.axis.remap("SSSSRRR", [i0, i1, i2, i3, i4, i5, i6])
                    T.reads(pad_temp[nn, rc, yy + ry, xx + rx], rxplaceholder_1[ff, rc, ry, rx])
                    T.writes(conv2d_nchw[nn, ff, yy, xx])
                    with T.init():
                        conv2d_nchw[nn, ff, yy, xx] = T.float32(0)
                    conv2d_nchw[nn, ff, yy, xx] = conv2d_nchw[nn, ff, yy, xx] + pad_temp[nn, rc, yy + ry, xx + rx] * rxplaceholder_1[ff, rc, ry, rx]
    # fmt: on

    mod = LegalizeOps()(Conv2d)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_conv2d_symbolic_group():
    # fmt: off
    @tvm.script.ir_module
    class Conv2d:
        @R.function
        def main(x: R.Tensor(("n", "c", 28, 28), "float32"), w: R.Tensor(("f", "c_div_8", 3, 3), "float32")) -> R.Tensor(("n", "f", 26, 26), "float32"):
            n = T.int64()
            f = T.int64()
            gv: R.Tensor((n, f, 26, 26), "float32") = R.nn.conv2d(x, w, groups=8)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor(("n", "c", 28, 28), dtype="float32"), w: R.Tensor(("f", "c_div_8", 3, 3), dtype="float32")) -> R.Tensor(("n", "f", 26, 26), dtype="float32"):
            n = T.int64()
            f = T.int64()
            gv = R.call_tir(Expected.conv2d, (x, w), out_ty=R.Tensor((n, f, 26, 26), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def conv2d(var_x: T.handle, var_w: T.handle, var_group_conv2d_nchw: T.handle):
            T.func_attr({"tirx.noalias": True})
            n, c = T.int64(), T.int64()
            x = T.match_buffer(var_x, (n, c, T.int64(28), T.int64(28)))
            f, c_div_8 = T.int64(), T.int64()
            w = T.match_buffer(var_w, (f, c_div_8, T.int64(3), T.int64(3)))
            group_conv2d_nchw = T.match_buffer(var_group_conv2d_nchw, (n, f, T.int64(26), T.int64(26)))
            pad_temp = T.sblock_alloc_buffer((n, c, T.int64(28), T.int64(28)))
            for i0, i1, i2, i3 in T.grid(n, c, T.int64(28), T.int64(28)):
                with T.sblock("pad_temp"):
                    v_i0, v_i1, v_i2, v_i3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
                    T.reads(x[v_i0, v_i1, v_i2, v_i3])
                    T.writes(pad_temp[v_i0, v_i1, v_i2, v_i3])
                    pad_temp[v_i0, v_i1, v_i2, v_i3] = x[v_i0, v_i1, v_i2, v_i3]
            for nn, ff, yy, xx, rc, ry, rx in T.grid(n, f, T.int64(26), T.int64(26), c // T.int64(8), T.int64(3), T.int64(3)):
                with T.sblock("group_conv2d_nchw"):
                    v_nn, v_ff, v_yy, v_xx, v_rc, v_ry, v_rx = T.axis.remap("SSSSRRR", [nn, ff, yy, xx, rc, ry, rx])
                    T.reads(pad_temp[v_nn, v_ff // (f // T.int64(8)) * (c // T.int64(8)) + v_rc, v_yy + v_ry, v_xx + v_rx], w[v_ff, v_rc, v_ry, v_rx])
                    T.writes(group_conv2d_nchw[v_nn, v_ff, v_yy, v_xx])
                    with T.init():
                        group_conv2d_nchw[v_nn, v_ff, v_yy, v_xx] = T.float32(0.0)
                    group_conv2d_nchw[v_nn, v_ff, v_yy, v_xx] = group_conv2d_nchw[v_nn, v_ff, v_yy, v_xx] + pad_temp[v_nn, v_ff // (f // T.int64(8)) * (c // T.int64(8)) + v_rc, v_yy + v_ry, v_xx + v_rx] * w[v_ff, v_rc, v_ry, v_rx]
    # fmt: on

    mod = LegalizeOps()(Conv2d)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_conv2d_transpose():
    # fmt: off
    @I.ir_module(s_tir=True)
    class Conv2dTranspose:
        @R.function
        def main(x: R.Tensor((2, 128, 28, 28), "float32"), w: R.Tensor((128, 16, 3, 3), "float32")):
            gv = R.nn.conv2d_transpose(x, w, strides=(2, 3), padding=(1, 1), dilation=(1, 1), output_padding=(1, 2), groups=8)
            return gv

    @I.ir_module(s_tir=True)
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 128, 28, 28), dtype="float32"), w: R.Tensor((128, 16, 3, 3), dtype="float32")) -> R.Tensor((2, 128, 56, 84), dtype="float32"):
            gv = R.call_tir(Expected.conv2d_transpose, (x, w), out_ty=R.Tensor((2, 128, 56, 84), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def conv2d_transpose(rxplaceholder: T.Buffer((T.int64(2), T.int64(128), T.int64(28), T.int64(28)), "float32"), rxplaceholder_1: T.Buffer((T.int64(128), T.int64(16), T.int64(3), T.int64(3)), "float32"), compute: T.Buffer((T.int64(2), T.int64(128), T.int64(56), T.int64(84)), "float32")):
            T.func_attr({"tirx.noalias": True})
            # with T.sblock("root"):
            data_dilate = T.sblock_alloc_buffer((T.int64(2), T.int64(128), T.int64(55), T.int64(82)))
            data_pad = T.sblock_alloc_buffer((T.int64(2), T.int64(128), T.int64(58), T.int64(86)))
            kernel_transform = T.sblock_alloc_buffer((T.int64(16), T.int64(128), T.int64(3), T.int64(3)))
            for i0, i1, i2, i3 in T.grid(T.int64(2), T.int64(128), T.int64(55), T.int64(82)):
                with T.sblock("data_dilate"):
                    v_i0, v_i1, v_i2, v_i3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
                    T.reads(rxplaceholder[v_i0, v_i1, v_i2 // T.int64(2), v_i3 // T.int64(3)])
                    T.writes(data_dilate[v_i0, v_i1, v_i2, v_i3])
                    data_dilate[v_i0, v_i1, v_i2, v_i3] = T.if_then_else(v_i2 % T.int64(2) == T.int64(0) and v_i3 % T.int64(3) == T.int64(0), rxplaceholder[v_i0, v_i1, v_i2 // T.int64(2), v_i3 // T.int64(3)], T.float32(0))
            for i0, i1, i2, i3 in T.grid(T.int64(2), T.int64(128), T.int64(58), T.int64(86)):
                with T.sblock("data_pad"):
                    v_i0, v_i1, v_i2, v_i3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
                    T.reads(data_dilate[v_i0, v_i1, v_i2 - T.int64(1), v_i3 - T.int64(1)])
                    T.writes(data_pad[v_i0, v_i1, v_i2, v_i3])
                    data_pad[v_i0, v_i1, v_i2, v_i3] = T.if_then_else(T.int64(1) <= v_i2 and v_i2 < T.int64(56) and T.int64(1) <= v_i3 and v_i3 < T.int64(83), data_dilate[v_i0, v_i1, v_i2 - T.int64(1), v_i3 - T.int64(1)], T.float32(0))
            for i, o, h, w in T.grid(T.int64(16), T.int64(128), T.int64(3), T.int64(3)):
                with T.sblock("kernel_transform"):
                    v_i, v_o, v_h, v_w = T.axis.remap("SSSS", [i, o, h, w])
                    T.reads(rxplaceholder_1[v_o, v_i, T.int64(2) - v_h, T.int64(2) - v_w])
                    T.writes(kernel_transform[v_i, v_o, v_h, v_w])
                    kernel_transform[v_i, v_o, v_h, v_w] = rxplaceholder_1[v_o, v_i, T.int64(2) - v_h, T.int64(2) - v_w]
            for b, c, h, w, dc, dh, dw in T.grid(T.int64(2), T.int64(128), T.int64(56), T.int64(84), T.int64(16), T.int64(3), T.int64(3)):
                with T.sblock("compute"):
                    v_b, v_c, v_h, v_w, v_dc, v_dh, v_dw = T.axis.remap("SSSSRRR", [b, c, h, w, dc, dh, dw])
                    T.reads(data_pad[v_b, v_c // T.int64(16) * T.int64(16) + v_dc, v_h + v_dh, v_w + v_dw], kernel_transform[v_c % T.int64(16), v_c // T.int64(16) * T.int64(16) + v_dc, v_dh, v_dw])
                    T.writes(compute[v_b, v_c, v_h, v_w])
                    with T.init():
                        compute[v_b, v_c, v_h, v_w] = T.float32(0)
                    compute[v_b, v_c, v_h, v_w] = compute[v_b, v_c, v_h, v_w] + data_pad[v_b, v_c // T.int64(16) * T.int64(16) + v_dc, v_h + v_dh, v_w + v_dw] * kernel_transform[v_c % T.int64(16), v_c // T.int64(16) * T.int64(16) + v_dc, v_dh, v_dw]
    # fmt: on

    mod = LegalizeOps()(Conv2dTranspose)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_conv3d_transpose():
    # fmt: off
    @tvm.script.ir_module
    class Conv3dTranspose:
        @R.function
        def main(x: R.Tensor((2, 3, 4, 4, 4), "float32"), w: R.Tensor((3, 4, 3, 3, 3), "float32")):
            gv = R.nn.conv3d_transpose(x, w)
            return gv

    @I.ir_module(s_tir=True)
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 3, 4, 4, 4), dtype="float32"), w: R.Tensor((3, 4, 3, 3, 3), dtype="float32")) -> R.Tensor((2, 4, 6, 6, 6), dtype="float32"):
            gv = R.call_tir(Expected.conv3d_transpose, (x, w), out_ty=R.Tensor((2, 4, 6, 6, 6), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def conv3d_transpose(x: T.Buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(4), T.int64(4)), "float32"), w: T.Buffer((T.int64(3), T.int64(4), T.int64(3), T.int64(3), T.int64(3)), "float32"), compute: T.Buffer((T.int64(2), T.int64(4), T.int64(6), T.int64(6), T.int64(6)), "float32")):
            T.func_attr({"tirx.noalias": True})
            data_dilate = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(4), T.int64(4)))
            data_pad = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(8), T.int64(8), T.int64(8)))
            kernel_transform = T.sblock_alloc_buffer((T.int64(4), T.int64(3), T.int64(3), T.int64(3), T.int64(3)))
            for i0, i1, i2, i3, i4 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(4), T.int64(4)):
                with T.sblock("data_dilate"):
                    v_i0, v_i1, v_i2, v_i3, v_i4 = T.axis.remap("SSSSS", [i0, i1, i2, i3, i4])
                    T.reads(x[v_i0, v_i1, v_i2, v_i3, v_i4])
                    T.writes(data_dilate[v_i0, v_i1, v_i2, v_i3, v_i4])
                    data_dilate[v_i0, v_i1, v_i2, v_i3, v_i4] = x[v_i0, v_i1, v_i2, v_i3, v_i4]
            for i0, i1, i2, i3, i4 in T.grid(T.int64(2), T.int64(3), T.int64(8), T.int64(8), T.int64(8)):
                with T.sblock("data_pad"):
                    v_i0, v_i1, v_i2, v_i3, v_i4 = T.axis.remap("SSSSS", [i0, i1, i2, i3, i4])
                    T.reads(data_dilate[v_i0, v_i1, v_i2 - T.int64(2), v_i3 - T.int64(2), v_i4 - T.int64(2)])
                    T.writes(data_pad[v_i0, v_i1, v_i2, v_i3, v_i4])
                    data_pad[v_i0, v_i1, v_i2, v_i3, v_i4] = T.if_then_else(T.int64(2) <= v_i2 and v_i2 < T.int64(6) and T.int64(2) <= v_i3 and v_i3 < T.int64(6) and T.int64(2) <= v_i4 and v_i4 < T.int64(6), data_dilate[v_i0, v_i1, v_i2 - T.int64(2), v_i3 - T.int64(2), v_i4 - T.int64(2)], T.float32(0.0))
            for o, i, d, h, w_1 in T.grid(T.int64(4), T.int64(3), T.int64(3), T.int64(3), T.int64(3)):
                with T.sblock("kernel_transform"):
                    v_o, v_i, v_d, v_h, v_w = T.axis.remap("SSSSS", [o, i, d, h, w_1])
                    T.reads(w[v_i, v_o, T.int64(2) - v_d, T.int64(2) - v_h, T.int64(2) - v_w])
                    T.writes(kernel_transform[v_o, v_i, v_d, v_h, v_w])
                    kernel_transform[v_o, v_i, v_d, v_h, v_w] = w[v_i, v_o, T.int64(2) - v_d, T.int64(2) - v_h, T.int64(2) - v_w]
            for b, c, d, h, w_1, dc, dd, dh, dw in T.grid(T.int64(2), T.int64(4), T.int64(6), T.int64(6), T.int64(6), T.int64(3), T.int64(3), T.int64(3), T.int64(3)):
                with T.sblock("compute"):
                    v_b, v_c, v_d, v_h, v_w, v_dc, v_dd, v_dh, v_dw = T.axis.remap("SSSSSRRRR", [b, c, d, h, w_1, dc, dd, dh, dw])
                    T.reads(data_pad[v_b, v_dc, v_d + v_dd, v_h + v_dh, v_w + v_dw], kernel_transform[v_c, v_dc, v_dd, v_dh, v_dw])
                    T.writes(compute[v_b, v_c, v_d, v_h, v_w])
                    with T.init():
                        compute[v_b, v_c, v_d, v_h, v_w] = T.float32(0.0)
                    compute[v_b, v_c, v_d, v_h, v_w] = compute[v_b, v_c, v_d, v_h, v_w] + data_pad[v_b, v_dc, v_d + v_dd, v_h + v_dh, v_w + v_dw] * kernel_transform[v_c, v_dc, v_dd, v_dh, v_dw]
    # fmt: on

    mod = LegalizeOps()(Conv3dTranspose)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_conv3d_transpose_with_out_dtype():
    # fmt: off
    @tvm.script.ir_module
    class Conv3dTranspose:
        @R.function
        def main(x: R.Tensor((2, 3, 4, 4, 4), "float32"), w: R.Tensor((3, 4, 3, 3, 3), "float32")):
            gv = R.nn.conv3d_transpose(x, w, out_dtype="float16")
            return gv

    @I.ir_module(s_tir=True)
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 3, 4, 4, 4), dtype="float32"), w: R.Tensor((3, 4, 3, 3, 3), dtype="float32")) -> R.Tensor((2, 4, 6, 6, 6), dtype="float16"):
            gv = R.call_tir(Expected.conv3d_transpose, (x, w), out_ty=R.Tensor((2, 4, 6, 6, 6), dtype="float16"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def conv3d_transpose(x: T.Buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(4), T.int64(4)), "float32"), w: T.Buffer((T.int64(3), T.int64(4), T.int64(3), T.int64(3), T.int64(3)), "float32"), compute: T.Buffer((T.int64(2), T.int64(4), T.int64(6), T.int64(6), T.int64(6)), "float16")):
            T.func_attr({"tirx.noalias": True})
            data_dilate = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(4), T.int64(4)))
            data_pad = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(8), T.int64(8), T.int64(8)))
            kernel_transform = T.sblock_alloc_buffer((T.int64(4), T.int64(3), T.int64(3), T.int64(3), T.int64(3)))
            for i0, i1, i2, i3, i4 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(4), T.int64(4)):
                with T.sblock("data_dilate"):
                    v_i0, v_i1, v_i2, v_i3, v_i4 = T.axis.remap("SSSSS", [i0, i1, i2, i3, i4])
                    T.reads(x[v_i0, v_i1, v_i2, v_i3, v_i4])
                    T.writes(data_dilate[v_i0, v_i1, v_i2, v_i3, v_i4])
                    data_dilate[v_i0, v_i1, v_i2, v_i3, v_i4] = x[v_i0, v_i1, v_i2, v_i3, v_i4]
            for i0, i1, i2, i3, i4 in T.grid(T.int64(2), T.int64(3), T.int64(8), T.int64(8), T.int64(8)):
                with T.sblock("data_pad"):
                    v_i0, v_i1, v_i2, v_i3, v_i4 = T.axis.remap("SSSSS", [i0, i1, i2, i3, i4])
                    T.reads(data_dilate[v_i0, v_i1, v_i2 - T.int64(2), v_i3 - T.int64(2), v_i4 - T.int64(2)])
                    T.writes(data_pad[v_i0, v_i1, v_i2, v_i3, v_i4])
                    data_pad[v_i0, v_i1, v_i2, v_i3, v_i4] = T.if_then_else(T.int64(2) <= v_i2 and v_i2 < T.int64(6) and T.int64(2) <= v_i3 and v_i3 < T.int64(6) and T.int64(2) <= v_i4 and v_i4 < T.int64(6), data_dilate[v_i0, v_i1, v_i2 - T.int64(2), v_i3 - T.int64(2), v_i4 - T.int64(2)], T.float32(0.0))
            for o, i, d, h, w_1 in T.grid(T.int64(4), T.int64(3), T.int64(3), T.int64(3), T.int64(3)):
                with T.sblock("kernel_transform"):
                    v_o, v_i, v_d, v_h, v_w = T.axis.remap("SSSSS", [o, i, d, h, w_1])
                    T.reads(w[v_i, v_o, T.int64(2) - v_d, T.int64(2) - v_h, T.int64(2) - v_w])
                    T.writes(kernel_transform[v_o, v_i, v_d, v_h, v_w])
                    kernel_transform[v_o, v_i, v_d, v_h, v_w] = w[v_i, v_o, T.int64(2) - v_d, T.int64(2) - v_h, T.int64(2) - v_w]
            for b, c, d, h, w_1, dc, dd, dh, dw in T.grid(T.int64(2), T.int64(4), T.int64(6), T.int64(6), T.int64(6), T.int64(3), T.int64(3), T.int64(3), T.int64(3)):
                with T.sblock("compute"):
                    v_b, v_c, v_d, v_h, v_w, v_dc, v_dd, v_dh, v_dw = T.axis.remap("SSSSSRRRR", [b, c, d, h, w_1, dc, dd, dh, dw])
                    T.reads(data_pad[v_b, v_dc, v_d + v_dd, v_h + v_dh, v_w + v_dw], kernel_transform[v_c, v_dc, v_dd, v_dh, v_dw])
                    T.writes(compute[v_b, v_c, v_d, v_h, v_w])
                    with T.init():
                        compute[v_b, v_c, v_d, v_h, v_w] = T.float16(0.0)
                    compute[v_b, v_c, v_d, v_h, v_w] = compute[v_b, v_c, v_d, v_h, v_w] + T.Cast("float16", data_pad[v_b, v_dc, v_d + v_dd, v_h + v_dh, v_w + v_dw]) * T.Cast("float16", kernel_transform[v_c, v_dc, v_dd, v_dh, v_dw])
    # fmt: on

    mod = LegalizeOps()(Conv3dTranspose)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_conv2d_transpose_with_out_dtype():
    # fmt: off
    @tvm.script.ir_module
    class Conv2dTranspose:
        @R.function
        def main(x: R.Tensor((2, 3, 28, 28), "float32"), w: R.Tensor((3, 4, 3, 3), "float32")):
            gv = R.nn.conv2d_transpose(x, w, out_dtype="float16")
            return gv

    @I.ir_module(s_tir=True)
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 3, 28, 28), dtype="float32"), w: R.Tensor((3, 4, 3, 3), dtype="float32")) -> R.Tensor((2, 4, 30, 30), dtype="float16"):
            gv = R.call_tir(Expected.conv2d_transpose, (x, w), out_ty=R.Tensor((2, 4, 30, 30), dtype="float16"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def conv2d_transpose(rxplaceholder: T.Buffer((T.int64(2), T.int64(3), T.int64(28), T.int64(28)), "float32"), rxplaceholder_1: T.Buffer((T.int64(3), T.int64(4), T.int64(3), T.int64(3)), "float32"), compute: T.Buffer((T.int64(2), T.int64(4), T.int64(30), T.int64(30)), "float16")):
            T.func_attr({"tirx.noalias": True})
            # with T.sblock("root"):
            data_dilate = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(28), T.int64(28)))
            data_pad = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(32), T.int64(32)))
            kernel_transform = T.sblock_alloc_buffer((T.int64(4), T.int64(3), T.int64(3), T.int64(3)))
            for i0, i1, i2, i3 in T.grid(T.int64(2), T.int64(3), T.int64(28), T.int64(28)):
                with T.sblock("data_dilate"):
                    v_i0, v_i1, v_i2, v_i3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
                    T.reads(rxplaceholder[v_i0, v_i1, v_i2, v_i3])
                    T.writes(data_dilate[v_i0, v_i1, v_i2, v_i3])
                    data_dilate[v_i0, v_i1, v_i2, v_i3] = rxplaceholder[v_i0, v_i1, v_i2, v_i3]
            for i0, i1, i2, i3 in T.grid(T.int64(2), T.int64(3), T.int64(32), T.int64(32)):
                with T.sblock("data_pad"):
                    v_i0, v_i1, v_i2, v_i3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
                    T.reads(data_dilate[v_i0, v_i1, v_i2 - T.int64(2), v_i3 - T.int64(2)])
                    T.writes(data_pad[v_i0, v_i1, v_i2, v_i3])
                    data_pad[v_i0, v_i1, v_i2, v_i3] = T.if_then_else(T.int64(2) <= v_i2 and v_i2 < T.int64(30) and T.int64(2) <= v_i3 and v_i3 < T.int64(30), data_dilate[v_i0, v_i1, v_i2 - T.int64(2), v_i3 - T.int64(2)], T.float32(0))
            for o, i, h, w in T.grid(T.int64(4), T.int64(3), T.int64(3), T.int64(3)):
                with T.sblock("kernel_transform"):
                    v_o, v_i, v_h, v_w = T.axis.remap("SSSS", [o, i, h, w])
                    T.reads(rxplaceholder_1[v_i, v_o, T.int64(2) - v_h, T.int64(2) - v_w])
                    T.writes(kernel_transform[v_o, v_i, v_h, v_w])
                    kernel_transform[v_o, v_i, v_h, v_w] = rxplaceholder_1[v_i, v_o, T.int64(2) - v_h, T.int64(2) - v_w]
            for b, c, h, w, dc, dh, dw in T.grid(T.int64(2), T.int64(4), T.int64(30), T.int64(30), T.int64(3), T.int64(3), T.int64(3)):
                with T.sblock("compute"):
                    v_b, v_c, v_h, v_w, v_dc, v_dh, v_dw = T.axis.remap("SSSSRRR", [b, c, h, w, dc, dh, dw])
                    T.reads(data_pad[v_b, v_dc, v_h + v_dh, v_w + v_dw], kernel_transform[v_c, v_dc, v_dh, v_dw])
                    T.writes(compute[v_b, v_c, v_h, v_w])
                    with T.init():
                        compute[v_b, v_c, v_h, v_w] = T.float16(0)
                    compute[v_b, v_c, v_h, v_w] = compute[v_b, v_c, v_h, v_w] + T.Cast("float16", data_pad[v_b, v_dc, v_h + v_dh, v_w + v_dw]) * T.Cast("float16", kernel_transform[v_c, v_dc, v_dh, v_dw])
    # fmt: on

    mod = LegalizeOps()(Conv2dTranspose)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_conv2d_transpose_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class Conv2dTranspose:
        @R.function
        def main(x: R.Tensor(("n", "c", "h", "w"), "float32"), kernel: R.Tensor(("f", "c", "kh", "kw"), "float32")):
            gv = R.nn.conv2d_transpose(x, kernel, strides=(3, 3))
            return gv

    @I.ir_module(s_tir=True)
    class Expected:
        @R.function
        def main(x: R.Tensor(("n", "c", "h", "w"), dtype="float32"), kernel: R.Tensor(("f", "c", "kh", "kw"), dtype="float32")) -> R.Tensor(("n", "c", "h * 3 + kh - 3", "w * 3 + kw - 3"), dtype="float32"):
            n = T.int64()
            c = T.int64()
            h = T.int64()
            kh = T.int64()
            w = T.int64()
            kw = T.int64()
            f = T.int64()
            gv = R.call_tir(Expected.conv2d_transpose, (x, kernel), out_ty=R.Tensor((n, c, h * 3 + kh - 3, w * 3 + kw - 3), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def conv2d_transpose(var_rxplaceholder: T.handle, var_rxplaceholder_1: T.handle, var_compute: T.handle):
            T.func_attr({"tirx.noalias": True})
            n = T.int64()
            c = T.int64()
            h = T.int64()
            w = T.int64()
            rxplaceholder = T.match_buffer(var_rxplaceholder, (n, c, h, w))
            f = T.int64()
            kh = T.int64()
            kw = T.int64()
            rxplaceholder_1 = T.match_buffer(var_rxplaceholder_1, (f, c, kh, kw))
            compute = T.match_buffer(var_compute, (n, c, h * T.int64(3) + kh - T.int64(3), w * T.int64(3) + kw - T.int64(3)))
            # with T.sblock("root"):
            data_dilate = T.sblock_alloc_buffer((n, c, h * T.int64(3) - T.int64(2), w * T.int64(3) - T.int64(2)))
            data_pad = T.sblock_alloc_buffer((n, c, h * T.int64(3) + kh * T.int64(2) - T.int64(4), w * T.int64(3) + kw * T.int64(2) - T.int64(4)))
            kernel_transform = T.sblock_alloc_buffer((c, c, kh, kw))
            for i0, i1, i2, i3 in T.grid(n, c, h * T.int64(3) - T.int64(2), w * T.int64(3) - T.int64(2)):
                with T.sblock("data_dilate"):
                    v_i0, v_i1, v_i2, v_i3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
                    T.reads(rxplaceholder[v_i0, v_i1, v_i2 // T.int64(3), v_i3 // T.int64(3)])
                    T.writes(data_dilate[v_i0, v_i1, v_i2, v_i3])
                    data_dilate[v_i0, v_i1, v_i2, v_i3] = T.if_then_else(v_i2 % T.int64(3) == T.int64(0) and v_i3 % T.int64(3) == T.int64(0), rxplaceholder[v_i0, v_i1, v_i2 // T.int64(3), v_i3 // T.int64(3)], T.float32(0))
            for i0, i1, i2, i3 in T.grid(n, c, h * T.int64(3) + kh * T.int64(2) - T.int64(4), w * T.int64(3) + kw * T.int64(2) - T.int64(4)):
                with T.sblock("data_pad"):
                    v_i0, v_i1, v_i2, v_i3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
                    T.reads(data_dilate[v_i0, v_i1, v_i2 + T.int64(1) - kh, v_i3 + T.int64(1) - kw])
                    T.writes(data_pad[v_i0, v_i1, v_i2, v_i3])
                    data_pad[v_i0, v_i1, v_i2, v_i3] = T.if_then_else(kh <= v_i2 + T.int64(1) and v_i2 + T.int64(3)< h * T.int64(3) + kh and kw <= v_i3 + T.int64(1) and v_i3 + T.int64(3) < w * T.int64(3) + kw , data_dilate[v_i0, v_i1, v_i2 + T.int64(1) - kh, v_i3 + T.int64(1) - kw], T.float32(0))
            for o, i, h_1, w_1 in T.grid(c, c, kh, kw):
                with T.sblock("kernel_transform"):
                    v_o, v_i, v_h, v_w = T.axis.remap("SSSS", [o, i, h_1, w_1])
                    T.reads(rxplaceholder_1[v_i, v_o, kh - v_h - T.int64(1), kw - v_w - T.int64(1)])
                    T.writes(kernel_transform[v_o, v_i, v_h, v_w])
                    kernel_transform[v_o, v_i, v_h, v_w] = rxplaceholder_1[v_i, v_o, kh - v_h - T.int64(1), kw - v_w - T.int64(1)]
            for b, c_1, h_1, w_1, dc, dh, dw in T.grid(n, c, h * T.int64(3) + kh - T.int64(3), w * T.int64(3) + kw - T.int64(3), c, kh, kw):
                with T.sblock("compute"):
                    v_b, v_c, v_h, v_w, v_dc, v_dh, v_dw = T.axis.remap("SSSSRRR", [b, c_1, h_1, w_1, dc, dh, dw])
                    T.reads(data_pad[v_b, v_dc, v_h + v_dh, v_w + v_dw], kernel_transform[v_c, v_dc, v_dh, v_dw])
                    T.writes(compute[v_b, v_c, v_h, v_w])
                    with T.init():
                        compute[v_b, v_c, v_h, v_w] = T.float32(0)
                    compute[v_b, v_c, v_h, v_w] = compute[v_b, v_c, v_h, v_w] + data_pad[v_b, v_dc, v_h + v_dh, v_w + v_dw] * kernel_transform[v_c, v_dc, v_dh, v_dw]
    # fmt: on

    mod = LegalizeOps()(Conv2dTranspose)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_conv2d_transpose_dilation():
    # fmt: off
    @tvm.script.ir_module
    class Conv2dTranspose:
        @R.function
        def main(x: R.Tensor((1, 1, 3, 3), "float32"), w: R.Tensor((1, 1, 2, 2), "float32")):
            gv = R.nn.conv2d_transpose(x, w, dilation=(2, 2))
            return gv

    @I.ir_module(s_tir=True)
    class Expected:
        @T.prim_func(private=True, s_tir=True)
        def conv2d_transpose(x: T.Buffer((T.int64(1), T.int64(1), T.int64(3), T.int64(3)), "float32"), w: T.Buffer((T.int64(1), T.int64(1), T.int64(2), T.int64(2)), "float32"), compute: T.Buffer((T.int64(1), T.int64(1), T.int64(5), T.int64(5)), "float32")):
            T.func_attr({"tirx.noalias": True})
            data_dilate = T.sblock_alloc_buffer((T.int64(1), T.int64(1), T.int64(3), T.int64(3)))
            data_pad = T.sblock_alloc_buffer((T.int64(1), T.int64(1), T.int64(7), T.int64(7)))
            kernel_dilate = T.sblock_alloc_buffer((T.int64(1), T.int64(1), T.int64(3), T.int64(3)))
            kernel_transform = T.sblock_alloc_buffer((T.int64(1), T.int64(1), T.int64(3), T.int64(3)))
            for i0, i1, i2, i3 in T.grid(T.int64(1), T.int64(1), T.int64(3), T.int64(3)):
                with T.sblock("data_dilate"):
                    v_i0, v_i1, v_i2, v_i3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
                    data_dilate[v_i0, v_i1, v_i2, v_i3] = x[v_i0, v_i1, v_i2, v_i3]
            for i0, i1, i2, i3 in T.grid(T.int64(1), T.int64(1), T.int64(7), T.int64(7)):
                with T.sblock("data_pad"):
                    v_i0, v_i1, v_i2, v_i3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
                    data_pad[v_i0, v_i1, v_i2, v_i3] = T.if_then_else(T.int64(2) <= v_i2 and v_i2 < T.int64(5) and T.int64(2) <= v_i3 and v_i3 < T.int64(5), data_dilate[v_i0, v_i1, v_i2 - T.int64(2), v_i3 - T.int64(2)], T.float32(0.0))
            for i0, i1, i2, i3 in T.grid(T.int64(1), T.int64(1), T.int64(3), T.int64(3)):
                with T.sblock("kernel_dilate"):
                    v_i0, v_i1, v_i2, v_i3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
                    kernel_dilate[v_i0, v_i1, v_i2, v_i3] = T.if_then_else(v_i2 % T.int64(2) == T.int64(0) and v_i3 % T.int64(2) == T.int64(0), w[v_i0, v_i1, v_i2 // T.int64(2), v_i3 // T.int64(2)], T.float32(0.0))
            for o, i, h, w_1 in T.grid(T.int64(1), T.int64(1), T.int64(3), T.int64(3)):
                with T.sblock("kernel_transform"):
                    v_o, v_i, v_h, v_w = T.axis.remap("SSSS", [o, i, h, w_1])
                    kernel_transform[v_o, v_i, v_h, v_w] = kernel_dilate[v_i, v_o, T.int64(2) - v_h, T.int64(2) - v_w]
            for b, c, h, w_1, dc, dh, dw in T.grid(T.int64(1), T.int64(1), T.int64(5), T.int64(5), T.int64(1), T.int64(3), T.int64(3)):
                with T.sblock("compute"):
                    v_b, v_c, v_h, v_w, v_dc, v_dh, v_dw = T.axis.remap("SSSSRRR", [b, c, h, w_1, dc, dh, dw])
                    with T.init():
                        compute[v_b, v_c, v_h, v_w] = T.float32(0.0)
                    compute[v_b, v_c, v_h, v_w] = compute[v_b, v_c, v_h, v_w] + data_pad[v_b, v_dc, v_h + v_dh, v_w + v_dw] * kernel_transform[v_c, v_dc, v_dh, v_dw]

        @R.function
        def main(x: R.Tensor((1, 1, 3, 3), dtype="float32"), w: R.Tensor((1, 1, 2, 2), dtype="float32")) -> R.Tensor((1, 1, 5, 5), dtype="float32"):
            cls = Expected
            gv = R.call_tir(cls.conv2d_transpose, (x, w), out_ty=R.Tensor((1, 1, 5, 5), dtype="float32"))
            return gv
    # fmt: on

    mod = LegalizeOps()(Conv2dTranspose)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_max_pool2d():
    # fmt: off
    @tvm.script.ir_module
    class MaxPool2D:
        @R.function
        def main(x: R.Tensor((4, 112, 112, 6), "float32")) -> R.Tensor((4, 56, 56, 6), "float32"):
            gv: R.Tensor((4, 56, 56, 6), "float32") = R.nn.max_pool2d(x, pool_size=[3, 3], strides=[2, 2], dilation=[1, 1], padding=[1, 1, 1, 1], layout="NHWC")
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((4, 112, 112, 6), "float32")) -> R.Tensor((4, 56, 56, 6), "float32"):
            gv = R.call_tir(Expected.max_pool2d, (x,), R.Tensor((4, 56, 56, 6), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def max_pool2d(rxplaceholder: T.Buffer((T.int64(4), T.int64(112), T.int64(112), T.int64(6)), "float32"), pool_max: T.Buffer((T.int64(4), T.int64(56), T.int64(56), T.int64(6)), "float32")):
            T.func_attr({"tirx.noalias": True})
            pad_temp = T.sblock_alloc_buffer([T.int64(4), T.int64(114), T.int64(114), T.int64(6)], dtype="float32")
            for i0, i1, i2, i3 in T.grid(T.int64(4), T.int64(114), T.int64(114), T.int64(6)):
                with T.sblock("pad_temp"):
                    ax0, ax1, ax2, ax3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
                    T.reads(rxplaceholder[ax0, ax1 - T.int64(1), ax2 - T.int64(1), ax3])
                    T.writes(pad_temp[ax0, ax1, ax2, ax3])
                    pad_temp[ax0, ax1, ax2, ax3] = T.if_then_else(T.int64(1) <= ax1 and ax1 < T.int64(113) and T.int64(1) <= ax2 and ax2 < T.int64(113), rxplaceholder[ax0, ax1 - T.int64(1), ax2 - T.int64(1), ax3], T.float32(-3.4028234663852886e+38), dtype="float32")
            for i0, i1, i2, i3, i4, i5 in T.grid(T.int64(4), T.int64(56), T.int64(56), T.int64(6), T.int64(3), T.int64(3)):
                with T.sblock("pool_max"):
                    ax0, ax1, ax2, ax3, rv0, rv1 = T.axis.remap("SSSSRR", [i0, i1, i2, i3, i4, i5])
                    T.reads(pad_temp[ax0, ax1 * T.int64(2) + rv0, ax2 * T.int64(2) + rv1, ax3])
                    T.writes(pool_max[ax0, ax1, ax2, ax3])
                    T.sblock_attr({"schedule_rule":"meta_schedule.pool_max"})
                    with T.init():
                        pool_max[ax0, ax1, ax2, ax3] = T.float32(-3.4028234663852886e+38)
                    pool_max[ax0, ax1, ax2, ax3] = T.max(pool_max[ax0, ax1, ax2, ax3], pad_temp[ax0, ax1 * T.int64(2) + rv0, ax2 * T.int64(2) + rv1, ax3])
    # fmt: on

    mod = LegalizeOps()(MaxPool2D)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_max_pool2d_NCHW16c():
    # fmt: off
    @tvm.script.ir_module
    class MaxPool2D:
        @R.function
        def main(x: R.Tensor((4, 4, 112, 112, 16), "float32")) -> R.Tensor((4, 4, 110, 110, 16), "float32"):
            gv: R.Tensor((4, 4, 110, 110, 16), "float32") = R.nn.max_pool2d(x, pool_size=[3, 3], layout="NCHW16c")
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((4, 4, 112, 112, 16), "float32")) -> R.Tensor((4, 4, 110, 110, 16), "float32"):
            gv = R.call_tir(Expected.max_pool2d, (x,), R.Tensor((4, 4, 110, 110, 16), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def max_pool2d(rxplaceholder: T.Buffer((T.int64(4), T.int64(4), T.int64(112), T.int64(112), T.int64(16)), "float32"), pool_max: T.Buffer((T.int64(4), T.int64(4), T.int64(110), T.int64(110), T.int64(16)), "float32")):
            T.func_attr({"tirx.noalias": True})
            for i0, i1, i2, i3, i4, i5, i6 in T.grid(T.int64(4), T.int64(4), T.int64(110), T.int64(110), T.int64(16), T.int64(3), T.int64(3)):
                with T.sblock("pool_max"):
                    ax0, ax1, ax2, ax3, ax4, rv0, rv1 = T.axis.remap("SSSSSRR", [i0, i1, i2, i3, i4, i5, i6])
                    T.reads(rxplaceholder[ax0, ax1, ax2 + rv0, ax3 + rv1, ax4])
                    T.writes(pool_max[ax0, ax1, ax2, ax3, ax4])
                    T.sblock_attr({"schedule_rule":"meta_schedule.pool_max"})
                    with T.init():
                        pool_max[ax0, ax1, ax2, ax3, ax4] = T.float32(-3.4028234663852886e+38)
                    pool_max[ax0, ax1, ax2, ax3, ax4] = T.max(pool_max[ax0, ax1, ax2, ax3, ax4], rxplaceholder[ax0, ax1, ax2 + rv0, ax3 + rv1, ax4])
    # fmt: on

    mod = LegalizeOps()(MaxPool2D)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_max_pool2d_ceil_mode():
    # fmt: off
    @tvm.script.ir_module
    class MaxPool2D:
        @R.function
        def main(x: R.Tensor((4, 6, 112, 112), "float32")) -> R.Tensor((4, 6, 38, 38), "float32"):
            gv: R.Tensor((4, 6, 38, 38), "float32") = R.nn.max_pool2d(x, pool_size=[3, 3], strides=[3, 3], dilation=[1, 1], padding=[1, 1, 1, 1], ceil_mode=True)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((4, 6, 112, 112), dtype="float32")) -> R.Tensor((4, 6, 38, 38), dtype="float32"):
            gv = R.call_tir(Expected.max_pool2d, (x,), R.Tensor((4, 6, 38, 38), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def max_pool2d(rxplaceholder: T.Buffer((T.int64(4), T.int64(6), T.int64(112), T.int64(112)), "float32"), pool_max: T.Buffer((T.int64(4), T.int64(6), T.int64(38), T.int64(38)), "float32")):
            T.func_attr({"tirx.noalias": True})
            pad_temp = T.sblock_alloc_buffer([T.int64(4), T.int64(6), T.int64(116), T.int64(116)], dtype="float32")
            for i0, i1, i2, i3 in T.grid(T.int64(4), T.int64(6), T.int64(116), T.int64(116)):
                with T.sblock("pad_temp"):
                    ax0, ax1, ax2, ax3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
                    T.reads(rxplaceholder[ax0, ax1, ax2 - T.int64(1), ax3 - T.int64(1)])
                    T.writes(pad_temp[ax0, ax1, ax2, ax3])
                    pad_temp[ax0, ax1, ax2, ax3] = T.if_then_else(T.int64(1) <= ax2 and ax2 < T.int64(113) and T.int64(1) <= ax3 and ax3 < T.int64(113), rxplaceholder[ax0, ax1, ax2 - T.int64(1), ax3 - T.int64(1)], T.float32(-3.4028234663852886e+38), dtype="float32")
            for i0, i1, i2, i3, i4, i5 in T.grid(T.int64(4), T.int64(6), T.int64(38), T.int64(38), T.int64(3), T.int64(3)):
                with T.sblock("pool_max"):
                    ax0, ax1, ax2, ax3, rv0, rv1 = T.axis.remap("SSSSRR", [i0, i1, i2, i3, i4, i5])
                    T.reads(pad_temp[ax0, ax1, ax2 * T.int64(3) + rv0, ax3 * T.int64(3) + rv1])
                    T.writes(pool_max[ax0, ax1, ax2, ax3])
                    T.sblock_attr({"schedule_rule":"meta_schedule.pool_max"})
                    with T.init():
                        pool_max[ax0, ax1, ax2, ax3] = T.float32(-3.4028234663852886e+38)
                    pool_max[ax0, ax1, ax2, ax3] = T.max(pool_max[ax0, ax1, ax2, ax3], pad_temp[ax0, ax1, ax2 * T.int64(3) + rv0, ax3 * T.int64(3) + rv1])
    # fmt: on

    mod = LegalizeOps()(MaxPool2D)
    tvm.ir.assert_structural_equal(mod, Expected)


@pytest.mark.skip("TOPI pooling casts every shape value to i32.")
def test_max_pool2d_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class MaxPool2D:
        @R.function
        def main(dumb_param: R.Tensor(("kh", "kw")), x: R.Tensor(("n", "c", "h", "w"), "float32")) -> R.Tensor(("n", "c", "h - kh + 1", "w - kw + 1"), "float32"):
            n = T.int64()
            c = T.int64()
            h = T.int64()
            w = T.int64()
            kh = T.int64()
            kw = T.int64()
            gv: R.Tensor((n, c, h - kh + 1, w - kw + 1), "float32") = R.nn.max_pool2d(x, pool_size=[kh, kw])
            return gv

    # fmt: on

    mod = LegalizeOps()(MaxPool2D)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_avg_pool2d():
    # fmt: off
    @tvm.script.ir_module
    class AvgPool2D:
        @R.function
        def main(x: R.Tensor((4, 112, 112, 6), "float32")) -> R.Tensor((4, 56, 56, 6), "float32"):
            gv: R.Tensor((4, 56, 56, 6), "float32") = R.nn.avg_pool2d(x, pool_size=[3, 3], strides=[2, 2], dilation=[1, 1], padding=[1, 1, 1, 1], layout="NHWC")
            return gv

    @I.ir_module(s_tir=True)
    class Expected:
        @T.prim_func(private=True, s_tir=True)
        def avg_pool2d(rxplaceholder: T.Buffer((T.int64(4), T.int64(112), T.int64(112), T.int64(6)), "float32"), pool_avg: T.Buffer((T.int64(4), T.int64(56), T.int64(56), T.int64(6)), "float32")):
            T.func_attr({"tirx.noalias": True})
            # with T.sblock("root"):
            pad_temp = T.sblock_alloc_buffer((T.int64(4), T.int64(114), T.int64(114), T.int64(6)))
            pool_sum = T.sblock_alloc_buffer((T.int64(4), T.int64(56), T.int64(56), T.int64(6)))
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(4), T.int64(114), T.int64(114), T.int64(6)):
                with T.sblock("pad_temp"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(rxplaceholder[v_ax0, v_ax1 - T.int64(1), v_ax2 - T.int64(1), v_ax3])
                    T.writes(pad_temp[v_ax0, v_ax1, v_ax2, v_ax3])
                    pad_temp[v_ax0, v_ax1, v_ax2, v_ax3] = T.if_then_else(T.int64(1) <= v_ax1 and v_ax1 < T.int64(113) and T.int64(1) <= v_ax2 and v_ax2 < T.int64(113), rxplaceholder[v_ax0, v_ax1 - T.int64(1), v_ax2 - T.int64(1), v_ax3], T.float32(0))
            for ax0, ax1, ax2, ax3, rv0, rv1 in T.grid(T.int64(4), T.int64(56), T.int64(56), T.int64(6), T.int64(3), T.int64(3)):
                with T.sblock("pool_sum"):
                    v_ax0, v_ax1, v_ax2, v_ax3, v_rv0, v_rv1 = T.axis.remap("SSSSRR", [ax0, ax1, ax2, ax3, rv0, rv1])
                    T.reads(pad_temp[v_ax0, v_ax1 * T.int64(2) + v_rv0, v_ax2 * T.int64(2) + v_rv1, v_ax3])
                    T.writes(pool_sum[v_ax0, v_ax1, v_ax2, v_ax3])
                    with T.init():
                        pool_sum[v_ax0, v_ax1, v_ax2, v_ax3] = T.float32(0)
                    pool_sum[v_ax0, v_ax1, v_ax2, v_ax3] = pool_sum[v_ax0, v_ax1, v_ax2, v_ax3] + pad_temp[v_ax0, v_ax1 * T.int64(2) + v_rv0, v_ax2 * T.int64(2) + v_rv1, v_ax3]
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(4), T.int64(56), T.int64(56), T.int64(6)):
                with T.sblock("pool_avg"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(pool_sum[v_ax0, v_ax1, v_ax2, v_ax3])
                    T.writes(pool_avg[v_ax0, v_ax1, v_ax2, v_ax3])
                    T.sblock_attr({"schedule_rule": "meta_schedule.pool_avg"})
                    pool_avg[v_ax0, v_ax1, v_ax2, v_ax3] = pool_sum[v_ax0, v_ax1, v_ax2, v_ax3] / T.Cast("float32", T.max((T.min(v_ax1 * T.int64(2) + T.int64(1), T.int64(111)) + T.int64(2) - T.max(T.int64(1) - v_ax1 * T.int64(2), T.int64(0)) - v_ax1 * T.int64(2)) * (T.min(v_ax2 * T.int64(2) + T.int64(1), T.int64(111)) + T.int64(2) - T.max(T.int64(1) - v_ax2 * T.int64(2), T.int64(0)) - v_ax2 * T.int64(2)), T.int64(1)))

        @R.function
        def main(x: R.Tensor((4, 112, 112, 6), dtype="float32")) -> R.Tensor((4, 56, 56, 6), dtype="float32"):
            gv = R.call_tir(Expected.avg_pool2d, (x,), out_ty=R.Tensor((4, 56, 56, 6), dtype="float32"))
            return gv
    # fmt: on

    mod = LegalizeOps()(AvgPool2D)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_avg_pool2d_NCHW16c():
    # fmt: off
    @tvm.script.ir_module
    class AvgPool2D:
        @R.function
        def main(x: R.Tensor((4, 4, 112, 112, 16), "float32")) -> R.Tensor((4, 4, 110, 110, 16), "float32"):
            gv: R.Tensor((4, 4, 110, 110, 16), "float32") = R.nn.avg_pool2d(x, pool_size=[3, 3], layout="NCHW16c")
            return gv

    @I.ir_module(s_tir=True)
    class Expected:
        @T.prim_func(private=True, s_tir=True)
        def avg_pool2d(rxplaceholder: T.Buffer((T.int64(4), T.int64(4), T.int64(112), T.int64(112), T.int64(16)), "float32"), pool_avg: T.Buffer((T.int64(4), T.int64(4), T.int64(110), T.int64(110), T.int64(16)), "float32")):
            T.func_attr({"tirx.noalias": True})
            # with T.sblock("root"):
            pool_sum = T.sblock_alloc_buffer((T.int64(4), T.int64(4), T.int64(110), T.int64(110), T.int64(16)))
            for ax0, ax1, ax2, ax3, ax4, rv0, rv1 in T.grid(T.int64(4), T.int64(4), T.int64(110), T.int64(110), T.int64(16), T.int64(3), T.int64(3)):
                with T.sblock("pool_sum"):
                    v_ax0, v_ax1, v_ax2, v_ax3, v_ax4, v_rv0, v_rv1 = T.axis.remap("SSSSSRR", [ax0, ax1, ax2, ax3, ax4, rv0, rv1])
                    T.reads(rxplaceholder[v_ax0, v_ax1, v_ax2 + v_rv0, v_ax3 + v_rv1, v_ax4])
                    T.writes(pool_sum[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4])
                    with T.init():
                        pool_sum[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4] = T.float32(0)
                    pool_sum[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4] = pool_sum[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4] + rxplaceholder[v_ax0, v_ax1, v_ax2 + v_rv0, v_ax3 + v_rv1, v_ax4]
            for ax0, ax1, ax2, ax3, ax4 in T.grid(T.int64(4), T.int64(4), T.int64(110), T.int64(110), T.int64(16)):
                with T.sblock("pool_avg"):
                    v_ax0, v_ax1, v_ax2, v_ax3, v_ax4 = T.axis.remap("SSSSS", [ax0, ax1, ax2, ax3, ax4])
                    T.reads(pool_sum[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4])
                    T.writes(pool_avg[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4])
                    T.sblock_attr({"schedule_rule": "meta_schedule.pool_avg"})
                    pool_avg[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4] = pool_sum[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4] / T.Cast("float32", T.max((T.min(T.int64(2), T.int64(111) - v_ax2) + T.int64(1) - T.max(T.int64(0) - v_ax2, T.int64(0))) * (T.min(T.int64(2), T.int64(111) - v_ax3) + T.int64(1) - T.max(T.int64(0) - v_ax3, T.int64(0))), T.int64(1)))
        @R.function
        def main(x: R.Tensor((4, 4, 112, 112, 16), dtype="float32")) -> R.Tensor((4, 4, 110, 110, 16), dtype="float32"):
            gv = R.call_tir(Expected.avg_pool2d, (x,), out_ty=R.Tensor((4, 4, 110, 110, 16), dtype="float32"))
            return gv
    # fmt: on

    mod = LegalizeOps()(AvgPool2D)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_avg_pool2d_ceil_mode():
    # fmt: off
    @tvm.script.ir_module
    class AvgPool2D:
        @R.function
        def main(x: R.Tensor((4, 6, 112, 112), "float32")) -> R.Tensor((4, 6, 38, 38), "float32"):
            gv: R.Tensor((4, 6, 38, 38), "float32") = R.nn.avg_pool2d(x, pool_size=[3, 3], strides=[3, 3], dilation=[1, 1], padding=[1, 1, 1, 1], ceil_mode=True)
            return gv

    @I.ir_module(s_tir=True)
    class Expected:
        @T.prim_func(private=True, s_tir=True)
        def avg_pool2d(rxplaceholder: T.Buffer((T.int64(4), T.int64(6), T.int64(112), T.int64(112)), "float32"), pool_avg: T.Buffer((T.int64(4), T.int64(6), T.int64(38), T.int64(38)), "float32")):
            T.func_attr({"tirx.noalias": True})
            # with T.sblock("root"):
            pad_temp = T.sblock_alloc_buffer((T.int64(4), T.int64(6), T.int64(116), T.int64(116)))
            pool_sum = T.sblock_alloc_buffer((T.int64(4), T.int64(6), T.int64(38), T.int64(38)))
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(4), T.int64(6), T.int64(116), T.int64(116)):
                with T.sblock("pad_temp"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(rxplaceholder[v_ax0, v_ax1, v_ax2 - T.int64(1), v_ax3 - T.int64(1)])
                    T.writes(pad_temp[v_ax0, v_ax1, v_ax2, v_ax3])
                    pad_temp[v_ax0, v_ax1, v_ax2, v_ax3] = T.if_then_else(T.int64(1) <= v_ax2 and v_ax2 < T.int64(113) and T.int64(1) <= v_ax3 and v_ax3 < T.int64(113), rxplaceholder[v_ax0, v_ax1, v_ax2 - T.int64(1), v_ax3 - T.int64(1)], T.float32(0))
            for ax0, ax1, ax2, ax3, rv0, rv1 in T.grid(T.int64(4), T.int64(6), T.int64(38), T.int64(38), T.int64(3), T.int64(3)):
                with T.sblock("pool_sum"):
                    v_ax0, v_ax1, v_ax2, v_ax3, v_rv0, v_rv1 = T.axis.remap("SSSSRR", [ax0, ax1, ax2, ax3, rv0, rv1])
                    T.reads(pad_temp[v_ax0, v_ax1, v_ax2 * T.int64(3) + v_rv0, v_ax3 * T.int64(3) + v_rv1])
                    T.writes(pool_sum[v_ax0, v_ax1, v_ax2, v_ax3])
                    with T.init():
                        pool_sum[v_ax0, v_ax1, v_ax2, v_ax3] = T.float32(0)
                    pool_sum[v_ax0, v_ax1, v_ax2, v_ax3] = pool_sum[v_ax0, v_ax1, v_ax2, v_ax3] + pad_temp[v_ax0, v_ax1, v_ax2 * T.int64(3) + v_rv0, v_ax3 * T.int64(3) + v_rv1]
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(4), T.int64(6), T.int64(38), T.int64(38)):
                with T.sblock("pool_avg"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(pool_sum[v_ax0, v_ax1, v_ax2, v_ax3])
                    T.writes(pool_avg[v_ax0, v_ax1, v_ax2, v_ax3])
                    T.sblock_attr({"schedule_rule": "meta_schedule.pool_avg"})
                    pool_avg[v_ax0, v_ax1, v_ax2, v_ax3] = pool_sum[v_ax0, v_ax1, v_ax2, v_ax3] / T.Cast("float32", T.max((T.min(v_ax2 * T.int64(3) + T.int64(1), T.int64(111)) + T.int64(2) - T.max(T.int64(1) - v_ax2 * T.int64(3), T.int64(0)) - v_ax2 * T.int64(3)) * (T.min(v_ax3 * T.int64(3) + T.int64(1), T.int64(111)) + T.int64(2) - T.max(T.int64(1) - v_ax3 * T.int64(3), T.int64(0)) - v_ax3 * T.int64(3)), T.int64(1)))

        @R.function
        def main(x: R.Tensor((4, 6, 112, 112), dtype="float32")) -> R.Tensor((4, 6, 38, 38), dtype="float32"):
            gv = R.call_tir(Expected.avg_pool2d, (x,), out_ty=R.Tensor((4, 6, 38, 38), dtype="float32"))
            return gv

    # fmt: on

    mod = LegalizeOps()(AvgPool2D)
    tvm.ir.assert_structural_equal(mod, Expected)


@pytest.mark.skip("TOPI pooling casts every shape value to i32.")
def test_avg_pool2d_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class AvgPool2D:
        @R.function
        def main(dumb_param: R.Tensor(("kh", "kw")), x: R.Tensor(("n", "c", "h", "w"), "float32")) -> R.Tensor(("n", "c", "h - kh + 1", "w - kw + 1"), "float32"):
            n = T.int64()
            c = T.int64()
            h = T.int64()
            w = T.int64()
            kh = T.int64()
            kw = T.int64()
            gv: R.Tensor((n, c, h - kh + 1, w - kw + 1), "float32") = R.nn.avg_pool2d(x, pool_size=[kh, kw])
            return gv

    # fmt: on

    mod = LegalizeOps()(AvgPool2D)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_adaptive_avg_pool2d():
    # fmt: off
    @tvm.script.ir_module
    class AdaptiveAvgPool2D:
        @R.function
        def main(x: R.Tensor((2, 4, 7, 7, 16), "float32")) -> R.Tensor((2, 4, 1, 1, 16), "float32"):
            gv: R.Tensor((2, 4, 1, 1, 16), "float32") = R.nn.adaptive_avg_pool2d(x, output_size=[1, 1], layout="NCHW16c")
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 4, 7, 7, 16), "float32")) -> R.Tensor((2, 4, 1, 1, 16), "float32"):
            gv = R.call_tir(Expected.adaptive_avg_pool2d, (x,), R.Tensor((2, 4, 1, 1, 16), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def adaptive_avg_pool2d(rxplaceholder: T.Buffer((T.int64(2), T.int64(4), T.int64(7), T.int64(7), T.int64(16)), "float32"), adaptive_pool_avg: T.Buffer((T.int64(2), T.int64(4), T.int64(1), T.int64(1), T.int64(16)), "float32")):
            T.func_attr({"tirx.noalias": True})
            adaptive_pool_sum = T.sblock_alloc_buffer([T.int64(2), T.int64(4), T.int64(1), T.int64(1), T.int64(16)], dtype="float32")
            for i0, i1, i2, i3, i4, i5, i6 in T.grid(T.int64(2), T.int64(4), T.int64(1), T.int64(1), T.int64(16), T.int64(7), T.int64(7)):
                with T.sblock("adaptive_pool_sum"):
                    ax0, ax1, ax2, ax3, ax4, rv0, rv1 = T.axis.remap("SSSSSRR", [i0, i1, i2, i3, i4, i5, i6])
                    T.reads(rxplaceholder[ax0, ax1, ax2 * T.int64(7) + rv0, ax3 * T.int64(7) + rv1, ax4])
                    T.writes(adaptive_pool_sum[ax0, ax1, ax2, ax3, ax4])
                    with T.init():
                        adaptive_pool_sum[ax0, ax1, ax2, ax3, ax4] = T.float32(0)
                    adaptive_pool_sum[ax0, ax1, ax2, ax3, ax4] = adaptive_pool_sum[ax0, ax1, ax2, ax3, ax4] + rxplaceholder[ax0, ax1, ax2 * T.int64(7) + rv0, ax3 * T.int64(7) + rv1, ax4]
            for i0, i1, i2, i3, i4 in T.grid(T.int64(2), T.int64(4), T.int64(1), T.int64(1), T.int64(16)):
                with T.sblock("adaptive_pool_avg"):
                    ax0, ax1, ax2, ax3, ax4 = T.axis.remap("SSSSS", [i0, i1, i2, i3, i4])
                    T.reads(adaptive_pool_sum[ax0, ax1, ax2, ax3, ax4])
                    T.writes(adaptive_pool_avg[ax0, ax1, ax2, ax3, ax4])
                    T.sblock_attr({"schedule_rule":"meta_schedule.adaptive_pool_avg"})
                    adaptive_pool_avg[ax0, ax1, ax2, ax3, ax4] = adaptive_pool_sum[ax0, ax1, ax2, ax3, ax4] / T.float32(49.0)
    # fmt: on

    mod = LegalizeOps()(AdaptiveAvgPool2D)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_adaptive_avg_pool2d_without_output_size():
    # fmt: off
    @tvm.script.ir_module
    class AdaptiveAvgPool2D:
        @R.function
        def main(x: R.Tensor((2, 16, 7, 7), "float32")) -> R.Tensor((2, 16, 7, 7), "float32"):
            gv: R.Tensor((2, 16, 7, 7), "float32") = R.nn.adaptive_avg_pool2d(x)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 16, 7, 7), "float32")) -> R.Tensor((2, 16, 7, 7), "float32"):
            gv = R.call_tir(Expected.adaptive_avg_pool2d, (x,), R.Tensor((2, 16, 7, 7), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def adaptive_avg_pool2d(rxplaceholder: T.Buffer((T.int64(2), T.int64(16), T.int64(7), T.int64(7)), "float32"), adaptive_pool_avg: T.Buffer((T.int64(2), T.int64(16), T.int64(7), T.int64(7)), "float32")):
            T.func_attr({"tirx.noalias": True})
            adaptive_pool_sum = T.sblock_alloc_buffer([T.int64(2), T.int64(16), T.int64(7), T.int64(7)], dtype="float32")
            for i0, i1, i2, i3, i4, i5 in T.grid(T.int64(2), T.int64(16), T.int64(7), T.int64(7), T.int64(1), T.int64(1)):
                with T.sblock("adaptive_pool_sum"):
                    ax0, ax1, ax2, ax3, rv0, rv1 = T.axis.remap("SSSSRR", [i0, i1, i2, i3, i4, i5])
                    T.reads(rxplaceholder[ax0, ax1, ax2 + rv0, ax3 + rv1])
                    T.writes(adaptive_pool_sum[ax0, ax1, ax2, ax3])
                    with T.init():
                        adaptive_pool_sum[ax0, ax1, ax2, ax3] = T.float32(0)
                    adaptive_pool_sum[ax0, ax1, ax2, ax3] = adaptive_pool_sum[ax0, ax1, ax2, ax3] + rxplaceholder[ax0, ax1, ax2 + rv0, ax3 + rv1]
            for i0, i1, i2, i3 in T.grid(T.int64(2), T.int64(16), T.int64(7), T.int64(7)):
                with T.sblock("adaptive_pool_avg"):
                    ax0, ax1, ax2, ax3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
                    T.reads(adaptive_pool_sum[ax0, ax1, ax2, ax3])
                    T.writes(adaptive_pool_avg[ax0, ax1, ax2, ax3])
                    T.sblock_attr({"schedule_rule":"meta_schedule.adaptive_pool_avg"})
                    adaptive_pool_avg[ax0, ax1, ax2, ax3] = adaptive_pool_sum[ax0, ax1, ax2, ax3]
    # fmt: on

    mod = LegalizeOps()(AdaptiveAvgPool2D)
    tvm.ir.assert_structural_equal(mod, Expected)


@pytest.mark.skip("TOPI pooling casts every shape value to i32.")
def test_adaptive_avg_pool2d_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class AdaptiveAvgPool2D:
        @R.function
        def main(dumb_param: R.Tensor(("oh", "ow")), x: R.Tensor(("n", "c", "h", "w"), "float32")) -> R.Tensor(("n", "c", "oh", "ow"), "float32"):
            n = T.int64()
            c = T.int64()
            oh = T.int64()
            ow = T.int64()
            gv: R.Tensor((n, c, oh, ow), "float32") = R.nn.adaptive_avg_pool2d(x, (oh, ow))
            return gv
    # fmt: on

    mod = LegalizeOps()(AdaptiveAvgPool2D)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_relu():
    # fmt: off
    @tvm.script.ir_module
    class Relu:
        @R.function
        def main(x: R.Tensor((2, 3), "float32")) -> R.Tensor((2, 3), "float32"):
            gv: R.Tensor((2, 3), "float32") = R.nn.relu(x)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 3), "float32")) -> R.Tensor((2, 3), "float32"):
            gv = R.call_tir(Expected.relu, (x,), R.Tensor((2, 3), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def relu(rxplaceholder: T.Buffer((T.int64(2), T.int64(3)), "float32"), compute: T.Buffer((T.int64(2), T.int64(3)), "float32")):
            T.func_attr({"tirx.noalias": True})
            for i0, i1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("compute"):
                    i0_1, i1_1 = T.axis.remap("SS", [i0, i1])
                    T.reads(rxplaceholder[i0_1, i1_1])
                    T.writes(compute[i0_1, i1_1])
                    compute[i0_1, i1_1] = T.max(rxplaceholder[i0_1, i1_1], T.float32(0))
    # fmt: on

    mod = LegalizeOps()(Relu)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_relu_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class Relu:
        @R.function
        def main(x: R.Tensor(("m", "n"), "float32")) -> R.Tensor(("m", "n"), "float32"):
            m = T.int64()
            n = T.int64()
            gv: R.Tensor((m, n), "float32") = R.nn.relu(x)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor(("m", "n"), "float32")) -> R.Tensor(("m", "n"), "float32"):
            m = T.int64()
            n = T.int64()
            gv = R.call_tir(Expected.relu, (x,), R.Tensor((m, n), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def relu(var_rxplaceholder: T.handle, var_compute: T.handle):
            T.func_attr({"tirx.noalias": True})
            m = T.int64()
            n = T.int64()
            rxplaceholder = T.match_buffer(var_rxplaceholder, [m, n], dtype="float32")
            compute = T.match_buffer(var_compute, [m, n], dtype="float32")
            for i0, i1 in T.grid(m, n):
                with T.sblock("compute"):
                    i0_1, i1_1 = T.axis.remap("SS", [i0, i1])
                    T.reads(rxplaceholder[i0_1, i1_1])
                    T.writes(compute[i0_1, i1_1])
                    compute[i0_1, i1_1] = T.max(rxplaceholder[i0_1, i1_1], T.float32(0))
    # fmt: on

    mod = LegalizeOps()(Relu)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_leakyrelu():
    # fmt: off
    @tvm.script.ir_module
    class LeakyRelu:
        @R.function
        def main(x: R.Tensor((2, 3), "float32")) -> R.Tensor((2, 3), "float32"):
            gv: R.Tensor((2, 3), "float32") = R.nn.leakyrelu(x, 0.02)
            return gv


    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 3), "float32")) -> R.Tensor((2, 3), "float32"):
            gv = R.call_tir(Expected.leaky_relu, (x,), R.Tensor((2, 3), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def leaky_relu(x: T.Buffer((T.int64(2), T.int64(3)), "float32"), compute: T.Buffer((T.int64(2), T.int64(3)), "float32")):
            T.func_attr({"tirx.noalias": True})
            for i0, i1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("compute"):
                    v_i0, v_i1 = T.axis.remap("SS", [i0, i1])
                    T.reads(x[v_i0, v_i1])
                    T.writes(compute[v_i0, v_i1])
                    compute[v_i0, v_i1] = T.Select(T.float32(0.0) < x[v_i0, v_i1], x[v_i0, v_i1], x[v_i0, v_i1] * T.float32(0.02))
    # fmt: on

    mod = LegalizeOps()(LeakyRelu)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_leakyrelu_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class LeakyRelu:
        @R.function
        def main(x: R.Tensor(("m", "n"), "float32")) -> R.Tensor(("m", "n"), "float32"):
            m = T.int64()
            n = T.int64()
            gv: R.Tensor((m, n), "float32") = R.nn.leakyrelu(x, 0.03)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor(("m", "n"), "float32")) -> R.Tensor(("m", "n"), "float32"):
            m = T.int64()
            n = T.int64()
            gv = R.call_tir(Expected.leaky_relu, (x, ), R.Tensor((m, n), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def leaky_relu(var_x: T.handle, var_compute: T.handle):
            T.func_attr({"tirx.noalias": True})
            m, n = T.int64(), T.int64()
            x = T.match_buffer(var_x, (m, n))
            compute = T.match_buffer(var_compute, (m, n))
            for i0, i1 in T.grid(m, n):
                with T.sblock("compute"):
                    v_i0, v_i1 = T.axis.remap("SS", [i0, i1])
                    T.reads(x[v_i0, v_i1])
                    T.writes(compute[v_i0, v_i1])
                    compute[v_i0, v_i1] = T.Select(T.float32(0.0) < x[v_i0, v_i1], x[v_i0, v_i1], x[v_i0, v_i1] * T.float32(0.029999999999999999))
    # fmt: on

    mod = LegalizeOps()(LeakyRelu)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_prelu():
    # fmt: off
    @tvm.script.ir_module
    class PRelu:
        @R.function
        def main(x: R.Tensor((2, 3), "float32"), y: R.Tensor((1,), "float32")) -> R.Tensor((2, 3), "float32"):
            gv: R.Tensor((2, 3), "float32") = R.nn.prelu(x, y)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 3), dtype="float32"), y: R.Tensor((1,), dtype="float32")) -> R.Tensor((2, 3), dtype="float32"):
            gv = R.call_tir(Expected.prelu, (x, y), out_ty=R.Tensor((2, 3), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def prelu(x: T.Buffer((T.int64(2), T.int64(3)), "float32"), y: T.Buffer((T.int64(1),), "float32"), compute: T.Buffer((T.int64(2), T.int64(3)), "float32")):
            T.func_attr({"tirx.noalias": True})
            # with T.sblock("root"):
            slope_broadcasted = T.sblock_alloc_buffer((T.int64(3),))
            for c in range(T.int64(3)):
                with T.sblock("slope_broadcasted"):
                    v_c = T.axis.spatial(T.int64(3), c)
                    T.reads(y[T.int64(0)])
                    T.writes(slope_broadcasted[v_c])
                    slope_broadcasted[v_c] = y[T.int64(0)]
            for i0, i1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("compute"):
                    v_i0, v_i1 = T.axis.remap("SS", [i0, i1])
                    T.reads(x[v_i0, v_i1], slope_broadcasted[v_i1])
                    T.writes(compute[v_i0, v_i1])
                    compute[v_i0, v_i1] = T.Select(T.float32(0.0) < x[v_i0, v_i1], x[v_i0, v_i1], x[v_i0, v_i1] * slope_broadcasted[v_i1])
    # fmt: on

    mod = LegalizeOps()(PRelu)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_prelu_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class PRelu:
        @R.function
        def main(x: R.Tensor(("m", 7), "float32"), y: R.Tensor((1,), "float32")) -> R.Tensor(("m", 7), "float32"):
            m = T.int64()
            gv: R.Tensor((m, 7), "float32") = R.nn.prelu(x, y)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor(("m", 7), dtype="float32"), y: R.Tensor((1,), dtype="float32")) -> R.Tensor(("m", 7), dtype="float32"):
            m = T.int64()
            gv = R.call_tir(Expected.prelu, (x, y), out_ty=R.Tensor((m, 7), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def prelu(var_x: T.handle, y: T.Buffer((T.int64(1),), "float32"), var_compute: T.handle):
            T.func_attr({"tirx.noalias": True})
            m = T.int64()
            x = T.match_buffer(var_x, (m, T.int64(7)))
            compute = T.match_buffer(var_compute, (m, T.int64(7)))
            # with T.sblock("root"):
            slope_broadcasted = T.sblock_alloc_buffer((T.int64(7),))
            for c in range(T.int64(7)):
                with T.sblock("slope_broadcasted"):
                    v_c = T.axis.spatial(T.int64(7), c)
                    T.reads(y[T.int64(0)])
                    T.writes(slope_broadcasted[v_c])
                    slope_broadcasted[v_c] = y[T.int64(0)]
            for i0, i1 in T.grid(m, T.int64(7)):
                with T.sblock("compute"):
                    v_i0, v_i1 = T.axis.remap("SS", [i0, i1])
                    T.reads(x[v_i0, v_i1], slope_broadcasted[v_i1])
                    T.writes(compute[v_i0, v_i1])
                    compute[v_i0, v_i1] = T.Select(T.float32(0.0) < x[v_i0, v_i1], x[v_i0, v_i1], x[v_i0, v_i1] * slope_broadcasted[v_i1])
    # fmt: on

    mod = LegalizeOps()(PRelu)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_gelu():
    # fmt: off
    @tvm.script.ir_module
    class Gelu:
        @R.function
        def main(x: R.Tensor((2, 3), "float32")) -> R.Tensor((2, 3), "float32"):
            gv: R.Tensor((2, 3), "float32") = R.nn.gelu(x)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 3), "float32")) -> R.Tensor((2, 3), "float32"):
            gv = R.call_tir(Expected.gelu, (x,), R.Tensor((2, 3), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def gelu(x: T.Buffer((T.int64(2), T.int64(3)), "float32"), T_multiply: T.Buffer((T.int64(2), T.int64(3)), "float32")):
            T.func_attr({"tirx.noalias": True})
            T_multiply_1 = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            compute = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            T_multiply_2 = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            T_add = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            for ax0, ax1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("T_multiply"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(x[v_ax0, v_ax1])
                    T.writes(T_multiply_1[v_ax0, v_ax1])
                    T_multiply_1[v_ax0, v_ax1] = x[v_ax0, v_ax1] * T.float32(0.70710678118654757)
            for i0, i1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("compute"):
                    v_i0, v_i1 = T.axis.remap("SS", [i0, i1])
                    T.reads(T_multiply_1[v_i0, v_i1])
                    T.writes(compute[v_i0, v_i1])
                    compute[v_i0, v_i1] = T.erf(T_multiply_1[v_i0, v_i1])
            for ax0, ax1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("T_multiply_1"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(compute[v_ax0, v_ax1])
                    T.writes(T_multiply_2[v_ax0, v_ax1])
                    T_multiply_2[v_ax0, v_ax1] = compute[v_ax0, v_ax1] * T.float32(0.5)
            for ax0, ax1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("T_add"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(T_multiply_2[v_ax0, v_ax1])
                    T.writes(T_add[v_ax0, v_ax1])
                    T_add[v_ax0, v_ax1] = T.float32(0.5) + T_multiply_2[v_ax0, v_ax1]
            for ax0, ax1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("T_multiply_2"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(x[v_ax0, v_ax1], T_add[v_ax0, v_ax1])
                    T.writes(T_multiply[v_ax0, v_ax1])
                    T_multiply[v_ax0, v_ax1] = x[v_ax0, v_ax1] * T_add[v_ax0, v_ax1]
    # fmt: on

    mod = LegalizeOps()(Gelu)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_gelu_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class Gelu:
        @R.function
        def main(x: R.Tensor(("m", "n"), "float32")) -> R.Tensor(("m", "n"), "float32"):
            m = T.int64()
            n = T.int64()
            gv: R.Tensor((m, n), "float32") = R.nn.gelu(x)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor(("m", "n"), "float32")) -> R.Tensor(("m", "n"), "float32"):
            m = T.int64()
            n = T.int64()
            gv = R.call_tir(Expected.gelu, (x,), R.Tensor((m, n), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def gelu(var_x: T.handle, var_T_multiply: T.handle):
            T.func_attr({"tirx.noalias": True})
            m, n = T.int64(), T.int64()
            x = T.match_buffer(var_x, (m, n))
            T_multiply = T.match_buffer(var_T_multiply, (m, n))
            T_multiply_1 = T.sblock_alloc_buffer((m, n))
            compute = T.sblock_alloc_buffer((m, n))
            T_multiply_2 = T.sblock_alloc_buffer((m, n))
            T_add = T.sblock_alloc_buffer((m, n))
            for ax0, ax1 in T.grid(m, n):
                with T.sblock("T_multiply"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(x[v_ax0, v_ax1])
                    T.writes(T_multiply_1[v_ax0, v_ax1])
                    T_multiply_1[v_ax0, v_ax1] = x[v_ax0, v_ax1] * T.float32(0.70710678118654757)
            for i0, i1 in T.grid(m, n):
                with T.sblock("compute"):
                    v_i0, v_i1 = T.axis.remap("SS", [i0, i1])
                    T.reads(T_multiply_1[v_i0, v_i1])
                    T.writes(compute[v_i0, v_i1])
                    compute[v_i0, v_i1] = T.erf(T_multiply_1[v_i0, v_i1])
            for ax0, ax1 in T.grid(m, n):
                with T.sblock("T_multiply_1"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(compute[v_ax0, v_ax1])
                    T.writes(T_multiply_2[v_ax0, v_ax1])
                    T_multiply_2[v_ax0, v_ax1] = compute[v_ax0, v_ax1] * T.float32(0.5)
            for ax0, ax1 in T.grid(m, n):
                with T.sblock("T_add"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(T_multiply_2[v_ax0, v_ax1])
                    T.writes(T_add[v_ax0, v_ax1])
                    T_add[v_ax0, v_ax1] = T.float32(0.5) + T_multiply_2[v_ax0, v_ax1]
            for ax0, ax1 in T.grid(m, n):
                with T.sblock("T_multiply_2"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(x[v_ax0, v_ax1], T_add[v_ax0, v_ax1])
                    T.writes(T_multiply[v_ax0, v_ax1])
                    T_multiply[v_ax0, v_ax1] = x[v_ax0, v_ax1] * T_add[v_ax0, v_ax1]
    # fmt: on

    mod = LegalizeOps()(Gelu)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_gelu_tanh():
    # fmt: off
    @tvm.script.ir_module
    class GeluTanh:
        @R.function
        def main(x: R.Tensor((2, 3), "float32")) -> R.Tensor((2, 3), "float32"):
            gv: R.Tensor((2, 3), "float32") = R.nn.gelu_tanh(x)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 3), dtype="float32")) -> R.Tensor((2, 3), dtype="float32"):
            gv = R.call_tir(Expected.gelu_tanh, (x,), out_ty=R.Tensor((2, 3), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def gelu_tanh(A: T.Buffer((T.int64(2), T.int64(3)), "float32"), T_multiply: T.Buffer((T.int64(2), T.int64(3)), "float32")):
            T.func_attr({"tirx.noalias": True})
            T_multiply_1 = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            T_multiply_2 = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            T_multiply_3 = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            T_multiply_4 = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            T_add = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            T_multiply_5 = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            compute = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            T_add_1 = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            for ax0, ax1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("T_multiply"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(A[v_ax0, v_ax1])
                    T.writes(T_multiply_1[v_ax0, v_ax1])
                    T_multiply_1[v_ax0, v_ax1] = T.float32(0.5) * A[v_ax0, v_ax1]
            for ax0, ax1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("T_multiply_1"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(A[v_ax0, v_ax1])
                    T.writes(T_multiply_2[v_ax0, v_ax1])
                    T_multiply_2[v_ax0, v_ax1] = T.float32(0.79788456080286541) * A[v_ax0, v_ax1]
            for ax0, ax1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("T_multiply_2"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(A[v_ax0, v_ax1])
                    T.writes(T_multiply_3[v_ax0, v_ax1])
                    T_multiply_3[v_ax0, v_ax1] = T.float32(0.044714999999999998) * A[v_ax0, v_ax1]
            for ax0, ax1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("T_multiply_3"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(T_multiply_3[v_ax0, v_ax1], A[v_ax0, v_ax1])
                    T.writes(T_multiply_4[v_ax0, v_ax1])
                    T_multiply_4[v_ax0, v_ax1] = T_multiply_3[v_ax0, v_ax1] * A[v_ax0, v_ax1]
            for ax0, ax1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("T_add"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(T_multiply_4[v_ax0, v_ax1])
                    T.writes(T_add[v_ax0, v_ax1])
                    T_add[v_ax0, v_ax1] = T.float32(1) + T_multiply_4[v_ax0, v_ax1]
            for ax0, ax1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("T_multiply_4"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(T_multiply_2[v_ax0, v_ax1], T_add[v_ax0, v_ax1])
                    T.writes(T_multiply_5[v_ax0, v_ax1])
                    T_multiply_5[v_ax0, v_ax1] = T_multiply_2[v_ax0, v_ax1] * T_add[v_ax0, v_ax1]
            for i0, i1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("compute"):
                    v_i0, v_i1 = T.axis.remap("SS", [i0, i1])
                    T.reads(T_multiply_5[v_i0, v_i1])
                    T.writes(compute[v_i0, v_i1])
                    compute[v_i0, v_i1] = T.tanh(T_multiply_5[v_i0, v_i1])
            for ax0, ax1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("T_add_1"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(compute[v_ax0, v_ax1])
                    T.writes(T_add_1[v_ax0, v_ax1])
                    T_add_1[v_ax0, v_ax1] = T.float32(1) + compute[v_ax0, v_ax1]
            for ax0, ax1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("T_multiply_5"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(T_multiply_1[v_ax0, v_ax1], T_add_1[v_ax0, v_ax1])
                    T.writes(T_multiply[v_ax0, v_ax1])
                    T_multiply[v_ax0, v_ax1] = T_multiply_1[v_ax0, v_ax1] * T_add_1[v_ax0, v_ax1]

    mod = LegalizeOps()(GeluTanh)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_gelu_tanh_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class GeluTanh:
        @R.function
        def main(x: R.Tensor(("m", "n"), "float32")) -> R.Tensor(("m", "n"), "float32"):
            m = T.int64()
            n = T.int64()
            gv: R.Tensor((m, n), "float32") = R.nn.gelu_tanh(x)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor(("m", "n"), dtype="float32")) -> R.Tensor(("m", "n"), dtype="float32"):
            m = T.int64()
            n = T.int64()
            gv = R.call_tir(Expected.gelu_tanh, (x,), out_ty=R.Tensor((m, n), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def gelu_tanh(var_A: T.handle, var_T_multiply: T.handle):
            T.func_attr({"tirx.noalias": True})
            m, n = T.int64(), T.int64()
            A = T.match_buffer(var_A, (m, n))
            T_multiply = T.match_buffer(var_T_multiply, (m, n))
            # with T.sblock("root"):
            T_multiply_1 = T.sblock_alloc_buffer((m, n))
            T_multiply_2 = T.sblock_alloc_buffer((m, n))
            T_multiply_3 = T.sblock_alloc_buffer((m, n))
            T_multiply_4 = T.sblock_alloc_buffer((m, n))
            T_add = T.sblock_alloc_buffer((m, n))
            T_multiply_5 = T.sblock_alloc_buffer((m, n))
            compute = T.sblock_alloc_buffer((m, n))
            T_add_1 = T.sblock_alloc_buffer((m, n))
            for ax0, ax1 in T.grid(m, n):
                with T.sblock("T_multiply"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(A[v_ax0, v_ax1])
                    T.writes(T_multiply_1[v_ax0, v_ax1])
                    T_multiply_1[v_ax0, v_ax1] = T.float32(0.5) * A[v_ax0, v_ax1]
            for ax0, ax1 in T.grid(m, n):
                with T.sblock("T_multiply_1"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(A[v_ax0, v_ax1])
                    T.writes(T_multiply_2[v_ax0, v_ax1])
                    T_multiply_2[v_ax0, v_ax1] = T.float32(0.79788456080286541) * A[v_ax0, v_ax1]
            for ax0, ax1 in T.grid(m, n):
                with T.sblock("T_multiply_2"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(A[v_ax0, v_ax1])
                    T.writes(T_multiply_3[v_ax0, v_ax1])
                    T_multiply_3[v_ax0, v_ax1] = T.float32(0.044714999999999998) * A[v_ax0, v_ax1]
            for ax0, ax1 in T.grid(m, n):
                with T.sblock("T_multiply_3"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(T_multiply_3[v_ax0, v_ax1], A[v_ax0, v_ax1])
                    T.writes(T_multiply_4[v_ax0, v_ax1])
                    T_multiply_4[v_ax0, v_ax1] = T_multiply_3[v_ax0, v_ax1] * A[v_ax0, v_ax1]
            for ax0, ax1 in T.grid(m, n):
                with T.sblock("T_add"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(T_multiply_4[v_ax0, v_ax1])
                    T.writes(T_add[v_ax0, v_ax1])
                    T_add[v_ax0, v_ax1] = T.float32(1) + T_multiply_4[v_ax0, v_ax1]
            for ax0, ax1 in T.grid(m, n):
                with T.sblock("T_multiply_4"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(T_multiply_2[v_ax0, v_ax1], T_add[v_ax0, v_ax1])
                    T.writes(T_multiply_5[v_ax0, v_ax1])
                    T_multiply_5[v_ax0, v_ax1] = T_multiply_2[v_ax0, v_ax1] * T_add[v_ax0, v_ax1]
            for i0, i1 in T.grid(m, n):
                with T.sblock("compute"):
                    v_i0, v_i1 = T.axis.remap("SS", [i0, i1])
                    T.reads(T_multiply_5[v_i0, v_i1])
                    T.writes(compute[v_i0, v_i1])
                    compute[v_i0, v_i1] = T.tanh(T_multiply_5[v_i0, v_i1])
            for ax0, ax1 in T.grid(m, n):
                with T.sblock("T_add_1"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(compute[v_ax0, v_ax1])
                    T.writes(T_add_1[v_ax0, v_ax1])
                    T_add_1[v_ax0, v_ax1] = T.float32(1) + compute[v_ax0, v_ax1]
            for ax0, ax1 in T.grid(m, n):
                with T.sblock("T_multiply_5"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(T_multiply_1[v_ax0, v_ax1], T_add_1[v_ax0, v_ax1])
                    T.writes(T_multiply[v_ax0, v_ax1])
                    T_multiply[v_ax0, v_ax1] = T_multiply_1[v_ax0, v_ax1] * T_add_1[v_ax0, v_ax1]


    mod = LegalizeOps()(GeluTanh)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_silu():
    # fmt: off
    @tvm.script.ir_module
    class Silu:
        @R.function
        def main(x: R.Tensor((2, 3), "float32")) -> R.Tensor((2, 3), "float32"):
            gv: R.Tensor((2, 3), "float32") = R.nn.silu(x)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 3), "float32")) -> R.Tensor((2, 3), "float32"):
            gv = R.call_tir(Expected.silu, (x,), R.Tensor((2, 3), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def silu(rxplaceholder: T.Buffer((T.int64(2), T.int64(3)), "float32"), T_multiply: T.Buffer((T.int64(2), T.int64(3)), "float32")):
            T.func_attr({"tirx.noalias": True})
            compute = T.sblock_alloc_buffer([T.int64(2), T.int64(3)], dtype="float32")
            for i0, i1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("compute"):
                    i0_1, i1_1 = T.axis.remap("SS", [i0, i1])
                    T.reads(rxplaceholder[i0_1, i1_1])
                    T.writes(compute[i0_1, i1_1])
                    compute[i0_1, i1_1] = T.sigmoid(rxplaceholder[i0_1, i1_1])
            for i0, i1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("T_multiply"):
                    ax0, ax1 = T.axis.remap("SS", [i0, i1])
                    T.reads(rxplaceholder[ax0, ax1], compute[ax0, ax1])
                    T.writes(T_multiply[ax0, ax1])
                    T_multiply[ax0, ax1] = rxplaceholder[ax0, ax1] * compute[ax0, ax1]
    # fmt: on

    mod = LegalizeOps()(Silu)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_silu_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class Silu:
        @R.function
        def main(x: R.Tensor(("m", "n"), "float32")) -> R.Tensor(("m", "n"), "float32"):
            m = T.int64()
            n = T.int64()
            gv: R.Tensor((m, n), "float32") = R.nn.silu(x)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor(("m", "n"), "float32")) -> R.Tensor(("m", "n"), "float32"):
            m = T.int64()
            n = T.int64()
            gv = R.call_tir(Expected.silu, (x,), R.Tensor((m, n), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def silu(var_rxplaceholder: T.handle, var_T_multiply: T.handle):
            T.func_attr({"tirx.noalias": True})
            m = T.int64()
            n = T.int64()
            rxplaceholder = T.match_buffer(var_rxplaceholder, [m, n], dtype="float32")
            T_multiply = T.match_buffer(var_T_multiply, [m, n], dtype="float32")
            compute = T.sblock_alloc_buffer([m, n], dtype="float32")
            for i0, i1 in T.grid(m, n):
                with T.sblock("compute"):
                    i0_1, i1_1 = T.axis.remap("SS", [i0, i1])
                    T.reads(rxplaceholder[i0_1, i1_1])
                    T.writes(compute[i0_1, i1_1])
                    compute[i0_1, i1_1] = T.sigmoid(rxplaceholder[i0_1, i1_1])
            for i0, i1 in T.grid(m, n):
                with T.sblock("T_multiply"):
                    ax0, ax1 = T.axis.remap("SS", [i0, i1])
                    T.reads(rxplaceholder[ax0, ax1], compute[ax0, ax1])
                    T.writes(T_multiply[ax0, ax1])
                    T_multiply[ax0, ax1] = rxplaceholder[ax0, ax1] * compute[ax0, ax1]
    # fmt: on

    mod = LegalizeOps()(Silu)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_softmax():
    # fmt: off
    @tvm.script.ir_module
    class Softmax:
        @R.function
        def main(x: R.Tensor((2, 3, 16, 32), "float32")) -> R.Tensor((2, 3, 16, 32), "float32"):
            gv: R.Tensor((2, 3, 16, 32), "float32") = R.nn.softmax(x, axis=-2)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 3, 16, 32), "float32")) -> R.Tensor((2, 3, 16, 32), "float32"):
            gv = R.call_tir(Expected.softmax, (x,), R.Tensor((2, 3, 16, 32), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def softmax(rxplaceholder: T.Buffer((T.int64(2), T.int64(3), T.int64(16), T.int64(32)), "float32"), T_softmax_norm: T.Buffer((T.int64(2), T.int64(3), T.int64(16), T.int64(32)), "float32")):
            T.func_attr({"tirx.noalias": True})
            T_softmax_maxelem = T.sblock_alloc_buffer([T.int64(2), T.int64(3), T.int64(32)], dtype="float32")
            T_softmax_exp = T.sblock_alloc_buffer([T.int64(2), T.int64(3), T.int64(16), T.int64(32)], dtype="float32")
            T_softmax_expsum = T.sblock_alloc_buffer([T.int64(2), T.int64(3), T.int64(32)], dtype="float32")
            for i0, i1, i2, i3 in T.grid(T.int64(2), T.int64(3), T.int64(32), T.int64(16)):
                with T.sblock("T_softmax_maxelem"):
                    i0_1, i1_1, i2_1, k = T.axis.remap("SSSR", [i0, i1, i2, i3])
                    T.reads(rxplaceholder[i0_1, i1_1, k, i2_1])
                    T.writes(T_softmax_maxelem[i0_1, i1_1, i2_1])
                    with T.init():
                        T_softmax_maxelem[i0_1, i1_1, i2_1] = T.float32(-3.4028234663852886e+38)
                    T_softmax_maxelem[i0_1, i1_1, i2_1] = T.max(T_softmax_maxelem[i0_1, i1_1, i2_1], rxplaceholder[i0_1, i1_1, k, i2_1])
            for i0, i1, i2, i3 in T.grid(T.int64(2), T.int64(3), T.int64(16), T.int64(32)):
                with T.sblock("T_softmax_exp"):
                    i0_2, i1_2, i2_2, i3_1 = T.axis.remap("SSSS", [i0, i1, i2, i3])
                    T.reads(rxplaceholder[i0_2, i1_2, i2_2, i3_1], T_softmax_maxelem[i0_2, i1_2, i3_1])
                    T.writes(T_softmax_exp[i0_2, i1_2, i2_2, i3_1])
                    T_softmax_exp[i0_2, i1_2, i2_2, i3_1] = T.exp(rxplaceholder[i0_2, i1_2, i2_2, i3_1] - T_softmax_maxelem[i0_2, i1_2, i3_1], dtype="float32")
            for i0_3, i1_3, i2_3, i3 in T.grid(T.int64(2), T.int64(3), T.int64(32), T.int64(16)):
                with T.sblock("T_softmax_expsum"):
                    i0_4, i1_4, i2_4, k = T.axis.remap("SSSR", [i0_3, i1_3, i2_3, i3])
                    T.reads(T_softmax_exp[i0_4, i1_4, k, i2_4])
                    T.writes(T_softmax_expsum[i0_4, i1_4, i2_4])
                    with T.init():
                        T_softmax_expsum[i0_4, i1_4, i2_4] = T.float32(0)
                    T_softmax_expsum[i0_4, i1_4, i2_4] = T_softmax_expsum[i0_4, i1_4, i2_4] + T_softmax_exp[i0_4, i1_4, k, i2_4]
            for i0_5, i1_5, i2_5, i3 in T.grid(T.int64(2), T.int64(3), T.int64(16), T.int64(32)):
                with T.sblock("T_softmax_norm"):
                    i0_6, i1_6, i2_6, i3_2 = T.axis.remap("SSSS", [i0_5, i1_5, i2_5, i3])
                    T.reads(T_softmax_exp[i0_6, i1_6, i2_6, i3_2], T_softmax_expsum[i0_6, i1_6, i3_2])
                    T.writes(T_softmax_norm[i0_6, i1_6, i2_6, i3_2])
                    T.sblock_attr({"axis":2})
                    T_softmax_norm[i0_6, i1_6, i2_6, i3_2] = T_softmax_exp[i0_6, i1_6, i2_6, i3_2] / T_softmax_expsum[i0_6, i1_6, i3_2]
    # fmt: on

    mod = LegalizeOps()(Softmax)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_softmax_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class Softmax:
        @R.function
        def main(x: R.Tensor(("a", "b", "c"), "float32")) -> R.Tensor(("a", "b", "c"), "float32"):
            a = T.int64()
            b = T.int64()
            c = T.int64()
            gv: R.Tensor((a, b, c), "float32") = R.nn.softmax(x)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor(("a", "b", "c"), "float32")) -> R.Tensor(("a", "b", "c"), "float32"):
            a = T.int64()
            b = T.int64()
            c = T.int64()
            gv = R.call_tir(Expected.softmax, (x,), R.Tensor((a, b, c), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def softmax(var_rxplaceholder: T.handle, var_T_softmax_norm: T.handle):
            T.func_attr({"tirx.noalias": True})
            a = T.int64()
            b = T.int64()
            c = T.int64()
            rxplaceholder = T.match_buffer(var_rxplaceholder, [a, b, c], dtype="float32")
            T_softmax_norm = T.match_buffer(var_T_softmax_norm, [a, b, c], dtype="float32")
            T_softmax_maxelem = T.sblock_alloc_buffer([a, b], dtype="float32")
            T_softmax_exp = T.sblock_alloc_buffer([a, b, c], dtype="float32")
            T_softmax_expsum = T.sblock_alloc_buffer([a, b], dtype="float32")
            for i0, i1, i2 in T.grid(a, b, c):
                with T.sblock("T_softmax_maxelem"):
                    i0_1, i1_1, k = T.axis.remap("SSR", [i0, i1, i2])
                    T.reads(rxplaceholder[i0_1, i1_1, k])
                    T.writes(T_softmax_maxelem[i0_1, i1_1])
                    with T.init():
                        T_softmax_maxelem[i0_1, i1_1] = T.float32(-3.4028234663852886e+38)
                    T_softmax_maxelem[i0_1, i1_1] = T.max(T_softmax_maxelem[i0_1, i1_1], rxplaceholder[i0_1, i1_1, k])
            for i0, i1, i2 in T.grid(a, b, c):
                with T.sblock("T_softmax_exp"):
                    i0_2, i1_2, i2_1 = T.axis.remap("SSS", [i0, i1, i2])
                    T.reads(rxplaceholder[i0_2, i1_2, i2_1], T_softmax_maxelem[i0_2, i1_2])
                    T.writes(T_softmax_exp[i0_2, i1_2, i2_1])
                    T_softmax_exp[i0_2, i1_2, i2_1] = T.exp(rxplaceholder[i0_2, i1_2, i2_1] - T_softmax_maxelem[i0_2, i1_2], dtype="float32")
            for i0_3, i1_3, i2 in T.grid(a, b, c):
                with T.sblock("T_softmax_expsum"):
                    i0_4, i1_4, k = T.axis.remap("SSR", [i0_3, i1_3, i2])
                    T.reads(T_softmax_exp[i0_4, i1_4, k])
                    T.writes(T_softmax_expsum[i0_4, i1_4])
                    with T.init():
                        T_softmax_expsum[i0_4, i1_4] = T.float32(0)
                    T_softmax_expsum[i0_4, i1_4] = T_softmax_expsum[i0_4, i1_4] + T_softmax_exp[i0_4, i1_4, k]
            for i0_5, i1_5, i2 in T.grid(a, b, c):
                with T.sblock("T_softmax_norm"):
                    i0_6, i1_6, i2_2 = T.axis.remap("SSS", [i0_5, i1_5, i2])
                    T.reads(T_softmax_exp[i0_6, i1_6, i2_2], T_softmax_expsum[i0_6, i1_6])
                    T.writes(T_softmax_norm[i0_6, i1_6, i2_2])
                    T.sblock_attr({"axis":2})
                    T_softmax_norm[i0_6, i1_6, i2_2] = T_softmax_exp[i0_6, i1_6, i2_2] / T_softmax_expsum[i0_6, i1_6]
    # fmt: on

    mod = LegalizeOps()(Softmax)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_log_softmax():
    # fmt: off
    @tvm.script.ir_module
    class LogSoftmax:
        @R.function
        def main(x: R.Tensor((2, 3, 16, 32), "float32")) -> R.Tensor(None, "float32", ndim=4):
            gv: R.Tensor((2, 3, 16, 32), "float32") = R.nn.log_softmax(x, axis=-2)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 3, 16, 32), dtype="float32")) -> R.Tensor((2, 3, 16, 32), dtype="float32"):
            gv = R.call_tir(Expected.log_softmax, (x,), R.Tensor((2, 3, 16, 32), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def log_softmax(rxplaceholder: T.Buffer((T.int64(2), T.int64(3), T.int64(16), T.int64(32)), "float32"), compute: T.Buffer((T.int64(2), T.int64(3), T.int64(16), T.int64(32)), "float32"),):
            T.func_attr({"tirx.noalias": True})
            T_softmax_maxelem = T.sblock_alloc_buffer([T.int64(2), T.int64(3), T.int64(32)], dtype="float32")
            compute_1 = T.sblock_alloc_buffer([T.int64(2), T.int64(3), T.int64(32)], dtype="float32")
            for i0, i1, i2, i3 in T.grid(T.int64(2), T.int64(3), T.int64(32), T.int64(16)):
                with T.sblock("T_softmax_maxelem"):
                    i0_1, i1_1, i2_1, k = T.axis.remap("SSSR", [i0, i1, i2, i3])
                    T.reads(rxplaceholder[i0_1, i1_1, k, i2_1])
                    T.writes(T_softmax_maxelem[i0_1, i1_1, i2_1])
                    with T.init():
                        T_softmax_maxelem[i0_1, i1_1, i2_1] = T.float32(-3.4028234663852886e38)
                    T_softmax_maxelem[i0_1, i1_1, i2_1] = T.max(T_softmax_maxelem[i0_1, i1_1, i2_1], rxplaceholder[i0_1, i1_1, k, i2_1])
            for i0, i1, i2, i3 in T.grid(T.int64(2), T.int64(3), T.int64(32), T.int64(16)):
                with T.sblock("compute"):
                    i0_2, i1_2, i2_2, k = T.axis.remap("SSSR", [i0, i1, i2, i3])
                    T.reads(rxplaceholder[i0_2, i1_2, k, i2_2], T_softmax_maxelem[i0_2, i1_2, i2_2])
                    T.writes(compute_1[i0_2, i1_2, i2_2])
                    with T.init():
                        compute_1[i0_2, i1_2, i2_2] = T.float32(0)
                    compute_1[i0_2, i1_2, i2_2] = compute_1[i0_2, i1_2, i2_2] + T.exp(rxplaceholder[i0_2, i1_2, k, i2_2] - T_softmax_maxelem[i0_2, i1_2, i2_2], dtype="float32")
            for i0_3, i1_3, i2_3, i3 in T.grid(T.int64(2), T.int64(3), T.int64(16), T.int64(32)):
                with T.sblock("compute_1"):
                    i0_4, i1_4, i2_4, i3_1 = T.axis.remap("SSSS", [i0_3, i1_3, i2_3, i3])
                    T.reads(rxplaceholder[i0_4, i1_4, i2_4, i3_1], T_softmax_maxelem[i0_4, i1_4, i3_1], compute_1[i0_4, i1_4, i3_1])
                    T.writes(compute[i0_4, i1_4, i2_4, i3_1])
                    T.sblock_attr({"axis": 2})
                    compute[i0_4, i1_4, i2_4, i3_1] = (rxplaceholder[i0_4, i1_4, i2_4, i3_1] - T_softmax_maxelem[i0_4, i1_4, i3_1] - T.log(compute_1[i0_4, i1_4, i3_1], dtype="float32"))
    # fmt: on

    mod = LegalizeOps()(LogSoftmax)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_log_softmax_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class LogSoftmax:
        @R.function
        def main(x: R.Tensor(("a", "b", "c"), "float32")) -> R.Tensor(("a", "b", "c"), "float32"):
            a = T.int64()
            b = T.int64()
            c = T.int64()
            gv: R.Tensor((a, b, c), "float32") = R.nn.log_softmax(x)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor(("a", "b", "c"), dtype="float32")) -> R.Tensor(("a", "b", "c"), dtype="float32"):
            a = T.int64()
            b = T.int64()
            c = T.int64()
            # block 0
            gv = R.call_tir(Expected.log_softmax, (x,), R.Tensor((a, b, c), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def log_softmax(var_rxplaceholder: T.handle, var_compute: T.handle):
            T.func_attr({"tirx.noalias": True})
            a = T.int64()
            b = T.int64()
            c = T.int64()
            rxplaceholder = T.match_buffer(var_rxplaceholder, [a, b, c], dtype="float32")
            compute = T.match_buffer(var_compute, [a, b, c], dtype="float32")
            T_softmax_maxelem = T.sblock_alloc_buffer([a, b], dtype="float32")
            compute_1 = T.sblock_alloc_buffer([a, b], dtype="float32")
            for i0, i1, k in T.grid(a, b, c):
                with T.sblock("T_softmax_maxelem"):
                    v_i0, v_i1, v_k = T.axis.remap("SSR", [i0, i1, k])
                    T.reads(rxplaceholder[v_i0, v_i1, v_k])
                    T.writes(T_softmax_maxelem[v_i0, v_i1])
                    with T.init():
                        T_softmax_maxelem[v_i0, v_i1] = T.float32(-3.4028234663852886e38)
                    T_softmax_maxelem[v_i0, v_i1] = T.max(T_softmax_maxelem[v_i0, v_i1], rxplaceholder[v_i0, v_i1, v_k])
            for i0, i1, k in T.grid(a, b, c):
                with T.sblock("compute"):
                    v_i0, v_i1, v_k = T.axis.remap("SSR", [i0, i1, k])
                    T.reads(rxplaceholder[v_i0, v_i1, v_k], T_softmax_maxelem[v_i0, v_i1])
                    T.writes(compute_1[v_i0, v_i1])
                    with T.init():
                        compute_1[v_i0, v_i1] = T.float32(0)
                    compute_1[v_i0, v_i1] = compute_1[v_i0, v_i1] + T.exp(rxplaceholder[v_i0, v_i1, v_k] - T_softmax_maxelem[v_i0, v_i1], dtype="float32")
            for i0, i1, i2 in T.grid(a, b, c):
                with T.sblock("compute_1"):
                    v_i0, v_i1, v_i2 = T.axis.remap("SSS", [i0, i1, i2])
                    T.reads(rxplaceholder[v_i0, v_i1, v_i2], T_softmax_maxelem[v_i0, v_i1], compute_1[v_i0, v_i1],)
                    T.writes(compute[v_i0, v_i1, v_i2])
                    T.sblock_attr({"axis": 2})
                    compute[v_i0, v_i1, v_i2] = (rxplaceholder[v_i0, v_i1, v_i2] - T_softmax_maxelem[v_i0, v_i1] - T.log(compute_1[v_i0, v_i1], dtype="float32"))
    # fmt: on

    mod = LegalizeOps()(LogSoftmax)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_cross_entropy_with_logits():
    # fmt: off
    @tvm.script.ir_module
    class CrossEntropyWithLogits:
        @R.function
        def main(x: R.Tensor((3,), "float32"), y: R.Tensor((3,), "float32")) -> R.Tensor(None, "float32", ndim=2):
            gv: R.Tensor((), "float32") = R.nn.cross_entropy_with_logits(x, y)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((3,), dtype="float32"), y: R.Tensor((3,), dtype="float32")):
            gv = R.call_tir(Expected.cross_entropy_with_logits, (x, y), R.Tensor((), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def cross_entropy_with_logits(x: T.Buffer((T.int64(3),), "float32"), y: T.Buffer((T.int64(3),), "float32"), T_multiply: T.Buffer((), "float32")):
            T.func_attr({"tirx.noalias": True})
            T_multiply_1 = T.sblock_alloc_buffer((T.int64(3),))
            T_multiply_red = T.sblock_alloc_buffer(())
            for ax0 in range(T.int64(3)):
                with T.sblock("T_multiply"):
                    v_ax0 = T.axis.spatial(T.int64(3), ax0)
                    T.reads(x[v_ax0], y[v_ax0])
                    T.writes(T_multiply_1[v_ax0])
                    T_multiply_1[v_ax0] = x[v_ax0] * y[v_ax0]
            for k0 in range(T.int64(3)):
                with T.sblock("T_multiply_red"):
                    v_k0 = T.axis.reduce(T.int64(3), k0)
                    T.reads(T_multiply_1[v_k0])
                    T.writes(T_multiply_red[()])
                    with T.init():
                        T_multiply_red[()] = T.float32(0.0)
                    T_multiply_red[()] = T_multiply_red[()] + T_multiply_1[v_k0]
            with T.sblock("T_multiply_1"):
                vi = T.axis.spatial(1, T.int64(0))
                T.reads(T_multiply_red[()])
                T.writes(T_multiply[()])
                T_multiply[()] = T_multiply_red[()] * T.float32(-1.0)
    # fmt: on

    mod = LegalizeOps()(CrossEntropyWithLogits)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_cross_entropy_with_logits_batch():
    # fmt: off
    @tvm.script.ir_module
    class CrossEntropyWithLogits:
        @R.function
        def main(x: R.Tensor((2, 3), "float32"), y: R.Tensor((2, 3), "float32")) -> R.Tensor(None, "float32", ndim=2):
            gv: R.Tensor((), "float32") = R.nn.cross_entropy_with_logits(x, y)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 3), dtype="float32"), y: R.Tensor((2, 3), dtype="float32")):
            gv = R.call_tir(Expected.cross_entropy_with_logits, (x, y), R.Tensor((), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def cross_entropy_with_logits(x: T.Buffer((T.int64(2), T.int64(3)), "float32"), y: T.Buffer((T.int64(2), T.int64(3)), "float32"), T_divide: T.Buffer((), "float32")):
            T.func_attr({"tirx.noalias": True})
            T_multiply = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            T_multiply_red = T.sblock_alloc_buffer(())
            T_multiply_1 = T.sblock_alloc_buffer(())
            for ax0, ax1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("T_multiply"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(x[v_ax0, v_ax1], y[v_ax0, v_ax1])
                    T.writes(T_multiply[v_ax0, v_ax1])
                    T_multiply[v_ax0, v_ax1] = x[v_ax0, v_ax1] * y[v_ax0, v_ax1]
            for k0, k1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("T_multiply_red"):
                    v_k0, v_k1 = T.axis.remap("RR", [k0, k1])
                    T.reads(T_multiply[v_k0, v_k1])
                    T.writes(T_multiply_red[()])
                    with T.init():
                        T_multiply_red[()] = T.float32(0.0)
                    T_multiply_red[()] = T_multiply_red[()] + T_multiply[v_k0, v_k1]
            with T.sblock("T_multiply_1"):
                vi = T.axis.spatial(1, T.int64(0))
                T.reads(T_multiply_red[()])
                T.writes(T_multiply_1[()])
                T_multiply_1[()] = T_multiply_red[()] * T.float32(-1.0)
            with T.sblock("T_divide"):
                vi = T.axis.spatial(1, T.int64(0))
                T.reads(T_multiply_1[()])
                T.writes(T_divide[()])
                T_divide[()] = T_multiply_1[()] / T.float32(2)
    # fmt: on

    mod = LegalizeOps()(CrossEntropyWithLogits)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_cross_entropy_with_logits_batch_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class CrossEntropyWithLogits:
        @R.function
        def main(x: R.Tensor(("n", "m"), "float32"), y: R.Tensor(("n", "m"), "float32")) -> R.Tensor(None, "float32", ndim=2):
            n = T.int64()
            m = T.int64()
            gv: R.Tensor((), "float32") = R.nn.cross_entropy_with_logits(x, y)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor(("n", "m"), dtype="float32"), y: R.Tensor(("n", "m"), dtype="float32")):
            gv = R.call_tir(Expected.cross_entropy_with_logits, (x, y), R.Tensor((), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def cross_entropy_with_logits(var_x: T.handle, var_y: T.handle, T_divide: T.Buffer((), "float32")):
            T.func_attr({"tirx.noalias": True})
            m, n = T.int64(), T.int64()
            x = T.match_buffer(var_x, (n, m))
            y = T.match_buffer(var_y, (n, m))
            T_multiply = T.sblock_alloc_buffer((n, m))
            T_multiply_red = T.sblock_alloc_buffer(())
            T_multiply_1 = T.sblock_alloc_buffer(())
            for ax0, ax1 in T.grid(n, m):
                with T.sblock("T_multiply"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(x[v_ax0, v_ax1], y[v_ax0, v_ax1])
                    T.writes(T_multiply[v_ax0, v_ax1])
                    T_multiply[v_ax0, v_ax1] = x[v_ax0, v_ax1] * y[v_ax0, v_ax1]
            for k0, k1 in T.grid(n, m):
                with T.sblock("T_multiply_red"):
                    v_k0, v_k1 = T.axis.remap("RR", [k0, k1])
                    T.reads(T_multiply[v_k0, v_k1])
                    T.writes(T_multiply_red[()])
                    with T.init():
                        T_multiply_red[()] = T.float32(0.0)
                    T_multiply_red[()] = T_multiply_red[()] + T_multiply[v_k0, v_k1]
            with T.sblock("T_multiply_1"):
                vi = T.axis.spatial(1, T.int64(0))
                T.reads(T_multiply_red[()])
                T.writes(T_multiply_1[()])
                T_multiply_1[()] = T_multiply_red[()] * T.float32(-1.0)
            with T.sblock("T_divide"):
                vi = T.axis.spatial(1, T.int64(0))
                T.reads(T_multiply_1[()])
                T.writes(T_divide[()])
                T_divide[()] = T_multiply_1[()] / T.Cast("float32", n)
    # fmt: on

    mod = LegalizeOps()(CrossEntropyWithLogits)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_batch_norm():
    # fmt: off
    @tvm.script.ir_module
    class BatchNorm:
        @R.function
        def main(x: R.Tensor((2, 3, 28, 28), "float32"), gamma: R.Tensor((3,), "float32"), beta: R.Tensor((3,), "float32"), moving_mean: R.Tensor((3,), "float32"), moving_var: R.Tensor((3,), "float32")) -> R.Tuple(R.Tensor((2, 3, 28, 28), "float32"), R.Tensor((3,), "float32"), R.Tensor((3,), "float32")):
            gv: R.Tuple(R.Tensor((2, 3, 28, 28), "float32"), R.Tensor((3,), "float32"), R.Tensor((3,), "float32")) = R.nn.batch_norm(x, gamma, beta, moving_mean, moving_var, axis=1)
            return gv

    @tvm.script.ir_module
    class Expected:
        @T.prim_func(private=True, s_tir=True)
        def batch_norm(var_x: T.handle, var_gamma: T.handle, var_beta: T.handle, var_moving_mean: T.handle, var_moving_var: T.handle, var_T_add: T.handle, var_T_add_1: T.handle, var_T_add_2: T.handle):
            T.func_attr({"tirx.noalias": True})
            x = T.match_buffer(var_x, (T.int64(2), T.int64(3), T.int64(28), T.int64(28)))
            gamma = T.match_buffer(var_gamma, (T.int64(3),))
            beta = T.match_buffer(var_beta, (T.int64(3),))
            moving_mean = T.match_buffer(var_moving_mean, (T.int64(3),))
            moving_var = T.match_buffer(var_moving_var, (T.int64(3),))
            T_add = T.match_buffer(var_T_add, (T.int64(2), T.int64(3), T.int64(28), T.int64(28)))
            T_add_1 = T.match_buffer(var_T_add_1, (T.int64(3),))
            T_add_2 = T.match_buffer(var_T_add_2, (T.int64(3),))
            with T.sblock("root"):
                T.reads()
                T.writes()
                x_red = T.sblock_alloc_buffer((T.int64(3),))
                T_divide = T.sblock_alloc_buffer((T.int64(3),))
                T_reshape = T.sblock_alloc_buffer((T.int64(1), T.int64(3), T.int64(1), T.int64(1)))
                T_subtract = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(28), T.int64(28)))
                T_subtract_1 = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(28), T.int64(28)))
                T_subtract_2 = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(28), T.int64(28)))
                T_multiply = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(28), T.int64(28)))
                T_multiply_red = T.sblock_alloc_buffer((T.int64(3),))
                T_divide_1 = T.sblock_alloc_buffer((T.int64(3),))
                T_reshape_1 = T.sblock_alloc_buffer((T.int64(1), T.int64(3), T.int64(1), T.int64(1)))
                T_add_3 = T.sblock_alloc_buffer((T.int64(1), T.int64(3), T.int64(1), T.int64(1)))
                compute = T.sblock_alloc_buffer((T.int64(1), T.int64(3), T.int64(1), T.int64(1)))
                T_divide_2 = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(28), T.int64(28)))
                T_reshape_2 = T.sblock_alloc_buffer((T.int64(1), T.int64(3), T.int64(1), T.int64(1)))
                T_multiply_1 = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(28), T.int64(28)))
                T_reshape_3 = T.sblock_alloc_buffer((T.int64(1), T.int64(3), T.int64(1), T.int64(1)))
                T_multiply_2 = T.sblock_alloc_buffer((T.int64(3),))
                T_multiply_3 = T.sblock_alloc_buffer((T.int64(3),))
                T_multiply_4 = T.sblock_alloc_buffer((T.int64(3),))
                T_multiply_5 = T.sblock_alloc_buffer((T.int64(3),))
                for ax0 in range(T.int64(3)):
                    for k0 in range(T.int64(2)):
                        for k2 in range(T.int64(28)):
                            for k3 in range(T.int64(28)):
                                with T.sblock("x_red"):
                                    v_ax0 = T.axis.spatial(T.int64(3), ax0)
                                    v_k0 = T.axis.reduce(T.int64(2), k0)
                                    v_k2 = T.axis.reduce(T.int64(28), k2)
                                    v_k3 = T.axis.reduce(T.int64(28), k3)
                                    T.reads(x[v_k0, v_ax0, v_k2, v_k3])
                                    T.writes(x_red[v_ax0])
                                    with T.init():
                                        x_red[v_ax0] = T.float32(0.0)
                                    x_red[v_ax0] = x_red[v_ax0] + x[v_k0, v_ax0, v_k2, v_k3]
                for ax0 in range(T.int64(3)):
                    with T.sblock("T_divide"):
                        v_ax0 = T.axis.spatial(T.int64(3), ax0)
                        T.reads(x_red[v_ax0])
                        T.writes(T_divide[v_ax0])
                        T_divide[v_ax0] = x_red[v_ax0] / T.float32(1568.0)
                for ax0 in range(T.int64(1)):
                    for ax1 in range(T.int64(3)):
                        for ax2 in range(T.int64(1)):
                            for ax3 in range(T.int64(1)):
                                with T.sblock("T_reshape"):
                                    v_ax0 = T.axis.spatial(T.int64(1), ax0)
                                    v_ax1 = T.axis.spatial(T.int64(3), ax1)
                                    v_ax2 = T.axis.spatial(T.int64(1), ax2)
                                    v_ax3 = T.axis.spatial(T.int64(1), ax3)
                                    T.reads(T_divide[(v_ax1 + v_ax2 + v_ax3) % T.int64(3)])
                                    T.writes(T_reshape[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_reshape[v_ax0, v_ax1, v_ax2, v_ax3] = T_divide[(v_ax1 + v_ax2 + v_ax3) % T.int64(3)]
                for ax0 in range(T.int64(2)):
                    for ax1 in range(T.int64(3)):
                        for ax2 in range(T.int64(28)):
                            for ax3 in range(T.int64(28)):
                                with T.sblock("T_subtract"):
                                    v_ax0 = T.axis.spatial(T.int64(2), ax0)
                                    v_ax1 = T.axis.spatial(T.int64(3), ax1)
                                    v_ax2 = T.axis.spatial(T.int64(28), ax2)
                                    v_ax3 = T.axis.spatial(T.int64(28), ax3)
                                    T.reads(x[v_ax0, v_ax1, v_ax2, v_ax3], T_reshape[T.int64(0), v_ax1, T.int64(0), T.int64(0)])
                                    T.writes(T_subtract[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_subtract[v_ax0, v_ax1, v_ax2, v_ax3] = x[v_ax0, v_ax1, v_ax2, v_ax3] - T_reshape[T.int64(0), v_ax1, T.int64(0), T.int64(0)]
                for ax0 in range(T.int64(2)):
                    for ax1 in range(T.int64(3)):
                        for ax2 in range(T.int64(28)):
                            for ax3 in range(T.int64(28)):
                                with T.sblock("T_subtract_1"):
                                    v_ax0 = T.axis.spatial(T.int64(2), ax0)
                                    v_ax1 = T.axis.spatial(T.int64(3), ax1)
                                    v_ax2 = T.axis.spatial(T.int64(28), ax2)
                                    v_ax3 = T.axis.spatial(T.int64(28), ax3)
                                    T.reads(x[v_ax0, v_ax1, v_ax2, v_ax3], T_reshape[T.int64(0), v_ax1, T.int64(0), T.int64(0)])
                                    T.writes(T_subtract_1[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_subtract_1[v_ax0, v_ax1, v_ax2, v_ax3] = x[v_ax0, v_ax1, v_ax2, v_ax3] - T_reshape[T.int64(0), v_ax1, T.int64(0), T.int64(0)]
                for ax0 in range(T.int64(2)):
                    for ax1 in range(T.int64(3)):
                        for ax2 in range(T.int64(28)):
                            for ax3 in range(T.int64(28)):
                                with T.sblock("T_subtract_2"):
                                    v_ax0 = T.axis.spatial(T.int64(2), ax0)
                                    v_ax1 = T.axis.spatial(T.int64(3), ax1)
                                    v_ax2 = T.axis.spatial(T.int64(28), ax2)
                                    v_ax3 = T.axis.spatial(T.int64(28), ax3)
                                    T.reads(x[v_ax0, v_ax1, v_ax2, v_ax3], T_reshape[T.int64(0), v_ax1, T.int64(0), T.int64(0)])
                                    T.writes(T_subtract_2[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_subtract_2[v_ax0, v_ax1, v_ax2, v_ax3] = x[v_ax0, v_ax1, v_ax2, v_ax3] - T_reshape[T.int64(0), v_ax1, T.int64(0), T.int64(0)]
                for ax0 in range(T.int64(2)):
                    for ax1 in range(T.int64(3)):
                        for ax2 in range(T.int64(28)):
                            for ax3 in range(T.int64(28)):
                                with T.sblock("T_multiply"):
                                    v_ax0 = T.axis.spatial(T.int64(2), ax0)
                                    v_ax1 = T.axis.spatial(T.int64(3), ax1)
                                    v_ax2 = T.axis.spatial(T.int64(28), ax2)
                                    v_ax3 = T.axis.spatial(T.int64(28), ax3)
                                    T.reads(T_subtract_1[v_ax0, v_ax1, v_ax2, v_ax3], T_subtract_2[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T.writes(T_multiply[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_multiply[v_ax0, v_ax1, v_ax2, v_ax3] = T_subtract_1[v_ax0, v_ax1, v_ax2, v_ax3] * T_subtract_2[v_ax0, v_ax1, v_ax2, v_ax3]
                for ax0 in range(T.int64(3)):
                    for k0 in range(T.int64(2)):
                        for k2 in range(T.int64(28)):
                            for k3 in range(T.int64(28)):
                                with T.sblock("T_multiply_red"):
                                    v_ax0 = T.axis.spatial(T.int64(3), ax0)
                                    v_k0 = T.axis.reduce(T.int64(2), k0)
                                    v_k2 = T.axis.reduce(T.int64(28), k2)
                                    v_k3 = T.axis.reduce(T.int64(28), k3)
                                    T.reads(T_multiply[v_k0, v_ax0, v_k2, v_k3])
                                    T.writes(T_multiply_red[v_ax0])
                                    with T.init():
                                        T_multiply_red[v_ax0] = T.float32(0.0)
                                    T_multiply_red[v_ax0] = T_multiply_red[v_ax0] + T_multiply[v_k0, v_ax0, v_k2, v_k3]
                for ax0 in range(T.int64(3)):
                    with T.sblock("T_divide_1"):
                        v_ax0 = T.axis.spatial(T.int64(3), ax0)
                        T.reads(T_multiply_red[v_ax0])
                        T.writes(T_divide_1[v_ax0])
                        T_divide_1[v_ax0] = T_multiply_red[v_ax0] / T.float32(1568.0)
                for ax0 in range(T.int64(1)):
                    for ax1 in range(T.int64(3)):
                        for ax2 in range(T.int64(1)):
                            for ax3 in range(T.int64(1)):
                                with T.sblock("T_reshape_1"):
                                    v_ax0 = T.axis.spatial(T.int64(1), ax0)
                                    v_ax1 = T.axis.spatial(T.int64(3), ax1)
                                    v_ax2 = T.axis.spatial(T.int64(1), ax2)
                                    v_ax3 = T.axis.spatial(T.int64(1), ax3)
                                    T.reads(T_divide_1[(v_ax1 + v_ax2 + v_ax3) % T.int64(3)])
                                    T.writes(T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3] = T_divide_1[(v_ax1 + v_ax2 + v_ax3) % T.int64(3)]
                for ax0 in range(T.int64(1)):
                    for ax1 in range(T.int64(3)):
                        for ax2 in range(T.int64(1)):
                            for ax3 in range(T.int64(1)):
                                with T.sblock("T_add"):
                                    v_ax0 = T.axis.spatial(T.int64(1), ax0)
                                    v_ax1 = T.axis.spatial(T.int64(3), ax1)
                                    v_ax2 = T.axis.spatial(T.int64(1), ax2)
                                    v_ax3 = T.axis.spatial(T.int64(1), ax3)
                                    T.reads(T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T.writes(T_add_3[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_add_3[v_ax0, v_ax1, v_ax2, v_ax3] = T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3] + T.float32(1.0000000000000001e-05)
                for i0 in range(T.int64(1)):
                    for i1 in range(T.int64(3)):
                        for i2 in range(T.int64(1)):
                            for i3 in range(T.int64(1)):
                                with T.sblock("compute"):
                                    v_i0 = T.axis.spatial(T.int64(1), i0)
                                    v_i1 = T.axis.spatial(T.int64(3), i1)
                                    v_i2 = T.axis.spatial(T.int64(1), i2)
                                    v_i3 = T.axis.spatial(T.int64(1), i3)
                                    T.reads(T_add_3[v_i0, v_i1, v_i2, v_i3])
                                    T.writes(compute[v_i0, v_i1, v_i2, v_i3])
                                    compute[v_i0, v_i1, v_i2, v_i3] = T.sqrt(T_add_3[v_i0, v_i1, v_i2, v_i3])
                for ax0 in range(T.int64(2)):
                    for ax1 in range(T.int64(3)):
                        for ax2 in range(T.int64(28)):
                            for ax3 in range(T.int64(28)):
                                with T.sblock("T_divide_2"):
                                    v_ax0 = T.axis.spatial(T.int64(2), ax0)
                                    v_ax1 = T.axis.spatial(T.int64(3), ax1)
                                    v_ax2 = T.axis.spatial(T.int64(28), ax2)
                                    v_ax3 = T.axis.spatial(T.int64(28), ax3)
                                    T.reads(T_subtract[v_ax0, v_ax1, v_ax2, v_ax3], compute[T.int64(0), v_ax1, T.int64(0), T.int64(0)])
                                    T.writes(T_divide_2[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_divide_2[v_ax0, v_ax1, v_ax2, v_ax3] = T_subtract[v_ax0, v_ax1, v_ax2, v_ax3] / compute[T.int64(0), v_ax1, T.int64(0), T.int64(0)]
                for ax0 in range(T.int64(1)):
                    for ax1 in range(T.int64(3)):
                        for ax2 in range(T.int64(1)):
                            for ax3 in range(T.int64(1)):
                                with T.sblock("T_reshape_2"):
                                    v_ax0 = T.axis.spatial(T.int64(1), ax0)
                                    v_ax1 = T.axis.spatial(T.int64(3), ax1)
                                    v_ax2 = T.axis.spatial(T.int64(1), ax2)
                                    v_ax3 = T.axis.spatial(T.int64(1), ax3)
                                    T.reads(gamma[(v_ax1 + v_ax2 + v_ax3) % T.int64(3)])
                                    T.writes(T_reshape_2[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_reshape_2[v_ax0, v_ax1, v_ax2, v_ax3] = gamma[(v_ax1 + v_ax2 + v_ax3) % T.int64(3)]
                for ax0 in range(T.int64(2)):
                    for ax1 in range(T.int64(3)):
                        for ax2 in range(T.int64(28)):
                            for ax3 in range(T.int64(28)):
                                with T.sblock("T_multiply_1"):
                                    v_ax0 = T.axis.spatial(T.int64(2), ax0)
                                    v_ax1 = T.axis.spatial(T.int64(3), ax1)
                                    v_ax2 = T.axis.spatial(T.int64(28), ax2)
                                    v_ax3 = T.axis.spatial(T.int64(28), ax3)
                                    T.reads(T_divide_2[v_ax0, v_ax1, v_ax2, v_ax3], T_reshape_2[T.int64(0), v_ax1, T.int64(0), T.int64(0)])
                                    T.writes(T_multiply_1[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_multiply_1[v_ax0, v_ax1, v_ax2, v_ax3] = T_divide_2[v_ax0, v_ax1, v_ax2, v_ax3] * T_reshape_2[T.int64(0), v_ax1, T.int64(0), T.int64(0)]
                for ax0 in range(T.int64(1)):
                    for ax1 in range(T.int64(3)):
                        for ax2 in range(T.int64(1)):
                            for ax3 in range(T.int64(1)):
                                with T.sblock("T_reshape_3"):
                                    v_ax0 = T.axis.spatial(T.int64(1), ax0)
                                    v_ax1 = T.axis.spatial(T.int64(3), ax1)
                                    v_ax2 = T.axis.spatial(T.int64(1), ax2)
                                    v_ax3 = T.axis.spatial(T.int64(1), ax3)
                                    T.reads(beta[(v_ax1 + v_ax2 + v_ax3) % T.int64(3)])
                                    T.writes(T_reshape_3[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_reshape_3[v_ax0, v_ax1, v_ax2, v_ax3] = beta[(v_ax1 + v_ax2 + v_ax3) % T.int64(3)]
                for ax0 in range(T.int64(2)):
                    for ax1 in range(T.int64(3)):
                        for ax2 in range(T.int64(28)):
                            for ax3 in range(T.int64(28)):
                                with T.sblock("T_add_1"):
                                    v_ax0 = T.axis.spatial(T.int64(2), ax0)
                                    v_ax1 = T.axis.spatial(T.int64(3), ax1)
                                    v_ax2 = T.axis.spatial(T.int64(28), ax2)
                                    v_ax3 = T.axis.spatial(T.int64(28), ax3)
                                    T.reads(T_multiply_1[v_ax0, v_ax1, v_ax2, v_ax3], T_reshape_3[T.int64(0), v_ax1, T.int64(0), T.int64(0)])
                                    T.writes(T_add[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_add[v_ax0, v_ax1, v_ax2, v_ax3] = T_multiply_1[v_ax0, v_ax1, v_ax2, v_ax3] + T_reshape_3[T.int64(0), v_ax1, T.int64(0), T.int64(0)]
                for ax0 in range(T.int64(3)):
                    with T.sblock("T_multiply_2"):
                        v_ax0 = T.axis.spatial(T.int64(3), ax0)
                        T.reads(moving_mean[v_ax0])
                        T.writes(T_multiply_2[v_ax0])
                        T_multiply_2[v_ax0] = T.float32(0.90000000000000002) * moving_mean[v_ax0]
                for ax0 in range(T.int64(3)):
                    with T.sblock("T_multiply_3"):
                        v_ax0 = T.axis.spatial(T.int64(3), ax0)
                        T.reads(T_divide[v_ax0])
                        T.writes(T_multiply_3[v_ax0])
                        T_multiply_3[v_ax0] = T.float32(0.10000000000000001) * T_divide[v_ax0]
                for ax0 in range(T.int64(3)):
                    with T.sblock("T_add_2"):
                        v_ax0 = T.axis.spatial(T.int64(3), ax0)
                        T.reads(T_multiply_2[v_ax0], T_multiply_3[v_ax0])
                        T.writes(T_add_1[v_ax0])
                        T_add_1[v_ax0] = T_multiply_2[v_ax0] + T_multiply_3[v_ax0]
                for ax0 in range(T.int64(3)):
                    with T.sblock("T_multiply_4"):
                        v_ax0 = T.axis.spatial(T.int64(3), ax0)
                        T.reads(moving_var[v_ax0])
                        T.writes(T_multiply_4[v_ax0])
                        T_multiply_4[v_ax0] = T.float32(0.90000000000000002) * moving_var[v_ax0]
                for ax0 in range(T.int64(3)):
                    with T.sblock("T_multiply_5"):
                        v_ax0 = T.axis.spatial(T.int64(3), ax0)
                        T.reads(T_divide_1[v_ax0])
                        T.writes(T_multiply_5[v_ax0])
                        T_multiply_5[v_ax0] = T.float32(0.10000000000000001) * T_divide_1[v_ax0]
                for ax0 in range(T.int64(3)):
                    with T.sblock("T_add_3"):
                        v_ax0 = T.axis.spatial(T.int64(3), ax0)
                        T.reads(T_multiply_4[v_ax0], T_multiply_5[v_ax0])
                        T.writes(T_add_2[v_ax0])
                        T_add_2[v_ax0] = T_multiply_4[v_ax0] + T_multiply_5[v_ax0]

        @R.function
        def main(x: R.Tensor((2, 3, 28, 28), dtype="float32"), gamma: R.Tensor((3,), dtype="float32"), beta: R.Tensor((3,), dtype="float32"), moving_mean: R.Tensor((3,), dtype="float32"), moving_var: R.Tensor((3,), dtype="float32")) -> R.Tuple(R.Tensor((2, 3, 28, 28), dtype="float32"), R.Tensor((3,), dtype="float32"), R.Tensor((3,), dtype="float32")):
            cls = Expected
            gv = R.call_tir(cls.batch_norm, (x, gamma, beta, moving_mean, moving_var), out_ty=[R.Tensor((2, 3, 28, 28), dtype="float32"), R.Tensor((3,), dtype="float32"), R.Tensor((3,), dtype="float32")])
            return gv
    # fmt: on

    mod = LegalizeOps()(BatchNorm)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_batch_norm_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class BatchNorm:
        @R.function
        def main(x: R.Tensor(("n", "h", "w", "c"), "float32"), gamma: R.Tensor(("c",), "float32"), beta: R.Tensor(("c",), "float32"), moving_mean: R.Tensor(("c",), "float32"), moving_var: R.Tensor(("c",), "float32")) -> R.Tuple(R.Tensor(("n", "h", "w", "c"), "float32"), R.Tensor(("c",), "float32"), R.Tensor(("c",), "float32")):
            n = T.int64()
            h = T.int64()
            w = T.int64()
            c = T.int64()
            gv: R.Tuple(R.Tensor((n, h, w, c), "float32"), R.Tensor((c,), "float32"), R.Tensor((c,), "float32")) = R.nn.batch_norm(x, gamma, beta, moving_mean, moving_var, axis=1)
            return gv

    @tvm.script.ir_module
    class Expected:
        @T.prim_func(private=True, s_tir=True)
        def batch_norm(var_x: T.handle, var_gamma: T.handle, var_beta: T.handle, var_moving_mean: T.handle, var_moving_var: T.handle, var_T_add: T.handle, var_T_add_1: T.handle, var_T_add_2: T.handle):
            T.func_attr({"tirx.noalias": True})
            n, h, w, c = T.int64(), T.int64(), T.int64(), T.int64()
            x = T.match_buffer(var_x, (n, h, w, c))
            gamma = T.match_buffer(var_gamma, (c,))
            beta = T.match_buffer(var_beta, (c,))
            moving_mean = T.match_buffer(var_moving_mean, (c,))
            moving_var = T.match_buffer(var_moving_var, (c,))
            T_add = T.match_buffer(var_T_add, (n, h, w, c))
            T_add_1 = T.match_buffer(var_T_add_1, (T.max(c, h),))
            T_add_2 = T.match_buffer(var_T_add_2, (T.max(c, h),))
            with T.sblock("root"):
                T.reads()
                T.writes()
                x_red = T.sblock_alloc_buffer((h,))
                T_divide = T.sblock_alloc_buffer((h,))
                T_reshape = T.sblock_alloc_buffer((T.int64(1), h, T.int64(1), T.int64(1)))
                T_subtract = T.sblock_alloc_buffer((n, h, w, c))
                T_subtract_1 = T.sblock_alloc_buffer((n, h, w, c))
                T_subtract_2 = T.sblock_alloc_buffer((n, h, w, c))
                T_multiply = T.sblock_alloc_buffer((n, h, w, c))
                T_multiply_red = T.sblock_alloc_buffer((h,))
                T_divide_1 = T.sblock_alloc_buffer((h,))
                T_reshape_1 = T.sblock_alloc_buffer((T.int64(1), h, T.int64(1), T.int64(1)))
                T_add_3 = T.sblock_alloc_buffer((T.int64(1), h, T.int64(1), T.int64(1)))
                compute = T.sblock_alloc_buffer((T.int64(1), h, T.int64(1), T.int64(1)))
                T_divide_2 = T.sblock_alloc_buffer((n, h, w, c))
                T_reshape_2 = T.sblock_alloc_buffer((T.int64(1), h, T.int64(1), T.int64(1)))
                T_multiply_1 = T.sblock_alloc_buffer((n, h, w, c))
                T_reshape_3 = T.sblock_alloc_buffer((T.int64(1), h, T.int64(1), T.int64(1)))
                T_multiply_2 = T.sblock_alloc_buffer((c,))
                T_multiply_3 = T.sblock_alloc_buffer((h,))
                T_multiply_4 = T.sblock_alloc_buffer((c,))
                T_multiply_5 = T.sblock_alloc_buffer((h,))
                for ax0 in range(h):
                    for k0 in range(n):
                        for k2 in range(w):
                            for k3 in range(c):
                                with T.sblock("x_red"):
                                    v_ax0 = T.axis.spatial(h, ax0)
                                    v_k0 = T.axis.reduce(n, k0)
                                    v_k2 = T.axis.reduce(w, k2)
                                    v_k3 = T.axis.reduce(c, k3)
                                    T.reads(x[v_k0, v_ax0, v_k2, v_k3])
                                    T.writes(x_red[v_ax0])
                                    with T.init():
                                        x_red[v_ax0] = T.float32(0.0)
                                    x_red[v_ax0] = x_red[v_ax0] + x[v_k0, v_ax0, v_k2, v_k3]
                for ax0 in range(h):
                    with T.sblock("T_divide"):
                        v_ax0 = T.axis.spatial(h, ax0)
                        T.reads(x_red[v_ax0])
                        T.writes(T_divide[v_ax0])
                        T_divide[v_ax0] = x_red[v_ax0] / T.Cast("float32", n * w * c)
                for ax0 in range(T.int64(1)):
                    for ax1 in range(h):
                        for ax2 in range(T.int64(1)):
                            for ax3 in range(T.int64(1)):
                                with T.sblock("T_reshape"):
                                    v_ax0 = T.axis.spatial(T.int64(1), ax0)
                                    v_ax1 = T.axis.spatial(h, ax1)
                                    v_ax2 = T.axis.spatial(T.int64(1), ax2)
                                    v_ax3 = T.axis.spatial(T.int64(1), ax3)
                                    T.reads(T_divide[(v_ax0 * h + v_ax1 + v_ax2 + v_ax3) % h])
                                    T.writes(T_reshape[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_reshape[v_ax0, v_ax1, v_ax2, v_ax3] = T_divide[(v_ax0 * h + v_ax1 + v_ax2 + v_ax3) % h]
                for ax0 in range(n):
                    for ax1 in range(h):
                        for ax2 in range(w):
                            for ax3 in range(c):
                                with T.sblock("T_subtract"):
                                    v_ax0 = T.axis.spatial(n, ax0)
                                    v_ax1 = T.axis.spatial(h, ax1)
                                    v_ax2 = T.axis.spatial(w, ax2)
                                    v_ax3 = T.axis.spatial(c, ax3)
                                    T.reads(x[v_ax0, v_ax1, v_ax2, v_ax3], T_reshape[T.int64(0), v_ax1, T.int64(0), T.int64(0)])
                                    T.writes(T_subtract[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_subtract[v_ax0, v_ax1, v_ax2, v_ax3] = x[v_ax0, v_ax1, v_ax2, v_ax3] - T_reshape[T.int64(0), v_ax1, T.int64(0), T.int64(0)]
                for ax0 in range(n):
                    for ax1 in range(h):
                        for ax2 in range(w):
                            for ax3 in range(c):
                                with T.sblock("T_subtract_1"):
                                    v_ax0 = T.axis.spatial(n, ax0)
                                    v_ax1 = T.axis.spatial(h, ax1)
                                    v_ax2 = T.axis.spatial(w, ax2)
                                    v_ax3 = T.axis.spatial(c, ax3)
                                    T.reads(x[v_ax0, v_ax1, v_ax2, v_ax3], T_reshape[T.int64(0), v_ax1, T.int64(0), T.int64(0)])
                                    T.writes(T_subtract_1[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_subtract_1[v_ax0, v_ax1, v_ax2, v_ax3] = x[v_ax0, v_ax1, v_ax2, v_ax3] - T_reshape[T.int64(0), v_ax1, T.int64(0), T.int64(0)]
                for ax0 in range(n):
                    for ax1 in range(h):
                        for ax2 in range(w):
                            for ax3 in range(c):
                                with T.sblock("T_subtract_2"):
                                    v_ax0 = T.axis.spatial(n, ax0)
                                    v_ax1 = T.axis.spatial(h, ax1)
                                    v_ax2 = T.axis.spatial(w, ax2)
                                    v_ax3 = T.axis.spatial(c, ax3)
                                    T.reads(x[v_ax0, v_ax1, v_ax2, v_ax3], T_reshape[T.int64(0), v_ax1, T.int64(0), T.int64(0)])
                                    T.writes(T_subtract_2[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_subtract_2[v_ax0, v_ax1, v_ax2, v_ax3] = x[v_ax0, v_ax1, v_ax2, v_ax3] - T_reshape[T.int64(0), v_ax1, T.int64(0), T.int64(0)]
                for ax0 in range(n):
                    for ax1 in range(h):
                        for ax2 in range(w):
                            for ax3 in range(c):
                                with T.sblock("T_multiply"):
                                    v_ax0 = T.axis.spatial(n, ax0)
                                    v_ax1 = T.axis.spatial(h, ax1)
                                    v_ax2 = T.axis.spatial(w, ax2)
                                    v_ax3 = T.axis.spatial(c, ax3)
                                    T.reads(T_subtract_1[v_ax0, v_ax1, v_ax2, v_ax3], T_subtract_2[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T.writes(T_multiply[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_multiply[v_ax0, v_ax1, v_ax2, v_ax3] = T_subtract_1[v_ax0, v_ax1, v_ax2, v_ax3] * T_subtract_2[v_ax0, v_ax1, v_ax2, v_ax3]
                for ax0 in range(h):
                    for k0 in range(n):
                        for k2 in range(w):
                            for k3 in range(c):
                                with T.sblock("T_multiply_red"):
                                    v_ax0 = T.axis.spatial(h, ax0)
                                    v_k0 = T.axis.reduce(n, k0)
                                    v_k2 = T.axis.reduce(w, k2)
                                    v_k3 = T.axis.reduce(c, k3)
                                    T.reads(T_multiply[v_k0, v_ax0, v_k2, v_k3])
                                    T.writes(T_multiply_red[v_ax0])
                                    with T.init():
                                        T_multiply_red[v_ax0] = T.float32(0.0)
                                    T_multiply_red[v_ax0] = T_multiply_red[v_ax0] + T_multiply[v_k0, v_ax0, v_k2, v_k3]
                for ax0 in range(h):
                    with T.sblock("T_divide_1"):
                        v_ax0 = T.axis.spatial(h, ax0)
                        T.reads(T_multiply_red[v_ax0])
                        T.writes(T_divide_1[v_ax0])
                        T_divide_1[v_ax0] = T_multiply_red[v_ax0] / T.Cast("float32", n * w * c)
                for ax0 in range(T.int64(1)):
                    for ax1 in range(h):
                        for ax2 in range(T.int64(1)):
                            for ax3 in range(T.int64(1)):
                                with T.sblock("T_reshape_1"):
                                    v_ax0 = T.axis.spatial(T.int64(1), ax0)
                                    v_ax1 = T.axis.spatial(h, ax1)
                                    v_ax2 = T.axis.spatial(T.int64(1), ax2)
                                    v_ax3 = T.axis.spatial(T.int64(1), ax3)
                                    T.reads(T_divide_1[(v_ax0 * h + v_ax1 + v_ax2 + v_ax3) % h])
                                    T.writes(T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3] = T_divide_1[(v_ax0 * h + v_ax1 + v_ax2 + v_ax3) % h]
                for ax0 in range(T.int64(1)):
                    for ax1 in range(h):
                        for ax2 in range(T.int64(1)):
                            for ax3 in range(T.int64(1)):
                                with T.sblock("T_add"):
                                    v_ax0 = T.axis.spatial(T.int64(1), ax0)
                                    v_ax1 = T.axis.spatial(h, ax1)
                                    v_ax2 = T.axis.spatial(T.int64(1), ax2)
                                    v_ax3 = T.axis.spatial(T.int64(1), ax3)
                                    T.reads(T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T.writes(T_add_3[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_add_3[v_ax0, v_ax1, v_ax2, v_ax3] = T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3] + T.float32(1.0000000000000001e-05)
                for i0 in range(T.int64(1)):
                    for i1 in range(h):
                        for i2 in range(T.int64(1)):
                            for i3 in range(T.int64(1)):
                                with T.sblock("compute"):
                                    v_i0 = T.axis.spatial(T.int64(1), i0)
                                    v_i1 = T.axis.spatial(h, i1)
                                    v_i2 = T.axis.spatial(T.int64(1), i2)
                                    v_i3 = T.axis.spatial(T.int64(1), i3)
                                    T.reads(T_add_3[v_i0, v_i1, v_i2, v_i3])
                                    T.writes(compute[v_i0, v_i1, v_i2, v_i3])
                                    compute[v_i0, v_i1, v_i2, v_i3] = T.sqrt(T_add_3[v_i0, v_i1, v_i2, v_i3])
                for ax0 in range(n):
                    for ax1 in range(h):
                        for ax2 in range(w):
                            for ax3 in range(c):
                                with T.sblock("T_divide_2"):
                                    v_ax0 = T.axis.spatial(n, ax0)
                                    v_ax1 = T.axis.spatial(h, ax1)
                                    v_ax2 = T.axis.spatial(w, ax2)
                                    v_ax3 = T.axis.spatial(c, ax3)
                                    T.reads(T_subtract[v_ax0, v_ax1, v_ax2, v_ax3], compute[T.int64(0), v_ax1, T.int64(0), T.int64(0)])
                                    T.writes(T_divide_2[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_divide_2[v_ax0, v_ax1, v_ax2, v_ax3] = T_subtract[v_ax0, v_ax1, v_ax2, v_ax3] / compute[T.int64(0), v_ax1, T.int64(0), T.int64(0)]
                for ax0 in range(T.int64(1)):
                    for ax1 in range(h):
                        for ax2 in range(T.int64(1)):
                            for ax3 in range(T.int64(1)):
                                with T.sblock("T_reshape_2"):
                                    v_ax0 = T.axis.spatial(T.int64(1), ax0)
                                    v_ax1 = T.axis.spatial(h, ax1)
                                    v_ax2 = T.axis.spatial(T.int64(1), ax2)
                                    v_ax3 = T.axis.spatial(T.int64(1), ax3)
                                    T.reads(gamma[(v_ax0 * h + v_ax1 + v_ax2 + v_ax3) % c])
                                    T.writes(T_reshape_2[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_reshape_2[v_ax0, v_ax1, v_ax2, v_ax3] = gamma[(v_ax0 * h + v_ax1 + v_ax2 + v_ax3) % c]
                for ax0 in range(n):
                    for ax1 in range(h):
                        for ax2 in range(w):
                            for ax3 in range(c):
                                with T.sblock("T_multiply_1"):
                                    v_ax0 = T.axis.spatial(n, ax0)
                                    v_ax1 = T.axis.spatial(h, ax1)
                                    v_ax2 = T.axis.spatial(w, ax2)
                                    v_ax3 = T.axis.spatial(c, ax3)
                                    T.reads(T_divide_2[v_ax0, v_ax1, v_ax2, v_ax3], T_reshape_2[T.int64(0), v_ax1, T.int64(0), T.int64(0)])
                                    T.writes(T_multiply_1[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_multiply_1[v_ax0, v_ax1, v_ax2, v_ax3] = T_divide_2[v_ax0, v_ax1, v_ax2, v_ax3] * T_reshape_2[T.int64(0), v_ax1, T.int64(0), T.int64(0)]
                for ax0 in range(T.int64(1)):
                    for ax1 in range(h):
                        for ax2 in range(T.int64(1)):
                            for ax3 in range(T.int64(1)):
                                with T.sblock("T_reshape_3"):
                                    v_ax0 = T.axis.spatial(T.int64(1), ax0)
                                    v_ax1 = T.axis.spatial(h, ax1)
                                    v_ax2 = T.axis.spatial(T.int64(1), ax2)
                                    v_ax3 = T.axis.spatial(T.int64(1), ax3)
                                    T.reads(beta[(v_ax0 * h + v_ax1 + v_ax2 + v_ax3) % c])
                                    T.writes(T_reshape_3[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_reshape_3[v_ax0, v_ax1, v_ax2, v_ax3] = beta[(v_ax0 * h + v_ax1 + v_ax2 + v_ax3) % c]
                for ax0 in range(n):
                    for ax1 in range(h):
                        for ax2 in range(w):
                            for ax3 in range(c):
                                with T.sblock("T_add_1"):
                                    v_ax0 = T.axis.spatial(n, ax0)
                                    v_ax1 = T.axis.spatial(h, ax1)
                                    v_ax2 = T.axis.spatial(w, ax2)
                                    v_ax3 = T.axis.spatial(c, ax3)
                                    T.reads(T_multiply_1[v_ax0, v_ax1, v_ax2, v_ax3], T_reshape_3[T.int64(0), v_ax1, T.int64(0), T.int64(0)])
                                    T.writes(T_add[v_ax0, v_ax1, v_ax2, v_ax3])
                                    T_add[v_ax0, v_ax1, v_ax2, v_ax3] = T_multiply_1[v_ax0, v_ax1, v_ax2, v_ax3] + T_reshape_3[T.int64(0), v_ax1, T.int64(0), T.int64(0)]
                for ax0 in range(c):
                    with T.sblock("T_multiply_2"):
                        v_ax0 = T.axis.spatial(c, ax0)
                        T.reads(moving_mean[v_ax0])
                        T.writes(T_multiply_2[v_ax0])
                        T_multiply_2[v_ax0] = T.float32(0.90000000000000002) * moving_mean[v_ax0]
                for ax0 in range(h):
                    with T.sblock("T_multiply_3"):
                        v_ax0 = T.axis.spatial(h, ax0)
                        T.reads(T_divide[v_ax0])
                        T.writes(T_multiply_3[v_ax0])
                        T_multiply_3[v_ax0] = T.float32(0.10000000000000001) * T_divide[v_ax0]
                for ax0 in range(T.max(c, h)):
                    with T.sblock("T_add_2"):
                        v_ax0 = T.axis.spatial(T.max(c, h), ax0)
                        T.reads(T_multiply_2[v_ax0], T_multiply_3[v_ax0])
                        T.writes(T_add_1[v_ax0])
                        T_add_1[v_ax0] = T_multiply_2[v_ax0] + T_multiply_3[v_ax0]
                for ax0 in range(c):
                    with T.sblock("T_multiply_4"):
                        v_ax0 = T.axis.spatial(c, ax0)
                        T.reads(moving_var[v_ax0])
                        T.writes(T_multiply_4[v_ax0])
                        T_multiply_4[v_ax0] = T.float32(0.90000000000000002) * moving_var[v_ax0]
                for ax0 in range(h):
                    with T.sblock("T_multiply_5"):
                        v_ax0 = T.axis.spatial(h, ax0)
                        T.reads(T_divide_1[v_ax0])
                        T.writes(T_multiply_5[v_ax0])
                        T_multiply_5[v_ax0] = T.float32(0.10000000000000001) * T_divide_1[v_ax0]
                for ax0 in range(T.max(c, h)):
                    with T.sblock("T_add_3"):
                        v_ax0 = T.axis.spatial(T.max(c, h), ax0)
                        T.reads(T_multiply_4[v_ax0], T_multiply_5[v_ax0])
                        T.writes(T_add_2[v_ax0])
                        T_add_2[v_ax0] = T_multiply_4[v_ax0] + T_multiply_5[v_ax0]

        @R.function
        def main(x: R.Tensor(("n", "h", "w", "c"), dtype="float32"), gamma: R.Tensor(("c",), dtype="float32"), beta: R.Tensor(("c",), dtype="float32"), moving_mean: R.Tensor(("c",), dtype="float32"), moving_var: R.Tensor(("c",), dtype="float32")) -> R.Tuple(R.Tensor(("n", "h", "w", "c"), dtype="float32"), R.Tensor(("T.max(c, h)",), dtype="float32"), R.Tensor(("T.max(c, h)",), dtype="float32")):
            n = T.int64()
            h = T.int64()
            w = T.int64()
            c = T.int64()
            cls = Expected
            gv = R.call_tir(cls.batch_norm, (x, gamma, beta, moving_mean, moving_var), out_ty=[R.Tensor((n, h, w, c), dtype="float32"), R.Tensor((T.max(c, h),), dtype="float32"), R.Tensor((T.max(c, h),), dtype="float32")])
            return gv

    mod = LegalizeOps()(BatchNorm)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_layer_norm():
    # fmt: off
    @tvm.script.ir_module
    class LayerNorm:
        @R.function
        def main(x: R.Tensor((2, 3, 4, 5), "float32"), gamma: R.Tensor((4, 5), "float32"), beta: R.Tensor((4, 5), "float32")) -> R.Tensor((2, 3, 4, 5), "float32"):
            gv: R.Tensor((2, 3, 4, 5), "float32") = R.nn.layer_norm(x, gamma, beta, axes=[-2, -1])
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 3, 4, 5), "float32"), gamma: R.Tensor((4, 5), "float32"), beta: R.Tensor((4, 5), "float32")) -> R.Tensor((2, 3, 4, 5), "float32"):
            gv = R.call_tir(Expected.layer_norm, (x, gamma, beta), R.Tensor((2, 3, 4, 5), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def layer_norm(x: T.Buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)), "float32"), gamma: T.Buffer((T.int64(4), T.int64(5)), "float32"), beta: T.Buffer((T.int64(4), T.int64(5)), "float32"), T_layer_norm: T.Buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)), "float32")):
            T.func_attr({"tirx.noalias": True})
            # with T.sblock("root"):
            x_sum = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            x_mean = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            x_var_sum = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            for ax0, ax1, k2, k3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("x_sum"):
                    v_ax0, v_ax1, v_k2, v_k3 = T.axis.remap("SSRR", [ax0, ax1, k2, k3])
                    T.reads(x[v_ax0, v_ax1, v_k2, v_k3])
                    T.writes(x_sum[v_ax0, v_ax1])
                    with T.init():
                        x_sum[v_ax0, v_ax1] = T.float32(0.0)
                    x_sum[v_ax0, v_ax1] = x_sum[v_ax0, v_ax1] + x[v_ax0, v_ax1, v_k2, v_k3]
            for ax0, ax1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("x_mean"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(x_sum[v_ax0, v_ax1])
                    T.writes(x_mean[v_ax0, v_ax1])
                    x_mean[v_ax0, v_ax1] = x_sum[v_ax0, v_ax1] / T.float32(20.0)
            for ax0, ax1, k2, k3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("x_var_sum"):
                    v_ax0, v_ax1, v_k2, v_k3 = T.axis.remap("SSRR", [ax0, ax1, k2, k3])
                    T.reads(x[v_ax0, v_ax1, v_k2, v_k3], x_mean[v_ax0, v_ax1])
                    T.writes(x_var_sum[v_ax0, v_ax1])
                    with T.init():
                        x_var_sum[v_ax0, v_ax1] = T.float32(0.0)
                    x_var_sum[v_ax0, v_ax1] = x_var_sum[v_ax0, v_ax1] + (x[v_ax0, v_ax1, v_k2, v_k3] - x_mean[v_ax0, v_ax1]) * (x[v_ax0, v_ax1, v_k2, v_k3] - x_mean[v_ax0, v_ax1])
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("T_layer_norm"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(x[v_ax0, v_ax1, v_ax2, v_ax3], x_mean[v_ax0, v_ax1], x_var_sum[v_ax0, v_ax1], gamma[v_ax2, v_ax3], beta[v_ax2, v_ax3])
                    T.writes(T_layer_norm[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_layer_norm[v_ax0, v_ax1, v_ax2, v_ax3] = (x[v_ax0, v_ax1, v_ax2, v_ax3] - x_mean[v_ax0, v_ax1]) * T.rsqrt(x_var_sum[v_ax0, v_ax1] / T.float32(20.0) + T.float32(1.0000000000000001e-05)) * gamma[v_ax2, v_ax3] + beta[v_ax2, v_ax3]
    # fmt: on
    mod = LegalizeOps()(LayerNorm)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_layer_norm_1d():
    # fmt: off
    @I.ir_module(s_tir=True)
    class LayerNorm_1D:
        @R.function
        def forward(x: R.Tensor((3,), dtype="float32"), layer_norm_weight: R.Tensor((3,), dtype="float32"), layer_norm_bias: R.Tensor((3,), dtype="float32")) -> R.Tensor((3,), dtype="float32"):
            R.func_attr({"num_input": 1})
            with R.dataflow():
                layer_norm: R.Tensor((3,), dtype="float32") = R.nn.layer_norm(x, layer_norm_weight, layer_norm_bias, axes=[-1], epsilon=1.0000000000000001e-05, center=True, scale=True)
                gv: R.Tensor((3,), dtype="float32") = layer_norm
                R.output(gv)
            return gv

    @I.ir_module(s_tir=True)
    class LayerNorm_1D_Expected:
        @T.prim_func(private=True, s_tir=True)
        def layer_norm(x: T.Buffer((T.int64(3),), "float32"), layer_norm_weight: T.Buffer((T.int64(3),), "float32"), layer_norm_bias: T.Buffer((T.int64(3),), "float32"), T_layer_norm: T.Buffer((T.int64(3),), "float32")):
            T.func_attr({"tirx.noalias": True})
            # with T.sblock("root"):
            x_sum = T.sblock_alloc_buffer(())
            x_mean = T.sblock_alloc_buffer(())
            x_var_sum = T.sblock_alloc_buffer(())
            for k0 in range(T.int64(3)):
                with T.sblock("x_sum"):
                    v_k0 = T.axis.reduce(T.int64(3), k0)
                    T.reads(x[v_k0])
                    T.writes(x_sum[()])
                    with T.init():
                        x_sum[()] = T.float32(0.0)
                    x_sum[()] = x_sum[()] + x[v_k0]
            with T.sblock("x_mean"):
                vi = T.axis.spatial(1, T.int64(0))
                T.reads(x_sum[()])
                T.writes(x_mean[()])
                x_mean[()] = x_sum[()] / T.float32(3.0)
            for k0 in range(T.int64(3)):
                with T.sblock("x_var_sum"):
                    v_k0 = T.axis.reduce(T.int64(3), k0)
                    T.reads(x[v_k0], x_mean[()])
                    T.writes(x_var_sum[()])
                    with T.init():
                        x_var_sum[()] = T.float32(0.0)
                    x_var_sum[()] = x_var_sum[()] + (x[v_k0] - x_mean[()]) * (x[v_k0] - x_mean[()])
            for ax0 in range(T.int64(3)):
                with T.sblock("T_layer_norm"):
                    v_ax0 = T.axis.spatial(T.int64(3), ax0)
                    T.reads(x[v_ax0], x_mean[()], x_var_sum[()], layer_norm_weight[v_ax0], layer_norm_bias[v_ax0])
                    T.writes(T_layer_norm[v_ax0])
                    T_layer_norm[v_ax0] = (x[v_ax0] - x_mean[()]) * T.rsqrt(x_var_sum[()] / T.float32(3.0) + T.float32(1.0000000000000001e-05)) * layer_norm_weight[v_ax0] + layer_norm_bias[v_ax0]

        @R.function
        def forward(x: R.Tensor((3,), dtype="float32"), layer_norm_weight: R.Tensor((3,), dtype="float32"), layer_norm_bias: R.Tensor((3,), dtype="float32")) -> R.Tensor((3,), dtype="float32"):
            R.func_attr({"num_input": 1})
            cls = LayerNorm_1D_Expected
            with R.dataflow():
                layer_norm = R.call_tir(cls.layer_norm, (x, layer_norm_weight, layer_norm_bias), out_ty=R.Tensor((3,), dtype="float32"))
                gv: R.Tensor((3,), dtype="float32") = layer_norm
                R.output(gv)
            return gv
    # fmt: on
    mod = LegalizeOps()(LayerNorm_1D)
    tvm.ir.assert_structural_equal(mod, LayerNorm_1D_Expected)


def test_layer_norm_fp16():
    # fmt: off
    @tvm.script.ir_module
    class LayerNorm:
        @R.function
        def main(x: R.Tensor((2, 3, 4, 5), "float16"), gamma: R.Tensor((4, 5), "float16"), beta: R.Tensor((4, 5), "float16")) -> R.Tensor((2, 3, 4, 5), "float16"):
            gv: R.Tensor((2, 3, 4, 5), "float16") = R.nn.layer_norm(x, gamma, beta, axes=[-2, -1])
            return gv

    @I.ir_module(s_tir=True)
    class Expected:
        @T.prim_func(private=True, s_tir=True)
        def layer_norm(
            x: T.Buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)), "float16"),
            gamma: T.Buffer((T.int64(4), T.int64(5)), "float16"),
            beta: T.Buffer((T.int64(4), T.int64(5)), "float16"),
            T_layer_norm: T.Buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)), "float16"),
        ):
            T.func_attr({"tirx.noalias": True})
            # with T.sblock("root"):
            x_sum = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            x_mean = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            x_var_sum = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            for ax0, ax1, k2, k3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("x_sum"):
                    v_ax0, v_ax1, v_k2, v_k3 = T.axis.remap("SSRR", [ax0, ax1, k2, k3])
                    T.reads(x[v_ax0, v_ax1, v_k2, v_k3])
                    T.writes(x_sum[v_ax0, v_ax1])
                    with T.init():
                        x_sum[v_ax0, v_ax1] = T.float32(0.0)
                    x_sum[v_ax0, v_ax1] = x_sum[v_ax0, v_ax1] + T.Cast("float32", x[v_ax0, v_ax1, v_k2, v_k3])
            for ax0, ax1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("x_mean"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(x_sum[v_ax0, v_ax1])
                    T.writes(x_mean[v_ax0, v_ax1])
                    x_mean[v_ax0, v_ax1] = x_sum[v_ax0, v_ax1] / T.float32(20.0)
            for ax0, ax1, k2, k3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("x_var_sum"):
                    v_ax0, v_ax1, v_k2, v_k3 = T.axis.remap("SSRR", [ax0, ax1, k2, k3])
                    T.reads(x[v_ax0, v_ax1, v_k2, v_k3], x_mean[v_ax0, v_ax1])
                    T.writes(x_var_sum[v_ax0, v_ax1])
                    with T.init():
                        x_var_sum[v_ax0, v_ax1] = T.float32(0.0)
                    x_var_sum[v_ax0, v_ax1] = x_var_sum[v_ax0, v_ax1] + (T.Cast("float32", x[v_ax0, v_ax1, v_k2, v_k3]) - x_mean[v_ax0, v_ax1]) * (T.Cast("float32", x[v_ax0, v_ax1, v_k2, v_k3]) - x_mean[v_ax0, v_ax1])
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("T_layer_norm"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(x[v_ax0, v_ax1, v_ax2, v_ax3], x_mean[v_ax0, v_ax1], x_var_sum[v_ax0, v_ax1], gamma[v_ax2, v_ax3], beta[v_ax2, v_ax3])
                    T.writes(T_layer_norm[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_layer_norm[v_ax0, v_ax1, v_ax2, v_ax3] = T.Cast("float16", (T.Cast("float32", x[v_ax0, v_ax1, v_ax2, v_ax3]) - x_mean[v_ax0, v_ax1]) * T.rsqrt(x_var_sum[v_ax0, v_ax1] / T.float32(20.0) + T.float32(1.0000000000000001e-05))) * gamma[v_ax2, v_ax3] + beta[v_ax2, v_ax3]

        @R.function
        def main(x: R.Tensor((2, 3, 4, 5), dtype="float16"), gamma: R.Tensor((4, 5), dtype="float16"), beta: R.Tensor((4, 5), dtype="float16")) -> R.Tensor((2, 3, 4, 5), dtype="float16"):
            gv = R.call_tir(Expected.layer_norm, (x, gamma, beta), out_ty=R.Tensor((2, 3, 4, 5), dtype="float16"))
            return gv
    # fmt: on
    mod = LegalizeOps()(LayerNorm)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_layer_norm_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class LayerNorm:
        @R.function
        def main(x: R.Tensor(("n", "s", "f"), "float32"), gamma: R.Tensor(("s", "f"), "float32"), beta: R.Tensor(("s", "f"), "float32")) -> R.Tensor(("n", "s", "f"), "float32"):
            n = T.int64()
            s = T.int64()
            f = T.int64()
            gv: R.Tensor((n, s, f), "float32") = R.nn.layer_norm(x, gamma, beta, axes=[1, 2])
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor(("n", "s", "f"), "float32"), gamma: R.Tensor(("s", "f"), "float32"), beta: R.Tensor(("s", "f"), "float32")) -> R.Tensor(("n", "s", "f"), "float32"):
            n = T.int64()
            s = T.int64()
            f = T.int64()
            gv = R.call_tir(Expected.layer_norm, (x, gamma, beta), R.Tensor((n, s, f), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def layer_norm(var_x: T.handle, var_gamma: T.handle, var_beta: T.handle, var_T_layer_norm: T.handle):
            T.func_attr({"tirx.noalias": True})
            n, s, f = T.int64(), T.int64(), T.int64()
            x = T.match_buffer(var_x, (n, s, f))
            gamma = T.match_buffer(var_gamma, (s, f))
            beta = T.match_buffer(var_beta, (s, f))
            T_layer_norm = T.match_buffer(var_T_layer_norm, (n, s, f))
            # with T.sblock("root"):
            x_sum = T.sblock_alloc_buffer((n,))
            x_mean = T.sblock_alloc_buffer((n,))
            x_var_sum = T.sblock_alloc_buffer((n,))
            for ax0, k1, k2 in T.grid(n, s, f):
                with T.sblock("x_sum"):
                    v_ax0, v_k1, v_k2 = T.axis.remap("SRR", [ax0, k1, k2])
                    T.reads(x[v_ax0, v_k1, v_k2])
                    T.writes(x_sum[v_ax0])
                    with T.init():
                        x_sum[v_ax0] = T.float32(0.0)
                    x_sum[v_ax0] = x_sum[v_ax0] + x[v_ax0, v_k1, v_k2]
            for ax0 in range(n):
                with T.sblock("x_mean"):
                    v_ax0 = T.axis.spatial(n, ax0)
                    T.reads(x_sum[v_ax0])
                    T.writes(x_mean[v_ax0])
                    x_mean[v_ax0] = x_sum[v_ax0] / (T.Cast("float32", s) * T.Cast("float32", f))
            for ax0, k1, k2 in T.grid(n, s, f):
                with T.sblock("x_var_sum"):
                    v_ax0, v_k1, v_k2 = T.axis.remap("SRR", [ax0, k1, k2])
                    T.reads(x[v_ax0, v_k1, v_k2], x_mean[v_ax0])
                    T.writes(x_var_sum[v_ax0])
                    with T.init():
                        x_var_sum[v_ax0] = T.float32(0.0)
                    x_var_sum[v_ax0] = x_var_sum[v_ax0] + (x[v_ax0, v_k1, v_k2] - x_mean[v_ax0]) * (x[v_ax0, v_k1, v_k2] - x_mean[v_ax0])
            for ax0, ax1, ax2 in T.grid(n, s, f):
                with T.sblock("T_layer_norm"):
                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
                    T.reads(x[v_ax0, v_ax1, v_ax2], x_mean[v_ax0], x_var_sum[v_ax0], gamma[v_ax1, v_ax2], beta[v_ax1, v_ax2])
                    T.writes(T_layer_norm[v_ax0, v_ax1, v_ax2])
                    T_layer_norm[v_ax0, v_ax1, v_ax2] = (x[v_ax0, v_ax1, v_ax2] - x_mean[v_ax0]) * T.rsqrt(x_var_sum[v_ax0] / (T.Cast("float32", s) * T.Cast("float32", f)) + T.float32(1.0000000000000001e-05)) * gamma[v_ax1, v_ax2] + beta[v_ax1, v_ax2]
    # fmt: on
    mod = LegalizeOps()(LayerNorm)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_group_norm():
    # fmt: off
    @tvm.script.ir_module
    class GroupNorm:
        @R.function
        def main(x: R.Tensor((2, 4, 4, 5), "float32"), gamma: R.Tensor((4,), "float32"), beta: R.Tensor((4,), "float32")) -> R.Tensor((2, 4, 4, 5), "float32"):
            gv: R.Tensor((2, 4, 4, 5), "float32") = R.nn.group_norm(x, gamma, beta, num_groups=2, channel_axis=1, axes=[2, 3])
            return gv

    @tvm.script.ir_module
    class Expected:
        @T.prim_func(private=True, s_tir=True)
        def group_norm(rxplaceholder: T.Buffer((T.int64(2), T.int64(4), T.int64(4), T.int64(5)), "float32"), rxplaceholder_1: T.Buffer((T.int64(4),), "float32"), rxplaceholder_2: T.Buffer((T.int64(4),), "float32"), T_reshape: T.Buffer((T.int64(2), T.int64(4), T.int64(4), T.int64(5)), "float32")):
            T.func_attr({"tirx.noalias": True})
            T_reshape_1 = T.sblock_alloc_buffer((T.int64(2), T.int64(2), T.int64(2), T.int64(4), T.int64(5)))
            rxplaceholder_red_temp_v0 = T.sblock_alloc_buffer((T.int64(2), T.int64(2)))
            rxplaceholder_red_temp_v1 = T.sblock_alloc_buffer((T.int64(2), T.int64(2)))
            T_reshape_2 = T.sblock_alloc_buffer((T.int64(2), T.int64(2)))
            T_reshape_3 = T.sblock_alloc_buffer((T.int64(2), T.int64(2)))
            T_group_norm = T.sblock_alloc_buffer((T.int64(2), T.int64(2), T.int64(2), T.int64(4), T.int64(5)))
            for ax0, ax1, ax2, ax3, ax4 in T.grid(T.int64(2), T.int64(2), T.int64(2), T.int64(4), T.int64(5)):
                with T.sblock("T_reshape"):
                    v_ax0, v_ax1, v_ax2, v_ax3, v_ax4 = T.axis.remap("SSSSS", [ax0, ax1, ax2, ax3, ax4])
                    T.reads(rxplaceholder[((v_ax1 * T.int64(2) + (v_ax4 // T.int64(5) + v_ax3) // T.int64(4) + v_ax2) // T.int64(4) + v_ax0) % T.int64(2), (v_ax1 * T.int64(2) + (v_ax4 // T.int64(5) + v_ax3) // T.int64(4) + v_ax2) % T.int64(4), (v_ax4 // T.int64(5) + v_ax3) % T.int64(4), v_ax4 % T.int64(5)])
                    T.writes(T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4])
                    T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4] = rxplaceholder[((v_ax1 * T.int64(2) + (v_ax4 // T.int64(5) + v_ax3) // T.int64(4) + v_ax2) // T.int64(4) + v_ax0) % T.int64(2), (v_ax1 * T.int64(2) + (v_ax4 // T.int64(5) + v_ax3) // T.int64(4) + v_ax2) % T.int64(4), (v_ax4 // T.int64(5) + v_ax3) % T.int64(4), v_ax4 % T.int64(5)]
            for ax0, ax1, k2, k3, k4 in T.grid(T.int64(2), T.int64(2), T.int64(2), T.int64(4), T.int64(5)):
                with T.sblock("rxplaceholder_red_temp"):
                    v_ax0, v_ax1, v_k2, v_k3, v_k4 = T.axis.remap("SSRRR", [ax0, ax1, k2, k3, k4])
                    T.reads(T_reshape_1[v_ax0, v_ax1, v_k2, v_k3, v_k4])
                    T.writes(rxplaceholder_red_temp_v0[v_ax0, v_ax1], rxplaceholder_red_temp_v1[v_ax0, v_ax1])
                    with T.init():
                        rxplaceholder_red_temp_v0[v_ax0, v_ax1] = T.float32(0)
                        rxplaceholder_red_temp_v1[v_ax0, v_ax1] = T.float32(0)
                    v_rxplaceholder_red_temp_v0: T.let[T.float32] = rxplaceholder_red_temp_v0[v_ax0, v_ax1] + T_reshape_1[v_ax0, v_ax1, v_k2, v_k3, v_k4]
                    v_rxplaceholder_red_temp_v1: T.let[T.float32] = rxplaceholder_red_temp_v1[v_ax0, v_ax1] + T_reshape_1[v_ax0, v_ax1, v_k2, v_k3, v_k4] * T_reshape_1[v_ax0, v_ax1, v_k2, v_k3, v_k4]
                    rxplaceholder_red_temp_v0[v_ax0, v_ax1] = v_rxplaceholder_red_temp_v0
                    rxplaceholder_red_temp_v1[v_ax0, v_ax1] = v_rxplaceholder_red_temp_v1
            for ax0, ax1 in T.grid(T.int64(2), T.int64(2)):
                with T.sblock("T_reshape_1"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(rxplaceholder_1[(v_ax0 * T.int64(2) + v_ax1) % T.int64(4)])
                    T.writes(T_reshape_2[v_ax0, v_ax1])
                    T_reshape_2[v_ax0, v_ax1] = rxplaceholder_1[(v_ax0 * T.int64(2) + v_ax1) % T.int64(4)]
            for ax0, ax1 in T.grid(T.int64(2), T.int64(2)):
                with T.sblock("T_reshape_2"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(rxplaceholder_2[(v_ax0 * T.int64(2) + v_ax1) % T.int64(4)])
                    T.writes(T_reshape_3[v_ax0, v_ax1])
                    T_reshape_3[v_ax0, v_ax1] = rxplaceholder_2[(v_ax0 * T.int64(2) + v_ax1) % T.int64(4)]
            for ax0, ax1, ax2, ax3, ax4 in T.grid(T.int64(2), T.int64(2), T.int64(2), T.int64(4), T.int64(5)):
                with T.sblock("T_group_norm"):
                    v_ax0, v_ax1, v_ax2, v_ax3, v_ax4 = T.axis.remap("SSSSS", [ax0, ax1, ax2, ax3, ax4])
                    T.reads(T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4], rxplaceholder_red_temp_v0[v_ax0, v_ax1], rxplaceholder_red_temp_v1[v_ax0, v_ax1], T_reshape_2[v_ax1, v_ax2], T_reshape_3[v_ax1, v_ax2])
                    T.writes(T_group_norm[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4])
                    T_group_norm[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4] = (T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4] - rxplaceholder_red_temp_v0[v_ax0, v_ax1] / T.float32(40)) * T.rsqrt(rxplaceholder_red_temp_v1[v_ax0, v_ax1] / T.float32(40) - rxplaceholder_red_temp_v0[v_ax0, v_ax1] / T.float32(40) * (rxplaceholder_red_temp_v0[v_ax0, v_ax1] / T.float32(40)) + T.float32(1.0000000000000001e-05)) * T_reshape_2[v_ax1, v_ax2] + T_reshape_3[v_ax1, v_ax2]
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(2), T.int64(4), T.int64(4), T.int64(5)):
                with T.sblock("T_reshape_3"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(T_group_norm[(((v_ax3 // T.int64(5) + v_ax2) // T.int64(4) + v_ax1) // T.int64(4) + v_ax0) % T.int64(2), ((v_ax3 // T.int64(5) + v_ax2) // T.int64(4) + v_ax1) % T.int64(4) // T.int64(2), ((v_ax3 // T.int64(5) + v_ax2) // T.int64(4) + v_ax1) % T.int64(2), (v_ax3 // T.int64(5) + v_ax2) % T.int64(4), v_ax3 % T.int64(5)])
                    T.writes(T_reshape[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_reshape[v_ax0, v_ax1, v_ax2, v_ax3] = T_group_norm[(((v_ax3 // T.int64(5) + v_ax2) // T.int64(4) + v_ax1) // T.int64(4) + v_ax0) % T.int64(2), ((v_ax3 // T.int64(5) + v_ax2) // T.int64(4) + v_ax1) % T.int64(4) // T.int64(2), ((v_ax3 // T.int64(5) + v_ax2) // T.int64(4) + v_ax1) % T.int64(2), (v_ax3 // T.int64(5) + v_ax2) % T.int64(4), v_ax3 % T.int64(5)]

        @R.function
        def main(x: R.Tensor((2, 4, 4, 5), dtype="float32"), gamma: R.Tensor((4,), dtype="float32"), beta: R.Tensor((4,), dtype="float32")) -> R.Tensor((2, 4, 4, 5), dtype="float32"):
            gv = R.call_tir(Expected.group_norm, (x, gamma, beta), out_ty=R.Tensor((2, 4, 4, 5), dtype="float32"))
            return gv
    # fmt: on
    mod = LegalizeOps()(GroupNorm)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_group_norm_fp16():
    # fmt: off
    @tvm.script.ir_module
    class GroupNorm:
        @R.function
        def main(x: R.Tensor((2, 4, 4, 5), "float16"), gamma: R.Tensor((4,), "float16"), beta: R.Tensor((4,), "float16")) -> R.Tensor((2, 4, 4, 5), "float16"):
            gv: R.Tensor((2, 4, 4, 5), "float16") = R.nn.group_norm(x, gamma, beta, num_groups=2, channel_axis=1, axes=[2, 3])
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 4, 4, 5), dtype="float16"), gamma: R.Tensor((4,), dtype="float16"), beta: R.Tensor((4,), dtype="float16")) -> R.Tensor((2, 4, 4, 5), dtype="float16"):
            gv = R.call_tir(Expected.group_norm, (x, gamma, beta), out_ty=R.Tensor((2, 4, 4, 5), dtype="float16"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def group_norm(rxplaceholder: T.Buffer((T.int64(2), T.int64(4), T.int64(4), T.int64(5)), "float16"), rxplaceholder_1: T.Buffer((T.int64(4),), "float16"), rxplaceholder_2: T.Buffer((T.int64(4),), "float16"), T_reshape: T.Buffer((T.int64(2), T.int64(4), T.int64(4), T.int64(5)), "float16")):
            T.func_attr({"tirx.noalias": True})
            # with T.sblock("root"):
            T_reshape_1 = T.sblock_alloc_buffer((T.int64(2), T.int64(2), T.int64(2), T.int64(4), T.int64(5)), "float16")
            T_cast = T.sblock_alloc_buffer((T.int64(2), T.int64(2), T.int64(2), T.int64(4), T.int64(5)))
            rxplaceholder_red_temp_v0 = T.sblock_alloc_buffer((T.int64(2), T.int64(2)))
            rxplaceholder_red_temp_v1 = T.sblock_alloc_buffer((T.int64(2), T.int64(2)))
            T_reshape_2 = T.sblock_alloc_buffer((T.int64(2), T.int64(2)), "float16")
            T_reshape_3 = T.sblock_alloc_buffer((T.int64(2), T.int64(2)), "float16")
            T_group_norm = T.sblock_alloc_buffer((T.int64(2), T.int64(2), T.int64(2), T.int64(4), T.int64(5)), "float16")
            for ax0, ax1, ax2, ax3, ax4 in T.grid(T.int64(2), T.int64(2), T.int64(2), T.int64(4), T.int64(5)):
                with T.sblock("T_reshape"):
                    v_ax0, v_ax1, v_ax2, v_ax3, v_ax4 = T.axis.remap("SSSSS", [ax0, ax1, ax2, ax3, ax4])
                    T.reads(rxplaceholder[((v_ax1 * T.int64(2) + (v_ax4 // T.int64(5) + v_ax3) // T.int64(4) + v_ax2) // T.int64(4) + v_ax0) % T.int64(2), (v_ax1 * T.int64(2) + (v_ax4 // T.int64(5) + v_ax3) // T.int64(4) + v_ax2) % T.int64(4), (v_ax4 // T.int64(5) + v_ax3) % T.int64(4), v_ax4 % T.int64(5)])
                    T.writes(T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4])
                    T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4] = rxplaceholder[((v_ax1 * T.int64(2) + (v_ax4 // T.int64(5) + v_ax3) // T.int64(4) + v_ax2) // T.int64(4) + v_ax0) % T.int64(2), (v_ax1 * T.int64(2) + (v_ax4 // T.int64(5) + v_ax3) // T.int64(4) + v_ax2) % T.int64(4), (v_ax4 // T.int64(5) + v_ax3) % T.int64(4), v_ax4 % T.int64(5)]
            for ax0, ax1, ax2, ax3, ax4 in T.grid(T.int64(2), T.int64(2), T.int64(2), T.int64(4), T.int64(5)):
                with T.sblock("T_cast"):
                    v_ax0, v_ax1, v_ax2, v_ax3, v_ax4 = T.axis.remap("SSSSS", [ax0, ax1, ax2, ax3, ax4])
                    T.reads(T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4])
                    T.writes(T_cast[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4])
                    T_cast[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4] = T.Cast("float32", T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4])
            for ax0, ax1, k2, k3, k4 in T.grid(T.int64(2), T.int64(2), T.int64(2), T.int64(4), T.int64(5)):
                with T.sblock("rxplaceholder_red_temp"):
                    v_ax0, v_ax1, v_k2, v_k3, v_k4 = T.axis.remap("SSRRR", [ax0, ax1, k2, k3, k4])
                    T.reads(T_cast[v_ax0, v_ax1, v_k2, v_k3, v_k4])
                    T.writes(rxplaceholder_red_temp_v0[v_ax0, v_ax1], rxplaceholder_red_temp_v1[v_ax0, v_ax1])
                    with T.init():
                        rxplaceholder_red_temp_v0[v_ax0, v_ax1] = T.float32(0)
                        rxplaceholder_red_temp_v1[v_ax0, v_ax1] = T.float32(0)
                    v_rxplaceholder_red_temp_v0: T.let[T.float32] = rxplaceholder_red_temp_v0[v_ax0, v_ax1] + T_cast[v_ax0, v_ax1, v_k2, v_k3, v_k4]
                    v_rxplaceholder_red_temp_v1: T.let[T.float32] = rxplaceholder_red_temp_v1[v_ax0, v_ax1] + T_cast[v_ax0, v_ax1, v_k2, v_k3, v_k4] * T_cast[v_ax0, v_ax1, v_k2, v_k3, v_k4]
                    rxplaceholder_red_temp_v0[v_ax0, v_ax1] = v_rxplaceholder_red_temp_v0
                    rxplaceholder_red_temp_v1[v_ax0, v_ax1] = v_rxplaceholder_red_temp_v1
            for ax0, ax1 in T.grid(T.int64(2), T.int64(2)):
                with T.sblock("T_reshape_1"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(rxplaceholder_1[(v_ax0 * T.int64(2) + v_ax1) % T.int64(4)])
                    T.writes(T_reshape_2[v_ax0, v_ax1])
                    T_reshape_2[v_ax0, v_ax1] = rxplaceholder_1[(v_ax0 * T.int64(2) + v_ax1) % T.int64(4)]
            for ax0, ax1 in T.grid(T.int64(2), T.int64(2)):
                with T.sblock("T_reshape_2"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(rxplaceholder_2[(v_ax0 * T.int64(2) + v_ax1) % T.int64(4)])
                    T.writes(T_reshape_3[v_ax0, v_ax1])
                    T_reshape_3[v_ax0, v_ax1] = rxplaceholder_2[(v_ax0 * T.int64(2) + v_ax1) % T.int64(4)]
            for ax0, ax1, ax2, ax3, ax4 in T.grid(T.int64(2), T.int64(2), T.int64(2), T.int64(4), T.int64(5)):
                with T.sblock("T_group_norm"):
                    v_ax0, v_ax1, v_ax2, v_ax3, v_ax4 = T.axis.remap("SSSSS", [ax0, ax1, ax2, ax3, ax4])
                    T.reads(T_cast[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4], rxplaceholder_red_temp_v0[v_ax0, v_ax1], rxplaceholder_red_temp_v1[v_ax0, v_ax1], T_reshape_2[v_ax1, v_ax2], T_reshape_3[v_ax1, v_ax2])
                    T.writes(T_group_norm[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4])
                    T_group_norm[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4] = T.Cast("float16", (T_cast[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4] - rxplaceholder_red_temp_v0[v_ax0, v_ax1] / T.float32(40)) * T.rsqrt(rxplaceholder_red_temp_v1[v_ax0, v_ax1] / T.float32(40) - rxplaceholder_red_temp_v0[v_ax0, v_ax1] / T.float32(40) * (rxplaceholder_red_temp_v0[v_ax0, v_ax1] / T.float32(40)) + T.float32(1.0000000000000001e-05))) * T_reshape_2[v_ax1, v_ax2] + T_reshape_3[v_ax1, v_ax2]
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(2), T.int64(4), T.int64(4), T.int64(5)):
                with T.sblock("T_reshape_3"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(T_group_norm[(((v_ax3 // T.int64(5) + v_ax2) // T.int64(4) + v_ax1) // T.int64(4) + v_ax0) % T.int64(2), ((v_ax3 // T.int64(5) + v_ax2) // T.int64(4) + v_ax1) % T.int64(4) // T.int64(2), ((v_ax3 // T.int64(5) + v_ax2) // T.int64(4) + v_ax1) % T.int64(2), (v_ax3 // T.int64(5) + v_ax2) % T.int64(4), v_ax3 % T.int64(5)])
                    T.writes(T_reshape[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_reshape[v_ax0, v_ax1, v_ax2, v_ax3] = T_group_norm[(((v_ax3 // T.int64(5) + v_ax2) // T.int64(4) + v_ax1) // T.int64(4) + v_ax0) % T.int64(2), ((v_ax3 // T.int64(5) + v_ax2) // T.int64(4) + v_ax1) % T.int64(4) // T.int64(2), ((v_ax3 // T.int64(5) + v_ax2) // T.int64(4) + v_ax1) % T.int64(2), (v_ax3 // T.int64(5) + v_ax2) % T.int64(4), v_ax3 % T.int64(5)]
    # fmt: on

    mod = LegalizeOps()(GroupNorm)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_group_norm_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class GroupNorm:
        @R.function
        def main(s: R.Shape(["c"]), x: R.Tensor(("n", "4 * c", "h", "w"), "float32"), gamma: R.Tensor(("4 * c",), "float32"), beta: R.Tensor(("4 * c",), "float32")) -> R.Tensor(("n", "4 * c", "h", "w"), "float32"):
            n = T.int64()
            c = T.int64()
            h = T.int64()
            w = T.int64()
            gv: R.Tensor((n, 4 * c, h, w), "float32") = R.nn.group_norm(x, gamma, beta, num_groups=4, channel_axis=1, axes=[2, 3])
            return gv

    @tvm.script.ir_module
    class Expected:
        @T.prim_func(private=True, s_tir=True)
        def group_norm(var_rxplaceholder: T.handle, var_rxplaceholder_1: T.handle, var_rxplaceholder_2: T.handle, c: T.int64, var_T_reshape: T.handle):
            T.func_attr({"tirx.noalias": True})
            n = T.int64()
            h = T.int64()
            w = T.int64()
            rxplaceholder = T.match_buffer(var_rxplaceholder, (n, T.int64(4) * c, h, w))
            rxplaceholder_1 = T.match_buffer(var_rxplaceholder_1, (T.int64(4) * c,))
            rxplaceholder_2 = T.match_buffer(var_rxplaceholder_2, (T.int64(4) * c,))
            T_reshape = T.match_buffer(var_T_reshape, (n, T.int64(4) * c, h, w))
            # with T.sblock("root"):
            T_reshape_1 = T.sblock_alloc_buffer((n, T.int64(4), T.int64(4) * c // T.int64(4), h, w))
            rxplaceholder_red_temp_v0 = T.sblock_alloc_buffer((n, T.int64(4)))
            rxplaceholder_red_temp_v1 = T.sblock_alloc_buffer((n, T.int64(4)))
            T_reshape_2 = T.sblock_alloc_buffer((T.int64(4), T.int64(4) * c // T.int64(4)))
            T_reshape_3 = T.sblock_alloc_buffer((T.int64(4), T.int64(4) * c // T.int64(4)))
            T_group_norm = T.sblock_alloc_buffer((n, T.int64(4), T.int64(4) * c // T.int64(4), h, w))
            for ax0, ax1, ax2, ax3, ax4 in T.grid(n, T.int64(4), c, h, w):
                with T.sblock("T_reshape"):
                    v_ax0, v_ax1, v_ax2, v_ax3, v_ax4 = T.axis.remap("SSSSS", [ax0, ax1, ax2, ax3, ax4])
                    T.reads(rxplaceholder[((((v_ax0 * T.int64(4) + v_ax1) * c + v_ax2) * h + v_ax3) * w + v_ax4) // w // h // (c * T.int64(4)) % n, ((((v_ax0 * T.int64(4) + v_ax1) * c + v_ax2) * h + v_ax3) * w + v_ax4) // w // h % (c * T.int64(4)), ((((v_ax0 * T.int64(4) + v_ax1) * c + v_ax2) * h + v_ax3) * w + v_ax4) // w % h, ((((v_ax0 * T.int64(4) + v_ax1) * c + v_ax2) * h + v_ax3) * w + v_ax4) % w])
                    T.writes(T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4])
                    T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4] = rxplaceholder[((((v_ax0 * T.int64(4) + v_ax1) * c + v_ax2) * h + v_ax3) * w + v_ax4) // w // h // (c * T.int64(4)) % n, ((((v_ax0 * T.int64(4) + v_ax1) * c + v_ax2) * h + v_ax3) * w + v_ax4) // w // h % (c * T.int64(4)), ((((v_ax0 * T.int64(4) + v_ax1) * c + v_ax2) * h + v_ax3) * w + v_ax4) // w % h, ((((v_ax0 * T.int64(4) + v_ax1) * c + v_ax2) * h + v_ax3) * w + v_ax4) % w]
            for ax0, ax1, k2, k3, k4 in T.grid(n, T.int64(4), c, h, w):
                with T.sblock("rxplaceholder_red_temp"):
                    v_ax0, v_ax1, v_k2, v_k3, v_k4 = T.axis.remap("SSRRR", [ax0, ax1, k2, k3, k4])
                    T.reads(T_reshape_1[v_ax0, v_ax1, v_k2, v_k3, v_k4])
                    T.writes(rxplaceholder_red_temp_v0[v_ax0, v_ax1], rxplaceholder_red_temp_v1[v_ax0, v_ax1])
                    with T.init():
                        rxplaceholder_red_temp_v0[v_ax0, v_ax1] = T.float32(0)
                        rxplaceholder_red_temp_v1[v_ax0, v_ax1] = T.float32(0)
                    v_rxplaceholder_red_temp_v0: T.let[T.float32] = rxplaceholder_red_temp_v0[v_ax0, v_ax1] + T_reshape_1[v_ax0, v_ax1, v_k2, v_k3, v_k4]
                    v_rxplaceholder_red_temp_v1: T.let[T.float32] = rxplaceholder_red_temp_v1[v_ax0, v_ax1] + T_reshape_1[v_ax0, v_ax1, v_k2, v_k3, v_k4] * T_reshape_1[v_ax0, v_ax1, v_k2, v_k3, v_k4]
                    rxplaceholder_red_temp_v0[v_ax0, v_ax1] = v_rxplaceholder_red_temp_v0
                    rxplaceholder_red_temp_v1[v_ax0, v_ax1] = v_rxplaceholder_red_temp_v1
            for ax0, ax1 in T.grid(T.int64(4), c):
                with T.sblock("T_reshape_1"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(rxplaceholder_1[(v_ax0 * c + v_ax1) % (c * T.int64(4))])
                    T.writes(T_reshape_2[v_ax0, v_ax1])
                    T_reshape_2[v_ax0, v_ax1] = rxplaceholder_1[(v_ax0 * c + v_ax1) % (c * T.int64(4))]
            for ax0, ax1 in T.grid(T.int64(4), c):
                with T.sblock("T_reshape_2"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(rxplaceholder_2[(v_ax0 * c + v_ax1) % (c * T.int64(4))])
                    T.writes(T_reshape_3[v_ax0, v_ax1])
                    T_reshape_3[v_ax0, v_ax1] = rxplaceholder_2[(v_ax0 * c + v_ax1) % (c * T.int64(4))]
            for ax0, ax1, ax2, ax3, ax4 in T.grid(n, T.int64(4), c, h, w):
                with T.sblock("T_group_norm"):
                    v_ax0, v_ax1, v_ax2, v_ax3, v_ax4 = T.axis.remap("SSSSS", [ax0, ax1, ax2, ax3, ax4])
                    T.reads(T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4], rxplaceholder_red_temp_v0[v_ax0, v_ax1], rxplaceholder_red_temp_v1[v_ax0, v_ax1], T_reshape_2[v_ax1, v_ax2], T_reshape_3[v_ax1, v_ax2])
                    T.writes(T_group_norm[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4])
                    T_group_norm[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4] = (T_reshape_1[v_ax0, v_ax1, v_ax2, v_ax3, v_ax4] - rxplaceholder_red_temp_v0[v_ax0, v_ax1] / (T.Cast("float32", c) * T.Cast("float32", h) * T.Cast("float32", w))) * T.rsqrt(rxplaceholder_red_temp_v1[v_ax0, v_ax1] / (T.Cast("float32", c) * T.Cast("float32", h) * T.Cast("float32", w)) - rxplaceholder_red_temp_v0[v_ax0, v_ax1] / (T.Cast("float32", c) * T.Cast("float32", h) * T.Cast("float32", w)) * (rxplaceholder_red_temp_v0[v_ax0, v_ax1] / (T.Cast("float32", c) * T.Cast("float32", h) * T.Cast("float32", w))) + T.float32(1.0000000000000001e-05)) * T_reshape_2[v_ax1, v_ax2] + T_reshape_3[v_ax1, v_ax2]
            for ax0, ax1, ax2, ax3 in T.grid(n, c * T.int64(4), h, w):
                with T.sblock("T_reshape_3"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(T_group_norm[(((v_ax0 * c * T.int64(4) + v_ax1) * h + v_ax2) * w + v_ax3) // w // h // c // T.int64(4) % n, (((v_ax0 * c * T.int64(4) + v_ax1) * h + v_ax2) * w + v_ax3) // w // h // c % T.int64(4), (((v_ax0 * c * T.int64(4) + v_ax1) * h + v_ax2) * w + v_ax3) // w // h % c, (((v_ax0 * c * T.int64(4) + v_ax1) * h + v_ax2) * w + v_ax3) // w % h, (((v_ax0 * c * T.int64(4) + v_ax1) * h + v_ax2) * w + v_ax3) % w])
                    T.writes(T_reshape[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_reshape[v_ax0, v_ax1, v_ax2, v_ax3] = T_group_norm[(((v_ax0 * c * T.int64(4) + v_ax1) * h + v_ax2) * w + v_ax3) // w // h // c // T.int64(4) % n, (((v_ax0 * c * T.int64(4) + v_ax1) * h + v_ax2) * w + v_ax3) // w // h // c % T.int64(4), (((v_ax0 * c * T.int64(4) + v_ax1) * h + v_ax2) * w + v_ax3) // w // h % c, (((v_ax0 * c * T.int64(4) + v_ax1) * h + v_ax2) * w + v_ax3) // w % h, (((v_ax0 * c * T.int64(4) + v_ax1) * h + v_ax2) * w + v_ax3) % w]

        @R.function
        def main(s: R.Shape(["c"]), x: R.Tensor(("n", "4 * c", "h", "w"), dtype="float32"), gamma: R.Tensor(("4 * c",), dtype="float32"), beta: R.Tensor(("4 * c",), dtype="float32")) -> R.Tensor(("n", "4 * c", "h", "w"), dtype="float32"):
            n = T.int64()
            c = T.int64()
            h = T.int64()
            w = T.int64()
            gv = R.call_tir(Expected.group_norm, (x, gamma, beta, c), out_ty=R.Tensor((n, 4 * c, h, w), dtype="float32"))
            return gv
    # fmt: on
    mod = LegalizeOps()(GroupNorm)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_rms_norm():
    # fmt: off
    @tvm.script.ir_module
    class RMSNorm:
        @R.function
        def main(x: R.Tensor((2, 3, 4, 5), "float32"), weight: R.Tensor((4, 5), "float32")) -> R.Tensor((2, 3, 4, 5), "float32"):
            gv: R.Tensor((2, 3, 4, 5), "float32") = R.nn.rms_norm(x, weight, axes=[-2, -1])
            return gv

    @tvm.script.ir_module
    class Expected:
        @T.prim_func(private=True, s_tir=True)
        def rms_norm(A: T.Buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)), "float32"), B: T.Buffer((T.int64(4), T.int64(5)), "float32"), T_cast: T.Buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)), "float32")):
            T.func_attr({"tirx.noalias": True})
            # with T.sblock("root"):
            T_cast_1 = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)))
            T_multiply = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)))
            T_multiply_red = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            rsqrt = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            T_cast_2 = T.sblock_alloc_buffer((T.int64(4), T.int64(5)))
            T_rms_norm = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)))
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("T_cast"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(A[v_ax0, v_ax1, v_ax2, v_ax3])
                    T.writes(T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3] = A[v_ax0, v_ax1, v_ax2, v_ax3]
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("T_multiply"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3])
                    T.writes(T_multiply[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_multiply[v_ax0, v_ax1, v_ax2, v_ax3] = T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3] * T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3]
            for ax0, ax1, k2, k3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("T_multiply_red"):
                    v_ax0, v_ax1, v_k2, v_k3 = T.axis.remap("SSRR", [ax0, ax1, k2, k3])
                    T.reads(T_multiply[v_ax0, v_ax1, v_k2, v_k3])
                    T.writes(T_multiply_red[v_ax0, v_ax1])
                    with T.init():
                        T_multiply_red[v_ax0, v_ax1] = T.float32(0)
                    T_multiply_red[v_ax0, v_ax1] = T_multiply_red[v_ax0, v_ax1] + T_multiply[v_ax0, v_ax1, v_k2, v_k3]
            for ax0, ax1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("rsqrt"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(T_multiply_red[v_ax0, v_ax1])
                    T.writes(rsqrt[v_ax0, v_ax1])
                    rsqrt[v_ax0, v_ax1] = T.rsqrt(T_multiply_red[v_ax0, v_ax1] / T.float32(20) + T.float32(1.0000000000000001e-05))
            for ax0, ax1 in T.grid(T.int64(4), T.int64(5)):
                with T.sblock("T_cast_1"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(B[v_ax0, v_ax1])
                    T.writes(T_cast_2[v_ax0, v_ax1])
                    T_cast_2[v_ax0, v_ax1] = B[v_ax0, v_ax1]
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("T_rms_norm"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(rsqrt[v_ax0, v_ax1], T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3], T_cast_2[v_ax2, v_ax3])
                    T.writes(T_rms_norm[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_rms_norm[v_ax0, v_ax1, v_ax2, v_ax3] = rsqrt[v_ax0, v_ax1] * T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3] * T_cast_2[v_ax2, v_ax3]
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("T_cast_2"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(T_rms_norm[v_ax0, v_ax1, v_ax2, v_ax3])
                    T.writes(T_cast[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_cast[v_ax0, v_ax1, v_ax2, v_ax3] = T_rms_norm[v_ax0, v_ax1, v_ax2, v_ax3]

        @R.function
        def main(x: R.Tensor((2, 3, 4, 5), dtype="float32"), weight: R.Tensor((4, 5), dtype="float32")) -> R.Tensor((2, 3, 4, 5), dtype="float32"):
            cls = Expected
            gv = R.call_tir(cls.rms_norm, (x, weight), out_ty=R.Tensor((2, 3, 4, 5), dtype="float32"))
            return gv
    # fmt: on
    mod = LegalizeOps()(RMSNorm)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_rms_norm_fp16():
    # fmt: off
    @tvm.script.ir_module
    class RMSNorm:
        @R.function
        def main(x: R.Tensor((2, 3, 4, 5), "float16"), weight: R.Tensor((4, 5), "float16")) -> R.Tensor((2, 3, 4, 5), "float16"):
            gv: R.Tensor((2, 3, 4, 5), "float16") = R.nn.rms_norm(x, weight, axes=[-2, -1])
            return gv

    @tvm.script.ir_module
    class Expected:
        @T.prim_func(private=True, s_tir=True)
        def rms_norm(A: T.Buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)), "float16"), B: T.Buffer((T.int64(4), T.int64(5)), "float16"), T_cast: T.Buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)), "float16")):
            T.func_attr({"tirx.noalias": True})
            # with T.sblock("root"):
            T_cast_1 = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)))
            T_multiply = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)))
            T_multiply_red = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            rsqrt = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            T_cast_2 = T.sblock_alloc_buffer((T.int64(4), T.int64(5)))
            T_rms_norm = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)))
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("T_cast"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(A[v_ax0, v_ax1, v_ax2, v_ax3])
                    T.writes(T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3] = T.Cast("float32", A[v_ax0, v_ax1, v_ax2, v_ax3])
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("T_multiply"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3])
                    T.writes(T_multiply[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_multiply[v_ax0, v_ax1, v_ax2, v_ax3] = T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3] * T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3]
            for ax0, ax1, k2, k3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("T_multiply_red"):
                    v_ax0, v_ax1, v_k2, v_k3 = T.axis.remap("SSRR", [ax0, ax1, k2, k3])
                    T.reads(T_multiply[v_ax0, v_ax1, v_k2, v_k3])
                    T.writes(T_multiply_red[v_ax0, v_ax1])
                    with T.init():
                        T_multiply_red[v_ax0, v_ax1] = T.float32(0)
                    T_multiply_red[v_ax0, v_ax1] = T_multiply_red[v_ax0, v_ax1] + T_multiply[v_ax0, v_ax1, v_k2, v_k3]
            for ax0, ax1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("rsqrt"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(T_multiply_red[v_ax0, v_ax1])
                    T.writes(rsqrt[v_ax0, v_ax1])
                    rsqrt[v_ax0, v_ax1] = T.rsqrt(T_multiply_red[v_ax0, v_ax1] / T.float32(20) + T.float32(1.0000000000000001e-05))
            for ax0, ax1 in T.grid(T.int64(4), T.int64(5)):
                with T.sblock("T_cast_1"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(B[v_ax0, v_ax1])
                    T.writes(T_cast_2[v_ax0, v_ax1])
                    T_cast_2[v_ax0, v_ax1] = T.Cast("float32", B[v_ax0, v_ax1])
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("T_rms_norm"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(rsqrt[v_ax0, v_ax1], T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3], T_cast_2[v_ax2, v_ax3])
                    T.writes(T_rms_norm[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_rms_norm[v_ax0, v_ax1, v_ax2, v_ax3] = rsqrt[v_ax0, v_ax1] * T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3] * T_cast_2[v_ax2, v_ax3]
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("T_cast_2"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(T_rms_norm[v_ax0, v_ax1, v_ax2, v_ax3])
                    T.writes(T_cast[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_cast[v_ax0, v_ax1, v_ax2, v_ax3] = T.Cast("float16", T_rms_norm[v_ax0, v_ax1, v_ax2, v_ax3])

        @R.function
        def main(x: R.Tensor((2, 3, 4, 5), dtype="float16"), weight: R.Tensor((4, 5), dtype="float16")) -> R.Tensor((2, 3, 4, 5), dtype="float16"):
            cls = Expected
            gv = R.call_tir(cls.rms_norm, (x, weight), out_ty=R.Tensor((2, 3, 4, 5), dtype="float16"))
            return gv
    # fmt: on
    mod = LegalizeOps()(RMSNorm)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_rms_norm_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class RMSNorm:
        @R.function
        def main(x: R.Tensor(("n", "s", "f"), "float32"), weight: R.Tensor(("s", "f"), "float32")) -> R.Tensor(("n", "s", "f"), "float32"):
            n = T.int64()
            s = T.int64()
            f = T.int64()
            gv: R.Tensor((n, s, f), "float32") = R.nn.rms_norm(x, weight, axes=[1, 2])
            return gv

    @tvm.script.ir_module
    class Expected:
        @T.prim_func(private=True, s_tir=True)
        def rms_norm(var_A: T.handle, var_B: T.handle, var_T_cast: T.handle):
            T.func_attr({"tirx.noalias": True})
            n, s, f = T.int64(), T.int64(), T.int64()
            A = T.match_buffer(var_A, (n, s, f))
            B = T.match_buffer(var_B, (s, f))
            T_cast = T.match_buffer(var_T_cast, (n, s, f))
            # with T.sblock("root"):
            T_cast_1 = T.sblock_alloc_buffer((n, s, f))
            T_multiply = T.sblock_alloc_buffer((n, s, f))
            T_multiply_red = T.sblock_alloc_buffer((n,))
            rsqrt = T.sblock_alloc_buffer((n,))
            T_cast_2 = T.sblock_alloc_buffer((s, f))
            T_rms_norm = T.sblock_alloc_buffer((n, s, f))
            for ax0, ax1, ax2 in T.grid(n, s, f):
                with T.sblock("T_cast"):
                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
                    T.reads(A[v_ax0, v_ax1, v_ax2])
                    T.writes(T_cast_1[v_ax0, v_ax1, v_ax2])
                    T_cast_1[v_ax0, v_ax1, v_ax2] = A[v_ax0, v_ax1, v_ax2]
            for ax0, ax1, ax2 in T.grid(n, s, f):
                with T.sblock("T_multiply"):
                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
                    T.reads(T_cast_1[v_ax0, v_ax1, v_ax2])
                    T.writes(T_multiply[v_ax0, v_ax1, v_ax2])
                    T_multiply[v_ax0, v_ax1, v_ax2] = T_cast_1[v_ax0, v_ax1, v_ax2] * T_cast_1[v_ax0, v_ax1, v_ax2]
            for ax0, k1, k2 in T.grid(n, s, f):
                with T.sblock("T_multiply_red"):
                    v_ax0, v_k1, v_k2 = T.axis.remap("SRR", [ax0, k1, k2])
                    T.reads(T_multiply[v_ax0, v_k1, v_k2])
                    T.writes(T_multiply_red[v_ax0])
                    with T.init():
                        T_multiply_red[v_ax0] = T.float32(0)
                    T_multiply_red[v_ax0] = T_multiply_red[v_ax0] + T_multiply[v_ax0, v_k1, v_k2]
            for ax0 in range(n):
                with T.sblock("rsqrt"):
                    v_ax0 = T.axis.spatial(n, ax0)
                    T.reads(T_multiply_red[v_ax0])
                    T.writes(rsqrt[v_ax0])
                    rsqrt[v_ax0] = T.rsqrt(T_multiply_red[v_ax0] / (T.Cast("float32", s) * T.Cast("float32", f)) + T.float32(1.0000000000000001e-05))
            for ax0, ax1 in T.grid(s, f):
                with T.sblock("T_cast_1"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(B[v_ax0, v_ax1])
                    T.writes(T_cast_2[v_ax0, v_ax1])
                    T_cast_2[v_ax0, v_ax1] = B[v_ax0, v_ax1]
            for ax0, ax1, ax2 in T.grid(n, s, f):
                with T.sblock("T_rms_norm"):
                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
                    T.reads(rsqrt[v_ax0], T_cast_1[v_ax0, v_ax1, v_ax2], T_cast_2[v_ax1, v_ax2])
                    T.writes(T_rms_norm[v_ax0, v_ax1, v_ax2])
                    T_rms_norm[v_ax0, v_ax1, v_ax2] = rsqrt[v_ax0] * T_cast_1[v_ax0, v_ax1, v_ax2] * T_cast_2[v_ax1, v_ax2]
            for ax0, ax1, ax2 in T.grid(n, s, f):
                with T.sblock("T_cast_2"):
                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
                    T.reads(T_rms_norm[v_ax0, v_ax1, v_ax2])
                    T.writes(T_cast[v_ax0, v_ax1, v_ax2])
                    T_cast[v_ax0, v_ax1, v_ax2] = T_rms_norm[v_ax0, v_ax1, v_ax2]

        @R.function
        def main(x: R.Tensor(("n", "s", "f"), dtype="float32"), weight: R.Tensor(("s", "f"), dtype="float32")) -> R.Tensor(("n", "s", "f"), dtype="float32"):
            n = T.int64()
            s = T.int64()
            f = T.int64()
            cls = Expected
            gv = R.call_tir(cls.rms_norm, (x, weight), out_ty=R.Tensor((n, s, f), dtype="float32"))
            return gv
    # fmt: on
    mod = LegalizeOps()(RMSNorm)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_rms_norm_no_bias():
    # fmt: off
    @tvm.script.ir_module
    class RMSNorm:
        @R.function
        def main(x: R.Tensor((2, 3, 4, 5), "float32"), weight: R.Tensor((4, 5), "float32")) -> R.Tensor((2, 3, 4, 5), "float32"):
            gv: R.Tensor((2, 3, 4, 5), "float32") = R.nn.rms_norm(x, weight, axes=[-2, -1])
            return gv

    @tvm.script.ir_module
    class Expected:
        @T.prim_func(private=True, s_tir=True)
        def rms_norm(A: T.Buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)), "float32"), B: T.Buffer((T.int64(4), T.int64(5)), "float32"), T_cast: T.Buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)), "float32")):
            T.func_attr({"tirx.noalias": True})
            # with T.sblock("root"):
            T_cast_1 = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)))
            T_multiply = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)))
            T_multiply_red = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            rsqrt = T.sblock_alloc_buffer((T.int64(2), T.int64(3)))
            T_cast_2 = T.sblock_alloc_buffer((T.int64(4), T.int64(5)))
            T_rms_norm = T.sblock_alloc_buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)))
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("T_cast"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(A[v_ax0, v_ax1, v_ax2, v_ax3])
                    T.writes(T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3] = A[v_ax0, v_ax1, v_ax2, v_ax3]
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("T_multiply"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3])
                    T.writes(T_multiply[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_multiply[v_ax0, v_ax1, v_ax2, v_ax3] = T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3] * T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3]
            for ax0, ax1, k2, k3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("T_multiply_red"):
                    v_ax0, v_ax1, v_k2, v_k3 = T.axis.remap("SSRR", [ax0, ax1, k2, k3])
                    T.reads(T_multiply[v_ax0, v_ax1, v_k2, v_k3])
                    T.writes(T_multiply_red[v_ax0, v_ax1])
                    with T.init():
                        T_multiply_red[v_ax0, v_ax1] = T.float32(0)
                    T_multiply_red[v_ax0, v_ax1] = T_multiply_red[v_ax0, v_ax1] + T_multiply[v_ax0, v_ax1, v_k2, v_k3]
            for ax0, ax1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("rsqrt"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(T_multiply_red[v_ax0, v_ax1])
                    T.writes(rsqrt[v_ax0, v_ax1])
                    rsqrt[v_ax0, v_ax1] = T.rsqrt(T_multiply_red[v_ax0, v_ax1] / T.float32(20) + T.float32(1.0000000000000001e-05))
            for ax0, ax1 in T.grid(T.int64(4), T.int64(5)):
                with T.sblock("T_cast_1"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(B[v_ax0, v_ax1])
                    T.writes(T_cast_2[v_ax0, v_ax1])
                    T_cast_2[v_ax0, v_ax1] = B[v_ax0, v_ax1]
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("T_rms_norm"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(rsqrt[v_ax0, v_ax1], T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3], T_cast_2[v_ax2, v_ax3])
                    T.writes(T_rms_norm[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_rms_norm[v_ax0, v_ax1, v_ax2, v_ax3] = rsqrt[v_ax0, v_ax1] * T_cast_1[v_ax0, v_ax1, v_ax2, v_ax3] * T_cast_2[v_ax2, v_ax3]
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(2), T.int64(3), T.int64(4), T.int64(5)):
                with T.sblock("T_cast_2"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(T_rms_norm[v_ax0, v_ax1, v_ax2, v_ax3])
                    T.writes(T_cast[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_cast[v_ax0, v_ax1, v_ax2, v_ax3] = T_rms_norm[v_ax0, v_ax1, v_ax2, v_ax3]

        @R.function
        def main(x: R.Tensor((2, 3, 4, 5), dtype="float32"), weight: R.Tensor((4, 5), dtype="float32")) -> R.Tensor((2, 3, 4, 5), dtype="float32"):
            cls = Expected
            gv = R.call_tir(cls.rms_norm, (x, weight), out_ty=R.Tensor((2, 3, 4, 5), dtype="float32"))
            return gv
    # fmt: on
    mod = LegalizeOps()(RMSNorm)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_attention():
    # fmt: off
    @tvm.script.ir_module
    class Attention:
        @R.function
        def main(q: R.Tensor((4, 16, 32, 8), "float32"), k: R.Tensor((4, 8, 32, 8), "float32"), v: R.Tensor((4, 8, 32, 16), "float32"), bias: R.Tensor((4, 32, 16, 8), "float32")):
            scale = I.meta_var(T.FloatImm("float32", 0.1))
            gv: R.Tensor((4, 16, 32, 16), "float32") = R.nn.attention(q, k, v, bias, scale=scale, causal_mask="TopLeft")
            return gv

    @tvm.script.ir_module
    class Expected:
        @T.prim_func(private=True, s_tir=True)
        def attention_bias(q: T.Buffer((T.int64(4), T.int64(16), T.int64(32), T.int64(8)), "float32"), k: T.Buffer((T.int64(4), T.int64(8), T.int64(32), T.int64(8)), "float32"), v: T.Buffer((T.int64(4), T.int64(8), T.int64(32), T.int64(16)), "float32"), bias: T.Buffer((T.int64(4), T.int64(32), T.int64(16), T.int64(8)), "float32"), T_transpose: T.Buffer((T.int64(4), T.int64(16), T.int64(32), T.int64(16)), "float32")):
            T.func_attr({"tirx.noalias": True})
            # with T.sblock("root"):
            T_transpose_1 = T.sblock_alloc_buffer((T.int64(4), T.int64(32), T.int64(16), T.int64(8)))
            T_reshape = T.sblock_alloc_buffer((T.int64(128), T.int64(16), T.int64(8)))
            T_transpose_2 = T.sblock_alloc_buffer((T.int64(4), T.int64(32), T.int64(8), T.int64(8)))
            T_reshape_1 = T.sblock_alloc_buffer((T.int64(128), T.int64(8), T.int64(8)))
            T_batch_matmul_NT = T.sblock_alloc_buffer((T.int64(128), T.int64(16), T.int64(8)))
            T_multiply = T.sblock_alloc_buffer((T.int64(128), T.int64(16), T.int64(8)))
            T_reshape_2 = T.sblock_alloc_buffer((T.int64(4), T.int64(32), T.int64(16), T.int64(8)))
            T_add = T.sblock_alloc_buffer((T.int64(4), T.int64(32), T.int64(16), T.int64(8)))
            T_reshape_3 = T.sblock_alloc_buffer((T.int64(128), T.int64(16), T.int64(8)))
            trilu = T.sblock_alloc_buffer((T.int64(128), T.int64(16), T.int64(8)))
            trilu_red = T.sblock_alloc_buffer((T.int64(128), T.int64(16), T.int64(1)))
            T_subtract = T.sblock_alloc_buffer((T.int64(128), T.int64(16), T.int64(8)))
            compute = T.sblock_alloc_buffer((T.int64(128), T.int64(16), T.int64(8)))
            trilu_1 = T.sblock_alloc_buffer((T.int64(128), T.int64(16), T.int64(8)))
            trilu_red_1 = T.sblock_alloc_buffer((T.int64(128), T.int64(16), T.int64(1)))
            T_divide = T.sblock_alloc_buffer((T.int64(128), T.int64(16), T.int64(8)))
            T_transpose_3 = T.sblock_alloc_buffer((T.int64(4), T.int64(32), T.int64(8), T.int64(16)))
            T_reshape_4 = T.sblock_alloc_buffer((T.int64(128), T.int64(8), T.int64(16)))
            T_batch_matmul_NN = T.sblock_alloc_buffer((T.int64(128), T.int64(16), T.int64(16)))
            T_reshape_5 = T.sblock_alloc_buffer((T.int64(4), T.int64(32), T.int64(16), T.int64(16)))
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(4), T.int64(32), T.int64(16), T.int64(8)):
                with T.sblock("T_transpose"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(q[v_ax0, v_ax2, v_ax1, v_ax3])
                    T.writes(T_transpose_1[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_transpose_1[v_ax0, v_ax1, v_ax2, v_ax3] = q[v_ax0, v_ax2, v_ax1, v_ax3]
            for ax0, ax1, ax2 in T.grid(T.int64(128), T.int64(16), T.int64(8)):
                with T.sblock("T_reshape"):
                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
                    T.reads(T_transpose_1[((v_ax2 // T.int64(8) + v_ax1) // T.int64(16) + v_ax0) % T.int64(128) // T.int64(32), ((v_ax2 // T.int64(8) + v_ax1) // T.int64(16) + v_ax0) % T.int64(32), (v_ax2 // T.int64(8) + v_ax1) % T.int64(16), v_ax2 % T.int64(8)])
                    T.writes(T_reshape[v_ax0, v_ax1, v_ax2])
                    T_reshape[v_ax0, v_ax1, v_ax2] = T_transpose_1[((v_ax2 // T.int64(8) + v_ax1) // T.int64(16) + v_ax0) % T.int64(128) // T.int64(32), ((v_ax2 // T.int64(8) + v_ax1) // T.int64(16) + v_ax0) % T.int64(32), (v_ax2 // T.int64(8) + v_ax1) % T.int64(16), v_ax2 % T.int64(8)]
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(4), T.int64(32), T.int64(8), T.int64(8)):
                with T.sblock("T_transpose_1"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(k[v_ax0, v_ax2, v_ax1, v_ax3])
                    T.writes(T_transpose_2[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_transpose_2[v_ax0, v_ax1, v_ax2, v_ax3] = k[v_ax0, v_ax2, v_ax1, v_ax3]
            for ax0, ax1, ax2 in T.grid(T.int64(128), T.int64(8), T.int64(8)):
                with T.sblock("T_reshape_1"):
                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
                    T.reads(T_transpose_2[((v_ax2 // T.int64(8) + v_ax1) // T.int64(8) + v_ax0) % T.int64(128) // T.int64(32), ((v_ax2 // T.int64(8) + v_ax1) // T.int64(8) + v_ax0) % T.int64(32), (v_ax2 // T.int64(8) + v_ax1) % T.int64(8), v_ax2 % T.int64(8)])
                    T.writes(T_reshape_1[v_ax0, v_ax1, v_ax2])
                    T_reshape_1[v_ax0, v_ax1, v_ax2] = T_transpose_2[((v_ax2 // T.int64(8) + v_ax1) // T.int64(8) + v_ax0) % T.int64(128) // T.int64(32), ((v_ax2 // T.int64(8) + v_ax1) // T.int64(8) + v_ax0) % T.int64(32), (v_ax2 // T.int64(8) + v_ax1) % T.int64(8), v_ax2 % T.int64(8)]
            for b, i, j, k_1 in T.grid(T.int64(128), T.int64(16), T.int64(8), T.int64(8)):
                with T.sblock("T_batch_matmul_NT"):
                    v_b, v_i, v_j, v_k = T.axis.remap("SSSR", [b, i, j, k_1])
                    T.reads(T_reshape[v_b, v_i, v_k], T_reshape_1[v_b, v_j, v_k])
                    T.writes(T_batch_matmul_NT[v_b, v_i, v_j])
                    T.sblock_attr({"layout_free_placeholders": [T_reshape_1]})
                    with T.init():
                        T_batch_matmul_NT[v_b, v_i, v_j] = T.float32(0.0)
                    T_batch_matmul_NT[v_b, v_i, v_j] = T_batch_matmul_NT[v_b, v_i, v_j] + T_reshape[v_b, v_i, v_k] * T_reshape_1[v_b, v_j, v_k]
            for ax0, ax1, ax2 in T.grid(T.int64(128), T.int64(16), T.int64(8)):
                with T.sblock("T_multiply"):
                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
                    T.reads(T_batch_matmul_NT[v_ax0, v_ax1, v_ax2])
                    T.writes(T_multiply[v_ax0, v_ax1, v_ax2])
                    T_multiply[v_ax0, v_ax1, v_ax2] = T_batch_matmul_NT[v_ax0, v_ax1, v_ax2] * T.float32(0.10000000000000001)
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(4), T.int64(32), T.int64(16), T.int64(8)):
                with T.sblock("T_reshape_2"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(T_multiply[(v_ax0 * T.int64(32) + (v_ax3 // T.int64(8) + v_ax2) // T.int64(16) + v_ax1) % T.int64(128), (v_ax3 // T.int64(8) + v_ax2) % T.int64(16), v_ax3 % T.int64(8)])
                    T.writes(T_reshape_2[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_reshape_2[v_ax0, v_ax1, v_ax2, v_ax3] = T_multiply[(v_ax0 * T.int64(32) + (v_ax3 // T.int64(8) + v_ax2) // T.int64(16) + v_ax1) % T.int64(128), (v_ax3 // T.int64(8) + v_ax2) % T.int64(16), v_ax3 % T.int64(8)]
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(4), T.int64(32), T.int64(16), T.int64(8)):
                with T.sblock("T_add"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(T_reshape_2[v_ax0, v_ax1, v_ax2, v_ax3], bias[v_ax0, v_ax1, v_ax2, v_ax3])
                    T.writes(T_add[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_add[v_ax0, v_ax1, v_ax2, v_ax3] = T_reshape_2[v_ax0, v_ax1, v_ax2, v_ax3] + bias[v_ax0, v_ax1, v_ax2, v_ax3]
            for ax0, ax1, ax2 in T.grid(T.int64(128), T.int64(16), T.int64(8)):
                with T.sblock("T_reshape_3"):
                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
                    T.reads(T_add[((v_ax2 // T.int64(8) + v_ax1) // T.int64(16) + v_ax0) % T.int64(128) // T.int64(32), ((v_ax2 // T.int64(8) + v_ax1) // T.int64(16) + v_ax0) % T.int64(32), (v_ax2 // T.int64(8) + v_ax1) % T.int64(16), v_ax2 % T.int64(8)])
                    T.writes(T_reshape_3[v_ax0, v_ax1, v_ax2])
                    T_reshape_3[v_ax0, v_ax1, v_ax2] = T_add[((v_ax2 // T.int64(8) + v_ax1) // T.int64(16) + v_ax0) % T.int64(128) // T.int64(32), ((v_ax2 // T.int64(8) + v_ax1) // T.int64(16) + v_ax0) % T.int64(32), (v_ax2 // T.int64(8) + v_ax1) % T.int64(16), v_ax2 % T.int64(8)]
            for i0, i1, i2 in T.grid(T.int64(128), T.int64(16), T.int64(8)):
                with T.sblock("trilu"):
                    v_i0, v_i1, v_i2 = T.axis.remap("SSS", [i0, i1, i2])
                    T.reads(T_reshape_3[v_i0, v_i1, v_i2])
                    T.writes(trilu[v_i0, v_i1, v_i2])
                    trilu[v_i0, v_i1, v_i2] = T.Select(v_i2 <= v_i1, T_reshape_3[v_i0, v_i1, v_i2], T.float32(0.0))
            for ax0, ax1, ax2, k2 in T.grid(T.int64(128), T.int64(16), T.int64(1), T.int64(8)):
                with T.sblock("trilu_red"):
                    v_ax0, v_ax1, v_ax2, v_k2 = T.axis.remap("SSSR", [ax0, ax1, ax2, k2])
                    T.reads(trilu[v_ax0, v_ax1, v_k2])
                    T.writes(trilu_red[v_ax0, v_ax1, v_ax2])
                    with T.init():
                        trilu_red[v_ax0, v_ax1, v_ax2] = T.float32(-340282346638528859811704183484516925440.0)
                    trilu_red[v_ax0, v_ax1, v_ax2] = T.max(trilu_red[v_ax0, v_ax1, v_ax2], trilu[v_ax0, v_ax1, v_k2])
            for ax0, ax1, ax2 in T.grid(T.int64(128), T.int64(16), T.int64(8)):
                with T.sblock("T_subtract"):
                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
                    T.reads(trilu[v_ax0, v_ax1, v_ax2], trilu_red[v_ax0, v_ax1, T.int64(0)])
                    T.writes(T_subtract[v_ax0, v_ax1, v_ax2])
                    T_subtract[v_ax0, v_ax1, v_ax2] = trilu[v_ax0, v_ax1, v_ax2] - trilu_red[v_ax0, v_ax1, T.int64(0)]
            for i0, i1, i2 in T.grid(T.int64(128), T.int64(16), T.int64(8)):
                with T.sblock("compute"):
                    v_i0, v_i1, v_i2 = T.axis.remap("SSS", [i0, i1, i2])
                    T.reads(T_subtract[v_i0, v_i1, v_i2])
                    T.writes(compute[v_i0, v_i1, v_i2])
                    compute[v_i0, v_i1, v_i2] = T.exp(T_subtract[v_i0, v_i1, v_i2])
            for i0, i1, i2 in T.grid(T.int64(128), T.int64(16), T.int64(8)):
                with T.sblock("trilu_1"):
                    v_i0, v_i1, v_i2 = T.axis.remap("SSS", [i0, i1, i2])
                    T.reads(compute[v_i0, v_i1, v_i2])
                    T.writes(trilu_1[v_i0, v_i1, v_i2])
                    trilu_1[v_i0, v_i1, v_i2] = T.Select(v_i2 <= v_i1, compute[v_i0, v_i1, v_i2], T.float32(0.0))
            for ax0, ax1, ax2, k2 in T.grid(T.int64(128), T.int64(16), T.int64(1), T.int64(8)):
                with T.sblock("trilu_red_1"):
                    v_ax0, v_ax1, v_ax2, v_k2 = T.axis.remap("SSSR", [ax0, ax1, ax2, k2])
                    T.reads(trilu_1[v_ax0, v_ax1, v_k2])
                    T.writes(trilu_red_1[v_ax0, v_ax1, v_ax2])
                    with T.init():
                        trilu_red_1[v_ax0, v_ax1, v_ax2] = T.float32(0.0)
                    trilu_red_1[v_ax0, v_ax1, v_ax2] = trilu_red_1[v_ax0, v_ax1, v_ax2] + trilu_1[v_ax0, v_ax1, v_k2]
            for ax0, ax1, ax2 in T.grid(T.int64(128), T.int64(16), T.int64(8)):
                with T.sblock("T_divide"):
                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
                    T.reads(trilu_1[v_ax0, v_ax1, v_ax2], trilu_red_1[v_ax0, v_ax1, T.int64(0)])
                    T.writes(T_divide[v_ax0, v_ax1, v_ax2])
                    T_divide[v_ax0, v_ax1, v_ax2] = trilu_1[v_ax0, v_ax1, v_ax2] / trilu_red_1[v_ax0, v_ax1, T.int64(0)]
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(4), T.int64(32), T.int64(8), T.int64(16)):
                with T.sblock("T_transpose_2"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(v[v_ax0, v_ax2, v_ax1, v_ax3])
                    T.writes(T_transpose_3[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_transpose_3[v_ax0, v_ax1, v_ax2, v_ax3] = v[v_ax0, v_ax2, v_ax1, v_ax3]
            for ax0, ax1, ax2 in T.grid(T.int64(128), T.int64(8), T.int64(16)):
                with T.sblock("T_reshape_4"):
                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
                    T.reads(T_transpose_3[((v_ax2 // T.int64(16) + v_ax1) // T.int64(8) + v_ax0) % T.int64(128) // T.int64(32), ((v_ax2 // T.int64(16) + v_ax1) // T.int64(8) + v_ax0) % T.int64(32), (v_ax2 // T.int64(16) + v_ax1) % T.int64(8), v_ax2 % T.int64(16)])
                    T.writes(T_reshape_4[v_ax0, v_ax1, v_ax2])
                    T_reshape_4[v_ax0, v_ax1, v_ax2] = T_transpose_3[((v_ax2 // T.int64(16) + v_ax1) // T.int64(8) + v_ax0) % T.int64(128) // T.int64(32), ((v_ax2 // T.int64(16) + v_ax1) // T.int64(8) + v_ax0) % T.int64(32), (v_ax2 // T.int64(16) + v_ax1) % T.int64(8), v_ax2 % T.int64(16)]
            for b, i, j, k_1 in T.grid(T.int64(128), T.int64(16), T.int64(16), T.int64(8)):
                with T.sblock("T_batch_matmul_NN"):
                    v_b, v_i, v_j, v_k = T.axis.remap("SSSR", [b, i, j, k_1])
                    T.reads(T_divide[v_b, v_i, v_k], T_reshape_4[v_b, v_k, v_j])
                    T.writes(T_batch_matmul_NN[v_b, v_i, v_j])
                    T.sblock_attr({"layout_free_placeholders": [T_reshape_4]})
                    with T.init():
                        T_batch_matmul_NN[v_b, v_i, v_j] = T.float32(0.0)
                    T_batch_matmul_NN[v_b, v_i, v_j] = T_batch_matmul_NN[v_b, v_i, v_j] + T_divide[v_b, v_i, v_k] * T_reshape_4[v_b, v_k, v_j]
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(4), T.int64(32), T.int64(16), T.int64(16)):
                with T.sblock("T_reshape_5"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(T_batch_matmul_NN[(v_ax0 * T.int64(32) + (v_ax3 // T.int64(16) + v_ax2) // T.int64(16) + v_ax1) % T.int64(128), (v_ax3 // T.int64(16) + v_ax2) % T.int64(16), v_ax3 % T.int64(16)])
                    T.writes(T_reshape_5[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_reshape_5[v_ax0, v_ax1, v_ax2, v_ax3] = T_batch_matmul_NN[(v_ax0 * T.int64(32) + (v_ax3 // T.int64(16) + v_ax2) // T.int64(16) + v_ax1) % T.int64(128), (v_ax3 // T.int64(16) + v_ax2) % T.int64(16), v_ax3 % T.int64(16)]
            for ax0, ax1, ax2, ax3 in T.grid(T.int64(4), T.int64(16), T.int64(32), T.int64(16)):
                with T.sblock("T_transpose_3"):
                    v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap("SSSS", [ax0, ax1, ax2, ax3])
                    T.reads(T_reshape_5[v_ax0, v_ax2, v_ax1, v_ax3])
                    T.writes(T_transpose[v_ax0, v_ax1, v_ax2, v_ax3])
                    T_transpose[v_ax0, v_ax1, v_ax2, v_ax3] = T_reshape_5[v_ax0, v_ax2, v_ax1, v_ax3]

        @R.function
        def main(q: R.Tensor((4, 16, 32, 8), dtype="float32"), k: R.Tensor((4, 8, 32, 8), dtype="float32"), v: R.Tensor((4, 8, 32, 16), dtype="float32"), bias: R.Tensor((4, 32, 16, 8), dtype="float32")) -> R.Tensor((4, 16, 32, 16), dtype="float32"):
            cls = Expected
            gv = R.call_tir(cls.attention_bias, (q, k, v, bias), out_ty=R.Tensor((4, 16, 32, 16), dtype="float32"))
            return gv
    # fmt: on
    mod = LegalizeOps()(Attention)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_dynamic_attention():
    """The sequence lengths may be dynamic

    In previous implementations, the `seq_len` and `seq_len_kv` were
    assumed to be static integers, and produced an exception during
    legalization.
    """

    @tvm.script.ir_module
    class Attention:
        @R.function
        def main(
            q: R.Tensor((4, "seq_len", 32, 8), "float32"),
            k: R.Tensor((4, "seq_len_kv", 32, 8), "float32"),
            v: R.Tensor((4, "seq_len_kv", 32, 16), "float32"),
            bias: R.Tensor((4, 32, "seq_len", "seq_len_kv"), "float32"),
        ):
            scale = I.meta_var(T.FloatImm("float32", 0.1))
            gv = R.nn.attention(q, k, v, bias, scale=scale, causal_mask="BottomRight")
            return gv

    LegalizeOps()(Attention)


def test_dynamic_batch_attention():
    """The batch dimension may be dynamic (symbolic).

    fix https://github.com/apache/tvm/issues/19696
    """

    @tvm.script.ir_module
    class Attention:
        @R.function
        def main(
            q: R.Tensor(("batch_size", 16, 32, 8), "float32"),
            k: R.Tensor(("batch_size", 8, 32, 8), "float32"),
            v: R.Tensor(("batch_size", 8, 32, 16), "float32"),
        ):
            gv = R.nn.attention(q, k, v)
            return gv

    LegalizeOps()(Attention)

    @tvm.script.ir_module
    class AttentionBias:
        @R.function
        def main(
            q: R.Tensor(("batch_size", 16, 32, 8), "float32"),
            k: R.Tensor(("batch_size", 8, 32, 8), "float32"),
            v: R.Tensor(("batch_size", 8, 32, 16), "float32"),
            bias: R.Tensor(("batch_size", 32, 16, 8), "float32"),
        ):
            scale = I.meta_var(T.FloatImm("float32", 0.1))
            gv = R.nn.attention(q, k, v, bias, scale=scale, causal_mask="BottomRight")
            return gv

    LegalizeOps()(AttentionBias)


def test_nll_loss():
    # fmt: off
    @tvm.script.ir_module
    class NLLLoss:
        @R.function
        def main(
                predictions: R.Tensor((2, 3, 4, 5), "float32"),
                targets: R.Tensor((2, 4, 5), "int64"),
                weights: R.Tensor((3,), "float32"),
        ) -> R.Tensor((), "float32"):
            gv = R.nn.nll_loss(predictions, targets, weights, reduction="mean", ignore_index=-1)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(
                predictions: R.Tensor((2, 3, 4, 5), dtype="float32"),
                targets: R.Tensor((2, 4, 5), dtype="int64"),
                weights: R.Tensor((3,), dtype="float32"),
        ) -> R.Tensor((), dtype="float32"):
            gv = R.call_tir(Expected.nll_loss, (predictions, targets, weights), R.Tensor((), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def nll_loss(
                predictions: T.Buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)), "float32"),
                targets: T.Buffer((T.int64(2), T.int64(4), T.int64(5)), "int64"),
                weights: T.Buffer(T.int64(3), "float32"),
                output: T.Buffer((), "float32"),
        ):
            # function attr dict
            T.func_attr({"tirx.noalias": True})
            # body
            # with T.sblock("root")
            nll_loss = T.sblock_alloc_buffer([T.int64(2), T.int64(4), T.int64(5)], dtype="float32")
            nll_loss_red = T.sblock_alloc_buffer([], dtype="float32")
            nll_loss_1 = T.sblock_alloc_buffer([T.int64(2), T.int64(4), T.int64(5)], dtype="float32")
            nll_loss_red_1 = T.sblock_alloc_buffer([], dtype="float32")
            for ax0, ax1, ax2 in T.grid(T.int64(2), T.int64(4), T.int64(5)):
                with T.sblock("nll_loss"):
                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
                    T.reads(targets[v_ax0, v_ax1, v_ax2], predictions[v_ax0, targets[v_ax0, v_ax1, v_ax2], v_ax1, v_ax2], weights[targets[v_ax0, v_ax1, v_ax2]])
                    T.writes(nll_loss[v_ax0, v_ax1, v_ax2])
                    nll_loss[v_ax0, v_ax1, v_ax2] = T.Select(targets[v_ax0, v_ax1, v_ax2] != T.int64(-1), (T.float32(0) - predictions[v_ax0, targets[v_ax0, v_ax1, v_ax2], v_ax1, v_ax2]) * weights[targets[v_ax0, v_ax1, v_ax2]], T.float32(0))
            for k0, k1, k2 in T.grid(T.int64(2), T.int64(4), T.int64(5)):
                with T.sblock("nll_loss_red"):
                    v_k0, v_k1, v_k2 = T.axis.remap("RRR", [k0, k1, k2])
                    T.reads(nll_loss[v_k0, v_k1, v_k2])
                    T.writes(nll_loss_red[()])
                    with T.init():
                        nll_loss_red[()] = T.float32(0)
                    nll_loss_red[()] = nll_loss_red[()] + nll_loss[v_k0, v_k1, v_k2]
            for ax0, ax1, ax2 in T.grid(T.int64(2), T.int64(4), T.int64(5)):
                with T.sblock("nll_loss_1"):
                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
                    T.reads(targets[v_ax0, v_ax1, v_ax2], weights[targets[v_ax0, v_ax1, v_ax2]])
                    T.writes(nll_loss_1[v_ax0, v_ax1, v_ax2])
                    nll_loss_1[v_ax0, v_ax1, v_ax2] = T.Select(targets[v_ax0, v_ax1, v_ax2] != T.int64(-1), weights[targets[v_ax0, v_ax1, v_ax2]], T.float32(0))
            for k0, k1, k2 in T.grid(T.int64(2), T.int64(4), T.int64(5)):
                with T.sblock("nll_loss_red_1"):
                    v_k0, v_k1, v_k2 = T.axis.remap("RRR", [k0, k1, k2])
                    T.reads(nll_loss_1[v_k0, v_k1, v_k2])
                    T.writes(nll_loss_red_1[()])
                    with T.init():
                        nll_loss_red_1[()] = T.float32(0)
                    nll_loss_red_1[()] = nll_loss_red_1[()] + nll_loss_1[v_k0, v_k1, v_k2]
            with T.sblock("T_divide"):
                vi = T.axis.spatial(1, T.int64(0))
                T.reads(nll_loss_red[()], nll_loss_red_1[()])
                T.writes(output[()])
                output[()] = nll_loss_red[()] / nll_loss_red_1[()]
    # fmt: on
    mod = LegalizeOps()(NLLLoss)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_nll_no_weight():
    # fmt: off
    @tvm.script.ir_module
    class NLLLoss:
        @R.function
        def main(predictions: R.Tensor((2, 3, 4, 5), "float32"), targets: R.Tensor((2, 4, 5), "int64")) -> R.Tensor((), "float32"):
            gv: R.Tensor((), "float32") = R.nn.nll_loss(predictions, targets, reduction="mean", ignore_index=-1)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(predictions: R.Tensor((2, 3, 4, 5), dtype="float32"), targets: R.Tensor((2, 4, 5), dtype="int64"),) -> R.Tensor((), dtype="float32"):
            # block 0
            gv = R.call_tir(Expected.nll_loss_without_weight, (predictions, targets), R.Tensor((), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def nll_loss_without_weight(rxplaceholder: T.Buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)), "float32"), rxplaceholder_1: T.Buffer((T.int64(2), T.int64(4), T.int64(5)), "int64"), T_divide: T.Buffer((), "float32"),):
            # function attr dict
            T.func_attr({"tirx.noalias": True})
            # body
            # with T.sblock("root")
            T_full = T.sblock_alloc_buffer([T.int64(3)], dtype="float32")
            nll_loss = T.sblock_alloc_buffer([T.int64(2), T.int64(4), T.int64(5)], dtype="float32")
            nll_loss_red = T.sblock_alloc_buffer([], dtype="float32")
            nll_loss_1 = T.sblock_alloc_buffer([T.int64(2), T.int64(4), T.int64(5)], dtype="float32")
            nll_loss_red_1 = T.sblock_alloc_buffer([], dtype="float32")
            for ax0 in T.serial(T.int64(3)):
                with T.sblock("T_full"):
                    v_ax0 = T.axis.spatial(T.int64(3), ax0)
                    T.reads()
                    T.writes(T_full[v_ax0])
                    T_full[v_ax0] = T.float32(1)
            for ax0, ax1, ax2 in T.grid(T.int64(2), T.int64(4), T.int64(5)):
                with T.sblock("nll_loss"):
                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
                    T.reads(rxplaceholder_1[v_ax0, v_ax1, v_ax2], rxplaceholder[v_ax0, rxplaceholder_1[v_ax0, v_ax1, v_ax2], v_ax1, v_ax2], T_full[rxplaceholder_1[v_ax0, v_ax1, v_ax2]])
                    T.writes(nll_loss[v_ax0, v_ax1, v_ax2])
                    nll_loss[v_ax0, v_ax1, v_ax2] = T.Select(rxplaceholder_1[v_ax0, v_ax1, v_ax2] != T.int64(-1), (T.float32(0) - rxplaceholder[v_ax0, rxplaceholder_1[v_ax0, v_ax1, v_ax2], v_ax1, v_ax2]) * T_full[rxplaceholder_1[v_ax0, v_ax1, v_ax2]], T.float32(0))
            for k0, k1, k2 in T.grid(T.int64(2), T.int64(4), T.int64(5)):
                with T.sblock("nll_loss_red"):
                    v_k0, v_k1, v_k2 = T.axis.remap("RRR", [k0, k1, k2])
                    T.reads(nll_loss[v_k0, v_k1, v_k2])
                    T.writes(nll_loss_red[()])
                    with T.init():
                        nll_loss_red[()] = T.float32(0)
                    nll_loss_red[()] = nll_loss_red[()] + nll_loss[v_k0, v_k1, v_k2]
            for ax0, ax1, ax2 in T.grid(T.int64(2), T.int64(4), T.int64(5)):
                with T.sblock("nll_loss_1"):
                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
                    T.reads(rxplaceholder_1[v_ax0, v_ax1, v_ax2], T_full[rxplaceholder_1[v_ax0, v_ax1, v_ax2]])
                    T.writes(nll_loss_1[v_ax0, v_ax1, v_ax2])
                    nll_loss_1[v_ax0, v_ax1, v_ax2] = T.Select(rxplaceholder_1[v_ax0, v_ax1, v_ax2] != T.int64(-1), T_full[rxplaceholder_1[v_ax0, v_ax1, v_ax2]], T.float32(0))
            for k0, k1, k2 in T.grid(T.int64(2), T.int64(4), T.int64(5)):
                with T.sblock("nll_loss_red_1"):
                    v_k0, v_k1, v_k2 = T.axis.remap("RRR", [k0, k1, k2])
                    T.reads(nll_loss_1[v_k0, v_k1, v_k2])
                    T.writes(nll_loss_red_1[()])
                    with T.init():
                        nll_loss_red_1[()] = T.float32(0)
                    nll_loss_red_1[()] = nll_loss_red_1[()] + nll_loss_1[v_k0, v_k1, v_k2]
            with T.sblock("T_divide"):
                vi = T.axis.spatial(1, T.int64(0))
                T.reads(nll_loss_red[()], nll_loss_red_1[()])
                T.writes(T_divide[()])
                T_divide[()] = nll_loss_red[()] / nll_loss_red_1[()]
    # fmt: on

    mod = LegalizeOps()(NLLLoss)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_nll_no_batch():
    # fmt: off
    @tvm.script.ir_module
    class NLLLoss:
        @R.function
        def main(predictions: R.Tensor(("C",), "float32"), targets: R.Tensor((), "int64"), weights: R.Tensor(("C",), "float32")) -> R.Tensor((), "float32"):
            gv = R.nn.nll_loss(predictions, targets, weights, reduction="mean", ignore_index=1)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(predictions: R.Tensor(("C",), dtype="float32"), targets: R.Tensor((), dtype="int64"), weights: R.Tensor(("C",), dtype="float32")) -> R.Tensor((), dtype="float32"):
            C = T.int64()
            gv = R.call_tir(Expected.nll_loss, (predictions, targets, weights), out_ty=R.Tensor((), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def nll_loss(var_rxplaceholder: T.handle, rxplaceholder: T.Buffer((), "int64"), var_rxplaceholder_1: T.handle, T_divide: T.Buffer((), "float32")):
            T.func_attr({"tirx.noalias": True})
            C = T.int64()
            rxplaceholder_1 = T.match_buffer(var_rxplaceholder, (C,))
            rxplaceholder_2 = T.match_buffer(var_rxplaceholder_1, (C,))
            # with T.sblock("root"):
            nll_loss = T.sblock_alloc_buffer(())
            nll_loss_1 = T.sblock_alloc_buffer(())
            with T.sblock("nll_loss"):
                vi = T.axis.spatial(T.int64(1), T.int64(0))
                T.reads(rxplaceholder[()], rxplaceholder_1[rxplaceholder[()]], rxplaceholder_2[rxplaceholder[()]])
                T.writes(nll_loss[()])
                nll_loss[()] = T.Select(rxplaceholder[()] != T.int64(1), (T.float32(0) - rxplaceholder_1[rxplaceholder[()]]) * rxplaceholder_2[rxplaceholder[()]], T.float32(0))
            with T.sblock("nll_loss_1"):
                vi = T.axis.spatial(T.int64(1), T.int64(0))
                T.reads(rxplaceholder[()], rxplaceholder_2[rxplaceholder[()]])
                T.writes(nll_loss_1[()])
                nll_loss_1[()] = T.Select(rxplaceholder[()] != T.int64(1), rxplaceholder_2[rxplaceholder[()]], T.float32(0))
            with T.sblock("T_divide"):
                vi = T.axis.spatial(1, T.int64(0))
                T.reads(nll_loss[()], nll_loss_1[()])
                T.writes(T_divide[()])
                T_divide[()] = nll_loss[()] / nll_loss_1[()]
    # fmt: on

    mod = LegalizeOps()(NLLLoss)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_nll_loss_symbolic():
    # fmt: off
    @tvm.script.ir_module
    class NLLLoss:
        @R.function
        def main(predictions: R.Tensor(("N", "C", "d1", "d2"), "float32"), targets: R.Tensor(("N", "d1", "d2"), "int64"), weights: R.Tensor(("C",), "float32")) -> R.Tensor((), "float32"):
            gv: R.Tensor((), "float32") = R.nn.nll_loss(predictions, targets, weights, reduction="mean", ignore_index=-1)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(predictions: R.Tensor(("N", "C", "d1", "d2"), dtype="float32"), targets: R.Tensor(("N", "d1", "d2"), dtype="int64"), weights: R.Tensor(("C",), dtype="float32")) -> R.Tensor((), dtype="float32"):
            # block 0
            gv = R.call_tir(Expected.nll_loss, (predictions, targets, weights), R.Tensor((), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def nll_loss(var_rxplaceholder: T.handle, var_rxplaceholder_1: T.handle, var_rxplaceholder_2: T.handle, T_divide: T.Buffer((), "float32"),):
            # function attr dict
            T.func_attr({"tirx.noalias": True})
            C = T.int64()
            N = T.int64()
            d1 = T.int64()
            d2 = T.int64()
            rxplaceholder = T.match_buffer(var_rxplaceholder, [N, C, d1, d2], dtype="float32")
            rxplaceholder_1 = T.match_buffer(var_rxplaceholder_1, [N, d1, d2], dtype="int64")
            rxplaceholder_2 = T.match_buffer(var_rxplaceholder_2, [C], dtype="float32")
            # body
            # with T.sblock("root")
            nll_loss = T.sblock_alloc_buffer([N, d1, d2], dtype="float32")
            nll_loss_red = T.sblock_alloc_buffer([], dtype="float32")
            nll_loss_1 = T.sblock_alloc_buffer([N, d1, d2], dtype="float32")
            nll_loss_red_1 = T.sblock_alloc_buffer([], dtype="float32")
            for ax0, ax1, ax2 in T.grid(N, d1, d2):
                with T.sblock("nll_loss"):
                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
                    T.reads(rxplaceholder_1[v_ax0, v_ax1, v_ax2], rxplaceholder[v_ax0, rxplaceholder_1[v_ax0, v_ax1, v_ax2], v_ax1, v_ax2],rxplaceholder_2[rxplaceholder_1[v_ax0, v_ax1, v_ax2]],)
                    T.writes(nll_loss[v_ax0, v_ax1, v_ax2])
                    nll_loss[v_ax0, v_ax1, v_ax2] = T.Select(rxplaceholder_1[v_ax0, v_ax1, v_ax2] != T.int64(-1), (T.float32(0) - rxplaceholder[v_ax0, rxplaceholder_1[v_ax0, v_ax1, v_ax2], v_ax1, v_ax2]) * rxplaceholder_2[rxplaceholder_1[v_ax0, v_ax1, v_ax2]], T.float32(0),)
            for k0, k1, k2 in T.grid(N, d1, d2):
                with T.sblock("nll_loss_red"):
                    v_k0, v_k1, v_k2 = T.axis.remap("RRR", [k0, k1, k2])
                    T.reads(nll_loss[v_k0, v_k1, v_k2])
                    T.writes(nll_loss_red[()])
                    with T.init():
                        nll_loss_red[()] = T.float32(0)
                    nll_loss_red[()] = nll_loss_red[()] + nll_loss[v_k0, v_k1, v_k2]
            for ax0, ax1, ax2 in T.grid(N, d1, d2):
                with T.sblock("nll_loss_1"):
                    v_ax0, v_ax1, v_ax2 = T.axis.remap("SSS", [ax0, ax1, ax2])
                    T.reads(rxplaceholder_1[v_ax0, v_ax1, v_ax2], rxplaceholder_2[rxplaceholder_1[v_ax0, v_ax1, v_ax2]],)
                    T.writes(nll_loss_1[v_ax0, v_ax1, v_ax2])
                    nll_loss_1[v_ax0, v_ax1, v_ax2] = T.Select(rxplaceholder_1[v_ax0, v_ax1, v_ax2] != T.int64(-1), rxplaceholder_2[rxplaceholder_1[v_ax0, v_ax1, v_ax2]], T.float32(0),)
            for k0, k1, k2 in T.grid(N, d1, d2):
                with T.sblock("nll_loss_red_1"):
                    v_k0, v_k1, v_k2 = T.axis.remap("RRR", [k0, k1, k2])
                    T.reads(nll_loss_1[v_k0, v_k1, v_k2])
                    T.writes(nll_loss_red_1[()])
                    with T.init():
                        nll_loss_red_1[()] = T.float32(0)
                    nll_loss_red_1[()] = nll_loss_red_1[()] + nll_loss_1[v_k0, v_k1, v_k2]
            with T.sblock("T_divide"):
                vi = T.axis.spatial(1, T.int64(0))
                T.reads(nll_loss_red[()], nll_loss_red_1[()])
                T.writes(T_divide[()])
                T_divide[()] = nll_loss_red[()] / nll_loss_red_1[()]
    # fmt: on
    mod = LegalizeOps()(NLLLoss)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_pad():
    @tvm.script.ir_module
    class Pad:
        @R.function
        def main(x: R.Tensor((2, 128, 28), "float32")) -> R.Tensor((2, 130, 30), "float32"):
            gv: R.Tensor((2, 130, 30), "float32") = R.nn.pad(x, (0, 0, 1, 1, 1, 1))
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(
            x: R.Tensor((2, 128, 28), dtype="float32"),
        ) -> R.Tensor((2, 130, 30), dtype="float32"):
            gv = R.call_tir(Expected.pad, (x), out_ty=R.Tensor((2, 130, 30), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def pad(
            A: T.Buffer((T.int64(2), T.int64(128), T.int64(28)), "float32"),
            PadInput: T.Buffer((T.int64(2), T.int64(130), T.int64(30)), "float32"),
        ):
            T.func_attr({"tirx.noalias": True})
            # with T.sblock("root"):
            for i0, i1, i2 in T.grid(T.int64(2), T.int64(130), T.int64(30)):
                with T.sblock("PadInput"):
                    v_i0, v_i1, v_i2 = T.axis.remap("SSS", [i0, i1, i2])
                    T.reads(A[v_i0, v_i1 - T.int64(1), v_i2 - T.int64(1)])
                    T.writes(PadInput[v_i0, v_i1, v_i2])
                    PadInput[v_i0, v_i1, v_i2] = T.if_then_else(
                        T.int64(1) <= v_i1
                        and v_i1 < T.int64(129)
                        and T.int64(1) <= v_i2
                        and v_i2 < T.int64(29),
                        A[v_i0, v_i1 - T.int64(1), v_i2 - T.int64(1)],
                        T.float32(0),
                    )

    mod = LegalizeOps()(Pad)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_batch_flatten():
    # fmt: off
    @tvm.script.ir_module
    class BatchFlatten:
        @R.function
        def main(x: R.Tensor((2, 3, 4, 5), "float32")) -> R.Tensor((2, 60), "float32"):
            gv: R.Tensor((2, 60), "float32") = R.nn.batch_flatten(x)
            return gv

    @tvm.script.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((2, 3, 4, 5), dtype="float32")) -> R.Tensor((2, 60), dtype="float32"):
            gv = R.call_tir(Expected.reshape, (x,), out_ty=R.Tensor((2, 60), dtype="float32"))
            return gv

        @T.prim_func(private=True, s_tir=True)
        def reshape(x: T.Buffer((T.int64(2), T.int64(3), T.int64(4), T.int64(5)), "float32"), T_reshape: T.Buffer((T.int64(2), T.int64(60)), "float32")):
            T.func_attr({"tirx.noalias": True})
            for ax0, ax1 in T.grid(T.int64(2), T.int64(60)):
                with T.sblock("T_reshape"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads(x[(v_ax1 // T.int64(60) + v_ax0) % T.int64(2), v_ax1 % T.int64(60) // T.int64(20), v_ax1 % T.int64(20) // T.int64(5), v_ax1 % T.int64(5)])
                    T.writes(T_reshape[v_ax0, v_ax1])
                    T_reshape[v_ax0, v_ax1] = x[(v_ax1 // T.int64(60) + v_ax0) % T.int64(2), v_ax1 % T.int64(60) // T.int64(20), v_ax1 % T.int64(20) // T.int64(5), v_ax1 % T.int64(5)]
    # fmt: on

    mod = LegalizeOps()(BatchFlatten)
    tvm.ir.assert_structural_equal(mod, Expected)


def test_batch_flatten_undefined_shape():
    @tvm.script.ir_module
    class BatchFlattenUndefinedShape:
        @R.function
        def main(x: R.Tensor(ndim=4, dtype="float32")) -> R.Tensor(ndim=2, dtype="float32"):
            gv: R.Tensor(ndim=2, dtype="float32") = R.nn.batch_flatten(x)
            return gv

    mod = LegalizeOps()(BatchFlattenUndefinedShape)
    tvm.ir.assert_structural_equal(mod, BatchFlattenUndefinedShape)


def test_dropout():
    # fmt: off
    @tvm.script.ir_module
    class Dropout:
        @R.function
        def main(x: R.Tensor((2, 3), "float32")) -> R.Tuple(R.Tensor((2, 3), "float32"), R.Tensor((2, 3), "float32")):
            gv: R.Tuple(R.Tensor((2, 3), "float32"), R.Tensor((2, 3), "float32")) = R.nn.dropout(x, rate=0.5)
            return gv

    @tvm.script.ir_module
    class Expected:
        @T.prim_func(private=True, s_tir=True)
        def dropout(x: T.Buffer((T.int64(2), T.int64(3)), "float32"), compute: T.Buffer((T.int64(2), T.int64(3)), "float32"), T_full_like: T.Buffer((T.int64(2), T.int64(3)), "float32")):
            T.func_attr({"tirx.noalias": True})
            for i0, i1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("compute"):
                    v_i0, v_i1 = T.axis.remap("SS", [i0, i1])
                    T.reads(x[v_i0, v_i1])
                    T.writes(compute[v_i0, v_i1])
                    compute[v_i0, v_i1] = x[v_i0, v_i1]
            for ax0, ax1 in T.grid(T.int64(2), T.int64(3)):
                with T.sblock("T_full_like"):
                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
                    T.reads()
                    T.writes(T_full_like[v_ax0, v_ax1])
                    T_full_like[v_ax0, v_ax1] = T.float32(1.0)

        @R.function
        def main(x: R.Tensor((2, 3), dtype="float32")) -> R.Tuple(R.Tensor((2, 3), dtype="float32"), R.Tensor((2, 3), dtype="float32")):
            cls = Expected
            gv = R.call_tir(cls.dropout, (x,), out_ty=[R.Tensor((2, 3), dtype="float32"), R.Tensor((2, 3), dtype="float32")])
            return gv
    # fmt: on

    mod = LegalizeOps()(Dropout)
    tvm.ir.assert_structural_equal(mod, Expected)


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