This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new 45e1b8233a Refactor Tensor arithmetic dispatch away from tirx.generic
(#19943)
45e1b8233a is described below
commit 45e1b8233a0f443311853d08e65a92c36b5c79bd
Author: Tianqi Chen <[email protected]>
AuthorDate: Sun Jul 5 08:18:43 2026 +0800
Refactor Tensor arithmetic dispatch away from tirx.generic (#19943)
## Summary
- Move whole-Tensor arithmetic and cast dispatch onto `te.Tensor` while
scalar TIRx smart constructors decline whole-Tensor operands.
- Remove the legacy `tirx.generic` module, TOPI import-time mutation
bridge, and obsolete aliases.
- Migrate scan and cast callers while preserving identity-gated Thrust
sum selection.
Whole-Tensor behavior now lives with TE, leaving scalar TIRx
construction independent of TOPI initialization.
---
python/tvm/relax/frontend/onnx/onnx_frontend.py | 3 +-
python/tvm/relax/transform/legalize_ops/qdq.py | 4 +-
python/tvm/te/__init__.py | 2 -
python/tvm/te/_te_tensor_overload.py | 61 +++++++
python/tvm/te/tensor.py | 202 +++++++++++++++++++++++-
python/tvm/tirx/__init__.py | 1 -
python/tvm/tirx/expr.py | 61 +++++--
python/tvm/tirx/generic.py | 145 -----------------
python/tvm/tirx/script/builder/ir.py | 9 +-
python/tvm/topi/__init__.py | 2 +-
python/tvm/topi/_te_tensor_overload.py | 64 ++++++++
python/tvm/topi/generic_op_impl.py | 105 ------------
python/tvm/topi/gpu/scan.py | 56 ++++---
python/tvm/topi/gpu/sort.py | 14 +-
python/tvm/topi/scan.py | 7 +-
15 files changed, 418 insertions(+), 318 deletions(-)
diff --git a/python/tvm/relax/frontend/onnx/onnx_frontend.py
b/python/tvm/relax/frontend/onnx/onnx_frontend.py
index ef08c3f690..f460418315 100644
--- a/python/tvm/relax/frontend/onnx/onnx_frontend.py
+++ b/python/tvm/relax/frontend/onnx/onnx_frontend.py
@@ -61,7 +61,6 @@ from tvm import relax, tirx, topi
from tvm.ir import IRModule
from tvm.ir.supply import UniqueNameSupply
from tvm.runtime import DataType, DataTypeCode
-from tvm.tirx.generic import cast
from tvm.topi.utils import get_const_tuple
from ..common import autopad
@@ -3257,7 +3256,7 @@ class Resize(OnnxOpConverter):
sizes = []
for i, dim in enumerate(x.ty.shape):
- sizes.append(cast(scales[i] * dim, "int64"))
+ sizes.append((scales[i] * dim).astype("int64"))
sizes = sizes[2:]
else:
if isinstance(sizes, relax.Constant):
diff --git a/python/tvm/relax/transform/legalize_ops/qdq.py
b/python/tvm/relax/transform/legalize_ops/qdq.py
index 0f85c6bbdd..9cea923146 100644
--- a/python/tvm/relax/transform/legalize_ops/qdq.py
+++ b/python/tvm/relax/transform/legalize_ops/qdq.py
@@ -147,8 +147,8 @@ def _dequantize(bb: BlockBuilder, call: Call) -> Expr:
if data.dtype.matches_code(DataTypeCode.FLOAT,
DataTypeCode.BFLOAT)
else "int32"
)
- sub = te.subtract(data[indices].astype(dtype), zp_value)
- out = te.multiply(sub, scale_value.astype("float32"))
+ sub = data[indices].astype(dtype) - zp_value
+ out = sub * scale_value.astype("float32")
if out_dtype == "float32":
return out
return clip_cast(out, out_dtype)
diff --git a/python/tvm/te/__init__.py b/python/tvm/te/__init__.py
index c923efde00..4773b8bcb9 100644
--- a/python/tvm/te/__init__.py
+++ b/python/tvm/te/__init__.py
@@ -27,8 +27,6 @@ from tvm.tirx import trunc, abs, round, nearbyint, power,
popcount, fmod, if_the
from tvm.tirx import isnan, isfinite, isinf
from tvm.tirx import div, indexdiv, indexmod, truncdiv, truncmod, floordiv,
floormod, logaddexp
from tvm.tirx import comm_reducer, min, max, sum
-from tvm.tirx import add, subtract, multiply
-
from .tensor import TensorSlice, Tensor
from .tag import tag_scope
from .operation import placeholder, compute, scan, extern, var, const
diff --git a/python/tvm/te/_te_tensor_overload.py
b/python/tvm/te/_te_tensor_overload.py
new file mode 100644
index 0000000000..d52ec703f1
--- /dev/null
+++ b/python/tvm/te/_te_tensor_overload.py
@@ -0,0 +1,61 @@
+# 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.
+"""Tensor overload hooks for TE tensors and tensor slices."""
+
+
+def __add__(_lhs, _rhs):
+ return NotImplemented
+
+
+def __radd__(_lhs, _rhs):
+ return NotImplemented
+
+
+def __sub__(_lhs, _rhs):
+ return NotImplemented
+
+
+def __rsub__(_lhs, _rhs):
+ return NotImplemented
+
+
+def __mul__(_lhs, _rhs):
+ return NotImplemented
+
+
+def __rmul__(_lhs, _rhs):
+ return NotImplemented
+
+
+def __div__(_lhs, _rhs):
+ return NotImplemented
+
+
+def __rdiv__(_lhs, _rhs):
+ return NotImplemented
+
+
+def __truediv__(_lhs, _rhs):
+ return NotImplemented
+
+
+def __rtruediv__(_lhs, _rhs):
+ return NotImplemented
+
+
+def astype(_value, _dtype, _span=None):
+ return NotImplemented
diff --git a/python/tvm/te/tensor.py b/python/tvm/te/tensor.py
index 29f9b185ad..ae1fad55a4 100644
--- a/python/tvm/te/tensor.py
+++ b/python/tvm/te/tensor.py
@@ -19,14 +19,18 @@
# pylint: disable=invalid-name
import tvm_ffi
-from tvm.runtime import Object, ObjectConvertible
+from tvm.runtime import Object, ObjectConvertible, const
from tvm.tirx import DataProducer
from tvm.tirx import expr as _expr
-from . import _ffi_api
+from . import _ffi_api, _te_tensor_overload
-class TensorSlice(ObjectConvertible, _expr.ExprOp):
+def _as_scalar_operand(value):
+ return value.asobject() if isinstance(value, TensorSlice) else value
+
+
+class TensorSlice(ObjectConvertible):
"""Auxiliary data structure for enable slicing syntax from tensor."""
def __init__(self, tensor, indices):
@@ -53,9 +57,199 @@ class TensorSlice(ObjectConvertible, _expr.ExprOp):
"""Compile-time element type of the tensor."""
return self.tensor.expr_ty()
+ def __add__(self, other):
+ result = _te_tensor_overload.__add__(self, other)
+ if result is not NotImplemented:
+ return result
+ return _expr.ExprOp.__add__(self.asobject(), _as_scalar_operand(other))
+
+ def __radd__(self, other):
+ result = _te_tensor_overload.__radd__(self, other)
+ if result is not NotImplemented:
+ return result
+ return _expr.ExprOp.__radd__(self.asobject(),
_as_scalar_operand(other))
+
+ def __sub__(self, other):
+ result = _te_tensor_overload.__sub__(self, other)
+ if result is not NotImplemented:
+ return result
+ return _expr.ExprOp.__sub__(self.asobject(), _as_scalar_operand(other))
+
+ def __rsub__(self, other):
+ result = _te_tensor_overload.__rsub__(self, other)
+ if result is not NotImplemented:
+ return result
+ return _expr.ExprOp.__rsub__(self.asobject(),
_as_scalar_operand(other))
+
+ def __mul__(self, other):
+ result = _te_tensor_overload.__mul__(self, other)
+ if result is not NotImplemented:
+ return result
+ return _expr.ExprOp.__mul__(self.asobject(), _as_scalar_operand(other))
+
+ def __rmul__(self, other):
+ result = _te_tensor_overload.__rmul__(self, other)
+ if result is not NotImplemented:
+ return result
+ return _expr.ExprOp.__rmul__(self.asobject(),
_as_scalar_operand(other))
+
+ def __div__(self, other):
+ result = _te_tensor_overload.__div__(self, other)
+ if result is not NotImplemented:
+ return result
+ return _expr.ExprOp.__div__(self.asobject(), _as_scalar_operand(other))
+
+ def __rdiv__(self, other):
+ result = _te_tensor_overload.__rdiv__(self, other)
+ if result is not NotImplemented:
+ return result
+ return _expr.ExprOp.__rdiv__(self.asobject(),
_as_scalar_operand(other))
+
+ def __truediv__(self, other):
+ result = _te_tensor_overload.__truediv__(self, other)
+ if result is not NotImplemented:
+ return result
+ return _expr.ExprOp.__truediv__(self.asobject(),
_as_scalar_operand(other))
+
+ def __rtruediv__(self, other):
+ result = _te_tensor_overload.__rtruediv__(self, other)
+ if result is not NotImplemented:
+ return result
+ return _expr.ExprOp.__rtruediv__(self.asobject(),
_as_scalar_operand(other))
+
+ def __floordiv__(self, other):
+ return _expr.ExprOp.__floordiv__(self.asobject(),
_as_scalar_operand(other))
+
+ def __rfloordiv__(self, other):
+ return _expr.ExprOp.__rfloordiv__(self.asobject(),
_as_scalar_operand(other))
+
+ def __mod__(self, other):
+ return _expr.ExprOp.__mod__(self.asobject(), _as_scalar_operand(other))
+
+ def __rmod__(self, other):
+ return _expr.ExprOp.__rmod__(self.asobject(),
_as_scalar_operand(other))
+
+ def __neg__(self):
+ return _expr.ExprOp.__neg__(self.asobject())
+
+ def __lshift__(self, other):
+ return _expr.ExprOp.__lshift__(self.asobject(),
_as_scalar_operand(other))
+
+ def __rlshift__(self, other):
+ return _expr.ExprOp.__rlshift__(self.asobject(),
_as_scalar_operand(other))
+
+ def __rshift__(self, other):
+ return _expr.ExprOp.__rshift__(self.asobject(),
_as_scalar_operand(other))
+
+ def __rrshift__(self, other):
+ return _expr.ExprOp.__rrshift__(self.asobject(),
_as_scalar_operand(other))
+
+ def __and__(self, other):
+ return _expr.ExprOp.__and__(self.asobject(), _as_scalar_operand(other))
+
+ def __rand__(self, other):
+ return _expr.ExprOp.__rand__(self.asobject(),
_as_scalar_operand(other))
+
+ def __or__(self, other):
+ return _expr.ExprOp.__or__(self.asobject(), _as_scalar_operand(other))
+
+ def __ror__(self, other):
+ return _expr.ExprOp.__ror__(self.asobject(), _as_scalar_operand(other))
+
+ def __xor__(self, other):
+ return _expr.ExprOp.__xor__(self.asobject(), _as_scalar_operand(other))
+
+ def __rxor__(self, other):
+ return _expr.ExprOp.__rxor__(self.asobject(),
_as_scalar_operand(other))
+
+ def __invert__(self):
+ return _expr.ExprOp.__invert__(self.asobject())
+
+ def __lt__(self, other):
+ return _expr.ExprOp.__lt__(self.asobject(), _as_scalar_operand(other))
+
+ def __le__(self, other):
+ return _expr.ExprOp.__le__(self.asobject(), _as_scalar_operand(other))
+
+ def __eq__(self, other):
+ return _expr.ExprOp.__eq__(self.asobject(), _as_scalar_operand(other))
+
+ def __ne__(self, other):
+ return _expr.ExprOp.__ne__(self.asobject(), _as_scalar_operand(other))
+
+ def __gt__(self, other):
+ return _expr.ExprOp.__gt__(self.asobject(), _as_scalar_operand(other))
+
+ def __ge__(self, other):
+ return _expr.ExprOp.__ge__(self.asobject(), _as_scalar_operand(other))
+
+ def __nonzero__(self):
+ return _expr.ExprOp.__nonzero__(self.asobject())
+
+ def __bool__(self):
+ return self.__nonzero__()
+
+ def equal(self, other, span=None):
+ return _expr.ExprOp.equal(self.asobject(), _as_scalar_operand(other),
span)
+
+ def astype(self, dtype, span=None):
+ return _expr.ExprOp.astype(self.asobject(), dtype, span)
+
+
+class TensorOpBase:
+ """Operator overloads for whole TE Tensor values."""
+
+ def __add__(self, other):
+ return _te_tensor_overload.__add__(self, other)
+
+ def __radd__(self, other):
+ return _te_tensor_overload.__radd__(self, other)
+
+ def __sub__(self, other):
+ return _te_tensor_overload.__sub__(self, other)
+
+ def __rsub__(self, other):
+ return _te_tensor_overload.__rsub__(self, other)
+
+ def __mul__(self, other):
+ return _te_tensor_overload.__mul__(self, other)
+
+ def __rmul__(self, other):
+ return _te_tensor_overload.__rmul__(self, other)
+
+ def __div__(self, other):
+ return _te_tensor_overload.__div__(self, other)
+
+ def __rdiv__(self, other):
+ return _te_tensor_overload.__rdiv__(self, other)
+
+ def __truediv__(self, other):
+ return _te_tensor_overload.__truediv__(self, other)
+
+ def __rtruediv__(self, other):
+ return _te_tensor_overload.__rtruediv__(self, other)
+
+ def __neg__(self):
+ return self.__mul__(const(-1, self.expr_ty()))
+
+ def __nonzero__(self):
+ return _expr.ExprOp.__nonzero__(self)
+
+ def __bool__(self):
+ return self.__nonzero__()
+
+ def equal(self, other, span=None):
+ return _expr.ExprOp.equal(self, other, span)
+
+ def astype(self, dtype, span=None):
+ result = _te_tensor_overload.astype(self, dtype, span)
+ if result is NotImplemented:
+ raise TypeError("TE Tensor overload astype is not registered")
+ return result
+
@tvm_ffi.register_object("te.Tensor")
-class Tensor(DataProducer, _expr.ExprOp):
+class Tensor(DataProducer, TensorOpBase):
"""Tensor object, to construct, see function.Tensor"""
def __call__(self, *indices):
diff --git a/python/tvm/tirx/__init__.py b/python/tvm/tirx/__init__.py
index 5a7c308bb9..efec3e35d6 100644
--- a/python/tvm/tirx/__init__.py
+++ b/python/tvm/tirx/__init__.py
@@ -82,7 +82,6 @@ from .op import start_profile_intrinsic, end_profile_intrinsic
from .op import vscale, get_active_lane_mask, get_vscale_expr
from .op import dp4a
from .op import ignore_loop_partition
-from .generic import add, subtract, multiply
# TIRX-specific imports (must come before subpackage imports to avoid circular
imports)
from .exec_scope import ExecScope, ScopeIdDef
diff --git a/python/tvm/tirx/expr.py b/python/tvm/tirx/expr.py
index f45539d3b8..2e64b9316a 100644
--- a/python/tvm/tirx/expr.py
+++ b/python/tvm/tirx/expr.py
@@ -38,7 +38,6 @@ from tvm.ir.base import Span
from tvm.runtime import DataTypeCode, Object, ObjectConvertible, Scriptable,
const
from . import _ffi_api
-from . import generic as _generic
from .buffer import Buffer, DataProducer
@@ -59,6 +58,8 @@ def _dtype_is_int(value):
return True
if isinstance(value, ExprOp):
return value.expr_ty().matches_code(DataTypeCode.INT)
+ if ir.is_prim_expr(value):
+ return value.ty.matches_code(DataTypeCode.INT)
return False
@@ -67,9 +68,21 @@ def _dtype_is_float(value):
return True
if isinstance(value, ExprOp):
return value.expr_ty().matches_code(DataTypeCode.FLOAT)
+ if ir.is_prim_expr(value):
+ return value.ty.matches_code(DataTypeCode.FLOAT)
return False
+def _is_scalar_operand(value):
+ if isinstance(value, ExprOp | int | float) or ir.is_prim_expr(value):
+ return True
+
+ # BufferRegion is a C++ PrimExprConvertible, but its Python wrapper is not
an ExprOp.
+ from .stmt import BufferRegion # pylint: disable=import-outside-toplevel
+
+ return isinstance(value, BufferRegion)
+
+
class ExprOp:
"""Operator overloading for Expr like expressions."""
@@ -83,48 +96,68 @@ class ExprOp:
raise TypeError(f"Cannot determine PrimType for {type(self).__name__}")
def __add__(self, other: Expr) -> Expr:
- return _generic.add(self, other)
+ if not _is_scalar_operand(other):
+ return NotImplemented
+ return _ffi_api._OpAdd(self, other, None) # type: ignore
def __radd__(self, other: Expr) -> Expr:
- return _generic.add(other, self)
+ if not _is_scalar_operand(other):
+ return NotImplemented
+ return _ffi_api._OpAdd(other, self, None) # type: ignore
def __sub__(self, other: Expr) -> Expr:
- return _generic.subtract(self, other)
+ if not _is_scalar_operand(other):
+ return NotImplemented
+ return _ffi_api._OpSub(self, other, None) # type: ignore
def __rsub__(self, other: Expr) -> Expr:
- return _generic.subtract(other, self)
+ if not _is_scalar_operand(other):
+ return NotImplemented
+ return _ffi_api._OpSub(other, self, None) # type: ignore
def __mul__(self, other: Expr) -> Expr:
- return _generic.multiply(self, other)
+ if not _is_scalar_operand(other):
+ return NotImplemented
+ return _ffi_api._OpMul(self, other, None) # type: ignore
def __rmul__(self, other: Expr) -> Expr:
- return _generic.multiply(other, self)
+ if not _is_scalar_operand(other):
+ return NotImplemented
+ return _ffi_api._OpMul(other, self, None) # type: ignore
def __div__(self, other: Expr) -> Expr:
+ if not _is_scalar_operand(other):
+ return NotImplemented
if _dtype_is_int(self) and _dtype_is_int(other):
raise div_ambiguity_error()
- return _generic.divide(self, other)
+ return _ffi_api._OpDiv(self, other, None) # type: ignore
def __rdiv__(self, other: Expr) -> Expr:
+ if not _is_scalar_operand(other):
+ return NotImplemented
if _dtype_is_int(self) and _dtype_is_int(other):
raise div_ambiguity_error()
- return _generic.divide(other, self)
+ return _ffi_api._OpDiv(other, self, None) # type: ignore
def __truediv__(self, other: Expr) -> Expr:
+ if not _is_scalar_operand(other):
+ return NotImplemented
if _dtype_is_int(self) and _dtype_is_int(other):
raise div_ambiguity_error()
- return _generic.divide(self, other)
+ return _ffi_api._OpDiv(self, other, None) # type: ignore
def __rtruediv__(self, other: Expr) -> Expr:
+ if not _is_scalar_operand(other):
+ return NotImplemented
if _dtype_is_int(self) and _dtype_is_int(other):
raise div_ambiguity_error()
- return _generic.divide(other, self)
+ return _ffi_api._OpDiv(other, self, None) # type: ignore
def __floordiv__(self, other: Expr) -> Expr:
- return _generic.floordiv(self, other)
+ return _ffi_api._OpFloorDiv(self, other, None) # type: ignore
def __rfloordiv__(self, other: Expr) -> Expr:
- return _generic.floordiv(other, self, None)
+ return _ffi_api._OpFloorDiv(other, self, None) # type: ignore
def __mod__(self, other: Expr) -> Expr:
return _ffi_api._OpFloorMod(self, other, None) # type: ignore
@@ -232,7 +265,7 @@ class ExprOp:
expr : Expr
Expression with new type
"""
- return _generic.cast(self, dtype, span)
+ return _ffi_api._cast(dtype, self, span) # type: ignore
_overload_prim_expr.__add__ = ExprOp.__add__
diff --git a/python/tvm/tirx/generic.py b/python/tvm/tirx/generic.py
deleted file mode 100644
index 132a09e302..0000000000
--- a/python/tvm/tirx/generic.py
+++ /dev/null
@@ -1,145 +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.
-"""Generic opertors in TVM.
-We follow the numpy naming convention for this interface
-(e.g., tvm.tirx.generic.multitply ~ numpy.multiply).
-The default implementation is used by tvm.ExprOp.
-"""
-
-# pylint: disable=unused-argument
-from . import _ffi_api
-
-# Operator precedence used when overloading.
-__op_priority__ = 0
-
-
-def add(lhs, rhs, span=None):
- """Generic add operator.
-
- Parameters
- ----------
- lhs : object
- The left operand.
- rhs : object
- The right operand.
- span : Optional[Span]
- The location of this operator in the source.
-
- Returns
- -------
- op : tvm.Expr
- The result Expr of add operaton.
- """
- return _ffi_api._OpAdd(lhs, rhs, span) # type: ignore
-
-
-def subtract(lhs, rhs, span=None):
- """Generic subtract operator.
-
- Parameters
- ----------
- lhs : object
- The left operand.
- rhs : object
- The right operand.
- span : Optional[Span]
- The location of this operator in the source.
-
- Returns
- -------
- op : tvm.Expr
- The result Expr of subtract operaton.
- """
- return _ffi_api._OpSub(lhs, rhs, span) # type: ignore
-
-
-def multiply(lhs, rhs, span=None):
- """Generic multiply operator.
-
- Parameters
- ----------
- lhs : object
- The left operand.
- rhs : object
- The right operand.
- span : Optional[Span]
- The location of this operator in the source.
-
- Returns
- -------
- op : tvm.Expr
- The result Expr of multiply operaton.
- """
- return _ffi_api._OpMul(lhs, rhs, span) # type: ignore
-
-
-def divide(lhs, rhs, span=None):
- """Generic divide operator.
-
- Parameters
- ----------
- lhs : object
- The left operand.
- rhs : object
- The right operand.
- span : Optional[Span]
- The location of this operator in the source.
-
- Returns
- -------
- op : tvm.Expr
- The result Expr of divide operaton.
- """
- return _ffi_api._OpDiv(lhs, rhs, span) # type: ignore
-
-
-def floordiv(lhs, rhs, span=None):
- """Generic floordiv operator.
-
- Parameters
- ----------
- lhs : object
- The left operand.
- rhs : object
- The right operand.
- span : Optional[Span]
- The location of this operator in the source.
-
- Returns
- -------
- op : tvm.Expr
- The result Expr of floordiv operaton.
- """
- return _ffi_api._OpFloorDiv(lhs, rhs, span) # type: ignore
-
-
-def cast(src, dtype, span=None):
- """Generic cast operator.
-
- Parameters
- ----------
- src : object
- The source operand.
- span : Optional[Span]
- The location of this operator in the source.
-
- Returns
- -------
- op : tvm.Expr
- The result Expr of cast operaton.
- """
- return _ffi_api._cast(dtype, src, span) # type: ignore
diff --git a/python/tvm/tirx/script/builder/ir.py
b/python/tvm/tirx/script/builder/ir.py
index 4a16cbffb2..4e80e6cd07 100644
--- a/python/tvm/tirx/script/builder/ir.py
+++ b/python/tvm/tirx/script/builder/ir.py
@@ -44,6 +44,7 @@ from tvm.target import Target
# pylint: disable=unused-import
from tvm.target.codegen import llvm_lookup_intrinsic_id
from tvm.tirx import Buffer, BufferRegion, Expr, IndexMap, type_annotation
+from tvm.tirx import _ffi_api as _tirx_ffi_api
from tvm.tirx import op as _tir_op
from tvm.tirx.exec_scope import ExecScope, ScopeIdDef, Var
@@ -82,7 +83,6 @@ from tvm.tirx.expr import (
StringImm,
Sub,
)
-from tvm.tirx.generic import cast
from tvm.tirx.layout import (
ComposeLayout,
Iter,
@@ -100,6 +100,11 @@ from .external_kernel import call_kernel
# pylint: enable=unused-import
+def cast(value, dtype, span=None):
+ """Cast an expression to the requested data type."""
+ return _tirx_ffi_api._cast(dtype, value, span) # type:
ignore[attr-defined]
+
+
def _current_s_tir() -> bool:
"""Return True if the innermost enclosing PrimFuncFrame has ``s_tir=True``.
@@ -2355,7 +2360,7 @@ def evaluate(value: Expr) -> None:
if isinstance(value, str):
value = StringImm(value)
if isinstance(value, bool):
- value = cast(value, "bool")
+ value = IntImm("bool", value)
return _ffi_api.Evaluate(value) # type: ignore[attr-defined] # pylint:
disable=no-member
diff --git a/python/tvm/topi/__init__.py b/python/tvm/topi/__init__.py
index f0db4488d3..f0db21581c 100644
--- a/python/tvm/topi/__init__.py
+++ b/python/tvm/topi/__init__.py
@@ -34,11 +34,11 @@ from . import cpp
from .math import *
from .tensor import *
-from .generic_op_impl import *
from .index_put import *
from .reduction import *
from .transform import *
from .broadcast import *
+from . import _te_tensor_overload
from .sort import *
from .scatter import *
from .scatter_elements import *
diff --git a/python/tvm/topi/_te_tensor_overload.py
b/python/tvm/topi/_te_tensor_overload.py
new file mode 100644
index 0000000000..67a5347438
--- /dev/null
+++ b/python/tvm/topi/_te_tensor_overload.py
@@ -0,0 +1,64 @@
+# 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.
+"""Register TOPI implementations for TE tensor overload hooks."""
+
+from tvm import te
+from tvm.te import _te_tensor_overload as _overload
+from tvm.tirx import expr as _expr
+
+from . import broadcast as _broadcast
+from . import math as _math
+
+
+def _is_integer(value):
+ if isinstance(value, te.Tensor | te.TensorSlice):
+ return value.dtype.matches_code(_expr.DataTypeCode.INT)
+ return _expr._dtype_is_int(value)
+
+
+def _binary(op, reflected=False, check_integer=False):
+ def implementation(lhs, rhs):
+ if not isinstance(lhs, te.Tensor) and not isinstance(rhs, te.Tensor):
+ return NotImplemented
+ if reflected:
+ lhs, rhs = rhs, lhs
+ if check_integer and _is_integer(lhs) and _is_integer(rhs):
+ raise _expr.div_ambiguity_error()
+ return op(lhs, rhs)
+
+ return implementation
+
+
+_overload.__add__ = _binary(_broadcast.add)
+_overload.__radd__ = _binary(_broadcast.add, reflected=True)
+_overload.__sub__ = _binary(_broadcast.subtract)
+_overload.__rsub__ = _binary(_broadcast.subtract, reflected=True)
+_overload.__mul__ = _binary(_broadcast.multiply)
+_overload.__rmul__ = _binary(_broadcast.multiply, reflected=True)
+_overload.__div__ = _binary(_broadcast.divide, check_integer=True)
+_overload.__rdiv__ = _binary(_broadcast.divide, reflected=True,
check_integer=True)
+_overload.__truediv__ = _overload.__div__
+_overload.__rtruediv__ = _overload.__rdiv__
+
+
+def _astype(value, dtype, span=None):
+ if not isinstance(value, te.Tensor):
+ return NotImplemented
+ return _math.cast(value, dtype, span)
+
+
+_overload.astype = _astype
diff --git a/python/tvm/topi/generic_op_impl.py
b/python/tvm/topi/generic_op_impl.py
deleted file mode 100644
index a26e435df9..0000000000
--- a/python/tvm/topi/generic_op_impl.py
+++ /dev/null
@@ -1,105 +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.
-"""Implementation of generic operators in the presence of Tensor"""
-
-# pylint: disable=invalid-name, too-many-arguments
-import tvm
-from tvm import te
-
-from . import broadcast as _broadcast
-from . import math as _math
-
-
-def _make_bop(broadcast_bop, orig_bop):
- """Make a specific overloaded binary operator of Tensor when applicable;
- apply the original operator if it is not supposed to be overloaded.
-
- Consider the following scenario:
- OP : + | - | * | /
- R0 : int | float | Expr | TensorSlice | Tensor (rank zero)
- R1 : Tensor (positive rank)
-
- In terms of (LHS OP RHS), we apply the following overloading rules:
- (1) We use broadcast_OP(LHS, RHS), when both LHS and RHS are R1.
- (2) We perform element-wise operation of Tensor and scalar,
- when one of LHS and RHS is R1 and another is R0.
- (3) We do not overload OP (i.e. stick to orig_bop) otherwise.
-
- Parameters
- ----------
- broadcast_bop : operator function
- Operator for broadcast tensor-tensor operation, for rule (1).
-
- orig_bop: operator function
- Operator before overloading, for rule (3).
-
- Returns
- -------
- ret : operator function
- The overloaded operator function if applicable or orig_bop otherwise.
- """
-
- name = orig_bop.__name__
-
- def _tensor_bop_impl(lhs, rhs):
- """Overloaded {op} operator.
-
- If both operands are non-zero-rank Tensors, it performs
- tensor-tensor {op} operation, and broadcasts inputs when necessary.
-
- If one operand is non-zero-rank Tensor, while the other operand is
- scalar like type (e.g., numeric types, Expr, or TensorSlice),
- it performs tensor-scalar {op} operation on an element-wise basis.
-
- Otherwise, it performs default generic.{op} operation, as defined
- in tvm.tirx.generic module.
-
- Parameters
- ----------
- lhs : object
- Left operand.
- rhs : object
- Right operand.
-
- Returns
- -------
- ret : tvm.te.Tensor (if at least one operand is non-zero-rank Tensor)
- tvm.Expr (otherwise)
- The result of {op} operation.
- """
- if not isinstance(lhs, te.tensor.Tensor) and not isinstance(rhs,
te.tensor.Tensor):
- return orig_bop(lhs, rhs)
- return broadcast_bop(lhs, rhs)
-
- _tensor_bop_impl.__doc__ = _tensor_bop_impl.__doc__.format(op=name)
- return _tensor_bop_impl
-
-
-def _bind_generic_ops():
- """Bind generic operators for Tensor."""
- # Check __op_priority__ to make sure the binding happens only once.
- __op_priority__ = 1
- if __op_priority__ > tvm.tirx.generic.__op_priority__:
- tvm.tirx.generic.__op_priority__ = __op_priority__
- tvm.tirx.generic.add = _make_bop(_broadcast.add, tvm.tirx.generic.add)
- tvm.tirx.generic.subtract = _make_bop(_broadcast.subtract,
tvm.tirx.generic.subtract)
- tvm.tirx.generic.multiply = _make_bop(_broadcast.multiply,
tvm.tirx.generic.multiply)
- tvm.tirx.generic.divide = _make_bop(_broadcast.divide,
tvm.tirx.generic.divide)
- tvm.tirx.generic.cast = _math.cast
-
-
-_bind_generic_ops()
diff --git a/python/tvm/topi/gpu/scan.py b/python/tvm/topi/gpu/scan.py
index 0235c8c3a6..a0b35bcb19 100644
--- a/python/tvm/topi/gpu/scan.py
+++ b/python/tvm/topi/gpu/scan.py
@@ -17,6 +17,7 @@
# pylint: disable=invalid-name, too-many-locals, too-many-statements
"Scan related operators"
+import operator
from collections.abc import Callable
import tvm
@@ -29,11 +30,13 @@ from ..math import cast, ceil_log2
from ..transform import expand_dims, reshape, squeeze, transpose
from ..utils import ceil_div, get_const_int, prod, swap
+_THRUST_SUM_SCAN = "tvm.contrib.thrust.sum_scan"
+
def _get_thrust_func_name(tvmop):
- tvmop_to_thrust_func_name = {tvm.tirx.generic.add:
"tvm.contrib.thrust.sum_scan"}
- assert tvmop in tvmop_to_thrust_func_name, f"{tvmop} not supported by
thrust"
- return tvmop_to_thrust_func_name[tvmop]
+ if tvmop is not operator.add:
+ raise ValueError(f"{tvmop} not supported by thrust")
+ return _THRUST_SUM_SCAN
def _can_use_scan_thrust(binop):
@@ -43,16 +46,15 @@ def _can_use_scan_thrust(binop):
target = tvm.target.Target.current()
if target is None:
return False
- # pylint: disable=comparison-with-callable
- return binop == tvm.tirx.generic.add and any(
+ return binop is operator.add and any(
[
- can_use_thrust(target, "tvm.contrib.thrust.sum_scan"),
- can_use_rocthrust(target, "tvm.contrib.thrust.sum_scan"),
+ can_use_thrust(target, _THRUST_SUM_SCAN),
+ can_use_rocthrust(target, _THRUST_SUM_SCAN),
]
)
-def exclusive_scan_ir(data, output, reduction=None,
binop=tvm.tirx.generic.add, identity_value=0):
+def exclusive_scan_ir(data, output, reduction=None, binop=operator.add,
identity_value=0):
"""Low level IR to do exclusive sum scan along rows of 2D input.
Parameters
@@ -68,8 +70,8 @@ def exclusive_scan_ir(data, output, reduction=None,
binop=tvm.tirx.generic.add,
binop: function, optional
A binary associative op to use for scan. The function takes two TIR
expressions
- and produce a new TIR expression. By default it uses
tvm.tirx.generic.add to compute
- prefix sum.
+ and produce a new TIR expression. By default it uses ``operator.add``
to compute prefix
+ sum.
identity_value: int or float
A value for the binary operation which provides the identity property.
E.g. if * is
@@ -140,9 +142,7 @@ def exclusive_scan_ir(data, output, reduction=None,
binop=tvm.tirx.generic.add,
T.attr(
bx,
"thread_extent",
- tvm.tirx.generic.cast(
- ceil_div(scan_axis_size, max_threads *
width), "int32"
- ),
+ cast(ceil_div(scan_axis_size, max_threads *
width), "int32"),
),
T.attr(by, "thread_extent", nthread_by),
]
@@ -188,9 +188,7 @@ def exclusive_scan_ir(data, output, reduction=None,
binop=tvm.tirx.generic.add,
T.attr(
bx,
"thread_extent",
- tvm.tirx.generic.cast(
- ceil_div(scan_axis_size, max_threads *
width), "int32"
- ),
+ cast(ceil_div(scan_axis_size, max_threads *
width), "int32"),
),
T.attr(by, "thread_extent", nthread_by),
]
@@ -218,7 +216,7 @@ def exclusive_scan_ir(data, output, reduction=None,
binop=tvm.tirx.generic.add,
return ib.get()
-def get_reduction_from_exclusive_scan(data, ex_scan_output,
binop=tvm.tirx.generic.add):
+def get_reduction_from_exclusive_scan(data, ex_scan_output,
binop=operator.add):
"""Return the sum of the last element of data and the exclusive scan
output.
The is the reduction of data along each row (for 2-D case).
@@ -232,8 +230,8 @@ def get_reduction_from_exclusive_scan(data, ex_scan_output,
binop=tvm.tirx.gener
binop: function, optional
A binary associative op to use for scan. The function takes two TIR
expressions
- and produce a new TIR expression. By default it uses
tvm.tirx.generic.add to compute
- prefix sum.
+ and produce a new TIR expression. By default it uses ``operator.add``
to compute prefix
+ sum.
Returns
-------
@@ -312,7 +310,7 @@ def scan_thrust(
output_dtype,
exclusive=True,
return_reduction=False,
- binop=tvm.tirx.generic.add,
+ binop=operator.add,
workspace=None,
):
"""Do exclusive or inclusive scan on 1D or multidimensional input, using
thrust.
@@ -336,7 +334,7 @@ def scan_thrust(
binop: function, optional
A binary associative op to use for scan. Since we need to lookup the
corresponding
thrust function, arbitrariy callables are not supported. Currently only
- tvm.tirx.generic.add can be passed in.
+ ``operator.add`` can be passed in.
workspace: Optional[tvm.te.Tensor]
A buffer to store intermediate results. The size of the workspace
should be sufficiently
@@ -397,7 +395,7 @@ def exclusive_scan(
axis=-1,
return_reduction=False,
output_dtype=None,
- binop=tvm.tirx.generic.add,
+ binop=operator.add,
identity_value=0,
workspace=None,
):
@@ -422,8 +420,8 @@ def exclusive_scan(
binop: function, optional
A binary associative op to use for scan. The function takes two TIR
expressions
- and produce a new TIR expression. By default it uses
tvm.tirx.generic.add to compute
- prefix sum.
+ and produce a new TIR expression. By default it uses ``operator.add``
to compute prefix
+ sum.
identity_value: int or float
A value for the binary operation which provides the identity property.
E.g. if * is
@@ -534,7 +532,7 @@ def exclusive_scan(
def inclusive_scan(
- data, axis=-1, output_dtype=None, binop=tvm.tirx.generic.add,
identity_value=0, workspace=None
+ data, axis=-1, output_dtype=None, binop=operator.add, identity_value=0,
workspace=None
):
"""Do inclusive scan on 1D or multidimensional input.
@@ -551,8 +549,8 @@ def inclusive_scan(
binop: function, optional
A binary associative op to use for scan. The function takes two TIR
expressions
- and produce a new TIR expression. By default it uses
tvm.tirx.generic.add to compute
- prefix sum.
+ and produce a new TIR expression. By default it uses ``operator.add``
to compute prefix
+ sum.
identity_value: int or float
A value for the binary operation which provides the identity property.
E.g. if * is
@@ -717,7 +715,7 @@ def cumsum(
"""
return scanop(
data=data,
- binop=tvm.tirx.generic.add,
+ binop=operator.add,
identity_value=0,
axis=axis,
dtype=dtype,
@@ -767,7 +765,7 @@ def cumprod(
"""
return scanop(
data=data,
- binop=tvm.tirx.generic.multiply,
+ binop=operator.mul,
identity_value=1,
axis=axis,
dtype=dtype,
diff --git a/python/tvm/topi/gpu/sort.py b/python/tvm/topi/gpu/sort.py
index 95ef358952..2ca4056438 100644
--- a/python/tvm/topi/gpu/sort.py
+++ b/python/tvm/topi/gpu/sort.py
@@ -494,12 +494,12 @@ def _sort_common(
target = tvm.target.Target.current()
if "vulkan" in str(target):
ntx = max_threads
- nbx = tvm.tirx.generic.cast(ceil_div(width, max_threads *
thread_work), "int32")
- nbz = tvm.tirx.generic.cast(ceil_div(size, width), "int32")
+ nbx = cast(ceil_div(width, max_threads * thread_work), "int32")
+ nbz = cast(ceil_div(size, width), "int32")
else:
- ntx = tvm.tirx.generic.cast(tvm.te.min(max_threads, width),
"int32")
- nbx = tvm.tirx.generic.cast(ceil_div(width, max_threads *
thread_work), "int32")
- nbz = tvm.tirx.generic.cast(ceil_div(size, width), "int32")
+ ntx = cast(tvm.te.min(max_threads, width), "int32")
+ nbx = cast(ceil_div(width, max_threads * thread_work), "int32")
+ nbz = cast(ceil_div(size, width), "int32")
tx, bx, by, _, _, _ = _get_threads(ntx, nbx, nthread_by * nbz)
with T.frame_scope(
@@ -635,9 +635,7 @@ def sort_ir(
indices_out,
value_init_func=(
lambda _, tid: (
- tvm.tirx.generic.cast(tid, indices_out_orig.dtype)
- if indices_out is not None
- else None
+ cast(tid, indices_out_orig.dtype) if indices_out
is not None else None
)
),
)
diff --git a/python/tvm/topi/scan.py b/python/tvm/topi/scan.py
index 07dc2cddd5..591eb9b9cd 100644
--- a/python/tvm/topi/scan.py
+++ b/python/tvm/topi/scan.py
@@ -17,6 +17,7 @@
# pylint: disable=invalid-name
"""Scan (cumulative binary) operators"""
+import operator
from collections.abc import Callable
import tvm
@@ -24,7 +25,7 @@ from tvm.script.ir_builder import IRBuilder
from tvm.script.ir_builder import tirx as T
from ..te import extern
-from ..tirx import decl_buffer, generic
+from ..tirx import decl_buffer
from . import utils
from .math import cast
@@ -186,7 +187,7 @@ def cumsum(
"""
return scanop(
data=data,
- binop=generic.add,
+ binop=operator.add,
identity_value=0,
op_name="cumsum_generic",
axis=axis,
@@ -230,7 +231,7 @@ def cumprod(
"""
return scanop(
data=data,
- binop=generic.multiply,
+ binop=operator.mul,
identity_value=1,
op_name="cumprod_generic",
axis=axis,