Files
paddlepaddle--paddle/python/paddle/base/dygraph/math_op_patch.py
T
2026-07-13 12:40:42 +08:00

853 lines
27 KiB
Python

# 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)