# 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 class for computation declaration.""" # pylint: disable=invalid-name import tvm_ffi from tvm.runtime import Object, ObjectConvertible, const from tvm.tirx import DataProducer from tvm.tirx import expr as _expr from . import _ffi_api, _te_tensor_overload 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): if not isinstance(indices, tuple): indices = (indices,) self.tensor = tensor self.indices = indices def __getitem__(self, indices): if not isinstance(indices, tuple): indices = (indices,) return TensorSlice(self.tensor, self.indices + indices) def asobject(self): """Convert slice to object.""" return self.tensor.__call__(*self.indices) @property def dtype(self): """Data content of the tensor.""" return self.tensor.dtype def expr_ty(self): """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, TensorOpBase): """Tensor object, to construct, see function.Tensor""" def __call__(self, *indices): ndim = self.ndim if len(indices) != ndim: raise ValueError( f"Need to provide {ndim} index in tensor but {len(indices)} was provided" ) return _expr.ProducerLoad(self, indices) def __getitem__(self, indices): return TensorSlice(self, indices) def __hash__(self): return _ffi_api.TensorHash(self) def __eq__(self, other): if not isinstance(other, Tensor): if isinstance(other, _expr.ExprOp): return _expr.EqualOp(self, other) return False if self.ndim == 0 and other.ndim == 0: raise ValueError( "Equal == comparison among rank-0 tensor is ambiguous, " "use Tensor.equal for content expression equvalence, " "use Tensor.same_as for exact reference comparison" ) return _ffi_api.TensorEqual(self, other) @property def ndim(self): """Dimension of the tensor.""" return len(self.shape) @property def dtype(self): """Data content of the tensor.""" return _ffi_api.TensorDType(self) def expr_ty(self): """Compile-time element type of the tensor.""" return self.dtype @property def name(self): op = self.op if op.num_outputs == 1: return op.name return f"{op.name}.v{self.value_index}" @tvm_ffi.register_object("te.Operation") class Operation(Object): """Represent an operation that generates a tensor""" def output(self, index): """Get the index-th output of the operation Parameters ---------- index : int The index size. Returns ------- out : Tensor The i-th output. """ return _ffi_api.OpGetOutput(self, index) @property def num_outputs(self): """Number of outputs from this op.""" return _ffi_api.OpNumOutputs(self) @property def input_tensors(self): """List of input tensors to this op.""" return _ffi_api.OpInputTensors(self) @tvm_ffi.register_object("te.PlaceholderOp") class PlaceholderOp(Operation): """Placeholder operation.""" @tvm_ffi.register_object("te.BaseComputeOp") class BaseComputeOp(Operation): """Compute operation.""" @tvm_ffi.register_object("te.ComputeOp") class ComputeOp(BaseComputeOp): """Scalar operation.""" @tvm_ffi.register_object("te.ScanOp") class ScanOp(Operation): """Scan operation.""" @tvm_ffi.register_object("te.ExternOp") class ExternOp(Operation): """External operation."""