# Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved. # # Licensed 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. from __future__ import annotations import logging from typing import TYPE_CHECKING import numpy as np import paddle from paddle import _C_ops from paddle.utils.decorator_utils import ( size_args_decorator_patch, ) from .. import core from ..framework import convert_nptype_to_datatype_or_vartype if TYPE_CHECKING: from typing import Any from numpy.typing import NDArray from paddle import Tensor from paddle._typing import ( DTypeLike, NestedNumericSequence, PlaceLike, ShapeLike, TensorLike, ) _supported_int_dtype_ = [ core.VarDesc.VarType.UINT8, core.VarDesc.VarType.INT8, core.VarDesc.VarType.INT16, core.VarDesc.VarType.INT32, core.VarDesc.VarType.INT64, core.VarDesc.VarType.BOOL, ] # NOTE(chenweihang): We currently do not fully support the type promotion # between tensors. Parting support here is because the interoperation of # real and complex numbers in paddle quantum is very frequent, such as the # binary operation between `float` and `complex64`, so we must support the # correct type promotion on the APIs paddle quantum used. # Now only check in dygraph (paddle quantum based dygraph) # Full type promotion support will need to be fully verified later. _supported_promote_complex_types_ = [ '__add__', '__radd__', '__sub__', '__rsub__', '__mul__', '__rmul__', '__div__', '__truediv__', '__rdiv__', '__rtruediv__', '__matmul__', ] _complex_dtypes = [ core.VarDesc.VarType.COMPLEX64, core.VarDesc.VarType.COMPLEX128, ] _already_patch_eager_tensor = False _supported_dtype_conversions = { # float 'float16': 'float16', 'half': 'float16', 'bfloat16': 'bfloat16', 'float32': 'float32', 'float': 'float32', 'float64': 'float64', 'double': 'float64', # int 'int8': 'int8', 'char': 'int8', # We handle uint8 conversion separately # 'uint8': 'uint8', # 'byte': 'uint8', 'int16': 'int16', 'short': 'int16', 'int32': 'int32', 'int': 'int32', 'int64': 'int64', 'long': 'int64', # other 'bool': 'bool', 'complex64': 'complex64', 'complex128': 'complex128', 'cfloat': 'complex64', 'cdouble': 'complex128', } def _rebuild_tensor( data: NDArray[Any], dtype: DTypeLike, device: PlaceLike, requires_grad, ) -> Tensor: return paddle.tensor( data, dtype, device, requires_grad, ) class TensorSize(int): as_shape: list[int] def __new__(cls, shape): instance = super().__new__(cls, int(np.prod(shape))) instance.as_shape = shape return instance def __call__(self, dim=None): shape = paddle.Size(self.as_shape) if dim is None: return shape return shape[dim] def monkey_patch_math_tensor(): """ Similar to monkey_patch_variable. The difference is, in dygraph mode, use auto-generated op functions for better performance. """ global paddle def astype(self: Tensor, dtype: DTypeLike) -> Tensor: """ Cast a Tensor to a specified data type if it differs from the current dtype; otherwise, return the original Tensor. Args: dtype: The target data type. Returns: Tensor: a new Tensor with target dtype Examples: .. code-block:: pycon >>> import paddle >>> import numpy as np >>> original_tensor = paddle.ones([2, 2]) >>> print("original tensor's dtype is: {}".format(original_tensor.dtype)) original tensor's dtype is: paddle.float32 >>> new_tensor = original_tensor.astype('float32') >>> print("new tensor's dtype is: {}".format(new_tensor.dtype)) new tensor's dtype is: paddle.float32 """ if not isinstance(dtype, (core.VarDesc.VarType, core.DataType)): dtype = convert_nptype_to_datatype_or_vartype(dtype) if self.dtype == dtype: return self return _C_ops.cast(self, dtype) def byte(self: Tensor) -> Tensor: # since paddle don't support float to uint8, so we need to convert it to int8 first if self.is_floating_point(): tensor = astype(self, 'int8') return astype(tensor, 'uint8') elif self.is_complex(): real = astype(self.real(), 'int8') logging.warning( "Casting complex values to real discards the imaginary part" ) return astype(real, 'uint8') else: return astype(self, 'uint8') def _create_dtype_conversion_methods(): """ Batch create all data type conversion methods """ methods = [] for method_name, target_dtype in _supported_dtype_conversions.items(): def make_conversion_method(dtype): def conversion_method(self: Tensor) -> Tensor: return astype(self, dtype) return conversion_method method_impl = make_conversion_method(target_dtype) method_impl.__name__ = method_name method_impl.__doc__ = f""" Cast a Tensor to {target_dtype} data type if it differs from the current dtype; otherwise, return the original Tensor. Returns: Tensor: a new Tensor with {target_dtype} dtype """ methods.append((method_name, method_impl)) return methods def type_as(self: Tensor, other: Tensor) -> Tensor: return self.astype(other.dtype) def _scalar_elementwise_op_( var: Tensor, scale: float, bias: float ) -> Tensor: return _C_ops.scale(var, float(scale), bias, True) def _neg_(var: Tensor) -> Tensor: return _scalar_elementwise_op_(var, -1.0, 0.0) def _abs_(var: Tensor) -> Tensor: return var.abs() def _complex_(var: Tensor) -> complex: numel = np.prod(var.shape, dtype="int64") assert numel == 1, ( "only one element variable can be converted to complex." ) assert var._is_initialized(), "variable's tensor is not initialized" if not var.is_complex(): var = var.astype('complex64') return complex(var.item()) def _float_(var: Tensor) -> float: numel = np.prod(var.shape, dtype="int64") assert numel == 1, ( "only one element variable can be converted to float." ) assert var._is_initialized(), "variable's tensor is not initialized" if ( var.dtype == core.VarDesc.VarType.BF16 or var.dtype == core.DataType.BFLOAT16 ): var = var.astype('float32') return float(var.item()) def _int_(var: Tensor) -> int: numel = np.prod(var.shape, dtype="int64") assert numel == 1, "only one element variable can be converted to int." assert var._is_initialized(), "variable's tensor is not initialized" if ( var.dtype == core.VarDesc.VarType.BF16 or var.dtype == core.DataType.BFLOAT16 ): var = var.astype('float32') return int(var.item()) def _len_(var: Tensor) -> int: assert var.ndim > 0, "len() of a 0-D tensor is wrong" if var.type == core.VarDesc.VarType.VOCAB: return len(var.value().get_map_tensor()) elif var.type == core.VarDesc.VarType.STRINGS: return len(var.value().get_string_tensor()) else: return var.shape[0] def _index_(var: Tensor) -> int: numel = np.prod(var.shape, dtype="int64") assert numel == 1, ( "only one element variable can be converted to python index." ) assert var._is_initialized(), "variable's tensor is not initialized" if ( var.dtype == core.VarDesc.VarType.BF16 or var.dtype == core.DataType.BFLOAT16 ): var = var.astype('float32') return int(var.item()) @property def _ndim(var: Tensor) -> int: return len(var.shape) def ndimension(var: Tensor) -> int: return len(var.shape) def dim(var: Tensor) -> int: return len(var.shape) @property def _size_(var: Tensor) -> int: return TensorSize(var.shape) def nelement(var: Tensor) -> int: """ Returns the number of elements for current Tensor. Alias for attribute ``size``. Returns: int: the number of elements for current Tensor """ return int(np.prod(var.shape)) @property def _T_(var: Tensor) -> Tensor: if len(var.shape) == 1: return var perm = list(reversed(range(len(var.shape)))) out = _C_ops.transpose(var, perm) return out @property def _mT_(var: Tensor) -> Tensor: """ Return the last two dimensions of a Tensor transposed. Args: var (Tensor): The input Tensor, which must have at least 2 dimensions. Returns: Tensor: A new Tensor with its last two dimensions swapped. Examples: .. code-block:: pycon >>> import paddle >>> x = paddle.randn([2, 3, 4]) >>> x_transposed = x.mT >>> x_transposed.shape paddle.Size([2, 4, 3]) """ if len(var.shape) < 2: raise ValueError( f"Tensor.ndim({var.ndim}) is required to be greater than or equal to 2." ) perm = list(range(len(var.shape))) perm[-1], perm[-2] = perm[-2], perm[-1] out = _C_ops.transpose(var, perm) return out @property def _mH_(var: Tensor) -> Tensor: """ Return the conjugate transpose of the last two dimensions of a Tensor. Accessing this property is equivalent to calling x.mT.conj(). Args: var (Tensor): The input Tensor, which must be at least 2-D or 0-D. Returns: Tensor: A new Tensor with its last two dimensions swapped and the elements conjugated. If the input is 0-D, returns the Tensor itself. Examples: .. code-block:: pycon >>> import paddle >>> x = paddle.to_tensor([[1.0 + 1.0j, 2.0 + 2.0j], [3.0 + 3.0j, 4.0 + 4.0j]]) >>> x_mH = x.mH >>> print(x_mH) Tensor(shape=[2, 2], dtype=complex64, place=Place(cpu), stop_gradient=True, [[(1-1j), (3-3j)], [(2-2j), (4-4j)]]) >>> x_0d = paddle.to_tensor(1.0 + 1.0j) >>> x_0d_mH = x_0d.mH >>> print(x_0d_mH) Tensor(shape=[], dtype=complex64, place=Place(cpu), stop_gradient=True, (1+1j)) """ if len(var.shape) == 0: return _C_ops.conj(var) if len(var.shape) < 2: raise ValueError( f"Tensor.ndim({var.ndim}) is required to be greater than or equal to 2 " f"or 0-D." ) perm = list(range(len(var.shape))) perm[-1], perm[-2] = perm[-2], perm[-1] out = _C_ops.transpose(var, perm) out = _C_ops.conj(out) return out @property def _H_(var: Tensor) -> Tensor: """ Return the conjugate transpose of a Tensor. The conjugate transpose of a 2-D Tensor is equivalent to transposing the Tensor and then taking the conjugate of each element (i.e., x.T.conj()). For 0-D Tensor, returns the conjugated Tensor. Args: var (Tensor): The input Tensor, which must be 0-D or 2-D. Returns: Tensor: A new Tensor with its dimensions transposed and elements conjugated. If the input is 0-D, returns the conjugated Tensor. Examples: .. code-block:: pycon >>> import paddle >>> x = paddle.to_tensor([[1.0 + 1.0j, 2.0 + 2.0j], [3.0 + 3.0j, 4.0 + 4.0j]]) >>> x_H = x.H >>> print(x_H) Tensor(shape=[2, 2], dtype=complex64, place=Place(cpu), stop_gradient=True, [[(1-1j), (3-3j)], [(2-2j), (4-4j)]]) >>> x_0d = paddle.to_tensor(1.0 + 1.0j) >>> x_0d_H = x_0d.H >>> print(x_0d_H) Tensor(shape=[], dtype=complex64, place=Place(cpu), stop_gradient=True, (1+1j)) """ if len(var.shape) == 0: return _C_ops.conj(var) if len(var.shape) != 2: raise ValueError( f"Only 0-D or 2-D tensors support .H (conjugate transpose), " f"but got tensor with {len(var.shape)} dimension(s)." ) out = _C_ops.transpose(var, [1, 0]) out = _C_ops.conj(out) return out def _new_full_( var: Tensor, size: ShapeLike, fill_value: bool | float | paddle.Tensor, *, dtype: DTypeLike | None = None, device: PlaceLike | None = None, requires_grad: bool = False, pin_memory: bool = False, ) -> Tensor: """ Create a new Tensor of specified shape and fill it with a given value. Args: var (Tensor): A reference Tensor for default dtype and device. size (ShapeLike): Shape of the new Tensor. fill_value (bool | float | Tensor): Value to fill the Tensor with. dtype (DTypeLike, optional): Desired data type of the new Tensor. Defaults to `var.dtype`. device (PlaceLike, optional): Device on which to place the new Tensor. Defaults to `var.place`. requires_grad (bool, optional): Whether to track gradients. Default: False. pin_memory (bool, optional): Whether to pin memory. Default: False. Returns: Tensor: A new Tensor filled with `fill_value`. Examples: .. code-block:: pycon >>> import paddle >>> x = paddle.ones([2, 2]) >>> y = x.new_full([3, 3], 5.0) >>> y.numpy() array([[5., 5., 5.], [5., 5., 5.], [5., 5., 5.]], dtype=float32) """ if dtype is None: dtype = var.dtype if device is None: device = var.place return paddle.full( size, fill_value, dtype=dtype, device=device, requires_grad=requires_grad, pin_memory=pin_memory, ) def _new_tensor_( var: Tensor, data: TensorLike | NestedNumericSequence, dtype: DTypeLike | None = None, device: PlaceLike | None = None, requires_grad: bool = False, ) -> Tensor: """ Creates a new tensor from ``data`` with the same device and dtype as this tensor. Args: var (Tensor): A reference Tensor for default dtype and device. data: Data for the new tensor. Can be a list, numpy array, or Tensor. dtype (DTypeLike|None, optional): Desired data type. If None, uses the dtype of this tensor. Default: None. device (PlaceLike|None, optional): Desired device. If None, uses the place of this tensor. Default: None. requires_grad (bool, optional): If True, gradient computation will be enabled for the new tensor. Default: False. Returns: Tensor: A new tensor on the specified device. """ if dtype is None: dtype = var.dtype if device is None: device = var.place return paddle.to_tensor( data, dtype=dtype, place=device, stop_gradient=not requires_grad ) @size_args_decorator_patch def _new_empty_( var: Tensor, size: ShapeLike, *, dtype: DTypeLike | None = None, device: PlaceLike | None = None, requires_grad: bool = False, pin_memory: bool = False, ) -> Tensor: """ Create a new uninitialized Tensor of the specified shape. Args: var (Tensor): A reference Tensor for default dtype and device. size (ShapeLike): Shape of the new Tensor. dtype (DTypeLike, optional): Desired data type of the new Tensor. Defaults to `var.dtype`. device (PlaceLike, optional): Device on which to place the new Tensor. Defaults to `var.place`. requires_grad (bool, optional): Whether to track gradients. Default: False. pin_memory (bool, optional): Whether to pin memory. Default: False. Returns: Tensor: A new uninitialized Tensor with the specified shape. Examples: .. code-block:: pycon >>> import paddle >>> x = paddle.ones([2, 2]) >>> y = x.new_empty(3, 3) # type: ignore >>> y.shape paddle.Size([3, 3]) """ if dtype is None: dtype = var.dtype if device is None: device = var.place return paddle.empty( size, dtype, device=device, requires_grad=requires_grad, pin_memory=pin_memory, ) @size_args_decorator_patch def _new_ones_( var: Tensor, size: ShapeLike, *, dtype: DTypeLike | None = None, device: PlaceLike | None = None, requires_grad: bool = False, pin_memory: bool = False, ) -> Tensor: """ Create a new Tensor of the specified shape filled with ones. Args: var (Tensor): A reference Tensor for default dtype and device. size (ShapeLike): Shape of the new Tensor. dtype (DTypeLike, optional): Desired data type of the new Tensor. Defaults to `var.dtype`. device (PlaceLike, optional): Device on which to place the new Tensor. Defaults to `var.place`. requires_grad (bool, optional): Whether to track gradients. Default: False. pin_memory (bool, optional): Whether to pin memory. Default: False. Returns: Tensor: A new Tensor filled with ones. Examples: .. code-block:: pycon >>> import paddle >>> x = paddle.zeros([2, 2]) >>> y = x.new_ones(3, 3) # type: ignore >>> y.numpy() array([[1., 1., 1.], [1., 1., 1.], [1., 1., 1.]], dtype=float32) """ if dtype is None: dtype = var.dtype if device is None: device = var.place return paddle.full( size, 1, dtype, device=device, requires_grad=requires_grad, pin_memory=pin_memory, ) @size_args_decorator_patch def _new_zeros_( var: Tensor, size: ShapeLike, *, dtype: DTypeLike | None = None, device: PlaceLike | None = None, requires_grad: bool = False, pin_memory: bool = False, ) -> Tensor: """ Create a new Tensor of the specified shape filled with zeros. Args: var (Tensor): A reference Tensor for default dtype and device. size (ShapeLike): Shape of the new Tensor. dtype (DTypeLike, optional): Desired data type of the new Tensor. Defaults to `var.dtype`. device (PlaceLike, optional): Device on which to place the new Tensor. Defaults to `var.place`. requires_grad (bool, optional): Whether to track gradients. Default: False. pin_memory (bool, optional): Whether to pin memory. Default: False. Returns: Tensor: A new Tensor filled with zeros. Examples: .. code-block:: pycon >>> import paddle >>> x = paddle.ones([2, 2]) >>> y = x.new_zeros(3, 3) # type: ignore[misc, arg-type] >>> y.numpy() array([[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]], dtype=float32) """ if dtype is None: dtype = var.dtype if device is None: device = var.place return paddle.full( size, 0, dtype, device=device, requires_grad=requires_grad, pin_memory=pin_memory, ) @property def requires_grad(self: Tensor) -> bool: """ Whether this Tensor requires gradient computation. This is a convenience property that returns the opposite of stop_gradient. Setting requires_grad=True is equivalent to setting stop_gradient=False. Examples: .. code-block:: pycon >>> import paddle >>> x = paddle.randn([2, 3]) >>> print(x.requires_grad) # False by default >>> >>> x.requires_grad = False >>> print(x.stop_gradient) # True """ return not self.stop_gradient @requires_grad.setter def requires_grad(self: Tensor, value: bool) -> None: """ Set whether this Tensor requires gradient computation. Args: value (bool): True to enable gradient computation, False to disable. """ if not isinstance(value, bool): raise TypeError( f"requires_grad must be bool, but got {type(value)}" ) self.stop_gradient = not value def requires_grad_(self, requires_grad: bool = True) -> Tensor: """ Set whether this Tensor requires gradient computation. Args: requires_grad (bool): True to enable gradient computation, False to disable. """ if not isinstance(requires_grad, bool): raise TypeError( f"requires_grad must be bool, but got {type(requires_grad)}" ) self.stop_gradient = not requires_grad return self @property def itemsize(self: Tensor) -> int: """ Returns the number of bytes allocated on the machine for a single element of the Tensor. Examples: .. code-block:: pycon >>> import paddle >>> x = paddle.randn((2, 3), dtype=paddle.float64) >>> x.itemsize 8 """ return self.element_size() @property def nbytes(self: Tensor) -> int: """ Returns the number of bytes allocated for elements of the dense Tensor. Defined to be ``size`` * ``element_size()`` """ if self.is_sparse(): raise RuntimeError( "nbytes is not defined for sparse tensors. " "Add nbytes of indices and values for sparse storage size, " "or multiply numel by element_size for the equivalent dense tensor." ) return self.size * self.element_size() def _reduce_ex_(self: Tensor, proto): data_numpy = self.numpy() place = str(self.place)[6:-1] # Place(gpu:1) -> gpu:1 dtype = str(self.dtype)[7:] # paddle.int32 -> int32 requires_grad = self.requires_grad return _rebuild_tensor, ( data_numpy, dtype, place, requires_grad, ) eager_methods = [ ('__neg__', _neg_), ('__abs__', _abs_), ('__complex__', _complex_), ('__float__', _float_), ('__int__', _int_), ('__len__', _len_), ('__index__', _index_), ('astype', astype), ('byte', byte), ('uint8', byte), ('type_as', type_as), ('dim', dim), ('ndimension', ndimension), ('ndim', _ndim), ('size', _size_), ('nelement', nelement), ('T', _T_), ('mT', _mT_), ('mH', _mH_), ('H', _H_), ('new_full', _new_full_), ('new_tensor', _new_tensor_), ('new_empty', _new_empty_), ('new_ones', _new_ones_), ('new_zeros', _new_zeros_), ("requires_grad", requires_grad), ("requires_grad_", requires_grad_), # for logical compare ('__array_ufunc__', None), ('itemsize', itemsize), ('nbytes', nbytes), ('__reduce_ex__', _reduce_ex_), ] dtype_conversion_methods = _create_dtype_conversion_methods() eager_methods.extend(dtype_conversion_methods) eager_cpp_level_patch = [ "__add__", "__radd__", '__sub__', '__rsub__', '__mul__', '__rmul__', '__div__', '__truediv__', '__rdiv__', '__rtruediv__', '__mod__', '__rmod__', '__matmul__', '__rmatmul__', '__gt__', '__ge__', '__lt__', '__le__', '__floordiv__', '__rfloordiv__', '__pow__', '__rpow__', '__eq__', '__ne__', ] global _already_patch_eager_tensor local_already_patch = _already_patch_eager_tensor _already_patch_eager_tensor = True local_tensor = core.eager.Tensor if not local_already_patch: for method_name in eager_cpp_level_patch: method_impl = getattr(local_tensor, method_name, None) if method_impl: setattr(local_tensor, method_name, method_impl) for method in eager_methods: method_name = method[0] method_impl = method[1] setattr(local_tensor, method_name, method_impl) else: import paddle.tensor # Tensor method from module paddle.tensor for method_name in paddle.tensor.tensor_method_func: if hasattr(local_tensor, method_name): continue method_impl = getattr(paddle.tensor, method_name, None) if method_impl: setattr(local_tensor, method_name, method_impl) for magic_method, origin_method in paddle.tensor.magic_method_func: impl = getattr(paddle.tensor, origin_method, None) if impl: setattr(local_tensor, magic_method, impl)