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

tqchen 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 e1d71b3720 [Unity] Add dlight.gpu.Fallback in DispatchSortScan, add 
argsort, topk, and cumprod (#16351)
e1d71b3720 is described below

commit e1d71b3720347ba566e5100a0dc7c4fc7fc054a5
Author: Yong Wu <[email protected]>
AuthorDate: Tue Jan 9 17:47:43 2024 -0800

    [Unity] Add dlight.gpu.Fallback in DispatchSortScan, add argsort, topk, and 
cumprod (#16351)
---
 include/tvm/relax/attrs/sort.h                     |  52 ----
 include/tvm/relax/attrs/sorting.h                  |  99 +++++++
 include/tvm/relax/attrs/statistical.h              |  20 +-
 python/tvm/relax/backend/dispatch_sort_scan.py     | 124 ++++++---
 python/tvm/relax/op/__init__.py                    |   4 +-
 python/tvm/relax/op/op_attrs.py                    |  16 +-
 python/tvm/relax/op/sort.py                        |  45 ---
 python/tvm/relax/op/sorting.py                     | 116 ++++++++
 python/tvm/relax/op/statistical.py                 |  72 ++++-
 .../relax/transform/legalize_ops/statistical.py    |  11 +-
 python/tvm/script/ir_builder/relax/ir.py           |   6 +
 src/relax/op/tensor/sort.cc                        |  56 ----
 src/relax/op/tensor/sorting.cc                     | 155 +++++++++++
 src/relax/op/tensor/{sort.h => sorting.h}          |  32 ++-
 src/relax/op/tensor/statistical.cc                 |  55 ++--
 src/relax/op/tensor/statistical.h                  |  21 +-
 .../relax/test_backend_dispatch_sort_scan.py       | 301 +++++++++++++++++++--
 tests/python/relax/test_op_sort.py                 | 192 +++++++++++++
 tests/python/relax/test_op_statistical.py          |  53 ++--
 .../python/relax/test_tvmscript_parser_op_sort.py  |  14 +-
 .../relax/test_tvmscript_parser_op_statistical.py  |   8 +-
 21 files changed, 1178 insertions(+), 274 deletions(-)

diff --git a/include/tvm/relax/attrs/sort.h b/include/tvm/relax/attrs/sort.h
deleted file mode 100644
index fc0c4e7189..0000000000
--- a/include/tvm/relax/attrs/sort.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.
- */
-
-/*!
- * \file tvm/relax/attrs/sort.h
- * \brief Attributes for sorting operators.
- */
-#ifndef TVM_RELAX_ATTRS_SORT_H_
-#define TVM_RELAX_ATTRS_SORT_H_
-
-#include <tvm/relax/expr.h>
-#include <tvm/tir/index_map.h>
-
-namespace tvm {
-namespace relax {
-
-/*! \brief Attributes used in sort operator */
-struct SortAttrs : public tvm::AttrsNode<SortAttrs> {
-  int axis;
-  bool descending;
-
-  TVM_DECLARE_ATTRS(SortAttrs, "relax.attrs.SortAttrs") {
-    TVM_ATTR_FIELD(axis).set_default(-1).describe(
-        "Axis along which the sort is computed."
-        "The default the last axis is used.");
-    TVM_ATTR_FIELD(descending)
-        .set_default(false)
-        .describe(
-            "Whether to sort in descending order."
-            "If it is not specified, it defaults to the ascending order.");
-  }
-};  // struct SortAttrs
-}  // namespace relax
-}  // namespace tvm
-
-#endif  // TVM_RELAX_ATTRS_SORT_H_
diff --git a/include/tvm/relax/attrs/sorting.h 
b/include/tvm/relax/attrs/sorting.h
new file mode 100644
index 0000000000..4daf7a45b2
--- /dev/null
+++ b/include/tvm/relax/attrs/sorting.h
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+/*!
+ * \file tvm/relax/attrs/sorting.h
+ * \brief Attributes for sorting operators.
+ */
+#ifndef TVM_RELAX_ATTRS_SORTING_H_
+#define TVM_RELAX_ATTRS_SORTING_H_
+
+#include <tvm/relax/expr.h>
+#include <tvm/tir/index_map.h>
+
+namespace tvm {
+namespace relax {
+
+/*! \brief Attributes used in sort operator */
+struct SortAttrs : public tvm::AttrsNode<SortAttrs> {
+  int axis;
+  bool descending;
+
+  TVM_DECLARE_ATTRS(SortAttrs, "relax.attrs.SortAttrs") {
+    TVM_ATTR_FIELD(axis).set_default(-1).describe(
+        "Axis along which the sort is computed."
+        "The default the last axis is used.");
+    TVM_ATTR_FIELD(descending)
+        .set_default(false)
+        .describe(
+            "Whether to sort in descending order."
+            "If it is not specified, it defaults to the ascending order.");
+  }
+};  // struct SortAttrs
+
+/*! \brief Attributes used in argsort operator */
+struct ArgsortAttrs : public tvm::AttrsNode<ArgsortAttrs> {
+  int axis;
+  bool descending;
+  DataType dtype;
+
+  TVM_DECLARE_ATTRS(ArgsortAttrs, "relax.attrs.ArgsortAttrs") {
+    TVM_ATTR_FIELD(axis).set_default(-1).describe(
+        "Axis along which the argsort is computed."
+        "The default the last axis is used.");
+    TVM_ATTR_FIELD(descending)
+        .set_default(false)
+        .describe(
+            "Whether to argsort in descending order."
+            "If it is not specified, it defaults to the ascending order.");
+    TVM_ATTR_FIELD(dtype)
+        .set_default(NullValue<DataType>())
+        .describe("DType of the output indices.");
+  }
+};  // struct ArgsortAttrs
+
+/*! \brief Attributes used in topk operator */
+struct TopKAttrs : public tvm::AttrsNode<TopKAttrs> {
+  int k;
+  int axis;
+  bool largest;
+  String ret_type;
+  DataType dtype;
+
+  TVM_DECLARE_ATTRS(TopKAttrs, "relax.attrs.TopKAttrs") {
+    TVM_ATTR_FIELD(k).describe("Number of top elements to select");
+    TVM_ATTR_FIELD(axis).set_default(-1).describe("Axis along which to sort 
the input tensor.");
+    TVM_ATTR_FIELD(ret_type).set_default("both").describe(
+        "The return type [both, values, indices]."
+        "both - return both top k data and indices."
+        "values - return top k data only."
+        "indices - return top k indices only.");
+    TVM_ATTR_FIELD(largest).set_default(true).describe(
+        "Whether to return largest or smallest elements."
+        "By default, return the largest k elements.");
+    TVM_ATTR_FIELD(dtype)
+        .set_default(NullValue<DataType>())
+        .describe("Data type of the output indices.");
+  }
+};  // struct TopKAttrs
+
+}  // namespace relax
+}  // namespace tvm
+
+#endif  // TVM_RELAX_ATTRS_SORTING_H_
diff --git a/include/tvm/relax/attrs/statistical.h 
b/include/tvm/relax/attrs/statistical.h
index d5d8d982b5..9f9a2fa870 100644
--- a/include/tvm/relax/attrs/statistical.h
+++ b/include/tvm/relax/attrs/statistical.h
@@ -42,20 +42,24 @@ struct StatisticalAttrs : public 
tvm::AttrsNode<StatisticalAttrs> {
   }
 };  // struct StatisticalAttrs
 
-/*! \brief Attributes used in cumsum operators */
-struct CumsumAttrs : public tvm::AttrsNode<CumsumAttrs> {
+/*! \brief Attributes used in scan operators like cumsum, cumprod */
+struct ScanopAttrs : public tvm::AttrsNode<ScanopAttrs> {
   Optional<Integer> axis;
   DataType dtype;
+  Bool exclusive = Bool(false);
 
-  TVM_DECLARE_ATTRS(CumsumAttrs, "relax.attrs.CumsumAttrs") {
+  TVM_DECLARE_ATTRS(ScanopAttrs, "relax.attrs.ScanopAttrs") {
     TVM_ATTR_FIELD(axis).describe(
-        "Axis along which the cumulative sum is computed."
-        "The default (None) is to compute the cumsum over the flattened 
array.");
+        "The axis along which to perform the scan computation."
+        "The default (None) is to compute over the flattened array.");
     TVM_ATTR_FIELD(dtype).describe(
-        "Type of the returned array and of the accumulator in which the 
elements are summed."
-        "If dtype is not specified, it defaults to the dtype of data.");
+        "The output data type."
+        "If dtype is not specified, it defaults to the dtype of input data.");
+    TVM_ATTR_FIELD(exclusive)
+        .describe("The first element is not included")
+        .set_default(Bool(false));
   }
-};  // struct CumsumAttrs
+};  // struct ScanopAttrs
 
 }  // namespace relax
 }  // namespace tvm
diff --git a/python/tvm/relax/backend/dispatch_sort_scan.py 
b/python/tvm/relax/backend/dispatch_sort_scan.py
index f0f1aa9063..bb3f57ce96 100644
--- a/python/tvm/relax/backend/dispatch_sort_scan.py
+++ b/python/tvm/relax/backend/dispatch_sort_scan.py
@@ -17,13 +17,13 @@
 # pylint: disable=invalid-name, unused-argument, redefined-argument-from-local
 """Dispatch sort and scan operators to platform dependent implementation."""
 
-from tvm import topi
+from tvm import topi, dlight, relax
 from tvm.ir import Op
 from tvm.ir.module import IRModule
 from tvm.ir.transform import PassContext, module_pass
 from tvm.target import Target
 from tvm.contrib.thrust import can_use_thrust
-from tvm.relax import Expr, Function, Call, PyExprMutator, expr_functor, 
TensorStructInfo
+from tvm.relax import PyExprMutator, expr_functor
 
 
 @expr_functor.mutator
@@ -36,13 +36,17 @@ class SortScanDispatcher(PyExprMutator):
     def __init__(self, mod):
         super().__init__(mod)
 
