This is an automated email from the ASF dual-hosted git repository.

junrushao pushed a commit to branch unity
in repository https://gitbox.apache.org/repos/asf/tvm.git


The following commit(s) were added to refs/heads/unity by this push:
     new 0d462a012d [Unity] nn.Module Module and Effect (#15438)
0d462a012d is described below

commit 0d462a012dacfa7eb0809375bce2d88647fb5f69
Author: Yaxing Cai <[email protected]>
AuthorDate: Sat Jul 29 15:38:36 2023 -0700

    [Unity] nn.Module Module and Effect (#15438)
    
    This PR introduces the Module and Effect in the nn frontend. It adds the 
built-in `Linear`,
    `RMSNorm` and `Embedding` modules. And it adds a `KVCache` effect.
    
    Also, this PR fix the interface of `R.nn.rms_norm` to support optional 
`bias`.
    
    Co-authored-by: Junru Shao <[email protected]>
---
 include/tvm/topi/nn/rms_norm.h                     |  11 +-
 python/tvm/relax/frontend/nn/__init__.py           |   4 +-
 python/tvm/relax/frontend/nn/core.py               |   4 +-
 python/tvm/relax/frontend/nn/modules.py            | 279 ++++++++++++++++++++-
 python/tvm/relax/frontend/nn/op.py                 |  12 +-
 python/tvm/relax/op/nn/nn.py                       |   7 +-
 python/tvm/relax/transform/legalize_ops/nn.py      |   6 +-
 python/tvm/topi/nn/rms_norm.py                     |   7 +-
 src/relax/op/nn/nn.cc                              |   9 +-
 src/relax/op/nn/nn.h                               |   2 +-
 src/topi/nn.cc                                     |   2 +-
 tests/python/relax/test_frontend_nn_modules.py     | 146 +++++++++++
 tests/python/relax/test_frontend_nn_op.py          |  17 +-
 .../python/relax/test_transform_legalize_ops_nn.py | 118 +++++++--
 14 files changed, 548 insertions(+), 76 deletions(-)

diff --git a/include/tvm/topi/nn/rms_norm.h b/include/tvm/topi/nn/rms_norm.h
index 44d38bae6d..55dac39b71 100644
--- a/include/tvm/topi/nn/rms_norm.h
+++ b/include/tvm/topi/nn/rms_norm.h
@@ -41,22 +41,18 @@ using namespace tvm::te;
  * \param data N-D tensor with shape [d_0, d_1, ..., d_{N-1}]
  * \param weight K-D tensor with shape [r_0, r_1, ..., r_{K-1}] where K == 
len(axis) and
  *               d_{axis_k} == r_k
- * \param bias Optional, K-D tensor with shape [r_0, r_1, ..., r_{K-1}] where
- *             d_{axis_k} == r_k
  * \param axis The axis to normalize over.
  * \param epsilon The epsilon value to avoid division by zero.
  * \param name The name of the operation.
  * \param tag The tag to mark the operation.
  * \return The normalized tensor, with the same shape as data.
  */
-inline Tensor rms_norm(const Tensor& data, const Tensor& weight, const Tensor& 
bias,
-                       const Array<Integer>& axis, double epsilon, std::string 
name = "T_rms_norm",
+inline Tensor rms_norm(const Tensor& data, const Tensor& weight, const 
Array<Integer>& axis,
+                       double epsilon, std::string name = "T_rms_norm",
                        std::string tag = kInjective) {
   const auto& data_type = data->dtype;
   const auto& weight_type = weight.defined() ? weight->dtype : data_type;
   ICHECK(data_type == weight_type) << "rms_norm: data and weight must have the 
same type";
-  const auto& bias_type = bias.defined() ? bias->dtype : data_type;
-  ICHECK(data_type == bias_type) << "rms_norm: data and bias must have the 
same type";
 
   auto square = multiply(data, data);
   auto square_sum = sum(square, axis, /*keepdims=*/false, /*atleast1d=*/true);
@@ -80,9 +76,6 @@ inline Tensor rms_norm(const Tensor& data, const Tensor& 
weight, const Tensor& b
     auto output =
         data(indices) * weight(reduce_indices) *
         tvm::rsqrt(square_sum(non_reduce_indices) / reduce_extent + 
make_const(data_type, epsilon));
-    if (bias.defined()) {
-      output += bias(reduce_indices);
-    }
     return output;
   };
   auto rms_norm = tvm::te::compute(data->shape, rms_norm_func, name, tag);
diff --git a/python/tvm/relax/frontend/nn/__init__.py 
b/python/tvm/relax/frontend/nn/__init__.py
index 8375cb6618..cb8ba85295 100644
--- a/python/tvm/relax/frontend/nn/__init__.py
+++ b/python/tvm/relax/frontend/nn/__init__.py
@@ -15,5 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 """A PyTorch-like API to build IRModules."""
-from . import spec
+from . import op, spec
 from .core import Effect, Module, ModuleList, Parameter, Tensor
+from .modules import Embedding, IOEffect, KVCache, Linear, RMSNorm
+from .op import *
diff --git a/python/tvm/relax/frontend/nn/core.py 
b/python/tvm/relax/frontend/nn/core.py
index 46197b7141..918c96d568 100644
--- a/python/tvm/relax/frontend/nn/core.py
+++ b/python/tvm/relax/frontend/nn/core.py
@@ -218,7 +218,7 @@ class Parameter(Tensor):
 
     def to(self, dtype: Optional[str] = None) -> None:  # pylint: 
disable=invalid-name
         """Change the dtype of the parameter if it is not bound to any 
concrete data"""
-        if dtype is not None and self._data is not None:
+        if dtype is not None:
             if self._data is not None:
                 raise ValueError(
                     "Changing the dtype of a Parameter that has been bound to 
concrete "
@@ -267,7 +267,7 @@ class Module:
 
         Yields
         ------
-        (str, Parameter) – Tuple containing the name and parameter
+        (str, Parameter) - Tuple containing the name and parameter
         """
         yield from _attribute_finder(
             self, prefix, condition_yield=lambda x: isinstance(x, Parameter)
diff --git a/python/tvm/relax/frontend/nn/modules.py 
b/python/tvm/relax/frontend/nn/modules.py
index 20f5a1d3c3..228f6bdd6e 100644
--- a/python/tvm/relax/frontend/nn/modules.py
+++ b/python/tvm/relax/frontend/nn/modules.py
@@ -15,11 +15,15 @@
 # specific language governing permissions and limitations
 # under the License.
 """Builtin Modules."""
-from typing import List, Optional
+from typing import List, Optional, Sequence, Union
 
 from tvm import relax as rx
+from tvm import tir
+from tvm._ffi import register_func
+from tvm.runtime import NDArray
 
-from .core import Effect, Tensor
+from . import op
+from .core import Effect, Module, Parameter, Tensor, get_default_dtype
 
 
 class IOEffect(Effect):
@@ -49,3 +53,274 @@ class IOEffect(Effect):
     def print_(self, tensor: Tensor) -> None:
         """Encloses the side effect of NDArray printing"""
         raise NotImplementedError
+
+
+@register_func("effect.print")
+def _print(_, array: NDArray) -> None:
+    print(f"effect.print: shape = {array.shape}, dtype = {array.dtype}, data 
=\n{array}")
+
+
+class Linear(Module):
+    """
+    Module for linear layer.
+    """
+
+    def __init__(  # pylint: disable=too-many-arguments
+        self,
+        in_features: int,
+        out_features: int,
+        bias: bool = True,
+        dtype: Optional[str] = None,
+        out_dtype: Optional[str] = None,
+    ):
+        super().__init__()
+        self.in_features = in_features
+        self.out_features = out_features
+        self.out_dtype = out_dtype
+        self.weight = Parameter((out_features, in_features), dtype)
+        if bias:
+            self.bias = Parameter((out_features,), dtype)
+        else:
+            self.bias = None
+
+    def forward(self, x: Tensor) -> Tensor:  # pylint: disable=invalid-name
+        """
+        Forward method for linear layer.
+
+        Parameters
+        ----------
+        x : Tensor
+            The input tensor.
+
+        Returns
+        -------
+        ret : Tensor
+            The output tensor for the linear layer.
+        """
+        # x: [*B, in_features]
+        # w: [in_features, out_features]
+        w = op.permute_dims(self.weight)  # pylint: disable=invalid-name
+        # x: [*B, out_features]
+        x = op.matmul(x, w, out_dtype=self.out_dtype)
+        if self.bias is not None:
+            x = x + self.bias
+        return x
+
+
+class RMSNorm(Module):
+    """
+    Module for rms norm layer.
+    """
+
+    def __init__(
+        self,
+        hidden_size: int,
+        axes: Union[int, List[int]],
+        epsilon: float = 1e-5,
+        bias: bool = True,
+        dtype: Optional[str] = None,
+    ):
+        super().__init__()
+        self.epsilon = epsilon
+        self.axes = axes
+        self.weight = Parameter((hidden_size,), dtype=dtype)
+        if bias:
+            self.bias = Parameter((hidden_size,), dtype=dtype)
+        else:
+            self.bias = None
+
+    # pylint: disable=invalid-name
+    def forward(self, x: Tensor):
+        """
+        Forward method for rms norm layer.
+
+        Parameters
+        ----------
+        x : Tensor
+            The input tensor.
+
+        Returns
+        -------
+        ret : Tensor
+            The output tensor for the rms norm layer.
+        """
+        out = op.rms_norm(x, weight=self.weight, axes=self.axes, 
epsilon=self.epsilon)
+        if self.bias:
+            out = op.add(out, self.bias)
+        return out
+
+    # pylint: enable=invalid-name
+
+
+class KVCache(Effect):
+    """
+    Effect to implement KVCache.
+    """
+
+    init_seq_len: int
+    unit_shape: List[int]
+    dtype: str
+    cache: Optional[rx.Var]
+
+    def __init__(
+        self,
+        init_seq_len: int,
+        unit_shape: Sequence[int],
+        dtype: Optional[str] = None,
+    ):
+        if dtype is None:
+            dtype = get_default_dtype()
+        # Usually the shape is: [init_seq_len, num_heads, head_dim]
+        # and unit_shape = [num_heads, head_dim]
+        self.init_seq_len = init_seq_len
+        self.unit_shape = [int(i) for i in unit_shape]
+        self.dtype = dtype
+
+    def emit_init(self, name_hint: str, bb: rx.BlockBuilder):  # pylint: 
disable=arguments-renamed
+        """
+        Emit the initialization of the KVCache effect.
+
+        Parameters
+        ----------
+        name_hint : str
+            The name hint of the initialization binding Var.
+
+        bb : relax.BlockBuilder
+            The relax BlockBuilder to emit.
+        """
+        init_shape = rx.ShapeExpr([self.init_seq_len] + self.unit_shape)
+        return [
+            bb.emit(
+                rx.Call(
+                    rx.extern("vm.builtin.attention_kv_cache_create"),
+                    args=[rx.op.zeros(init_shape, self.dtype), init_shape, 
rx.PrimValue(0)],
+                    sinfo_args=[rx.ObjectStructInfo()],
+                ),
+                name_hint=name_hint,
+            )
+        ]
+
+    def create(self, name_hint: str) -> rx.Var:
+        """
+        Create the implicit inputs to a relax.Function that represents the 
KVCache effect.
+
+        Parameters
+        ----------
+        name_hint : str
+            The name hint of the relax.Var.
+
+        Returns
+        -------
+        ret : relax.Var
+            The relax.Var for KVCache.
+        """
+        self.cache = rx.Var(name_hint, struct_info=rx.ObjectStructInfo())
+        return [self.cache]
+
+    def finalize(self) -> List[rx.Var]:
+        """
+        Finalize the KVCache effect as the implicit return value of a 
relax.Function.
+
+        Returns
+        -------
+        ret : List[rx.Var]
+            The output relax.Var as KVCache.
+        """
+        result = self.cache
+        self.cache = None
+        return [result]
+
+    def to(self, dtype: Optional[str] = None) -> None:
+        """
+        Convert the KVCache effect to specific dtype.
+
+        Parameters
+        ----------
+        dtype : Optional[str]
+            The target data type to convert.
+        """
+        if dtype is not None:
+            self.dtype = dtype
+
+    def view(self, seq_len: tir.Var) -> Tensor:
+        """
+        View the last elements in KVCache.
+
+        Parameters
+        ----------
+        seq_len : tir.Var
+            The number of last elements to view.
+
+        Returns
+        -------
+        ret : Tensor
+            The last tensor to view.
+        """
+        shape = rx.ShapeExpr([seq_len] + self.unit_shape)
+        return Tensor(
+            _expr=rx.BlockBuilder.current().emit(
+                rx.Call(
+                    rx.extern("vm.builtin.attention_kv_cache_view"),
+                    args=[self.cache, shape],
+                    sinfo_args=[rx.TensorStructInfo(shape, self.dtype)],
+                )
+            )
+        )
+
+    def append(self, new_element: Tensor) -> None:
+        """
+        Append a new element in KVCache.
+
+        Parameters
+        ----------
+        new_element : Tensor
+            The new tensor to append.
+        """
+        if new_element.dtype != self.dtype:
+            raise TypeError(
+                f'KVCache has been set to use dtype "{self.dtype}", '
+                f'but got "{new_element.dtype}"'
+            )
+        self.cache = rx.BlockBuilder.current().emit(
+            rx.Call(
+                rx.extern("vm.builtin.attention_kv_cache_append"),
+                args=[self.cache, new_element._expr],  # pylint: 
disable=protected-access
+                sinfo_args=[rx.ObjectStructInfo()],
+            )
+        )
+
+
+class Embedding(Module):
+    """
+    Module for embedding layer.
+    """
+
+    def __init__(self, num: int, dim: int, dtype: Optional[str] = None):
+        self.num = num
+        self.dim = dim
+        self.weight = Parameter((num, dim), dtype=dtype)
+
+    def forward(self, x: Tensor):  # pylint: disable=invalid-name
+        """
+        Forward method for embedding layer.
+
+        Parameters
+        ----------
+        x : Tensor
+            The input tensor.
+
+        Returns
+        -------
+        ret : Tensor
+            The output tensor for the embedding layer.
+        """
+        if x.ndim == 1:
+            return op.take(self.weight, x, axis=0)
+        return op.reshape(
+            op.take(
+                self.weight,
+                op.reshape(x, shape=[-1]),
+                axis=0,
+            ),
+            shape=[*x.shape, self.dim],  # TODO(@junrushao): revisit and 
remove self.dim
+        )
diff --git a/python/tvm/relax/frontend/nn/op.py 
b/python/tvm/relax/frontend/nn/op.py
index 74a337d8a3..9a98aa1adb 100644
--- a/python/tvm/relax/frontend/nn/op.py
+++ b/python/tvm/relax/frontend/nn/op.py
@@ -488,7 +488,6 @@ def softmax(x: Tensor, axis: int = -1, name: str = 
"softmax") -> Tensor:
 def rms_norm(
     x: Tensor,
     weight: Tensor,
-    bias: Optional[Tensor],
     axes: Union[int, List[int]],
     epsilon: float = 1e-5,
     name: str = "rms_norm",
@@ -501,7 +500,7 @@ def rms_norm(
 
     .. math::
 
-        out = \frac{data}{\sqrt{mean(data, axis)+\epsilon}} * weight + bias
+        out = \frac{data}{\sqrt{mean(data, axis)+\epsilon}} * weight
 
     Parameters
     ----------
@@ -511,9 +510,6 @@ def rms_norm(
     weight : Tensor
         The scale factor.
 
-    bias : Tensor
-        Optional offset factor.
-
     axes : Union[int, List[int]]
         The axes that along which the normalization is applied.
 
@@ -528,11 +524,7 @@ def rms_norm(
     result : Tensor
         The computed result.
     """
-    if bias is None:
-        bias = _op.zeros(weight.shape, dtype=weight.dtype)
-    else:
-        bias = bias._expr
-    return _wrap_nested(_op.nn.rms_norm(x._expr, weight._expr, bias, axes, 
epsilon), name)
+    return _wrap_nested(_op.nn.rms_norm(x._expr, weight._expr, axes, epsilon), 
name)
 
 
 def triu(x: Tensor, diagonal: int = 0, name: str = "triu") -> Tensor:
diff --git a/python/tvm/relax/op/nn/nn.py b/python/tvm/relax/op/nn/nn.py
index edba3c505f..aa5c7b18f6 100644
--- a/python/tvm/relax/op/nn/nn.py
+++ b/python/tvm/relax/op/nn/nn.py
@@ -20,8 +20,8 @@ from typing import List, Optional, Tuple, Union
 from tvm import DataType
 from tvm.tir import FloatImm
 
-from . import _ffi_api
 from ...expr import Expr
+from . import _ffi_api
 
 
 def conv1d(
@@ -928,8 +928,7 @@ def group_norm(
 def rms_norm(
     data: Expr,
     weight: Expr,
-    bias: Expr,
-    axes: Union[int, List[int]],
+    axes: Union[int, List[int]] = -1,
     epsilon: float = 1e-5,
 ) -> Expr:
     r"""
@@ -966,7 +965,7 @@ def rms_norm(
     """
     if isinstance(axes, int):
         axes = [axes]
-    return _ffi_api.rms_norm(data, weight, bias, axes, epsilon)  # type: ignore
+    return _ffi_api.rms_norm(data, weight, axes, epsilon)  # type: ignore
 
 
 def dropout(data: Expr, rate: float = 0.5) -> Expr:
diff --git a/python/tvm/relax/transform/legalize_ops/nn.py 
b/python/tvm/relax/transform/legalize_ops/nn.py
index b66b2d7b5d..5386fbf7cb 100644
--- a/python/tvm/relax/transform/legalize_ops/nn.py
+++ b/python/tvm/relax/transform/legalize_ops/nn.py
@@ -20,10 +20,11 @@ import logging
 import math
 from typing import Optional
 
-from tvm import topi, tir, te
+from tvm import te, tir, topi
+
 from ...block_builder import BlockBuilder
 from ...expr import Call, Expr
-from .common import register_legalize, _call_topi_without_attr
+from .common import _call_topi_without_attr, register_legalize
 
 
 @register_legalize("relax.nn.conv1d")
@@ -340,7 +341,6 @@ def _nn_rms_norm(bb: BlockBuilder, call: Call) -> Expr:
         topi.nn.rms_norm,
         call.args[0],
         call.args[1],
-        call.args[2],
         axis=call.attrs.axes,
         epsilon=call.attrs.epsilon,
     )
diff --git a/python/tvm/topi/nn/rms_norm.py b/python/tvm/topi/nn/rms_norm.py
index f2f5a7e674..9284517468 100644
--- a/python/tvm/topi/nn/rms_norm.py
+++ b/python/tvm/topi/nn/rms_norm.py
@@ -18,7 +18,7 @@
 from .. import cpp
 
 
-def rms_norm(data, weight, bias, axis, epsilon=1e-5):
+def rms_norm(data, weight, axis, epsilon=1e-5):
     """Root mean square normalization operator. The output will have the same 
data type as input.
 
     Parameters
@@ -29,9 +29,6 @@ def rms_norm(data, weight, bias, axis, epsilon=1e-5):
     weight: tvm.te.Tensor
         K-D with shape (r_0, r_1, ..., r_{K-1}) where K == len(axis) and 
d_{axis_k} == r_k
 
-    bias: tvm.te.Tensor
-        Optional, K-D with shape (r_0, r_1, ..., r_{K-1}) where K == len(axis) 
and d_{axis_k} == r_k
-
     axis : list of int
         Axis over the normalization applied
 
@@ -43,4 +40,4 @@ def rms_norm(data, weight, bias, axis, epsilon=1e-5):
     result : tvm.te.Tensor
         N-D with shape (d_0, d_1, ..., d_{N-1})
     """
-    return cpp.nn.rms_norm(data, weight, bias, axis, epsilon)
+    return cpp.nn.rms_norm(data, weight, axis, epsilon)
diff --git a/src/relax/op/nn/nn.cc b/src/relax/op/nn/nn.cc
index 79ea8650dd..cbd71da848 100644
--- a/src/relax/op/nn/nn.cc
+++ b/src/relax/op/nn/nn.cc
@@ -440,13 +440,13 @@ TVM_REGISTER_OP("relax.nn.group_norm")
 /* relax.nn.rms_norm */
 TVM_REGISTER_NODE_TYPE(RMSNormAttrs);
 
-Expr rms_norm(Expr data, Expr weight, Expr bias, Array<Integer> axes, double 
epsilon) {
+Expr rms_norm(Expr data, Expr weight, Array<Integer> axes, double epsilon) {
   ObjectPtr<RMSNormAttrs> attrs = make_object<RMSNormAttrs>();
   attrs->axes = std::move(axes);
   attrs->epsilon = epsilon;
 
   static const Op& op = Op::Get("relax.nn.rms_norm");
-  return Call(op, {std::move(data), std::move(weight), std::move(bias)}, 
Attrs{attrs}, {});
+  return Call(op, {std::move(data), std::move(weight)}, Attrs{attrs}, {});
 }
 
 TVM_REGISTER_GLOBAL("relax.op.nn.rms_norm").set_body_typed(rms_norm);
@@ -466,7 +466,7 @@ InferLayoutOutput InferLayoutRMSNorm(const Call& call,
                                      const VarLayoutMap& var_layout_map) {
   ICHECK(NoDesiredLayout(call, desired_layouts));
   std::vector<NLayout> initial_layouts;
-  for (size_t i = 0; i < 3; ++i) {
+  for (size_t i = 0; i < 2; ++i) {
     const auto* tensor_sinfo = 
GetStructInfoAs<TensorStructInfoNode>(call->args[i]);
     ICHECK(tensor_sinfo != nullptr) << "Invalid Call";
     ICHECK(!tensor_sinfo->IsUnknownNdim()) << "Only support known ndim";
@@ -488,10 +488,9 @@ InferLayoutOutput InferLayoutRMSNorm(const Call& call,
 
 TVM_REGISTER_OP("relax.nn.rms_norm")
     .set_attrs_type<RMSNormAttrs>()
-    .set_num_inputs(3)
+    .set_num_inputs(2)
     .add_argument("data", "Tensor", "Input to which rms_norm will be applied.")
     .add_argument("weight", "Tensor", "The scale factor.")
-    .add_argument("bias", "Tensor", "The offset factor.")
     .set_attr<FInferStructInfo>("FInferStructInfo", InferStructInfoRMSNorm)
     .set_attr<FRelaxInferLayout>("FRelaxInferLayout", InferLayoutRMSNorm)
     .set_attr<TMixedPrecisionPolicy>("TMixedPrecisionPolicy", 
MixedPrecisionPolicyKind::kFollow)
diff --git a/src/relax/op/nn/nn.h b/src/relax/op/nn/nn.h
index 624cfe9078..a3658fed54 100644
--- a/src/relax/op/nn/nn.h
+++ b/src/relax/op/nn/nn.h
@@ -79,7 +79,7 @@ Expr group_norm(Expr data, Expr gamma, Expr beta, int 
num_groups, int channel_ax
                 Array<Integer> axes, double epsilon, bool center, bool scale);
 
 /*! \brief Compute root mean square normalization. */
-Expr rms_norm(Expr data, Expr weight, Expr bias, Array<Integer> axes, double 
epsilon);
+Expr rms_norm(Expr data, Expr weight, Array<Integer> axes, double epsilon);
 
 /*!
  * \brief Applies the dropout operation to the input tensor.
diff --git a/src/topi/nn.cc b/src/topi/nn.cc
index ba88f01c68..b6198b6ad4 100644
--- a/src/topi/nn.cc
+++ b/src/topi/nn.cc
@@ -179,7 +179,7 @@ 
TVM_REGISTER_GLOBAL("topi.nn.instance_norm").set_body([](TVMArgs args, TVMRetVal
 
 /* Ops from nn/rms_norm.h */
 TVM_REGISTER_GLOBAL("topi.nn.rms_norm").set_body([](TVMArgs args, TVMRetValue* 
rv) {
-  *rv = nn::rms_norm(args[0], args[1], args[2], args[3], 
static_cast<double>(args[4]));
+  *rv = nn::rms_norm(args[0], args[1], args[2], args[3].operator double());
 });
 
 }  // namespace topi
diff --git a/tests/python/relax/test_frontend_nn_modules.py 
b/tests/python/relax/test_frontend_nn_modules.py
new file mode 100644
index 0000000000..27bbd68329
--- /dev/null
+++ b/tests/python/relax/test_frontend_nn_modules.py
@@ -0,0 +1,146 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+import numpy as np
+import pytest
+
+import tvm
+import tvm.testing
+from tvm import relax
+from tvm.ir import assert_structural_equal
+from tvm.relax.frontend.nn import core, modules, spec
+from tvm.script import ir as I
+from tvm.script import relax as R
+
+
+def test_linear():
+    @R.function
+    def forward(
+        x: R.Tensor((1, 4), dtype="float32"),
+        weight: R.Tensor((8, 4), dtype="float32"),
+        bias: R.Tensor((8,), dtype="float32"),
+        _io: R.Object,
+    ) -> R.Tuple(R.Tensor((1, 8), dtype="float32"), R.Tuple(R.Object)):
+        with R.dataflow():
+            permute_dims: R.Tensor((4, 8), dtype="float32") = 
R.permute_dims(weight, axes=None)
+            matmul: R.Tensor((1, 8), dtype="float32") = R.matmul(x, 
permute_dims, out_dtype="void")
+            add: R.Tensor((1, 8), dtype="float32") = R.add(matmul, bias)
+            gv1: R.Tuple(R.Tensor((1, 8), dtype="float32"), R.Tuple(R.Object)) 
= add, (_io,)
+            R.output(gv1)
+        return gv1
+
+    mod = modules.Linear(4, 8)
+    tvm_mod, _ = mod.export_tvm(spec={"forward": {"x": spec.Tensor((1, 4), 
"float32")}})
+    assert_structural_equal(tvm_mod["forward"], forward, True)
+
+
+def test_rms_norm():
+    @R.function
+    def forward(
+        x: R.Tensor((2, 4, 8), dtype="float32"),
+        weight: R.Tensor((8,), dtype="float32"),
+        _io: R.Object,
+    ) -> R.Tuple(R.Tensor((2, 4, 8), dtype="float32"), R.Tuple(R.Object)):
+        with R.dataflow():
+            rms_norm: R.Tensor((2, 4, 8), dtype="float32") = R.nn.rms_norm(
+                x, weight, axes=[2], epsilon=1.0000000000000001e-05
+            )
+            gv1: R.Tuple(R.Tensor((2, 4, 8), dtype="float32"), 
R.Tuple(R.Object)) = rms_norm, (_io,)
+            R.output(gv1)
+        return gv1
+
+    mod = modules.RMSNorm(8, [2], bias=False)
+    tvm_mod, _ = mod.export_tvm(spec={"forward": {"x": spec.Tensor((2, 4, 8), 
"float32")}})
+    tvm_mod.show()
+    assert_structural_equal(tvm_mod["forward"], forward, True)
+
+
+def test_embedding():
+    @R.function
+    def forward(
+        x: R.Tensor((1, 4), dtype="int32"),
+        weight: R.Tensor((4, 8), dtype="float32"),
+        _io: R.Object,
+    ) -> R.Tuple(R.Tensor((1, 4, 8), dtype="float32"), R.Tuple(R.Object)):
+        with R.dataflow():
+            reshape: R.Tensor((4,), dtype="int32") = R.reshape(x, R.shape([4]))
+            take: R.Tensor((4, 8), dtype="float32") = R.take(weight, reshape, 
axis=0)
+            reshape1: R.Tensor((1, 4, 8), dtype="float32") = R.reshape(take, 
R.shape([1, 4, 8]))
+            gv1: R.Tuple(R.Tensor((1, 4, 8), dtype="float32"), 
R.Tuple(R.Object)) = reshape1, (_io,)
+            R.output(gv1)
+        return gv1
+
+    mod = modules.Embedding(4, 8, "float32")
+    tvm_mod, _ = mod.export_tvm(spec={"forward": {"x": spec.Tensor((1, 4), 
"int32")}})
+    assert_structural_equal(tvm_mod["forward"], forward, True)
+
+
+def test_kv_cache():
+    @I.ir_module
+    class Module:
+        @R.function
+        def _initialize_effect() -> R.Tuple(R.Object, R.Object):
+            with R.dataflow():
+                _io: R.Object = R.null_value()
+                lv: R.Tensor((8, 2, 4), dtype="float32") = R.zeros(
+                    R.shape([8, 2, 4]), dtype="float32"
+                )
+                cache: R.Object = R.call_packed(
+                    "vm.builtin.attention_kv_cache_create",
+                    lv,
+                    R.shape([8, 2, 4]),
+                    R.prim_value(0),
+                    sinfo_args=(R.Object,),
+                )
+                lv1: R.Tuple(R.Object, R.Object) = _io, cache
+                gv: R.Tuple(R.Object, R.Object) = lv1
+                R.output(gv)
+            return gv
+
+        @R.function
+        def forward(
+            x: R.Tensor((2, 4), dtype="float32"), _io: R.Object, cache: 
R.Object
+        ) -> R.Tuple(R.Tensor((4, 2, 4), dtype="float32"), R.Tuple(R.Object, 
R.Object)):
+            with R.dataflow():
+                lv2: R.Object = R.call_packed(
+                    "vm.builtin.attention_kv_cache_append", cache, x, 
sinfo_args=(R.Object,)
+                )
+                lv3: R.Tensor((4, 2, 4), dtype="float32") = R.call_packed(
+                    "vm.builtin.attention_kv_cache_view",
+                    lv2,
+                    R.shape([4, 2, 4]),
+                    sinfo_args=(R.Tensor((4, 2, 4), dtype="float32"),),
+                )
+                gv1: R.Tuple(
+                    R.Tensor((4, 2, 4), dtype="float32"), R.Tuple(R.Object, 
R.Object)
+                ) = lv3, (_io, lv2)
+                R.output(gv1)
+            return gv1
+
+    class KVCacheTest(modules.Module):
+        def __init__(self) -> None:
+            self.cache = modules.KVCache(8, [2, 4])
+
+        def forward(self, x: core.Tensor) -> core.Tensor:
+            self.cache.append(x)
+            return self.cache.view(4)
+
+    tvm_mod, _ = KVCacheTest().export_tvm(spec={"forward": {"x": 
spec.Tensor((2, 4), "float32")}})
+    assert_structural_equal(tvm_mod, Module, True)
+
+
+if __name__ == "__main__":
+    tvm.testing.main()
diff --git a/tests/python/relax/test_frontend_nn_op.py 
b/tests/python/relax/test_frontend_nn_op.py
index 6668f02743..d2501cb0ef 100644
--- a/tests/python/relax/test_frontend_nn_op.py
+++ b/tests/python/relax/test_frontend_nn_op.py
@@ -15,13 +15,14 @@
 # specific language governing permissions and limitations
 # under the License.
 import pytest
+
 import tvm
 import tvm.testing
 from tvm import relax, te, tir
-from tvm.relax.frontend.nn import Tensor, Module, spec, op
+from tvm.relax.frontend.nn import Module, Tensor, op, spec
+from tvm.script import ir as I
 from tvm.script import relax as R
 from tvm.script import tir as T
-from tvm.script import ir as I
 
 
 def test_binary():
@@ -136,21 +137,21 @@ def test_datatype():
 
 def test_nn():
     class Model(Module):
-        def test(self, x: Tensor, weight: Tensor, bias: Tensor):
+        def test(self, x: Tensor, weight: Tensor):
             silu_out = op.silu(x)
             softmax_out = op.softmax(x, axis=2)
-            rms_norm_out = op.rms_norm(x, weight, bias, axes=[-2, -1])
-            rms_norm_with_bias_out = op.rms_norm(x, weight, bias, axes=[-2, 
-1])
+            rms_norm_out = op.rms_norm(x, weight, axes=[-2, -1])
+            rms_norm_with_bias_out = op.rms_norm(x, weight, axes=[-2, -1])
             return x
 
     # fmt: off
     @R.function
-    def test(x: R.Tensor((2, 3, 4, 5), dtype="float32"), weight: R.Tensor((4, 
5), dtype="float32"), bias: R.Tensor((4, 5), dtype="float32"), _io: R.Object) 
-> R.Tuple(R.Tensor((2, 3, 4, 5), dtype="float32"), R.Tuple(R.Object)):
+    def test(x: R.Tensor((2, 3, 4, 5), dtype="float32"), weight: R.Tensor((4, 
5), dtype="float32"), _io: R.Object) -> R.Tuple(R.Tensor((2, 3, 4, 5), 
dtype="float32"), R.Tuple(R.Object)):
         with R.dataflow():
             silu: R.Tensor((2, 3, 4, 5), dtype="float32") = R.nn.silu(x)
             softmax: R.Tensor((2, 3, 4, 5), dtype="float32") = R.nn.softmax(x, 
axis=2)
-            rms_norm: R.Tensor((2, 3, 4, 5), dtype="float32") = 
R.nn.rms_norm(x, weight, bias, axes=[-2, -1], epsilon=1.0000000000000001e-05)
-            rms_norm1: R.Tensor((2, 3, 4, 5), dtype="float32") = 
R.nn.rms_norm(x, weight, bias, axes=[-2, -1], epsilon=1.0000000000000001e-05)
+            rms_norm: R.Tensor((2, 3, 4, 5), dtype="float32") = 
R.nn.rms_norm(x, weight, axes=[-2, -1], epsilon=1.0000000000000001e-05)
+            rms_norm1: R.Tensor((2, 3, 4, 5), dtype="float32") = 
R.nn.rms_norm(x, weight, axes=[-2, -1], epsilon=1.0000000000000001e-05)
             gv1: R.Tuple(R.Tensor((2, 3, 4, 5), dtype="float32"), 
R.Tuple(R.Object)) = x, (_io,)
             R.output(gv1)
         return gv1
diff --git a/tests/python/relax/test_transform_legalize_ops_nn.py 
b/tests/python/relax/test_transform_legalize_ops_nn.py
index 66da2bae55..d750901b59 100644
--- a/tests/python/relax/test_transform_legalize_ops_nn.py
+++ b/tests/python/relax/test_transform_legalize_ops_nn.py
@@ -16,11 +16,13 @@
 # under the License.
 
 import pytest
+
 import tvm
-from tvm.relax.transform import LegalizeOps
-from tvm.script import relax as R, tir as T, ir as I
 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 tir as T
 
 ##################### Neural network #####################
 
@@ -2747,8 +2749,8 @@ def test_rms_norm():
     @tvm.script.ir_module
     class RMSNorm:
         @R.function
-        def main(x: R.Tensor((2, 3, 4, 5), "float32"), weight: R.Tensor((4, 
5), "float32"), bias: 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, 
bias, axes=[-2, -1])
+        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
@@ -2757,7 +2759,6 @@ def test_rms_norm():
         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"),
-            C: T.Buffer((T.int64(4), T.int64(5)), "float32"),
             T_rms_norm: T.Buffer(
                 (T.int64(2), T.int64(3), T.int64(4), T.int64(5)), "float32"
             ),
@@ -2795,7 +2796,6 @@ def test_rms_norm():
                         A[v_ax0, v_ax1, v_ax2, v_ax3],
                         B[v_ax2, v_ax3],
                         T_multiply_red[v_ax0, v_ax1],
-                        C[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] = (
@@ -2805,19 +2805,17 @@ def test_rms_norm():
                             T_multiply_red[v_ax0, v_ax1] * T.float32(0.05)
                             + T.float32(1e-5)
                         )
-                        + C[v_ax2, v_ax3]
                     )
 
         @R.function
         def main(
             x: R.Tensor((2, 3, 4, 5), dtype="float32"),
             weight: R.Tensor((4, 5), dtype="float32"),
-            bias: 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, bias),
+                (x, weight),
                 out_sinfo=R.Tensor((2, 3, 4, 5), dtype="float32"),
             )
             return gv
@@ -2831,8 +2829,8 @@ def test_rms_norm_fp16():
     @tvm.script.ir_module
     class RMSNorm:
         @R.function
-        def main(x: R.Tensor((2, 3, 4, 5), "float16"), weight: R.Tensor((4, 
5), "float16"), bias: 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, 
bias, axes=[-2, -1])
+        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
@@ -2841,7 +2839,6 @@ def test_rms_norm_fp16():
         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"),
-            C: T.Buffer((T.int64(4), T.int64(5)), "float16"),
             T_rms_norm: T.Buffer(
                 (T.int64(2), T.int64(3), T.int64(4), T.int64(5)), "float16"
             ),
@@ -2881,7 +2878,6 @@ def test_rms_norm_fp16():
                         A[v_ax0, v_ax1, v_ax2, v_ax3],
                         B[v_ax2, v_ax3],
                         T_multiply_red[v_ax0, v_ax1],
-                        C[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] = (
@@ -2891,19 +2887,17 @@ def test_rms_norm_fp16():
                             T_multiply_red[v_ax0, v_ax1] / (T.float16(4) * 
T.float16(5))
                             + T.float16(1e-5)
                         )
-                        + C[v_ax2, v_ax3]
                     )
 
         @R.function
         def main(
             x: R.Tensor((2, 3, 4, 5), dtype="float16"),
             weight: R.Tensor((4, 5), dtype="float16"),
-            bias: 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, bias),
+                (x, weight),
                 out_sinfo=R.Tensor((2, 3, 4, 5), dtype="float16"),
             )
             return gv
@@ -2917,24 +2911,23 @@ def test_rms_norm_symbolic():
     @tvm.script.ir_module
     class RMSNorm:
         @R.function
-        def main(x: R.Tensor(("n", "s", "f"), "float32"), weight: 
R.Tensor(("s", "f"), "float32"), bias: R.Tensor(("s", "f"), "float32")) -> 
R.Tensor(("n", "s", "f"), "float32"):
+        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, 
bias, axes=[1, 2])
+            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
         def rms_norm(
-            var_A: T.handle, var_B: T.handle, var_C: T.handle, var_T_rms_norm: 
T.handle
+            var_A: T.handle, var_B: T.handle, var_T_rms_norm: T.handle
         ):
             T.func_attr({"tir.noalias": T.bool(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))
-            C = T.match_buffer(var_C, (s, f))
             T_rms_norm = T.match_buffer(var_T_rms_norm, (n, s, f))
             # with T.block("root"):
             T_multiply = T.alloc_buffer((n, s, f))
@@ -2964,7 +2957,6 @@ def test_rms_norm_symbolic():
                         A[v_ax0, v_ax1, v_ax2],
                         B[v_ax1, v_ax2],
                         T_multiply_red[v_ax0],
-                        C[v_ax1, v_ax2],
                     )
                     T.writes(T_rms_norm[v_ax0, v_ax1, v_ax2])
                     T_rms_norm[v_ax0, v_ax1, v_ax2] = (
@@ -2975,14 +2967,12 @@ def test_rms_norm_symbolic():
                             / (T.Cast("float32", s) * T.Cast("float32", f))
                             + T.float32(1e-5)
                         )
-                        + C[v_ax1, v_ax2]
                     )
 
         @R.function
         def main(
             x: R.Tensor(("n", "s", "f"), dtype="float32"),
             weight: R.Tensor(("s", "f"), dtype="float32"),
-            bias: R.Tensor(("s", "f"), dtype="float32"),
         ) -> R.Tensor(("n", "s", "f"), dtype="float32"):
             n = T.int64()
             s = T.int64()
@@ -2990,7 +2980,7 @@ def test_rms_norm_symbolic():
             cls = Expected
             gv = R.call_tir(
                 cls.rms_norm,
-                (x, weight, bias),
+                (x, weight),
                 out_sinfo=R.Tensor((n, s, f), dtype="float32"),
             )
             return gv
@@ -2999,6 +2989,84 @@ def test_rms_norm_symbolic():
     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
+        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_rms_norm: T.Buffer(
+                (T.int64(2), T.int64(3), T.int64(4), T.int64(5)), "float32"
+            ),
+        ):
+            T.func_attr({"tir.noalias": T.bool(True)})
+            # with T.block("root"):
+            T_multiply = T.alloc_buffer((T.int64(2), T.int64(3), T.int64(4), 
T.int64(5)))
+            T_multiply_red = T.alloc_buffer((T.int64(2), T.int64(3)))
+            for ax0, ax1, ax2, ax3 in T.grid(
+                T.int64(2), T.int64(3), T.int64(4), T.int64(5)
+            ):
+                with T.block("T_multiply"):
+                    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_multiply[v_ax0, v_ax1, v_ax2, v_ax3])
+                    T_multiply[v_ax0, v_ax1, v_ax2, v_ax3] = (
+                        A[v_ax0, v_ax1, v_ax2, v_ax3] * A[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.block("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, ax2, ax3 in T.grid(
+                T.int64(2), T.int64(3), T.int64(4), T.int64(5)
+            ):
+                with T.block("T_rms_norm"):
+                    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],
+                        B[v_ax2, v_ax3],
+                        T_multiply_red[v_ax0, v_ax1],
+                    )
+                    T.writes(T_rms_norm[v_ax0, v_ax1, v_ax2, v_ax3])
+                    T_rms_norm[v_ax0, v_ax1, v_ax2, v_ax3] = (
+                        A[v_ax0, v_ax1, v_ax2, v_ax3]
+                        * B[v_ax2, v_ax3]
+                        * T.rsqrt(
+                            T_multiply_red[v_ax0, v_ax1] * T.float32(0.05)
+                            + T.float32(1e-05)
+                        )
+                    )
+
+        @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_sinfo=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


Reply via email to