Files
2026-07-13 12:40:42 +08:00

1882 lines
61 KiB
Python

# Copyright (c) 2023 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 copy
import inspect
import textwrap
import warnings
from functools import reduce
from typing import TYPE_CHECKING
import numpy as np
from paddle import _C_ops
from paddle.base.libpaddle import DataType
from paddle.base.wrapped_decorator import wrap_decorator
from paddle.utils.decorator_utils import (
size_args_decorator_patch,
)
from . import Value
if TYPE_CHECKING:
from paddle import Tensor
from paddle._typing import (
DTypeLike,
NestedNumericSequence,
PlaceLike,
ShapeLike,
TensorLike,
)
_already_patch_value = False
_supported_int_dtype_ = [
DataType.BOOL,
DataType.UINT8,
DataType.INT8,
DataType.INT16,
DataType.INT32,
DataType.INT64,
]
_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',
}
SUPPORT_PROMOTION_OPS = [
"__add__",
"__radd__",
"__sub__",
"__rsub__",
"__mul__",
"__rmul__",
"__mod__",
"__rmod__",
"__div__",
"__rdiv__",
"__truediv__",
"__rtruediv__",
"__floordiv__",
"__rfloordiv__",
"__pow__",
"__rpow__",
"__eq__",
"__ne__",
"__lt__",
"__le__",
"__gt__",
"__ge__",
]
def _fake_interface_only_(func):
def __impl__(*args, **kwargs):
raise AssertionError(
f"'{func.__name__}' only can be called by `paddle.Tensor` in dynamic graph mode. Suggestions:\n"
" 1. If you are in static graph mode, you can switch to dynamic graph mode by turning off `paddle.enable_static()` or calling `paddle.disable_static()`.\n"
" 2. If you are using `@paddle.jit.to_static`, you can call `paddle.jit.enable_to_static(False)`. "
f"If you have to translate dynamic graph to static graph, please use other API to replace '{func.__name__}'."
)
return __impl__
fake_interface_only = wrap_decorator(_fake_interface_only_)
def create_tensor_with_batchsize(ref_var, value, dtype):
assert isinstance(ref_var, Value)
value = float(value)
batch_dim = -1
out_shape = []
for i, d in enumerate(ref_var.shape):
if d < 0:
if batch_dim < 0:
batch_dim = i
out_shape.append(d)
else:
out_shape.append(1)
else:
out_shape.append(d)
assert batch_dim != -1
from paddle.framework import core
out = _C_ops.full_batch_size_like(
ref_var, out_shape, dtype, value, batch_dim, batch_dim, core.Place()
)
out.stop_gradient = True
return out
def monkey_patch_value():
def safe_get_dtype(var):
try:
dtype = var.dtype
except:
raise ValueError("Cannot get data type from var")
return dtype
def cpu(self):
"""
In dy2static, Tensor also needs cpu() and cuda() interface.
But, the underneath operator has only forward op but not backward one.
Returns:
The tensor which has copied to cpu place.
Examples:
In Static Graph Mode:
.. code-block:: pycon
>>> import paddle
>>> paddle.enable_static()
>>> x = paddle.static.data(name="x", shape=[2, 2], dtype='float32')
>>> y = x.cpu()
"""
# 0 means cpu place, see paddle/phi/kernels/memcpy_kernel.cc
return _C_ops.memcpy(self, 0)
def cuda(self, device_id=None, blocking=True):
"""
In dy2static, Tensor also needs cpu() and cuda() interface.
But, the underneath operator has only forward op but not backward one.
Args:
self(Tensor): The variable itself.
device_id(int, optional): The destination GPU device id. Default: None, means current device.
We add this argument for dy2static translation, please do not use it.
blocking(bool, optional): Whether blocking or not, Default: True.
We add this argument for dy2static translation, please do not use it.
Returns:
The tensor which has copied to cuda place.
Examples:
In Static Graph Mode:
.. code-block:: pycon
>>> import paddle
>>> paddle.enable_static()
>>> x = paddle.static.data(name="x", shape=[2, 2], dtype='float32')
>>> y = x.cpu()
>>> z = y.cuda()
"""
if device_id is not None:
warnings.warn("device_id is not supported, and it will be ignored.")
if blocking is not True:
warnings.warn("blocking is not supported, and it will be ignored.")
# 1 means cuda/xpu/custom_device place, see paddle/phi/kernels/memcpy_kernel.cc
return _C_ops.memcpy(self, 1)
@property
def is_cuda(self):
"""
Tensor don't have 'is_cuda' interface in static graph mode
But this interface can greatly facilitate dy2static.
So we give a warning here and return None.
"""
warnings.warn(
"Tensor do not have 'is_cuda' interface for pir graph mode, try not to use it."
)
from paddle import framework
if hasattr(self, 'place') and isinstance(
self.place, framework.core.CUDAPlace
):
return True
else:
expected_place = framework._current_expected_place_()
return isinstance(expected_place, framework.core.CUDAPlace)
@property
def is_cpu(self):
"""
Tensor don't have 'is_cpu' interface in static graph mode
But this interface can greatly facilitate dy2static.
So we give a warning here and return None.
"""
warnings.warn(
"Tensor do not have 'is_cpu' interface for pir graph mode, try not to use it."
)
from paddle import framework
if hasattr(self, 'place') and isinstance(
self.place, framework.core.CPUPlace
):
return True
else:
expected_place = framework._current_expected_place_()
return isinstance(expected_place, framework.core.CPUPlace)
@property
def place(self):
"""
Tensor don't have 'place' interface in static graph mode
But this interface can greatly facilitate dy2static.
So we give a warning here and return None.
"""
warnings.warn(
"Tensor do not have 'place' interface for pir graph mode, try not to use it. None will be returned."
)
@property
def device(self):
"""
Tensor don't have 'device' interface in static graph mode
But this interface can greatly facilitate dy2static.
So we give a warning here and return None.
"""
warnings.warn(
"Tensor do not have 'device' interface for pir graph mode, try not to use it. None will be returned."
)
def contiguous(self):
"""
Tensor don't have 'contiguous' interface in static graph mode
But this interface can greatly facilitate dy2static.
So we give a warning here and return None.
"""
warnings.warn(
"Tensor do not have 'contiguous' interface for static graph mode, try not to use it. self will be returned."
)
return self
def is_contiguous(self):
"""
Tensor don't have 'is_contiguous' interface in static graph mode
But this interface can greatly facilitate dy2static.
So we give a warning here and return None.
"""
warnings.warn(
"Tensor do not have 'is_contiguous' interface for static graph mode, try not to use it. True will be returned."
)
return True
@property
def _ndim(self):
"""
Returns the dimension of current Tensor
Returns:
the dimension
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.enable_static()
>>> # create a static Tensor
>>> x = paddle.static.data(name='x', shape=[3, 2, 1])
>>> # print the dimension of the Tensor
>>> print(x.ndim)
3
"""
return len(self.shape)
def ndimension(self):
"""
Returns the dimension of current Tensor
Returns:
the dimension
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.enable_static()
>>> # create a static Tensor
>>> x = paddle.static.data(name='x', shape=[3, 2, 1])
>>> # print the dimension of the Tensor
>>> print(x.ndimension())
3
"""
return len(self.shape)
def dim(self):
"""
Returns the dimension of current Tensor
Returns:
the dimension
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.enable_static()
>>> # create a static Tensor
>>> x = paddle.static.data(name='x', shape=[3, 2, 1])
>>> # print the dimension of the Tensor
>>> print(x.dim())
3
"""
return len(self.shape)
def _item(self, *args: int):
"""
In order to be compatible with the item interface introduced by the dynamic graph, it does nothing but returns self.
It will check that the shape must be a 1-D tensor
"""
if self.is_dist() and not self._is_initialized():
return None
from paddle.jit.dy2static import Shape
# Python implementation of the input validation logic for the C++ function `tensor__getitem_from_offset`.
dims = Shape(self)
numel = reduce(lambda x, y: int(x * y), dims) if len(dims) != 0 else 1
offset = 0
if len(args) == 0:
if not isinstance(numel, paddle.pir.Value) and numel != 1:
raise ValueError(
"only one element tensors can be converted to Python "
"scalars when no input coordinates"
)
# NOTE: This is to maintain consistency with the original code.
return self
elif len(args) == 1:
(offset,) = args
if not isinstance(numel, paddle.pir.Value) and offset >= numel:
raise ValueError(
f"index {offset} is out of bounds for size {numel}"
)
else:
if len(args) != len(dims):
raise ValueError("incorrect number of indices for Tensor")
# TODO(dev): In certain cases, the stride calculation of the tensor may be modified by as_strided.
# This scenario needs to be considered in the future.
strides = [1] * len(dims)
for i in range(1, len(strides)):
strides[-i - 1] = strides[-i] * dims[-i]
for i in range(len(args)):
index = args[i]
if not isinstance(index, int):
raise TypeError(
f"argument (position {i}) must be long, but got {type(index)}",
)
if (
not isinstance(dims[i], paddle.pir.Value)
and index >= dims[i]
):
raise ValueError(
f"index {index} is out of bounds for axis {i} with size {dims[i]}"
)
offset += index * strides[i]
return self.flatten()[offset]
def astype(self, dtype):
"""
**Notes**:
Convert a value to a specified data type if it differs from the current dtype;
otherwise, return the original value.
Args:
self(Tensor): The source Tensor
dtype: The target data type
Returns:
Tensor: Tensor with new dtype
Examples:
In Static Graph Mode:
.. code-block:: pycon
>>> import paddle
>>> paddle.enable_static()
>>> startup_prog = paddle.static.Program()
>>> main_prog = paddle.static.Program()
>>> with paddle.static.program_guard(startup_prog, main_prog):
... original_value = paddle.static.data(name="new_value", shape=[2, 2], dtype='float32')
... new_value = original_value.astype('int64')
... print(f"new value's dtype is: {new_value.dtype}")
new Tensor's dtype is: paddle.int64
"""
if not isinstance(dtype, DataType):
dtype = paddle.pir.core.convert_nptype_to_datatype(dtype)
if self.dtype == dtype:
return self
return _C_ops.cast(self, dtype)
def byte(self):
# 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')
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):
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, other):
return self.astype(other.dtype)
def _scalar_add_(var, value):
return paddle.scale(var, 1.0, value)
def _scalar_sub_(var, value):
return paddle.scale(var, 1.0, -value)
def _scalar_rsub_(var, value):
return paddle.scale(var, -1.0, value)
def _scalar_mul_(var, value):
return paddle.scale(var, value, 0.0)
def _scalar_div_(var, value):
return paddle.scale(var, 1.0 / value, 0.0)
def _scalar_neg_(var):
return paddle.scale(var, -1.0, 0.0)
def _scalar_abs_(var):
return paddle.abs(var)
def _binary_creator_(
method_name,
python_api,
reverse=False,
scalar_method=None,
):
def __impl__(self, other_var):
# 1. scalar exists cases
# we need combine the tensor.dtype and scalar.dtype, cast correct object
if isinstance(other_var, float):
# in all cases(+, -, *, /, **, //, %), we need cast tensor.dtype to float
if self.dtype in _supported_int_dtype_:
self = astype(self, DataType.FLOAT32)
# here use `scale` replace `elementwise` to get better performance
# but only +, -, *, / can use this method
if scalar_method is not None:
return scalar_method(self, other_var)
elif isinstance(other_var, int):
# in all cases(+, -, *, /, **, //, %), we can cast it to float
# because the output tensor.dtype depend on the type of input tensor
other_var = float(other_var)
# division is a special case
# NOTE(chenweihang): because we cast tensor to float32 instead float64,
# the division result can only guarantee the numerical accuracy of 6 digits
# after the decimal point. The result of numpy calculation is of float64 type,
# so the calculation result here and the calculation result of numpy are
# different after 6 decimal point. If necessary, we can also use float64 here.
# torch's behavior here is consistent with ours
if (
python_api == paddle.divide
and self.dtype in _supported_int_dtype_
):
self = paddle.cast(self, DataType.FLOAT32)
# bool(tensor) + int(scalar) will do type promotion to int64
if self.dtype == paddle.bool:
self = paddle.cast(self, DataType.INT64)
# here use `scale` replace `elementwise` to get better performance
# but only +, -, *, / can use this method
if scalar_method is not None:
return scalar_method(self, other_var)
elif other_var is None:
if method_name == "__eq__":
return False
elif method_name == "__ne__":
return True
else:
pass
else:
# do nothing
pass
# 2. create Value for scalar
lhs_dtype = safe_get_dtype(self)
if not isinstance(other_var, Value):
if reverse:
for elem in self.shape:
if elem < 0:
other_var = create_tensor_with_batchsize(
self, other_var, lhs_dtype
)
break
else:
# when break is not triggered, enter the else branch
other_var = paddle.tensor.creation.fill_constant(
self.shape,
lhs_dtype,
other_var,
)
else:
# add fill_op to current_block
other_var = paddle.tensor.creation.fill_constant(
[],
lhs_dtype,
other_var,
)
if reverse:
tmp = self
self = other_var
other_var = tmp
out = python_api(self, other_var)
return out
__impl__.__doc__ = """
Args:
self(Tensor): left hand Tensor
other_var(Tensor|float|int): right hand Tensor
Returns:
Tensor
"""
__impl__.__name__ = method_name
return __impl__
@property
def _size_(self):
"""
Returns the number of elements for current Tensor, which is a int64 Tensor with shape [] .
Returns:
Tensor, the number of elements for current Tensor
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.enable_static()
>>> startup_prog = paddle.static.Program()
>>> main_prog = paddle.static.Program()
>>> with paddle.static.program_guard(startup_prog, main_prog):
... x = paddle.assign(np.random.rand(2, 3, 4).astype("float32"))
... (output_x,) = exe.run(main_program, fetch_list=[x.size])
... print(f"value's size is: {output_x}")
value's size is: 24
"""
return paddle.numel(self)
def nelement(self):
"""
Returns the number of elements for current Tensor, which is a int64 Tensor with shape [] . Alias for attribute `size`.
Returns:
Tensor, the number of elements for current Tensor
"""
return paddle.numel(self)
@property
def _T_(self):
"""
Permute current Tensor with its dimensions reversed.
If `n` is the dimensions of `x` , `x.T` is equivalent to `x.transpose([n-1, n-2, ..., 0])`.
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.enable_static()
>>> x = paddle.ones(shape=[2, 3, 5])
>>> x_T = x.T
>>> exe = paddle.static.Executor()
>>> x_T_np = exe.run(paddle.static.default_main_program(), fetch_list=[x_T])[0]
>>> print(x_T_np.shape)
(5, 3, 2)
"""
if len(self.shape) == 1:
return self
perm = list(reversed(range(len(self.shape))))
return _C_ops.transpose(self, perm)
@property
def _mT_(self):
"""
Permute current Value with its last two dimensions reversed.
If `n` is the dimensions of `x` , `x.mT` is equivalent to `x.transpose([0, 1, ..., n-1, n-2])`.
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.enable_static()
>>> x = paddle.ones(shape=[2, 3, 5])
>>> x_mT = x.mT
>>> exe = paddle.static.Executor()
>>> x_mT_np = exe.run(paddle.static.default_main_program(), fetch_list=[x_mT])[0]
>>> print(x_mT_np.shape)
(2, 5, 3)
"""
if len(self.shape) < 2:
raise ValueError(
f"Tensor.ndim({len(self.shape)}) is required to be greater than or equal to 2."
)
perm = list(range(len(self.shape)))
perm[-1], perm[-2] = perm[-2], perm[-1]
return _C_ops.transpose(self, perm)
@property
def _mH_(self):
"""
Return the conjugate transpose of the last two dimensions of a Tensor.
Accessing this property is equivalent to calling x.mT.conj().
Args:
self: 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
>>> paddle.enable_static()
>>> x = paddle.ones(shape=[2, 3, 5])
>>> x_mH = x.mH
>>> exe = paddle.static.Executor()
>>> x_mH_np = exe.run(paddle.static.default_main_program(), fetch_list=[x_mH])[0]
>>> print(x_mH_np.shape)
(2, 5, 3)
"""
if len(self.shape) == 0:
return _C_ops.conj(self)
if len(self.shape) < 2:
raise ValueError(
f"Tensor.ndim({len(self.shape)}) is required to be greater than or equal to 2 "
f"or 0-D."
)
perm = list(range(len(self.shape)))
perm[-1], perm[-2] = perm[-2], perm[-1]
return _C_ops.conj(_C_ops.transpose(self, perm))
@property
def _H_(self):
"""
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.
For 0-D Tensor, returns the conjugated Tensor.
Args:
self: 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
>>> paddle.enable_static()
>>> x = paddle.to_tensor([[1.0 + 1.0j, 2.0 + 2.0j], [3.0 + 3.0j, 4.0 + 4.0j]])
>>> x_H = x.H
>>> exe = paddle.static.Executor()
>>> x_H_np = exe.run(paddle.static.default_main_program(), fetch_list=[x_H])[0]
>>> print(x_H_np)
[[(1-1j), (3-3j)],
[(2-2j), (4-4j)]]
"""
if len(self.shape) == 0:
return _C_ops.conj(self)
if len(self.shape) != 2:
raise ValueError(
f"Only 0-D or 2-D tensors support .H (conjugate transpose), "
f"but got tensor with {len(self.shape)} dimension(s)."
)
return _C_ops.conj(_C_ops.transpose(self, [1, 0]))
def _new_full_(
self,
size: ShapeLike,
fill_value: bool | float | paddle.Tensor,
*,
dtype: DTypeLike | None = None,
device: PlaceLike | None = None,
requires_grad: bool = False,
pin_memory: bool = False,
):
"""
Returns a Tensor of size ``size`` filled with ``fill_value``.
By default, the returned Tensor has the same dtype and place as this tensor.
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.enable_static()
>>> x = paddle.ones(shape=[2, 3, 5])
>>> x_new = x.new_full([2, 3], 3.14, dtype="float64", device="cpu")
>>> exe = paddle.static.Executor()
>>> x_new_np = exe.run(paddle.static.default_main_program(), fetch_list=[x_new])[0]
>>> print(x_new_np.shape)
(2, 5, 3)
>>> print(str(x_new_np.dtype))
'paddle.float64'
>>> print(x_new_np.place)
Place(cpu)
"""
if dtype is None:
dtype = self.dtype
if device is None:
device = self.place
return paddle.full(
size,
fill_value,
dtype=dtype,
device=device,
requires_grad=requires_grad,
pin_memory=pin_memory,
)
def _new_tensor_(
self,
data: TensorLike | NestedNumericSequence,
dtype: DTypeLike | None = None,
device: PlaceLike | None = None,
requires_grad: bool = False,
):
"""
Creates a new tensor from ``data`` with the same device and dtype as this tensor.
Args:
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.
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.enable_static()
>>> x = paddle.ones(shape=[2, 3])
>>> y = x.new_tensor([1, 2, 3], dtype="float64", device="cpu")
>>> exe = paddle.static.Executor()
>>> y_np = exe.run(paddle.static.default_main_program(), fetch_list=[y])[0]
>>> print(y_np)
[1. 2. 3.]
"""
if dtype is None:
dtype = self.dtype
if device is None:
device = self.place
return paddle.to_tensor(
data, dtype=dtype, place=device, stop_gradient=not requires_grad
)
@size_args_decorator_patch
def _new_empty_(
self,
size: ShapeLike,
*,
dtype: DTypeLike | None = None,
device: PlaceLike | None = None,
requires_grad: bool = False,
pin_memory: bool = False,
):
"""
Returns a Tensor of size ``size`` filled with uninitialized data.
By default, the returned Tensor has the same dtype and place as this tensor.
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.enable_static()
>>> x = paddle.ones(shape=[2, 3, 5])
>>> x_new = x.new_empty([2, 3], dtype="float64", device="cpu")
>>> exe = paddle.static.Executor()
>>> x_new_np = exe.run(paddle.static.default_main_program(), fetch_list=[x_new])[0]
>>> print(x_new_np.shape)
(2, 3)
>>> print(str(x_new_np.dtype))
'paddle.float64'
>>> print(x_new_np.place)
Place(cpu)
"""
if dtype is None:
dtype = self.dtype
if device is None:
device = self.place
return paddle.empty(
size,
dtype=dtype,
device=device,
requires_grad=requires_grad,
pin_memory=pin_memory,
)
@size_args_decorator_patch
def _new_ones_(
self,
size: ShapeLike,
*,
dtype: DTypeLike | None = None,
device: PlaceLike | None = None,
requires_grad: bool = False,
pin_memory: bool = False,
):
"""
Returns a Tensor of size ``size`` filled with ``1``.
By default, the returned Tensor has the same dtype and place as this tensor.
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.enable_static()
>>> x = paddle.ones(shape=[2, 3, 5])
>>> x_new = x.new_ones([2, 3], dtype="float64", device="cpu")
>>> exe = paddle.static.Executor()
>>> x_new_np = exe.run(paddle.static.default_main_program(), fetch_list=[x_new])[0]
>>> print(x_new_np.shape)
(2, 3)
>>> print(str(x_new_np.dtype))
'paddle.float64'
>>> print(x_new_np.place)
Place(cpu)
"""
if dtype is None:
dtype = self.dtype
if device is None:
device = self.place
return paddle.full(
size,
1,
dtype=dtype,
device=device,
requires_grad=requires_grad,
pin_memory=pin_memory,
)
@size_args_decorator_patch
def _new_zeros_(
self,
size: ShapeLike,
*,
dtype: DTypeLike | None = None,
device: PlaceLike | None = None,
requires_grad: bool = False,
pin_memory: bool = False,
):
"""
Returns a Tensor of size ``size`` filled with ``0``.
By default, the returned Tensor has the same dtype and place as this tensor.
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.enable_static()
>>> x = paddle.ones(shape=[2, 3, 5])
>>> x_new = x.new_zeros([2, 3], dtype="float64", device="cpu")
>>> exe = paddle.static.Executor()
>>> x_new_np = exe.run(paddle.static.default_main_program(), fetch_list=[x_new])[0]
>>> print(x_new_np.shape)
(2, 3)
>>> print(str(x_new_np.dtype))
'paddle.float64'
>>> print(x_new_np.place)
Place(cpu)
"""
if dtype is None:
dtype = self.dtype
if device is None:
device = self.place
return paddle.full(
size,
0,
dtype=dtype,
device=device,
requires_grad=requires_grad,
pin_memory=pin_memory,
)
def _int_(self):
error_msg = """\
int(Tensor) is not supported in static graph mode. Because it's value is not available during the static mode.
It's usually triggered by the logging implicitly, for example:
>>> logging.info("The value of x is: {int(x)}")
^ `x` is Tensor, `int(x)` triggers int(Tensor)
There are two common workarounds available:
If you are logging Tensor values, then consider logging only at dynamic graphs, for example:
Modify the following code
>>> logging.info("The value of x is: {int(x)}")
to
>>> if paddle.in_dynamic_mode():
... logging.info("The value of x is: {int(x)}")
If you need to convert the Tensor type, for example:
Modify the following code
>>> x = int(x)
to
>>> x = x.astype("int64")
"""
raise TypeError(textwrap.dedent(error_msg))
def _float_(self):
error_msg = """\
float(Tensor) is not supported in static graph mode. Because it's value is not available during the static mode.
It's usually triggered by the logging implicitly, for example:
>>> logging.info("The value of x is: {float(x)}")
^ `x` is Tensor, `float(x)` triggers float(Tensor)
There are two common workarounds available:
If you are logging Tensor values, then consider logging only at dynamic graphs, for example:
Modify the following code
>>> logging.info("The value of x is: {float(x)}")
to
>>> if paddle.in_dynamic_mode():
... logging.info("The value of x is: {float(x)}")
If you need to convert the Tensor type, for example:
Modify the following code
>>> x = float(x)
to
>>> x = x.astype("float64")
"""
raise TypeError(textwrap.dedent(error_msg))
def _bool_(self):
error_msg = """\
bool(Tensor) is not supported in static graph mode. Because it's value is not available during the static mode.
If you haven't call bool(Tensor) explicitly, it's usually triggered by the control flow implicitly, for example:
>>> if x > 0:
^ `x` is Tensor, `x` > 0 is also a Tensor, `if x > 0` triggers bool(Tensor)
... y = y + 1
There are two common workarounds available:
If you are checking for Tensor values, then consider checking only at dynamic graphs, for example:
Modify the following code
>>> if x > 0:
... raise ValueError("x should be positive")
to
>>> if paddle.in_dynamic_mode() and x < 0:
>>> raise ValueError("x should be positive")
If you need to control the flow of execution based on the value of the Tensor, then you need to rewrite the code as a control flow, for example:
Modify the following code
>>> if x < y:
... y = y + 1
... else:
... y = y - 1
to
>>> pred = paddle.less_than(x=x, y=y, name=None)
>>> y = paddle.static.nn.cond(pred, lambda: y + 1, lambda: y - 1)
For more info, please refer to https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/static/nn/cond_cn.html
"""
raise TypeError(textwrap.dedent(error_msg))
def _complex_(self):
error_msg = """\
complex(Tensor) is not supported in static graph mode. Because it's value is not available during the static mode.
It's usually triggered by the logging implicitly, for example:
>>> logging.info("The value of x is: {complex(x)}")
^ `x` is Tensor, `complex(x)` triggers complex(Tensor)
There are two common workarounds available:
If you are logging Tensor values, then consider logging only at dynamic graphs, for example:
Modify the following code
>>> logging.info("The value of x is: {complex(x)}")
to
>>> if paddle.in_dynamic_mode():
... logging.info("The value of x is: {complex(x)}")
If you need to convert the Tensor type, for example:
Modify the following code
>>> x = complex(x)
to
>>> x = x.astype("complex64")
"""
raise TypeError(textwrap.dedent(error_msg))
def clone(self):
"""
Returns a new static Tensor, which is the clone of the original static
Tensor. It remains in the current graph, that is, the cloned Tensor
provides gradient propagation. Calling ``out = tensor.clone()`` is same
as ``out = assign(tensor)`` .
Returns:
Tensor, The cloned Tensor.
Examples:
.. code-block:: pycon
>>> import paddle
>>> paddle.enable_static()
>>> # create a static Tensor
>>> x = paddle.static.data(name='x', shape=[3, 2, 1])
>>> # create a cloned Tensor
>>> y = x.clone()
"""
return paddle.assign(self)
@fake_interface_only
def clear_gradient(self):
"""
**Notes**:
**1. This API is ONLY available in Dygraph mode**
**2. Use it only Tensor has gradient, normally we use this for Parameters since other temporal Tensor will be deleted by Python's GC**
Clear (set to ``0`` ) the Gradient of Current Tensor
Returns: None
Examples:
.. code-block:: pycon
>>> import paddle
>>> import numpy as np
>>> x = np.ones([2, 2], np.float32)
>>> inputs2 = []
>>> for _ in range(10):
>>> tmp = paddle.to_tensor(x)
>>> tmp.stop_gradient=False
>>> inputs2.append(tmp)
>>> ret2 = paddle.add_n(inputs2)
>>> loss2 = paddle.sum(ret2)
>>> loss2.retain_grads()
>>> loss2.backward()
>>> print(loss2.gradient())
>>> loss2.clear_gradient()
>>> print("After clear {}".format(loss2.gradient()))
1.0
After clear 0.0
"""
pass
def append(self, var):
"""
Notes:
The type of Tensor must be Tensor Array.
"""
if not self.is_dense_tensor_array_type():
raise TypeError(
f"Only Tensor with DenseTensorArray support `append` method, but received {self}"
)
from paddle.tensor.array import array_length, array_write
array_write(x=var, i=array_length(self), array=self)
def pop(self, *args):
"""
The type of Tensor must be Tensor Array.
When self is TensorArray, calling pop is similar to Python's pop on list.
This interface is used to simplify dygraph to static graph operations.
Args:
self(Tensor): The source variable, which must be DenseTensorArray
*args: optional, a int means index.
Returns:
Tensor: self[index]
"""
if not self.is_dense_tensor_array_type():
raise TypeError(
f"Only Tensor with DenseTensorArray support `pop` method, but received {self}"
)
if len(args) == 0:
idx = -1
else:
idx = args[0]
return paddle._pir_ops.array_pop(self, idx)
def to_dense(self):
return _C_ops.sparse_to_dense(self)
def values(self):
return _C_ops.sparse_values(self)
def indices(self):
return _C_ops.sparse_indices(self)
def set_shape(self, shape):
assert paddle.base.dygraph.base.in_to_static_mode(), (
"We only support call 'set_shape' in to_static mode."
)
if self.is_dense_tensor_type() or self.is_selected_row_type():
type = paddle.pir.create_shaped_type(self.type(), shape)
self.set_type(type)
else:
raise ValueError(
"Currently, we can only set shape for dense and selected_row tensor"
)
def value_hash(self):
return hash(id(self))
def _to(
self,
device=None,
dtype=None,
blocking=None,
copy_tensor=None,
):
if device is None and dtype is None and blocking is None:
return self
if device is not None:
if isinstance(device, str):
device = paddle.device._convert_to_place(device)
elif isinstance(
device,
(
paddle.core.Place,
paddle.CPUPlace,
paddle.CUDAPlace,
paddle.CUDAPinnedPlace,
# paddle.XPUPlace, # no support
# paddle.CustomPlace, # no support
),
):
pass
else:
raise ValueError(
"device value error, must be str, paddle.CPUPlace(), paddle.CUDAPlace(), paddle.CUDAPinnedPlace(), paddle.XPUPlace() or paddle.CustomPlace(), but the type of device is "
+ type(device).__name__
)
if blocking is None:
blocking = True
else:
assert isinstance(blocking, bool), (
"blocking value error, must be the True, False or None"
)
def transform(t, device, dtype, blocking, copy_tensor):
if dtype is None:
dtype = t.dtype
t_used = t
# 1. cast Tensor to dtype
if dtype != t_used.dtype:
with paddle.base.framework._dygraph_place_guard(
place=t_used.place
):
t_casted = t_used.cast(dtype=dtype)
copy_tensor = False
else:
t_casted = t_used
# 2. Copy casted Tensor(in CPU or GPU) to device
if isinstance(device, paddle.CUDAPlace):
new_t = t_casted.cuda(blocking=blocking)
copy_tensor = False
elif isinstance(device, paddle.CUDAPinnedPlace):
if blocking is not True:
warnings.warn(
"blocking is not supported, and it will be ignored."
)
new_t = _C_ops.memcpy(self, 2)
copy_tensor = False
elif isinstance(device, paddle.CPUPlace):
new_t = t_casted.cpu()
copy_tensor = False
else:
new_t = t_casted
if copy_tensor:
return copy.deepcopy(new_t)
return new_t
return transform(self, device, dtype, blocking, copy_tensor)
def __deepcopy__(self, memo):
new_tensor = self.clone()
memo[id(self)] = new_tensor
return new_tensor
def to(self, *args, **kwargs):
"""
Performs Tensor dtype and/or device conversion. A paddle.dtype and place
are inferred from the arguments of ``self.to(*args, **kwargs)``.There are
three ways to call `to`:
1. to(dtype, blocking=True)
2. to(device, dtype=None, blocking=True)
3. to(other, blocking=True)
Returns:
Tensor: self
Examples:
.. code-block:: pycon
>>> import paddle
>>> x = paddle.to_tensor([1, 2, 3])
>>> print(x)
Tensor(shape=[3], dtype=int64, place=Place(gpu:0), stop_gradient=True,
[1, 2, 3])
>>> x = x.to("cpu")
>>> print(x.place)
Place(cpu)
>>> x = x.to("float32")
>>> print(x.dtype)
paddle.float32
>>> x = x.to("gpu", "int16")
>>> print(x)
Tensor(shape=[3], dtype=int16, place=Place(gpu:0), stop_gradient=True,
[1, 2, 3])
>>> y = paddle.to_tensor([4, 5, 6])
>>> y
Tensor(shape=[3], dtype=int64, place=Place(gpu:0), stop_gradient=True,
[4, 5, 6])
>>> y = y.to(x)
>>> print(y)
Tensor(shape=[3], dtype=int16, place=Place(gpu:0), stop_gradient=True,
[4, 5, 6])
"""
if "non_blocking" in kwargs:
non_blocking = kwargs.pop("non_blocking")
else:
non_blocking = False
if "copy" in kwargs:
copy_tensor = kwargs.pop("copy")
else:
copy_tensor = False
size_args = len(args)
size_kwargs = len(kwargs)
if size_args + size_kwargs > 3 or size_args + size_kwargs == 0:
raise TypeError(
"to() received too many arguments - expected one of:\n \
* (Union[str, paddle.CPUPlace(), paddle.CUDAPlace(), paddle.CUDAPinnedPlace(), paddle.XPUPlace(), paddle.CustomPlace()] \
device, Union[str, paddle.dtype, numpy.dtype] dtype, bool blocking)\n \
* (Union[str, paddle.dtype, numpy.dtype] dtype, bool blocking)\n \
* (paddle.Tensor other, bool blocking) "
)
valid_keys = {"device", "dtype", "blocking", "other"}
invalid_keys = set(kwargs.keys()) - valid_keys
if len(invalid_keys) != 0:
raise TypeError(
"to() got an unexpected keyword argument "
+ next(iter(invalid_keys))
)
def dtype_first_sig(dtype, blocking=None): ...
def device_first_sig(device, dtype=None, blocking=None): ...
def tensor_like_first_sig(other, blocking=None): ...
class _NoArg: ...
def is_dtype(arg):
valid_dtypes = [
"bfloat16",
"float16",
"float32",
"float64",
"int8",
"int16",
"int32",
"int64",
"uint8",
"complex64",
"complex128",
"bool",
]
return isinstance(arg, (paddle.dtype, np.dtype)) or (
isinstance(arg, str) and arg.lower() in valid_dtypes
)
def is_device(arg):
# in dy2static, arg can be None
return arg is None or isinstance(arg, (paddle.core.Place, str))
def is_tensor(arg):
return isinstance(arg, paddle.pir.Value)
def create_positional_arg_extractor(position: int):
def extract_positional_arg(args, kwargs):
if len(args) > position:
return args[position]
return _NoArg()
return extract_positional_arg
def create_keyword_arg_extractor(key: str, position: int):
def extract_keyword_arg(args, kwargs):
if (
key in kwargs
and len(kwargs) > position
and list(kwargs.keys())[position] == key
):
return kwargs[key]
return _NoArg()
return extract_keyword_arg
def chain_extractors(*extractors):
def chain(args, kwargs):
for extractor in extractors:
if not isinstance(arg := extractor(args, kwargs), _NoArg):
return arg
return _NoArg()
return chain
def dispatch_to_signature(*args, **kwargs):
# dict[signature, (extractor, condition)]
signature_map = {
dtype_first_sig: (
chain_extractors(
create_positional_arg_extractor(position=0),
create_keyword_arg_extractor(key="dtype", position=0),
),
is_dtype,
),
device_first_sig: (
chain_extractors(
create_positional_arg_extractor(position=0),
create_keyword_arg_extractor(key="device", position=0),
),
is_device,
),
tensor_like_first_sig: (
chain_extractors(
create_positional_arg_extractor(position=0),
create_keyword_arg_extractor(key="other", position=0),
),
is_tensor,
),
}
for sig, (extractor, condition) in signature_map.items():
if not isinstance(
arg := extractor(args, kwargs), _NoArg
) and condition(arg):
bound_args = inspect.signature(sig).bind(*args, **kwargs)
bound_args.apply_defaults()
return bound_args.arguments
raise ValueError("No matching signature found.")
args = dispatch_to_signature(*args, **kwargs)
other = args.get("other", None)
if other is not None:
args.pop("other")
args["dtype"] = other.dtype
# in dy2static, we need show warning for this case
other.place # noqa: B018
args["blocking"] = (
False if not args.get("blocking", False) or non_blocking else True
)
args["copy_tensor"] = copy_tensor
res = self._to(**args)
return res
@fake_interface_only
def numpy(self, *, force=True):
"""
**Notes**:
**This API is ONLY available in Dygraph mode**
Returns a numpy array shows the value of current :ref:`api_guide_Variable_en`
Returns:
ndarray: The numpy value of current Variable.
Returns type:
ndarray: dtype is same as current Variable
Examples:
.. code-block:: pycon
>>> import paddle
>>> import paddle.base as base
>>> from paddle.nn import Linear
>>> import numpy as np
>>> data = np.random.uniform(-1, 1, [30, 10, 32]).astype('float32')
>>> with base.dygraph.guard():
... linear = Linear(32, 64)
... data_tensor = paddle.to_tensor(data)
... x = linear(data_tensor)
... print(x.numpy())
"""
pass
@fake_interface_only
def tolist(self):
"""
**Notes**:
**This API is ONLY available in Dygraph mode**
Returns a Python list that contains the elements of current :ref:`api_guide_Variable_en`
Returns:
list: The Python list containing the elements of current Variable.
Returns type:
list: Elements have the same dtype as current Variable
Examples:
.. code-block:: pycon
>>> import paddle
>>> import paddle.base as base
>>> import numpy as np
>>> data = np.random.uniform(-1, 1, [2, 3]).astype('float32')
>>> with base.dygraph.guard():
... x = paddle.to_tensor(data)
... print(x.tolist()) # Convert tensor to Python list
"""
pass
@fake_interface_only
def register_hook(self, hook):
"""
Value don't have 'register_hook' interface in static graph mode
But this interface can greatly facilitate dy2static.
So we give a error here.
"""
pass
@property
def requires_grad(self) -> 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, 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) -> 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_coo_tensor_type() or self.is_sparse_csr_tensor_type():
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()
import paddle
def get_device(self) -> None:
"""
Tensor don't have 'get_device' interface in static graph mode
But this interface can greatly facilitate dy2static.
So we give a warning here and return None.
"""
warnings.warn(
"Tensor do not have 'get_device' interface for pir graph mode, try not to use it. None will be returned."
)
value_methods = [
('cpu', cpu),
('cuda', cuda),
('place', place),
('device', device),
('contiguous', contiguous),
('is_cuda', is_cuda),
('is_cpu', is_cpu),
('is_contiguous', is_contiguous),
('item', _item),
('dim', dim),
('ndimension', ndimension),
('ndim', _ndim),
('astype', astype),
('byte', byte),
('uint8', byte),
('type_as', type_as),
('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_),
('clone', clone),
('clear_gradient', clear_gradient),
('append', append),
('pop', pop),
('set_shape', set_shape),
('__hash__', value_hash),
('to_dense', to_dense),
('indices', indices),
('values', values),
("_to", _to),
("to", to),
("tolist", tolist),
("numpy", numpy),
("register_hook", register_hook),
("get_device", get_device),
("__deepcopy__", __deepcopy__),
# For basic operators
(
'__add__',
_binary_creator_('__add__', paddle.tensor.add, False, _scalar_add_),
),
# a+b == b+a. Do not need to reverse explicitly
(
'__radd__',
_binary_creator_(
'__radd__', paddle.tensor.add, False, _scalar_add_
),
),
(
'__sub__',
_binary_creator_(
'__sub__', paddle.tensor.subtract, False, _scalar_sub_
),
),
(
'__rsub__',
_binary_creator_(
'__rsub__', paddle.tensor.subtract, True, _scalar_rsub_
),
),
(
'__mul__',
_binary_creator_(
'__mul__', paddle.tensor.multiply, False, _scalar_mul_
),
),
# a*b == b*a. Do not need to reverse explicitly
(
'__rmul__',
_binary_creator_(
'__rmul__', paddle.tensor.multiply, False, _scalar_mul_
),
),
(
'__div__',
_binary_creator_(
'__div__', paddle.tensor.divide, False, _scalar_div_
),
),
(
'__truediv__',
_binary_creator_(
'__truediv__', paddle.tensor.divide, False, _scalar_div_
),
),
(
'__rdiv__',
_binary_creator_('__rdiv__', paddle.tensor.divide, True, None),
),
(
'__rtruediv__',
_binary_creator_('__rtruediv__', paddle.tensor.divide, True, None),
),
(
'__pow__',
_binary_creator_('__pow__', paddle.tensor.pow, False, None),
),
(
'__rpow__',
_binary_creator_('__rpow__', paddle.tensor.pow, True, None),
),
(
'__floordiv__',
_binary_creator_(
'__floordiv__', paddle.tensor.floor_divide, False, None
),
),
(
'__rfloordiv__',
_binary_creator_(
'__rfloordiv__', paddle.tensor.floor_divide, True, None
),
),
(
'__mod__',
_binary_creator_('__mod__', paddle.tensor.remainder, False, None),
),
(
'__rmod__',
_binary_creator_('__rmod__', paddle.tensor.remainder, True, None),
),
(
'__matmul__',
_binary_creator_('__matmul__', paddle.tensor.matmul, False, None),
),
(
'__rmatmul__',
_binary_creator_('__rmatmul__', paddle.tensor.matmul, True, None),
),
('__neg__', _scalar_neg_),
('__abs__', _scalar_abs_),
# For compare operators
(
'__eq__',
_binary_creator_('__eq__', paddle.tensor.equal, False, None),
),
(
'__ne__',
_binary_creator_('__ne__', paddle.tensor.not_equal, False, None),
),
(
'__lt__',
_binary_creator_('__lt__', paddle.tensor.less_than, False, None),
),
(
'__le__',
_binary_creator_('__le__', paddle.tensor.less_equal, False, None),
),
(
'__gt__',
_binary_creator_('__gt__', paddle.tensor.greater_than, False, None),
),
(
'__ge__',
_binary_creator_(
'__ge__', paddle.tensor.greater_equal, False, None
),
),
('__float__', _float_),
('__int__', _int_),
('__bool__', _bool_),
('__complex__', _complex_),
('itemsize', itemsize),
('nbytes', nbytes),
]
dtype_conversion_methods = _create_dtype_conversion_methods()
value_methods.extend(dtype_conversion_methods)
global _already_patch_value
if not _already_patch_value:
for method in value_methods:
method_name = method[0]
method_impl = method[1]
setattr(Value, method_name, method_impl)
# Handling Tensor Methods
import paddle.tensor
for method_name in paddle.tensor.tensor_method_func:
if hasattr(Value, method_name):
continue
method_impl = getattr(paddle.tensor, method_name, None)
if method_impl:
setattr(Value, method_name, method_impl)
# Bit operation symbol
for magic_method, origin_method in paddle.tensor.magic_method_func:
impl = getattr(paddle.tensor, origin_method, None)
if impl:
setattr(Value, magic_method, impl)
# Handling __getitem__
from ..base.variable_index import _getitem_static, _setitem_static
Value.__getitem__ = _getitem_static
Value.__setitem__ = _setitem_static
_already_patch_value = True