-    def _get_target(self, expr: Expr) -> Target:
-        sinfo = expr.struct_info
+    def _get_target(self, sinfo: relax.StructInfo) -> Target:
         # Get target information from TensorStructInfo
-        if isinstance(sinfo, TensorStructInfo):
+        if isinstance(sinfo, relax.TensorStructInfo):
             vdevice = sinfo.vdevice
             if vdevice is not None:
                 return vdevice.target
+        elif isinstance(sinfo, relax.TupleStructInfo):
+            for f in sinfo.fields:
+                tgt = self._get_target(f)
+                if tgt != Target.current():
+                    return tgt
         # Return the target in current context
         target = Target.current()
         if target is None:
@@ -52,38 +56,94 @@ class SortScanDispatcher(PyExprMutator):
             )
         return target
 
-    def visit_call_(self, call: Call) -> Expr:
+    def _apply_dlight_gpu_fallback(self, target: Target, tir_call: relax.Call) 
-> None:
+        # Apply dlight.gpu.Fallback() on GPU
+        gvar = tir_call.args[0]
+        assert isinstance(gvar, relax.GlobalVar)
+        scan_prim_func = self.builder_.get()[gvar]
+        sch = dlight.base.transform._apply_rules(
+            scan_prim_func,
+            target,
+            [
+                dlight.gpu.Fallback(),
+            ],
+            False,
+        )
+        if sch is not None:
+            assert len(sch) == 1
+            self.builder_.update_func(gvar, 
sch[0].mod["main"].with_attr("tir.is_scheduled", 1))
+
+    def visit_call_(self, call: relax.Call) -> relax.Expr:
         if not isinstance(call.op, Op):
             return super().visit_call_(call)
 
         if call.op.name == "relax.sort":
-            tgt = self._get_target(call)
+            tgt = self._get_target(call.struct_info)
+            te_func = topi.sort
             with tgt:
                 if can_use_thrust(tgt, "tvm.contrib.thrust.sort"):
-                    return self.builder_.call_te(
-                        topi.cuda.sort_thrust,
-                        call.args[0],
-                        call.attrs.axis,
-                        not call.attrs.descending,
-                    )
-                return self.builder_.call_te(
-                    topi.cuda.sort if tgt.kind.name == "cuda" else topi.sort,
-                    call.args[0],
-                    call.attrs.axis,
-                    not call.attrs.descending,
-                )
-
-        if call.op.name == "relax.cumsum":
-            tgt = self._get_target(call)
-            axis = int(call.attrs.axis) if call.attrs.axis is not None else 
call.attrs.axis
+                    te_func = topi.cuda.sort_thrust
+                elif tgt.kind.name == "cuda":
+                    te_func = topi.cuda.sort
+            return self.builder_.call_te(
+                te_func,
+                call.args[0],
+                call.attrs.axis,
+                not call.attrs.descending,
+            )
+        if call.op.name == "relax.argsort":
+            tgt = self._get_target(call.struct_info)
+            te_func = topi.argsort
             with tgt:
-                return self.builder_.call_te(
-                    topi.cuda.cumsum if tgt.kind.name == "cuda" else 
topi.cumsum,
-                    call.args[0],
-                    axis,
-                    call.attrs.dtype,
-                )
-
+                if can_use_thrust(tgt, "tvm.contrib.thrust.sort"):
+                    te_func = topi.cuda.argsort_thrust
+                elif tgt.kind.name == "cuda":
+                    te_func = topi.cuda.argsort
+            return self.builder_.call_te(
+                te_func,
+                call.args[0],
+                axis=call.attrs.axis,
+                is_ascend=not call.attrs.descending,
+                dtype=call.attrs.dtype,
+            )
+        if call.op.name == "relax.topk":
+            tgt = self._get_target(call.struct_info)
+            te_func = topi.topk
+            if can_use_thrust(tgt, "tvm.contrib.thrust.sort"):
+                te_func = topi.cuda.topk_thrust
+            elif tgt.kind.name == "cuda":
+                te_func = topi.cuda.topk
+            tir_call = self.builder_.call_te(
+                te_func,
+                call.args[0],
+                axis=call.attrs.axis,
+                ret_type=call.attrs.ret_type,
+                is_ascend=not call.attrs.largest,
+                dtype=call.attrs.dtype,
+            )
+            if tgt.kind.name != "cuda":
+                return tir_call
+            # apply dlight gpu fallback
+            self._apply_dlight_gpu_fallback(tgt, tir_call)
+            return tir_call
+        if call.op.name in ("relax.cumprod", "relax.cumsum"):
+            tgt = self._get_target(call.struct_info)
+            axis = int(call.attrs.axis) if call.attrs.axis is not None else 
call.attrs.axis
+            te_func = topi.cuda.cumsum if tgt.kind.name == "cuda" else 
topi.cumsum
+            if call.op.name == "relax.cumprod":
+                te_func = topi.cuda.cumprod if tgt.kind.name == "cuda" else 
topi.cumprod
+            tir_call = self.builder_.call_te(
+                te_func,
+                call.args[0],
+                axis,
+                call.attrs.dtype,
+                call.attrs.exclusive,
+            )
+            if tgt.kind.name != "cuda":
+                return tir_call
+            # apply dlight gpu fallback
+            self._apply_dlight_gpu_fallback(tgt, tir_call)
+            return tir_call
         return super().visit_call_(call)
 
 
@@ -96,7 +156,7 @@ class DispatchSortScan:
     def transform_module(self, mod: IRModule, ctx: PassContext) -> IRModule:
         sort_scan_dispater = SortScanDispatcher(mod)
         for gv, func in mod.functions_items():
-            if isinstance(func, Function):
+            if isinstance(func, relax.Function):
                 func = sort_scan_dispater.visit_expr(func)
                 sort_scan_dispater.builder_.update_func(gv, func)
-        return sort_scan_dispater.builder_.get()
+        return sort_scan_dispater.builder_.finalize()
diff --git a/python/tvm/relax/op/__init__.py b/python/tvm/relax/op/__init__.py
index 085761f15d..5b585e18b4 100644
--- a/python/tvm/relax/op/__init__.py
+++ b/python/tvm/relax/op/__init__.py
@@ -99,8 +99,8 @@ from .mask import masked_fill
 from .qdq import quantize, dequantize
 from .search import argmax, argmin, where
 from .set import unique
-from .sort import sort
-from .statistical import cumsum, max, mean, min, prod, std, sum, variance
+from .sorting import sort, argsort, topk
+from .statistical import cumsum, cumprod, max, mean, min, prod, std, sum, 
variance
 from .ternary import ewise_fma
 from .unary import (
     abs,
diff --git a/python/tvm/relax/op/op_attrs.py b/python/tvm/relax/op/op_attrs.py
index 4dbbc17cf2..a3d46428c5 100644
--- a/python/tvm/relax/op/op_attrs.py
+++ b/python/tvm/relax/op/op_attrs.py
@@ -119,6 +119,11 @@ class SortAttrs(Attrs):
     """Attributes for sort operator"""
 
 
+@tvm._ffi.register_object("relax.attrs.ArgsortAttrs")
+class ArgsortAttrs(Attrs):
+    """Attributes for argsort operator"""
+
+
 @tvm._ffi.register_object("relax.attrs.SplitAttrs")
 class SplitAttrs(Attrs):
     """Attributes used in split operator"""
@@ -154,9 +159,14 @@ class TileAttrs(Attrs):
     """Attributes for tile operator"""
 
 
-@tvm._ffi.register_object("relax.attrs.CumsumAttrs")
-class CumsumAttrs(Attrs):
-    """Attributes for cumsum operator"""
+@tvm._ffi.register_object("relax.attrs.ScanopAttrs")
+class ScanopAttrs(Attrs):
+    """Attributes for scan operators"""
+
+
+@tvm._ffi.register_object("relax.attrs.TopKAttrs")
+class TopKAttrs(Attrs):
+    """Attributes for topk operators"""
 
 
 @tvm._ffi.register_object("relax.attrs.EinsumAttrs")
diff --git a/python/tvm/relax/op/sort.py b/python/tvm/relax/op/sort.py
deleted file mode 100644
index b139eefcdf..0000000000
--- a/python/tvm/relax/op/sort.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# 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.
-"""Sortings operators."""
-
-from . import _ffi_api
-from ..expr import Expr
-
-
-def sort(x: Expr, axis: int = -1, descending: bool = False):
-    """Performs sorting along the given axis and returns an array
-    in sorted order.
-
-    Parameters
-    ----------
-    x : relax.Expr
-        The input tensor.
-
-    axis : int
-        Axis along which to sort the input tensor.
-        By default the last axis of the input is used.
-
-    descending : bool
-        Whether to sort in descending order, the default is False
-
-    Returns
-    -------
-    out : relax.Expr
-        Sorted tensor.
-
-    """
-    return _ffi_api.sort(x, axis, descending)  # type: ignore
diff --git a/python/tvm/relax/op/sorting.py b/python/tvm/relax/op/sorting.py
new file mode 100644
index 0000000000..13937933c4
--- /dev/null
+++ b/python/tvm/relax/op/sorting.py
@@ -0,0 +1,116 @@
+# 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.
+"""Sortings operators."""
+from . import _ffi_api
+from ..expr import Expr, Constant
+
+
+def sort(x: Expr, axis: int = -1, descending: bool = False):
+    """Performs sorting along the given axis and returns an array
+    in sorted order.
+
+    Parameters
+    ----------
+    x : relax.Expr
+        The input tensor.
+
+    axis : int
+        Axis along which to sort the input tensor.
+        By default the last axis of the input is used.
+
+    descending : bool
+        Whether to sort in descending order, the default is False
+
+    Returns
+    -------
+    out : relax.Expr
+        Sorted tensor.
+
+    """
+    return _ffi_api.sort(x, axis, descending)  # type: ignore
+
+
+def argsort(data: Expr, axis: int = -1, descending: bool = False, dtype: str = 
"int32"):
+    """Performs sorting along the given axis and returns an array of indices
+    having same shape as an input array that index data in sorted order.
+
+    Parameters
+    ----------
+    data : relax.Expr
+        The input data tensor.
+
+    axis : int
+        Axis long which to sort the input tensor.
+
+    descending : bool
+        Whether to sort in descending order, the default is False
+
+    dtype : str
+        The data type of the output indices.
+
+    Returns
+    -------
+    out : relax.Expr
+        Tensor with same shape as data.
+    """
+    return _ffi_api.argsort(data, axis, descending, dtype)  # type: ignore
+
+
+def topk(
+    data: Expr,
+    k: int = 1,
+    axis: int = -1,
+    ret_type: str = "both",
+    largest: bool = True,
+    dtype: str = "int32",
+):
+    """Get the top k elements in an input tensor along the given axis.
+
+    ret_type specifies the return type, can be one of ("both", "values", 
"indices").
+
+    Parameters
+    ----------
+    data : relax.Expr
+        The input data tensor.
+
+    k : int
+        Number of top elements to select. Return all elements if k < 1.
+
+    axis : int
+        Axis long which to sort the input tensor.
+
+    ret_type: str
+        The return type [both, values, indices].
+        "both": return both top k data and indices.
+        "values": return top k data only.
+        "indices": return top k indices only.
+
+    largest : bool
+        Whether to return largest or smallest elements.
+        The k smallest elements are returned if largest is False.
+
+    dtype : str
+        The data type of the indices output.
+
+    Returns
+    -------
+    out : relax.Expr or List[relax.Expr]
+        The computed result.
+    """
+    if isinstance(k, Constant):
+        k = k.data.numpy().item()
+    return _ffi_api.topk(data, k, axis, ret_type, largest, dtype)  # type: 
ignore
diff --git a/python/tvm/relax/op/statistical.py 
b/python/tvm/relax/op/statistical.py
index f187f9d456..eb44696871 100644
--- a/python/tvm/relax/op/statistical.py
+++ b/python/tvm/relax/op/statistical.py
@@ -191,7 +191,71 @@ def sum(x: Expr, axis: Optional[Union[int, List[int]]] = 
None, keepdims: bool =
     return _ffi_api.sum(x, axis, keepdims)  # type: ignore
 
 
-def cumsum(data: Expr, axis: Optional[int] = None, dtype: Optional[Union[str, 
DataType]] = None):
+def cumprod(
+    data: Expr,
+    axis: Optional[int] = None,
+    dtype: Optional[Union[str, DataType]] = None,
+    exclusive: Optional[bool] = None,
+):
+    """Numpy style cumprod op. Return the cumulative product of the elements 
along
+    a given axis.
+
+    Parameters
+    ----------
+    data : relax.Expr
+        The input data to the operator.
+
+    axis : Optional[int]
+        Axis along which the cumulative product is computed. The default 
(None) is to compute
+        the cumprod over the flattened array.
+
+    dtype : Optional[Union[str, DataType]]
+        Type of the returned array and of the accumulator in which the 
elements are computed.
+        If dtype is not specified, it defaults to the dtype of data.
+
+    exclusive : Optional[bool]
+        If true will return exclusive sum in which the first element is not
+        included.
+
+    Returns
+    -------
+    result : relax.Expr
+        The result has the same size as data, and the same shape as data if 
axis is not None.
+        If axis is None, the result is a 1-d array.
+
+    Examples
+    --------
+    .. code-block:: python
+
+        a = [[1, 2, 3], [4, 5, 6]]
+
+        cumprod(a)  # if axis is not provided, cumprod is done over the 
flattened input.
+        -> [ 1,  2,  6, 24, 120, 720]
+
+        cumprod(a, dtype="float32")
+        -> [  1.,  2.,  6., 24., 120., 720.]
+
+        cumprod(a, axis=0)  # multiply over rows for each of the 3 columns
+        -> [[1, 2, 3],
+            [4, 10, 18]]
+
+        cumprod(a, axis=1)
+        -> [[ 1,  2,  6],
+            [ 4,  20, 120]]
+
+        a = [1, 1, 1, 0, 1, 1, 0]  # a is a boolean array
+        cumprod(a, dtype=int32)  # dtype should be provided to get the 
expected results
+        -> [1, 1, 1, 0, 0, 0, 0]
+    """
+    return _ffi_api.cumprod(data, axis, dtype, exclusive)  # type: ignore
+
+
+def cumsum(
+    data: Expr,
+    axis: Optional[int] = None,
+    dtype: Optional[Union[str, DataType]] = None,
+    exclusive: Optional[bool] = None,
+):
     """Numpy style cumsum op. Return the cumulative inclusive sum of the 
elements along
     a given axis.
 
@@ -208,6 +272,10 @@ def cumsum(data: Expr, axis: Optional[int] = None, dtype: 
Optional[Union[str, Da
         Type of the returned array and of the accumulator in which the 
elements are summed.
         If dtype is not specified, it defaults to the dtype of data.
 
+    exclusive : Optional[bool]
+        If true will return exclusive sum in which the first element is not
+        included.
+
     Returns
     -------
     result : relax.Expr
@@ -238,7 +306,7 @@ def cumsum(data: Expr, axis: Optional[int] = None, dtype: 
Optional[Union[str, Da
         cumsum(a, dtype=int32)  # dtype should be provided to get the expected 
results
         -> [1, 1, 2, 2, 3, 4, 4]
     """
-    return _ffi_api.cumsum(data, axis, dtype)  # type: ignore
+    return _ffi_api.cumsum(data, axis, dtype, exclusive)  # type: ignore
 
 
 def variance(x: Expr, axis: Optional[Union[int, List[int]]] = None, keepdims: 
bool = False) -> Expr:
diff --git a/python/tvm/relax/transform/legalize_ops/statistical.py 
b/python/tvm/relax/transform/legalize_ops/statistical.py
index e1f273bda0..1181b3b2a7 100644
--- a/python/tvm/relax/transform/legalize_ops/statistical.py
+++ b/python/tvm/relax/transform/legalize_ops/statistical.py
@@ -89,4 +89,13 @@ register_legalize("relax.sum", _statistical(topi.sum))
 
 @register_legalize("relax.cumsum")
 def _cumsum(bb: BlockBuilder, call: Call) -> Expr:
-    return bb.call_te(topi.cumsum, call.args[0], call.attrs.axis, 
call.attrs.dtype)
+    return bb.call_te(
+        topi.cumsum, call.args[0], call.attrs.axis, call.attrs.dtype, 
call.attrs.exclusive
+    )
+
+
+@register_legalize("relax.cumprod")
+def _cumprod(bb: BlockBuilder, call: Call) -> Expr:
+    return bb.call_te(
+        topi.cumprod, call.args[0], call.attrs.axis, call.attrs.dtype, 
call.attrs.exclusive
+    )
diff --git a/python/tvm/script/ir_builder/relax/ir.py 
b/python/tvm/script/ir_builder/relax/ir.py
index 7c0be2a722..9105fce00f 100644
--- a/python/tvm/script/ir_builder/relax/ir.py
+++ b/python/tvm/script/ir_builder/relax/ir.py
@@ -43,6 +43,7 @@ from tvm.relax.op import (
     arange,
     argmax,
     argmin,
+    argsort,
     assert_op,
     astype,
     bitwise_and,
@@ -65,6 +66,7 @@ from tvm.relax.op import (
     concat,
     cos,
     cosh,
+    cumprod,
     cumsum,
     einsum,
     scatter_elements,
@@ -145,6 +147,7 @@ from tvm.relax.op import (
     tanh,
     erf,
     tile,
+    topk,
     tril,
     triu,
     unique,
@@ -644,6 +647,7 @@ __all__ = [
     "arg",
     "argmax",
     "argmin",
+    "argsort",
     "assert_op",
     "astype",
     "bitwise_and",
@@ -670,6 +674,7 @@ __all__ = [
     "const",
     "cpu",
     "cuda",
+    "cumprod",
     "cumsum",
     "einsum",
     "scatter_elements",
@@ -773,6 +778,7 @@ __all__ = [
     "tan",
     "tanh",
     "tile",
+    "topk",
     "to_vdevice",
     "tril",
     "triu",
diff --git a/src/relax/op/tensor/sort.cc b/src/relax/op/tensor/sort.cc
deleted file mode 100644
index 31de102ccf..0000000000
--- a/src/relax/op/tensor/sort.cc
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.
- */
-
-/*!
- * \file sort.cc
- * \brief sorting operators.
- */
-
-#include "sort.h"
-
-namespace tvm {
-namespace relax {
-
-/* relax.sort */
-TVM_REGISTER_NODE_TYPE(SortAttrs);
-
-Expr sort(Expr data, int axis, bool descending) {
-  auto attrs = make_object<SortAttrs>();
-  attrs->axis = std::move(axis);
-  attrs->descending = std::move(descending);
-
-  static const Op& op = Op::Get("relax.sort");
-  return Call(op, {std::move(data)}, Attrs{attrs}, {});
-}
-
-TVM_REGISTER_GLOBAL("relax.op.sort").set_body_typed(sort);
-
-StructInfo InferStructInfoSort(const Call& call, const BlockBuilder& ctx) {
-  return GetUnaryInputTensorStructInfo(call, ctx);
-}
-
-TVM_REGISTER_OP("relax.sort")
-    .set_attrs_type<SortAttrs>()
-    .set_num_inputs(1)
-    .add_argument("data", "Tensor", "The input tensor.")
-    .set_attr<FInferStructInfo>("FInferStructInfo", InferStructInfoSort)
-    .set_attr<Bool>("FPurity", Bool(true));
-
-}  // namespace relax
-}  // namespace tvm
diff --git a/src/relax/op/tensor/sorting.cc b/src/relax/op/tensor/sorting.cc
new file mode 100644
index 0000000000..c4c4c5a614
--- /dev/null
+++ b/src/relax/op/tensor/sorting.cc
@@ -0,0 +1,155 @@
+/*
+ * 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.
+ */
+
+/*!
+ * \file sorting.cc
+ * \brief sorting operators.
+ */
+
+#include "sorting.h"
+
+#include <vector>
+
+namespace tvm {
+namespace relax {
+
+/* relax.sort */
+TVM_REGISTER_NODE_TYPE(SortAttrs);
+
+Expr sort(Expr data, int axis, bool descending) {
+  auto attrs = make_object<SortAttrs>();
+  attrs->axis = std::move(axis);
+  attrs->descending = std::move(descending);
+
+  static const Op& op = Op::Get("relax.sort");
+  return Call(op, {std::move(data)}, Attrs{attrs}, {});
+}
+
+TVM_REGISTER_GLOBAL("relax.op.sort").set_body_typed(sort);
+
+StructInfo InferStructInfoSort(const Call& call, const BlockBuilder& ctx) {
+  return GetUnaryInputTensorStructInfo(call, ctx);
+}
+
+TVM_REGISTER_OP("relax.sort")
+    .set_attrs_type<SortAttrs>()
+    .set_num_inputs(1)
+    .add_argument("data", "Tensor", "The input tensor.")
+    .set_attr<FInferStructInfo>("FInferStructInfo", InferStructInfoSort)
+    .set_attr<Bool>("FPurity", Bool(true));
+
+/* relax.argsort */
+TVM_REGISTER_NODE_TYPE(ArgsortAttrs);
+
+Expr argsort(Expr data, int axis, bool descending, DataType dtype) {
+  auto attrs = make_object<ArgsortAttrs>();
+  attrs->axis = std::move(axis);
+  attrs->descending = std::move(descending);
+  attrs->dtype = std::move(dtype);
+
+  static const Op& op = Op::Get("relax.argsort");
+  return Call(op, {std::move(data)}, Attrs{attrs}, {});
+}
+
+TVM_REGISTER_GLOBAL("relax.op.argsort").set_body_typed(argsort);
+
+StructInfo InferStructInfoArgsort(const Call& call, const BlockBuilder& ctx) {
+  TensorStructInfo data_sinfo = GetUnaryInputTensorStructInfo(call, ctx);
+  const auto* attrs = call->attrs.as<ArgsortAttrs>();
+  DataType out_type = attrs->dtype.is_void() ? data_sinfo->dtype : 
attrs->dtype;
+  if (data_sinfo->shape.defined()) {
+    return TensorStructInfo(data_sinfo->shape.value(), out_type, 
data_sinfo->vdevice);
+  }
+  return TensorStructInfo(out_type, data_sinfo->ndim, data_sinfo->vdevice);
+}
+
+TVM_REGISTER_OP("relax.argsort")
+    .set_attrs_type<ArgsortAttrs>()
+    .set_num_inputs(1)
+    .add_argument("data", "Tensor", "The input tensor.")
+    .set_attr<FInferStructInfo>("FInferStructInfo", InferStructInfoArgsort)
+    .set_attr<Bool>("FPurity", Bool(true));
+
+/* relax.topk */
+TVM_REGISTER_NODE_TYPE(TopKAttrs);
+
+Expr topk(Expr data, int k, int axis, String ret_type, bool largest, DataType 
dtype) {
+  auto attrs = make_object<TopKAttrs>();
+  attrs->k = std::move(k);
+  attrs->axis = std::move(axis);
+  attrs->ret_type = std::move(ret_type);
+  attrs->largest = std::move(largest);
+  attrs->dtype = std::move(dtype);
+
+  static const Op& op = Op::Get("relax.topk");
+  return Call(op, {std::move(data)}, Attrs{attrs}, {});
+}
+
+TVM_REGISTER_GLOBAL("relax.op.topk").set_body_typed(topk);
+
+StructInfo InferStructInfoTopK(const Call& call, const BlockBuilder& ctx) {
+  TensorStructInfo data_sinfo = GetUnaryInputTensorStructInfo(call, ctx);
+  const auto* data_shape = data_sinfo->shape.as<ShapeExprNode>();
+  const auto* attrs = call->attrs.as<TopKAttrs>();
+  DataType indices_type = attrs->dtype.is_void() ? data_sinfo->dtype : 
attrs->dtype;
+  int ndim = data_sinfo->ndim;
+  int k = attrs->k;
+  String ret_type = attrs->ret_type;
+  int axis = attrs->axis;
+  if (axis < 0 && ndim > 0) {
+    axis += ndim;
+  }
+
+  std::vector<StructInfo> output_sinfos;
+  output_sinfos.reserve(2);
+  if (data_shape == nullptr) {
+    output_sinfos.push_back(
+        TensorStructInfo(data_sinfo->dtype, data_sinfo->ndim, 
data_sinfo->vdevice));
+    output_sinfos.push_back(TensorStructInfo(indices_type, data_sinfo->ndim, 
data_sinfo->vdevice));
+  } else {
+    Array<PrimExpr> out_shape = data_shape->values;
+    const auto* int_dim = out_shape[axis].as<IntImmNode>();
+    if (k > 0 && (int_dim == nullptr || k < int_dim->value)) {
+      out_shape.Set(axis, k);
+    }
+    output_sinfos.push_back(
+        TensorStructInfo(ShapeExpr(out_shape), data_sinfo->dtype, 
data_sinfo->vdevice));
+    output_sinfos.push_back(
+        TensorStructInfo(ShapeExpr(out_shape), indices_type, 
data_sinfo->vdevice));
+  }
+
+  if (ret_type == "both") {
+    return TupleStructInfo(output_sinfos);
+  } else if (ret_type == "values") {
+    return output_sinfos[0];
+  } else if (ret_type == "indices") {
+    return output_sinfos[1];
+  }
+  LOG(FATAL) << "Unsupported ret type: " << ret_type;
+}
+
+TVM_REGISTER_OP("relax.topk")
+    .set_attrs_type<TopKAttrs>()
+    .set_num_inputs(1)
+    .add_argument("data", "Tensor", "The input tensor.")
+    .set_attr<FInferStructInfo>("FInferStructInfo", InferStructInfoTopK)
+    .set_attr<Bool>("FPurity", Bool(true));
+
+}  // namespace relax
+}  // namespace tvm
diff --git a/src/relax/op/tensor/sort.h b/src/relax/op/tensor/sorting.h
similarity index 55%
rename from src/relax/op/tensor/sort.h
rename to src/relax/op/tensor/sorting.h
index 92203034aa..8a785bc4e2 100644
--- a/src/relax/op/tensor/sort.h
+++ b/src/relax/op/tensor/sorting.h
@@ -18,13 +18,13 @@
  */
 
 /*!
- * \file sort.h
+ * \file sorting.h
  * \brief The functions to make Relax tensor sorting operator calls.
  */
-#ifndef TVM_RELAX_OP_TENSOR_SORT_H_
-#define TVM_RELAX_OP_TENSOR_SORT_H_
+#ifndef TVM_RELAX_OP_TENSOR_SORTING_H_
+#define TVM_RELAX_OP_TENSOR_SORTING_H_
 
-#include <tvm/relax/attrs/sort.h>
+#include <tvm/relax/attrs/sorting.h>
 
 #include <algorithm>
 #include <utility>
@@ -43,7 +43,29 @@ namespace relax {
  */
 Expr sort(Expr data, int axis, bool descending);
 
+/*!
+ * \brief Performs sorting along the given axis and returns an array of 
indices.
+ * \param data The input tensor.
+ * \param axis The axis to sort on.
+ * \param descending Whether to sort in descending order.
+ * \param dtype The data type of the output indices.
+ * \return The computed result.
+ */
+Expr argsort(Expr data, int axis, bool descending, DataType dtype);
+
+/*!
+ * \brief Get the top k elements in an input tensor along the given axis.
+ * \param data The input tensor.
+ * \param k Number of top elements.
+ * \param axis The axis to sort on.
+ * \param ret_type The return type, can be set to one of [both, values, 
indices].
+ * \param largest Whether to return largest or smallest elements.
+ * \param dtype The data type of the indices output.
+ * \return The computed result.
+ */
+Expr topk(Expr data, int k, int axis, String ret_type, bool largest, DataType 
dtype);
+
 }  // namespace relax
 }  // namespace tvm
 
-#endif  // TVM_RELAX_OP_TENSOR_SORT_H_
+#endif  // TVM_RELAX_OP_TENSOR_SORTING_H_
diff --git a/src/relax/op/tensor/statistical.cc 
b/src/relax/op/tensor/statistical.cc
index b861aafe21..24ccde4559 100644
--- a/src/relax/op/tensor/statistical.cc
+++ b/src/relax/op/tensor/statistical.cc
@@ -135,23 +135,11 @@ InferLayoutOutput InferLayoutStatistical(const Call& call,
                            Attrs(new_attrs));
 }
 
-/* relax.cumsum */
-TVM_REGISTER_NODE_TYPE(CumsumAttrs);
-
-Expr cumsum(Expr data, Optional<Integer> axis, DataType dtype) {
-  auto attrs = make_object<CumsumAttrs>();
-  attrs->axis = std::move(axis);
-  attrs->dtype = std::move(dtype);
-
-  static const Op& op = Op::Get("relax.cumsum");
-  return Call(op, {std::move(data)}, Attrs{attrs}, {});
-}
+TVM_REGISTER_NODE_TYPE(ScanopAttrs);
 
-TVM_REGISTER_GLOBAL("relax.op.cumsum").set_body_typed(cumsum);
-
-StructInfo InferStructInfoCumsum(const Call& call, const BlockBuilder& ctx) {
+StructInfo InferStructInfoScan(const Call& call, const BlockBuilder& ctx) {
   TensorStructInfo data_sinfo = GetUnaryInputTensorStructInfo(call, ctx);
-  const auto* attrs = call->attrs.as<CumsumAttrs>();
+  const auto* attrs = call->attrs.as<ScanopAttrs>();
 
   DataType out_type = attrs->dtype.is_void() ? data_sinfo->dtype : 
attrs->dtype;
 
@@ -177,11 +165,44 @@ StructInfo InferStructInfoCumsum(const Call& call, const 
BlockBuilder& ctx) {
   }
 }
 
+/* relax.cumprod */
+Expr cumprod(Expr data, Optional<Integer> axis, DataType dtype, Bool 
exclusive) {
+  auto attrs = make_object<ScanopAttrs>();
+  attrs->axis = std::move(axis);
+  attrs->dtype = std::move(dtype);
+  attrs->exclusive = std::move(exclusive);
+
+  static const Op& op = Op::Get("relax.cumprod");
+  return Call(op, {std::move(data)}, Attrs{attrs}, {});
+}
+
+TVM_REGISTER_GLOBAL("relax.op.cumprod").set_body_typed(cumprod);
+
+TVM_REGISTER_OP("relax.cumprod")
+    .set_attrs_type<ScanopAttrs>()
+    .set_num_inputs(1)
+    .add_argument("data", "Tensor", "The input tensor.")
+    .set_attr<FInferStructInfo>("FInferStructInfo", InferStructInfoScan)
+    .set_attr<Bool>("FPurity", Bool(true));
+
+/* relax.cumsum */
+Expr cumsum(Expr data, Optional<Integer> axis, DataType dtype, Bool exclusive) 
{
+  auto attrs = make_object<ScanopAttrs>();
+  attrs->axis = std::move(axis);
+  attrs->dtype = std::move(dtype);
+  attrs->exclusive = std::move(exclusive);
+
+  static const Op& op = Op::Get("relax.cumsum");
+  return Call(op, {std::move(data)}, Attrs{attrs}, {});
+}
+
+TVM_REGISTER_GLOBAL("relax.op.cumsum").set_body_typed(cumsum);
+
 TVM_REGISTER_OP("relax.cumsum")
-    .set_attrs_type<CumsumAttrs>()
+    .set_attrs_type<ScanopAttrs>()
     .set_num_inputs(1)
     .add_argument("data", "Tensor", "The input tensor.")
-    .set_attr<FInferStructInfo>("FInferStructInfo", InferStructInfoCumsum)
+    .set_attr<FInferStructInfo>("FInferStructInfo", InferStructInfoScan)
     .set_attr<Bool>("FPurity", Bool(true));
 
 TVM_REGISTER_NODE_TYPE(StatisticalAttrs);
diff --git a/src/relax/op/tensor/statistical.h 
b/src/relax/op/tensor/statistical.h
index 23a6da99f1..310c87f7d6 100644
--- a/src/relax/op/tensor/statistical.h
+++ b/src/relax/op/tensor/statistical.h
@@ -85,6 +85,22 @@ Expr std(Expr x, Optional<Array<Integer>> axis, bool 
keepdims);
 /*! \brief Computes the sum of tensor elements over given axes. */
 Expr sum(Expr x, Optional<Array<Integer>> axis, bool keepdims);
 
+/*!
+ * \brief Numpy style cumprod op. Return the cumulative inclusive product of 
the elements along
+ * a given axis.
+ * \param data The input tensor.
+ * \param axis Axis along which the cumulative product is computed. The 
default (None) is to compute
+ * the cumprod over the flattened array.
+ * \param dtype Type of the returned array and of the accumulator in which the 
elements are
+ * computed. If dtype is not specified, it defaults to the dtype of data.
+ * \param exclusive Whehter the first element is exclusive. If true will 
return exclusive sum in
+ * which the first element is not included.
+ * \return The computed
+ * result.
+ */
+Expr cumprod(Expr data, Optional<Integer> axis = NullOpt, DataType dtype = 
DataType::Void(),
+             Bool exclusive = Bool(false));
+
 /*!
  * \brief Numpy style cumsum op. Return the cumulative inclusive sum of the 
elements along
  * a given axis.
@@ -93,9 +109,12 @@ Expr sum(Expr x, Optional<Array<Integer>> axis, bool 
keepdims);
  * the cumsum over the flattened array.
  * \param dtype Type of the returned array and of the accumulator in which the 
elements are summed.
  * If dtype is not specified, it defaults to the dtype of data.
+ * \param exclusive Whehter the first element is exclusive. If true will 
return exclusive sum in
+ * which the first element is not included.
  * \return The computed result.
  */
-Expr cumsum(Expr data, Optional<Integer> axis = NullOpt, DataType dtype = 
DataType::Void());
+Expr cumsum(Expr data, Optional<Integer> axis = NullOpt, DataType dtype = 
DataType::Void(),
+            Bool exclusive = Bool(false));
 
 /*! \brief Computes the variance of tensor elements over given axes. */
 Expr variance(Expr x, Optional<Array<Integer>> axis, bool keepdims);
diff --git a/tests/python/relax/test_backend_dispatch_sort_scan.py 
b/tests/python/relax/test_backend_dispatch_sort_scan.py
index c21dd4882f..8921372f2f 100644
--- a/tests/python/relax/test_backend_dispatch_sort_scan.py
+++ b/tests/python/relax/test_backend_dispatch_sort_scan.py
@@ -18,7 +18,7 @@
 import pytest
 
 import tvm
-from tvm import topi, relax, tir
+from tvm import topi, relax, tir, dlight
 import tvm.script
 import tvm.testing
 from tvm.script import relax as R, tir as T, ir as I
@@ -29,21 +29,22 @@ from tvm.relax.backend import DispatchSortScan
 from tvm.ir.base import assert_structural_equal
 
 
-def test_dispatch_cumsum():
+def test_dispatch_scanop():
     @I.ir_module
     class Before:
-        I.module_global_infos({"vdevice": [I.vdevice("cuda", 0), 
I.vdevice("llvm", 0)]})
+        I.module_global_infos({"vdevice": [I.vdevice("llvm", 0)]})
 
         @R.function
         def foo(x: R.Tensor((2, 3), "float32", "llvm")):
             with R.dataflow():
-                gv = R.cumsum(x, axis=1, dtype="float64")
+                lv0 = R.cumsum(x, axis=1, dtype="float64", exclusive=False)
+                gv = R.cumprod(lv0, axis=1, dtype="float64", exclusive=False)
                 R.output(gv)
             return gv
 
     @I.ir_module
     class Expected:
-        I.module_global_infos({"vdevice": [I.vdevice("cuda", 0), 
I.vdevice("llvm", 0)]})
+        I.module_global_infos({"vdevice": [I.vdevice("llvm", 0)]})
 
         @T.prim_func(private=True)
         def cumsum(var_A: T.handle, out_buf: T.Buffer((T.int64(2), 
T.int64(3)), "float64")):
@@ -72,13 +73,44 @@ def test_dispatch_cumsum():
                             ],
                         )
 
+        @T.prim_func(private=True)
+        def cumprod(var_A: T.handle, out_buf: T.Buffer((T.int64(2), 
T.int64(3)), "float64")):
+            T.func_attr({"tir.noalias": T.bool(True)})
+            A = T.match_buffer(var_A, (T.int64(2), T.int64(3)), "float64", 
offset_factor=1)
+            with T.block("cumprod_generic"):
+                T.reads(A[T.int64(0) : T.int64(2), T.int64(0) : T.int64(3)])
+                T.writes(out_buf[T.int64(0) : T.int64(2), T.int64(0) : 
T.int64(3)])
+                for fused in T.parallel(T.int64(2)):
+                    out_buf[fused * T.int64(3) // T.int64(3), fused * 
T.int64(3) % T.int64(3)] = A[
+                        fused * T.int64(3) // T.int64(3), fused * T.int64(3) % 
T.int64(3)
+                    ]
+                    for _k in range(T.int64(2)):
+                        out_buf[
+                            (fused * T.int64(3) + (_k + T.int64(1))) // 
T.int64(3),
+                            (fused * T.int64(3) + (_k + T.int64(1))) % 
T.int64(3),
+                        ] = (
+                            out_buf[
+                                (fused * T.int64(3) + (_k + T.int64(1) - 
T.int64(1))) // T.int64(3),
+                                (fused * T.int64(3) + (_k + T.int64(1) - 
T.int64(1))) % T.int64(3),
+                            ]
+                            * A[
+                                (fused * T.int64(3) + (_k + T.int64(1))) // 
T.int64(3),
+                                (fused * T.int64(3) + (_k + T.int64(1))) % 
T.int64(3),
+                            ]
+                        )
+
         @R.function
         def foo(
             x: R.Tensor((2, 3), dtype="float32", vdevice="llvm")
         ) -> R.Tensor((2, 3), dtype="float64", vdevice="llvm"):
             cls = Expected
             with R.dataflow():
-                gv = R.call_tir(cls.cumsum, (x,), out_sinfo=R.Tensor((2, 3), 
"float64", "llvm"))
+                lv0 = R.call_tir(cls.cumsum, (x,), out_sinfo=R.Tensor((2, 3), 
"float64", "llvm"))
+                gv = R.call_tir(
+                    cls.cumprod,
+                    (lv0,),
+                    out_sinfo=R.Tensor((2, 3), dtype="float64", 
vdevice="llvm"),
+                )
                 R.output(gv)
             return gv
 
@@ -86,33 +118,39 @@ def test_dispatch_cumsum():
     assert_structural_equal(mod, Expected)
 
 
-def test_dispatch_cumsum_cuda():
+def test_dispatch_scanop_cuda():
     @I.ir_module
     class Before:
-        I.module_global_infos({"vdevice": [I.vdevice("cuda", 0), 
I.vdevice("llvm", 0)]})
+        I.module_global_infos({"vdevice": [I.vdevice("cuda", 0)]})
 
         @R.function
         def main(x: R.Tensor(("m", 3), "float32", "cuda")):
             with R.dataflow():
-                lv = R.cumsum(x, axis=1)
-                gv = lv
+                lv0 = R.cumsum(x, axis=1)
+                lv1 = R.cumprod(lv0, axis=1)
+                gv = lv1
                 R.output(gv)
             return gv
 
     target = tvm.target.Target("cuda", host="llvm")
 
-    vdevices = [I.vdevice("cuda", 0), I.vdevice("llvm", 0)]
+    vdevices = [I.vdevice("cuda", 0)]
     m = tir.Var("m", "int64")
     x = relax.Var("x", R.Tensor((m, 3), "float32", vdevices[0]))
     bb = relax.BlockBuilder()
     with target:
         with bb.function("main", (x,), {"global_symbol": "main"}):
             with bb.dataflow():
-                out = bb.emit_te(
+                lv = bb.emit_te(
                     topi.cuda.cumsum,
                     x,
                     axis=1,
                 )
+                out = bb.emit_te(
+                    topi.cuda.cumprod,
+                    lv,
+                    axis=1,
+                )
                 out = bb.emit_output(out)
             bb.emit_func_output(out)
     expected_mod = bb.finalize()
@@ -120,14 +158,15 @@ def test_dispatch_cumsum_cuda():
 
     with target:
         mod = DispatchSortScan()(Before)
+        expected_mod = 
dlight.ApplyDefaultSchedule(dlight.gpu.Fallback())(expected_mod)
 
-    assert_structural_equal(mod, expected_mod)
+    assert_structural_equal(mod, expected_mod, map_free_vars=True)
 
 
 def test_dispatch_sort():
     @I.ir_module
     class Before:
-        I.module_global_infos({"vdevice": [I.vdevice("cuda", 0), 
I.vdevice("llvm", 0)]})
+        I.module_global_infos({"vdevice": [I.vdevice("llvm", 0)]})
 
         @R.function
         def foo(x: R.Tensor(("m", 3), "float32", "llvm")):
@@ -139,7 +178,7 @@ def test_dispatch_sort():
 
     @I.ir_module
     class Expected:
-        I.module_global_infos({"vdevice": [I.vdevice("cuda", 0), 
I.vdevice("llvm", 0)]})
+        I.module_global_infos({"vdevice": [I.vdevice("llvm", 0)]})
 
         @T.prim_func(private=True)
         def sort(var_A: T.handle, var_sort_cpu: T.handle):
@@ -192,7 +231,7 @@ def test_dispatch_sort():
 def test_dispatch_sort_cuda():
     @I.ir_module
     class Before:
-        I.module_global_infos({"vdevice": [I.vdevice("cuda"), 
I.vdevice("llvm")]})
+        I.module_global_infos({"vdevice": [I.vdevice("cuda")]})
 
         @R.function
         def foo(x: R.Tensor((2, 3), "float32", "cuda")):
@@ -212,7 +251,7 @@ def test_dispatch_sort_cuda():
 
     target = tvm.target.Target("cuda -libs=thrust", host="llvm")
 
-    vdevices = [I.vdevice("cuda", 0), I.vdevice("llvm", 0)]
+    vdevices = [I.vdevice("cuda", 0)]
     x = relax.Var("x", R.Tensor((2, 3), "float32", vdevices[0]))
     y = relax.Var("y", R.Tensor((2, 3), "float32"))
     bb = relax.BlockBuilder()
@@ -244,7 +283,233 @@ def test_dispatch_sort_cuda():
     with target:
         mod = DispatchSortScan()(Before)
 
-    assert_structural_equal(mod, expected_mod, map_free_vars=True)
+    assert_structural_equal(mod, expected_mod)
+
+
+def test_dispatch_argsort():
+    @I.ir_module
+    class Before:
+        I.module_global_infos({"vdevice": [I.vdevice("llvm", 0)]})
+
+        @R.function
+        def foo(x: R.Tensor(("m", 3), "float32", "llvm")):
+            m = T.int64()
+            with R.dataflow():
+                gv = R.argsort(x, axis=1, descending=False)
+                R.output(gv)
+            return gv
+
+    @I.ir_module
+    class Expected:
+        I.module_global_infos({"vdevice": [I.vdevice("llvm", 0)]})
+
+        @T.prim_func(private=True)
+        def argsort(var_A: T.handle, var_argsort_cpu: T.handle):
+            T.func_attr({"tir.noalias": T.bool(True)})
+            m = T.int64()
+            data_buf = T.match_buffer(var_A, (m, T.int64(3)), align=8)
+            out_buf = T.match_buffer(var_argsort_cpu, (m, T.int64(3)), 
"int32", align=8)
+            with T.block("argsort_cpu"):
+                T.reads(data_buf[T.int64(0) : m, T.int64(0) : T.int64(3)])
+                T.writes(out_buf[T.int64(0) : m, T.int64(0) : T.int64(3)])
+                T.call_packed(
+                    "tvm.contrib.sort.argsort",
+                    T.tvm_stack_make_array(
+                        data_buf.data,
+                        T.tvm_stack_make_shape(m, T.int64(3)),
+                        0,
+                        2,
+                        T.float32(0),
+                        T.int64(0),
+                    ),
+                    T.tvm_stack_make_array(
+                        out_buf.data, T.tvm_stack_make_shape(m, T.int64(3)), 
0, 2, 0, T.int64(0)
+                    ),
+                    1,
+                    T.bool(True),
+                )
+
+        @R.function
+        def foo(
+            x: R.Tensor(("m", 3), dtype="float32", vdevice="llvm")
+        ) -> R.Tensor(("m", 3), dtype="int32", vdevice="llvm"):
+            m = T.int64()
+            cls = Expected
+            with R.dataflow():
+                gv = R.call_tir(
+                    cls.argsort, (x,), out_sinfo=R.Tensor((m, 3), 
dtype="int32", vdevice="llvm")
+                )
+                R.output(gv)
+            return gv
+
+    mod = DispatchSortScan()(Before)
+    assert_structural_equal(mod, Expected)
+
+
+def test_dispatch_argsort_cuda():
+    @I.ir_module
+    class Before:
+        I.module_global_infos({"vdevice": [I.vdevice("cuda")]})
+
+        @R.function
+        def foo(x: R.Tensor((2, 3), "float32", "cuda")):
+            with R.dataflow():
+                lv = R.argsort(x, axis=1, descending=False)
+                gv = lv
+                R.output(gv)
+            return gv
+
+        @R.function
+        def foo2(y: R.Tensor((2, 3), "float32")):
+            with R.dataflow():
+                lv = R.argsort(y, axis=0, descending=True, dtype="int64")
+                gv = lv
+                R.output(gv)
+            return gv
+
+    target = tvm.target.Target("cuda -libs=thrust", host="llvm")
+
+    vdevices = [I.vdevice("cuda", 0)]
+    x = relax.Var("x", R.Tensor((2, 3), "float32", vdevices[0]))
+    y = relax.Var("y", R.Tensor((2, 3), "float32"))
+    bb = relax.BlockBuilder()
+    with target:
+        with bb.function("foo", (x,), {"global_symbol": "foo"}):
+            with bb.dataflow():
+                out = bb.emit_te(topi.cuda.argsort, x, axis=1, is_ascend=True, 
dtype="int32")
+                out = bb.emit_output(out)
+            bb.emit_func_output(out)
+        with bb.function("foo2", (y,), {"global_symbol": "foo2"}):
+            with bb.dataflow():
+                out = bb.emit_te(
+                    topi.cuda.argsort_thrust
+                    if can_use_thrust(target, "tvm.contrib.thrust.sort")
+                    else topi.cuda.argsort,
+                    y,
+                    0,
+                    False,
+                    "int64",
+                )
+                out = bb.emit_output(out)
+            bb.emit_func_output(out)
+    expected_mod = bb.finalize()
+    expected_mod.update_global_info("vdevice", vdevices)
+
+    with target:
+        mod = DispatchSortScan()(Before)
+
+    assert_structural_equal(mod, expected_mod)
+
+
+def test_dispatch_topk():
+    @I.ir_module
+    class Before:
+        I.module_global_infos({"vdevice": [I.vdevice("llvm", 0)]})
+
+        @R.function
+        def foo(x: R.Tensor(("m", 3), "float32", "llvm")):
+            m = T.int64()
+            with R.dataflow():
+                gv = R.topk(x, k=2, axis=1, largest=True)
+                R.output(gv)
+            return gv
+
+    @I.ir_module
+    class Expected:
+        I.module_global_infos({"vdevice": [I.vdevice("llvm", 0)]})
+
+        @T.prim_func(private=True)
+        def topk(var_A: T.handle, var_topk_cpu_v0: T.handle, var_topk_cpu_v1: 
T.handle):
+            T.func_attr({"tir.noalias": T.bool(True)})
+            m = T.int64()
+            data_buf = T.match_buffer(var_A, (m, T.int64(3)), align=8)
+            value_buf = T.match_buffer(var_topk_cpu_v0, (m, T.int64(1)), 
align=8)
+            indices_buf = T.match_buffer(var_topk_cpu_v1, (m, T.int64(1)), 
"int32", align=8)
+            with T.block("topk_cpu"):
+                T.reads(data_buf[T.int64(0) : m, T.int64(0) : T.int64(3)])
+                T.writes(
+                    value_buf[T.int64(0) : m, T.int64(0)], 
indices_buf[T.int64(0) : m, T.int64(0)]
+                )
+                T.call_packed(
+                    "tvm.contrib.sort.topk",
+                    T.tvm_stack_make_array(
+                        data_buf.data,
+                        T.tvm_stack_make_shape(m, T.int64(3)),
+                        0,
+                        2,
+                        T.float32(0),
+                        T.int64(0),
+                    ),
+                    T.tvm_stack_make_array(
+                        value_buf.data, T.tvm_stack_make_shape(m, 1), 0, 2, 
T.float32(0), T.int64(0)
+                    ),
+                    T.tvm_stack_make_array(
+                        indices_buf.data, T.tvm_stack_make_shape(m, 1), 0, 2, 
0, T.int64(0)
+                    ),
+                    1,
+                    1,
+                    "both",
+                    T.bool(False),
+                )
+
+        @R.function
+        def foo(
+            x: R.Tensor(("m", 3), dtype="float32", vdevice="llvm")
+        ) -> R.Tuple(
+            R.Tensor(("m", 1), dtype="float32", vdevice="llvm"),
+            R.Tensor(("m", 1), dtype="int32", vdevice="llvm"),
+        ):
+            m = T.int64()
+            cls = Expected
+            with R.dataflow():
+                gv = R.call_tir(
+                    cls.topk,
+                    (x,),
+                    out_sinfo=[
+                        R.Tensor((m, 1), dtype="float32", vdevice="llvm"),
+                        R.Tensor((m, 1), dtype="int32", vdevice="llvm"),
+                    ],
+                )
+                R.output(gv)
+            return gv
+
+    mod = DispatchSortScan()(Before)
+    assert_structural_equal(mod, Expected)
+
+
+def test_dispatch_topk_cuda():
+    @I.ir_module
+    class Before:
+        I.module_global_infos({"vdevice": [I.vdevice("cuda")]})
+
+        @R.function
+        def foo(x: R.Tensor((2, 3), "float32", "cuda")):
+            with R.dataflow():
+                lv = R.topk(x, k=2, axis=1, largest=True)
+                gv = lv
+                R.output(gv)
+            return gv
+
+    target = tvm.target.Target("cuda -libs=thrust", host="llvm")
+
+    vdevices = [I.vdevice("cuda", 0)]
+    x = relax.Var("x", R.Tensor((2, 3), "float32", vdevices[0]))
+    y = relax.Var("y", R.Tensor((2, 3), "float32"))
+    bb = relax.BlockBuilder()
+    with target:
+        with bb.function("foo", (x,), {"global_symbol": "foo"}):
+            with bb.dataflow():
+                out = bb.emit_te(topi.cuda.topk, x, axis=1, is_ascend=False, 
dtype="int32")
+                out = bb.emit_output(out)
+            bb.emit_func_output(out)
+    expected_mod = bb.finalize()
+    expected_mod.update_global_info("vdevice", vdevices)
+
+    with target:
+        mod = DispatchSortScan()(Before)
+        expected_mod = 
dlight.ApplyDefaultSchedule(dlight.gpu.Fallback())(expected_mod)
+
+    assert_structural_equal(mod, expected_mod)
 
 
 if __name__ == "__main__":
diff --git a/tests/python/relax/test_op_sort.py 
b/tests/python/relax/test_op_sort.py
index b6a064a641..ed47570b82 100644
--- a/tests/python/relax/test_op_sort.py
+++ b/tests/python/relax/test_op_sort.py
@@ -26,6 +26,8 @@ from tvm.script import relax as R
 def test_op_correctness():
     x = relax.Var("x", R.Tensor((3, 4, 5), "float32"))
     assert relax.op.sort(x, axis=1).op == Op.get("relax.sort")
+    assert relax.op.argsort(x, axis=1).op == Op.get("relax.argsort")
+    assert relax.op.topk(x, k=1, axis=1).op == Op.get("relax.topk")
 
 
 def _check_inference(bb: relax.BlockBuilder, call: relax.Call, expected_sinfo: 
relax.StructInfo):
@@ -98,5 +100,195 @@ def test_sort_wrong_input():
         bb.normalize(relax.op.sort(x1, axis=1))
 
 
+def test_argsort_infer_struct_info():
+    bb = relax.BlockBuilder()
+    vdev0 = VDevice("llvm")
+    x0 = relax.Var("x", R.Tensor((2, 10, 4), "float32"))
+    x1 = relax.Var("x", R.Tensor("float32", ndim=3))
+    x2 = relax.Var("x", R.Tensor("float32"))
+    x3 = relax.Var("x", R.Tensor((2, 10, 4)))
+    x4 = relax.Var("x", R.Tensor(ndim=3))
+    x5 = relax.Var("x", R.Tensor())
+    x6 = relax.Var("x", R.Tensor((2, 10, 4), "float32", vdev0))
+
+    _check_inference(
+        bb,
+        relax.op.argsort(x0, axis=1, descending=False, dtype="int64"),
+        relax.TensorStructInfo((2, 10, 4), "int64"),
+    )
+    _check_inference(
+        bb, relax.op.argsort(x6, axis=1), relax.TensorStructInfo((2, 10, 4), 
"int32", vdev0)
+    )
+    _check_inference(
+        bb, relax.op.argsort(x1, axis=1), 
relax.TensorStructInfo(dtype="int32", ndim=3)
+    )
+    _check_inference(
+        bb, relax.op.argsort(x2, axis=1, dtype="float16"), 
relax.TensorStructInfo(dtype="float16")
+    )
+    _check_inference(
+        bb, relax.op.argsort(x3, axis=1), relax.TensorStructInfo((2, 10, 4), 
dtype="int32")
+    )
+    _check_inference(
+        bb, relax.op.argsort(x4, axis=1), 
relax.TensorStructInfo(dtype="int32", ndim=3)
+    )
+    _check_inference(bb, relax.op.argsort(x5, axis=1), 
relax.TensorStructInfo(dtype="int32"))
+    _check_inference(bb, relax.op.argsort(x0), relax.TensorStructInfo((2, 10, 
4), "int32"))
+    _check_inference(
+        bb,
+        relax.op.argsort(x0, axis=1, descending=False),
+        relax.TensorStructInfo((2, 10, 4), "int32"),
+    )
+
+
+def test_argsort_infer_struct_info_shape_symbolic():
+    bb = relax.BlockBuilder()
+    a = tir.Var("a", "int64")
+    b = tir.Var("b", "int64")
+    c = tir.Var("c", "int64")
+    x = relax.Var("x", R.Tensor((a, b, c), "float32"))
+
+    _check_inference(bb, relax.op.argsort(x, axis=1), 
relax.TensorStructInfo((a, b, c), "int32"))
+    _check_inference(bb, relax.op.argsort(x), relax.TensorStructInfo((a, b, 
c), "int32"))
+
+
+def test_topk_infer_struct_info():
+    bb = relax.BlockBuilder()
+    vdev0 = VDevice("llvm")
+    x0 = relax.Var("x", R.Tensor((2, 10, 4), "float32"))
+    x1 = relax.Var("x", R.Tensor("float32", ndim=3))
+    x2 = relax.Var("x", R.Tensor("float32"))
+    x3 = relax.Var("x", R.Tensor((2, 10, 4)))
+    x4 = relax.Var("x", R.Tensor(ndim=3))
+    x5 = relax.Var("x", R.Tensor())
+    x6 = relax.Var("x", R.Tensor((2, 10, 4), "float32", vdev0))
+
+    _check_inference(
+        bb,
+        relax.op.topk(x0, k=5, axis=1, ret_type="both", largest=False, 
dtype="int64"),
+        relax.TupleStructInfo(
+            [
+                relax.TensorStructInfo((2, 5, 4), "float32"),
+                relax.TensorStructInfo((2, 5, 4), "int64"),
+            ]
+        ),
+    )
+    _check_inference(
+        bb,
+        relax.op.topk(x6),
+        relax.TupleStructInfo(
+            [
+                relax.TensorStructInfo((2, 10, 1), "float32", vdev0),
+                relax.TensorStructInfo((2, 10, 1), "int32", vdev0),
+            ]
+        ),
+    )
+    _check_inference(
+        bb,
+        relax.op.topk(x1, k=3, axis=1),
+        relax.TupleStructInfo(
+            [
+                relax.TensorStructInfo(dtype="float32", ndim=3),
+                relax.TensorStructInfo(dtype="int32", ndim=3),
+            ]
+        ),
+    )
+    _check_inference(
+        bb,
+        relax.op.topk(x2),
+        relax.TupleStructInfo(
+            [relax.TensorStructInfo(dtype="float32"), 
relax.TensorStructInfo(dtype="int32")]
+        ),
+    )
+    _check_inference(
+        bb,
+        relax.op.topk(x3, axis=0),
+        relax.TupleStructInfo(
+            [
+                relax.TensorStructInfo((1, 10, 4), None),
+                relax.TensorStructInfo((1, 10, 4), dtype="int32"),
+            ]
+        ),
+    )
+    _check_inference(
+        bb,
+        relax.op.topk(x4, axis=1),
+        relax.TupleStructInfo(
+            [
+                relax.TensorStructInfo(ndim=3, dtype=None),
+                relax.TensorStructInfo(dtype="int32", ndim=3),
+            ]
+        ),
+    )
+    _check_inference(
+        bb,
+        relax.op.topk(x5, axis=1),
+        relax.TupleStructInfo(
+            [
+                relax.TensorStructInfo(dtype=None),
+                relax.TensorStructInfo(dtype="int32"),
+            ]
+        ),
+    )
+    _check_inference(
+        bb,
+        relax.op.topk(x0),
+        relax.TupleStructInfo(
+            [
+                relax.TensorStructInfo((2, 10, 1), "float32"),
+                relax.TensorStructInfo((2, 10, 1), "int32"),
+            ]
+        ),
+    )
+    _check_inference(
+        bb,
+        relax.op.topk(x0, k=-1),
+        relax.TupleStructInfo(
+            [
+                relax.TensorStructInfo((2, 10, 4), "float32"),
+                relax.TensorStructInfo((2, 10, 4), "int32"),
+            ]
+        ),
+    )
+    _check_inference(
+        bb,
+        relax.op.topk(x0, k=6),
+        relax.TupleStructInfo(
+            [
+                relax.TensorStructInfo((2, 10, 4), "float32"),
+                relax.TensorStructInfo((2, 10, 4), "int32"),
+            ]
+        ),
+    )
+
+
+def test_topk_infer_struct_info_shape_symbolic():
+    bb = relax.BlockBuilder()
+    a = tir.Var("a", "int64")
+    b = tir.Var("b", "int64")
+    c = tir.Var("c", "int64")
+    x = relax.Var("x", R.Tensor((a, b, c), "float32"))
+
+    _check_inference(
+        bb,
+        relax.op.topk(x, axis=1),
+        relax.TupleStructInfo(
+            [
+                relax.TensorStructInfo((a, 1, c), "float32"),
+                relax.TensorStructInfo((a, 1, c), "int32"),
+            ]
+        ),
+    )
+    _check_inference(
+        bb,
+        relax.op.topk(x, k=3),
+        relax.TupleStructInfo(
+            [
+                relax.TensorStructInfo((a, b, 3), "float32"),
+                relax.TensorStructInfo((a, b, 3), "int32"),
+            ]
+        ),
+    )
+
+
 if __name__ == "__main__":
     tvm.testing.main()
diff --git a/tests/python/relax/test_op_statistical.py 
b/tests/python/relax/test_op_statistical.py
index 5c7d56556c..0f32c964f4 100644
--- a/tests/python/relax/test_op_statistical.py
+++ b/tests/python/relax/test_op_statistical.py
@@ -14,6 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+from typing import Callable
 import pytest
 import tvm
 import tvm.testing
@@ -205,7 +206,13 @@ def test_statistical_infer_struct_info_wrong_input_type():
         bb.normalize(relax.op.variance(x1))
 
 
-def test_cumsum_infer_struct_info():
+(scan_op,) = tvm.testing.parameters(
+    (relax.op.cumprod,),
+    (relax.op.cumsum,),
+)
+
+
+def test_scan_op_infer_struct_info(scan_op: Callable):
     bb = relax.BlockBuilder()
     vdev0 = VDevice("llvm")
     x0 = relax.Var("x", R.Tensor((2, 10, 4), "float32"))
@@ -216,60 +223,56 @@ def test_cumsum_infer_struct_info():
     x5 = relax.Var("x", R.Tensor())
     x6 = relax.Var("x", R.Tensor((2, 10, 4), "float32", vdev0))
 
-    _check_inference(bb, relax.op.cumsum(x0, axis=1), 
relax.TensorStructInfo((2, 10, 4), "float32"))
-    _check_inference(
-        bb, relax.op.cumsum(x6, axis=1), relax.TensorStructInfo((2, 10, 4), 
"float32", vdev0)
-    )
-    _check_inference(
-        bb, relax.op.cumsum(x1, axis=1), 
relax.TensorStructInfo(dtype="float32", ndim=3)
-    )
-    _check_inference(bb, relax.op.cumsum(x2, axis=1), 
relax.TensorStructInfo(dtype="float32"))
-    _check_inference(bb, relax.op.cumsum(x3, axis=1), 
relax.TensorStructInfo((2, 10, 4), dtype=""))
-    _check_inference(bb, relax.op.cumsum(x4, axis=1), 
relax.TensorStructInfo(dtype="", ndim=3))
-    _check_inference(bb, relax.op.cumsum(x5, axis=1), 
relax.TensorStructInfo(dtype=""))
-    _check_inference(bb, relax.op.cumsum(x0), relax.TensorStructInfo((80,), 
"float32"))
+    _check_inference(bb, scan_op(x0, axis=1), relax.TensorStructInfo((2, 10, 
4), "float32"))
+    _check_inference(bb, scan_op(x6, axis=1), relax.TensorStructInfo((2, 10, 
4), "float32", vdev0))
+    _check_inference(bb, scan_op(x1, axis=1), 
relax.TensorStructInfo(dtype="float32", ndim=3))
+    _check_inference(bb, scan_op(x2, axis=1), 
relax.TensorStructInfo(dtype="float32"))
+    _check_inference(bb, scan_op(x3, axis=1), relax.TensorStructInfo((2, 10, 
4), dtype=""))
+    _check_inference(bb, scan_op(x4, axis=1), relax.TensorStructInfo(dtype="", 
ndim=3))
+    _check_inference(bb, scan_op(x5, axis=1), relax.TensorStructInfo(dtype=""))
+    _check_inference(bb, scan_op(x0), relax.TensorStructInfo((80,), "float32"))
     _check_inference(
-        bb, relax.op.cumsum(x0, axis=1, dtype="int32"), 
relax.TensorStructInfo((2, 10, 4), "int32")
+        bb, scan_op(x0, axis=1, dtype="int32"), relax.TensorStructInfo((2, 10, 
4), "int32")
     )
 
 
-def test_cumsum_infer_struct_info_shape_symbolic():
+def test_scan_op_infer_struct_info_shape_symbolic(scan_op: Callable):
     bb = relax.BlockBuilder()
     a = tir.Var("a", "int64")
     b = tir.Var("b", "int64")
     c = tir.Var("c", "int64")
     x = relax.Var("x", R.Tensor((a, b, c), "float32"))
 
-    _check_inference(bb, relax.op.cumsum(x, axis=1), 
relax.TensorStructInfo((a, b, c), "float32"))
-    _check_inference(bb, relax.op.cumsum(x), relax.TensorStructInfo((a * b * 
c,), "float32"))
+    _check_inference(bb, scan_op(x, axis=1), relax.TensorStructInfo((a, b, c), 
"float32"))
+    _check_inference(bb, scan_op(x), relax.TensorStructInfo((a * b * c,), 
"float32"))
 
 
-def test_cumsum_infer_struct_info_more_input_dtype():
+def test_scan_op_infer_struct_info_more_input_dtype(scan_op: Callable):
     bb = relax.BlockBuilder()
     x0 = relax.Var("x", R.Tensor((2, 3, 4), "float16"))
     x1 = relax.Var("x", R.Tensor((2, 3, 4), "int8"))
 
-    _check_inference(bb, relax.op.cumsum(x0, axis=1), 
relax.TensorStructInfo((2, 3, 4), "float16"))
-    _check_inference(bb, relax.op.cumsum(x1, axis=1), 
relax.TensorStructInfo((2, 3, 4), "int8"))
+    _check_inference(bb, scan_op(x0, axis=1), relax.TensorStructInfo((2, 3, 
4), "float16"))
+    _check_inference(bb, scan_op(x1, axis=1), relax.TensorStructInfo((2, 3, 
4), "int8"))
 
 
-def test_cumsum_wrong_input_number():
+def test_scan_op_wrong_input_number(scan_op: Callable):
     x = relax.Var("x", R.Tensor((3, 4, 5), "float32"))
     y = relax.Var("y", R.Tensor((2, 3, 4), "float32"))
 
     with pytest.raises(TVMError):
-        relax.op.cumsum(x, y)
+        scan_op(x, y)
 
 
-def test_cumsum_infer_struct_info_wrong_input_type():
+def test_scan_opinfer_struct_info_wrong_input_type(scan_op: Callable):
     bb = relax.BlockBuilder()
     x0 = relax.Var("x", relax.ShapeStructInfo((2, 3, 4, 5)))
     x1 = relax.Var("x", relax.FuncStructInfo([], R.Tensor((2, 3, 4, 5), 
"float32")))
 
     with pytest.raises(TVMError):
-        bb.normalize(relax.op.cumsum(x0, axis=1))
+        bb.normalize(scan_op(x0, axis=1))
     with pytest.raises(TVMError):
-        bb.normalize(relax.op.cumsum(x1, axis=1))
+        bb.normalize(scan_op(x1, axis=1))
 
 
 if __name__ == "__main__":
diff --git a/tests/python/relax/test_tvmscript_parser_op_sort.py 
b/tests/python/relax/test_tvmscript_parser_op_sort.py
index 8b94fa0ab9..044fba3d8d 100644
--- a/tests/python/relax/test_tvmscript_parser_op_sort.py
+++ b/tests/python/relax/test_tvmscript_parser_op_sort.py
@@ -37,15 +37,21 @@ def _check(
 
 def test_sort():
     @R.function
-    def foo(x: R.Tensor((2, 3), "int32")) -> R.Tensor((2, 3), "int32"):
-        r = R.sort(x, axis=1)
+    def foo(
+        x: R.Tensor((2, 3), "int32")
+    ) -> R.Tuple(R.Tensor((2, 2), dtype="int32"), R.Tensor((2, 2), 
dtype="int32")):
+        lv0 = R.sort(x, axis=1)
+        lv1 = R.argsort(lv0)
+        r = R.topk(lv1, axis=1, k=2)
         return r
 
     x = relax.Var("x", R.Tensor((2, 3), "int32"))
     bb = relax.BlockBuilder()
     with bb.function("foo", (x,)):
-        tensor = bb.emit(relax.op.sort(x, axis=1))
-        bb.emit_func_output(tensor)
+        lv0 = bb.emit(relax.op.sort(x, axis=1))
+        lv1 = bb.emit(relax.op.argsort(lv0))
+        r = bb.emit(relax.op.topk(lv1, axis=1, k=2))
+        bb.emit_func_output(r)
 
     _check(foo, bb.get()["foo"])
 
diff --git a/tests/python/relax/test_tvmscript_parser_op_statistical.py 
b/tests/python/relax/test_tvmscript_parser_op_statistical.py
index 87446cedf3..910c08bf1e 100644
--- a/tests/python/relax/test_tvmscript_parser_op_statistical.py
+++ b/tests/python/relax/test_tvmscript_parser_op_statistical.py
@@ -170,16 +170,18 @@ def test_std():
     _check(foo, bb.get()["foo"])
 
 
-def test_cumsum():
+def test_scan():
     @R.function
     def foo(x: R.Tensor((2, 3, 4), "float32")):
-        gv = R.cumsum(x, axis=1, dtype="int32")
+        lv = R.cumsum(x, axis=1, dtype="int32")
+        gv = R.cumprod(lv, axis=1, dtype="int32")
         return gv
 
     x = relax.Var("x", R.Tensor((2, 3, 4), "float32"))
     bb = relax.BlockBuilder()
     with bb.function("foo", [x]):
-        gv = bb.emit(relax.op.cumsum(x, axis=1, dtype="int32"))
+        lv = bb.emit(relax.op.cumsum(x, axis=1, dtype="int32"))
+        gv = bb.emit(relax.op.cumprod(lv, axis=1, dtype="int32"))
         bb.emit_func_output(gv)
 
     _check(foo, bb.get()["foo"])

Reply via email to