diff --git a/paddle/fluid/pybind/arg_pre_process.cc b/paddle/fluid/pybind/arg_pre_process.cc index 5cdee6bf04e3e..86e857c535848 100644 --- a/paddle/fluid/pybind/arg_pre_process.cc +++ b/paddle/fluid/pybind/arg_pre_process.cc @@ -563,6 +563,91 @@ void PixelShufflePreProcess(std::string* data_format) { } } +// Eigh input validation for dygraph +void EighPreProcess(Tensor* x, std::string* UPLO) { + auto x_shape = x->dims(); + int64_t rank = x_shape.size(); + PADDLE_ENFORCE_GE(rank, + 2, + phi::errors::InvalidArgument( + "Input(input) only support >=2 tensor, but received " + "length of Input(input) is %ld.", + rank)); + PADDLE_ENFORCE_EQ(x_shape[rank - 1], + x_shape[rank - 2], + phi::errors::InvalidArgument( + "The input matrix must be batches of square matrices. " + "But received x's dimension: [%s]", + x_shape)); + PADDLE_ENFORCE_EQ( + *UPLO == "L" || *UPLO == "U", + true, + phi::errors::InvalidArgument( + "UPLO must be L or U. But received UPLO is: %s", UPLO->c_str())); +} + +// Eigh input validation for static graph +void EighPreProcess(Value* x, std::string* UPLO) { + auto x_shape = pir::GetShapeFromValue(*x); + int64_t rank = x_shape.size(); + PADDLE_ENFORCE_GE(rank, + 2, + phi::errors::InvalidArgument( + "Input(input) only support >=2 tensor, but received " + "length of Input(input) is %ld.", + rank)); + if (x_shape[rank - 1] > 0 && x_shape[rank - 2] > 0) { + PADDLE_ENFORCE_EQ( + x_shape[rank - 1], + x_shape[rank - 2], + phi::errors::InvalidArgument( + "The input matrix must be batches of square matrices. " + "But received x's dimension.")); + } + PADDLE_ENFORCE_EQ( + *UPLO == "L" || *UPLO == "U", + true, + phi::errors::InvalidArgument( + "UPLO must be L or U. But received UPLO is: %s", UPLO->c_str())); +} + +// Cholesky input validation for dygraph +void CholeskyPreProcess(Tensor* x, bool* upper) { + auto x_shape = x->dims(); + int64_t rank = x_shape.size(); + PADDLE_ENFORCE_GE( + rank, + 2, + phi::errors::InvalidArgument("Shape must have at least 2 dimensions. " + "But received x's dimension: %ld.", + rank)); + PADDLE_ENFORCE_EQ( + x_shape[rank - 1], + x_shape[rank - 2], + phi::errors::InvalidArgument("The last two dimensions must be equal. " + "But received x's dimension: [%s]", + x_shape)); +} + +// Cholesky input validation for static graph +void CholeskyPreProcess(Value* x, bool* upper) { + auto x_shape = pir::GetShapeFromValue(*x); + int64_t rank = x_shape.size(); + PADDLE_ENFORCE_GE( + rank, + 2, + phi::errors::InvalidArgument("Shape must have at least 2 dimensions. " + "But received x's dimension: %ld.", + rank)); + if (x_shape[rank - 1] > 0 && x_shape[rank - 2] > 0) { + PADDLE_ENFORCE_EQ( + x_shape[rank - 1], + x_shape[rank - 2], + phi::errors::InvalidArgument("The last two dimensions must be equal. " + "But received x's dimension.")); + } +} + // Renorm preprocessing: handle negative axis void NegativeAxisPreProcess(Tensor* x, int* axis) { int rank = x->dims().size(); diff --git a/paddle/fluid/pybind/arg_pre_process.h b/paddle/fluid/pybind/arg_pre_process.h index 9e2d0d0e35051..af00a71166c63 100644 --- a/paddle/fluid/pybind/arg_pre_process.h +++ b/paddle/fluid/pybind/arg_pre_process.h @@ -82,6 +82,15 @@ void NegativeAxisPreProcess(Value* x, int* axis); void PixelShufflePreProcess(std::string* data_format); +// Eigh input validation: check shape >= 2D, last two dims equal, UPLO is 'L' or +// 'U' +void EighPreProcess(Tensor* x, std::string* UPLO); +void EighPreProcess(Value* x, std::string* UPLO); + +// Cholesky input validation: check shape >= 2D, last two dims equal +void CholeskyPreProcess(Tensor* x, bool* upper); +void CholeskyPreProcess(Value* x, bool* upper); + // Inplace API broadcast validation for dygraph void InplaceShapePreProcess(Tensor* x, Tensor* y); diff --git a/paddle/phi/ops/yaml/python_api_info.yaml b/paddle/phi/ops/yaml/python_api_info.yaml index b4f77f5d12458..908280fc0dbaa 100644 --- a/paddle/phi/ops/yaml/python_api_info.yaml +++ b/paddle/phi/ops/yaml/python_api_info.yaml @@ -287,6 +287,13 @@ args_alias : use_default_mapping : True +- op : cholesky + name : [paddle.cholesky, paddle.linalg.cholesky, paddle.Tensor.cholesky] + args_alias : + use_default_mapping : True + pre_process : + func : CholeskyPreProcess(x, upper) + - op : conj name: [paddle.conj, paddle.Tensor.conj] args_alias : @@ -325,12 +332,24 @@ axis1 : [dim1] axis2 : [dim2] +- op : det + name : [paddle.det, paddle.linalg.det, paddle.Tensor.det] + args_alias : + x : [input, A] + - op : dot name : [paddle.dot, paddle.Tensor.dot] args_alias : x : [input] y : [tensor] +- op : eigh + name : [paddle.linalg.eigh, paddle.Tensor.eigh] + args_alias : + use_default_mapping : True + pre_process : + func : EighPreProcess(x, UPLO) + - op : erf name : [paddle.erf, paddle.Tensor.erf] args_alias : diff --git a/python/paddle/__init__.py b/python/paddle/__init__.py index d96156edbf2e6..6d69cfadfe321 100644 --- a/python/paddle/__init__.py +++ b/python/paddle/__init__.py @@ -444,12 +444,14 @@ def new_init(self, *args, **kwargs): histogram, histogram_bin_edges, histogramdd, + logdet, matmul, matrix_transpose, mv, norm, permute, pinv, + qr, t, t_, transpose, @@ -515,6 +517,7 @@ def new_init(self, *args, **kwargs): dstack, expand, expand_as, + expand_copy, flatten, flatten_, flip, @@ -622,7 +625,10 @@ def new_init(self, *args, **kwargs): broadcast_shapes, cartesian_prod, ceil, + clamp_max, + clamp_min, clip, + clip_, combinations, conj, copysign, @@ -1055,6 +1061,8 @@ def __dir__(self): concatenate = concat take_along_dim = take_along_axis clamp = clip +clamp_ = clip_ +true_divide_ = divide_ ger = outer div = divide div_ = divide_ @@ -1080,6 +1088,7 @@ def __dir__(self): negative_ = neg_ pinverse = pinv + __all__ = [ 'block_diag', 'gt', @@ -1215,7 +1224,11 @@ def __dir__(self): 'less_', 'kron', 'clip', + 'clip_', 'clamp', + 'clamp_', + 'clamp_max', + 'clamp_min', 'Tensor', 'FloatTensor', 'DoubleTensor', @@ -1329,6 +1342,7 @@ def __dir__(self): 'CPUPlace', 'matmul', 'pinverse', + 'qr', 'seed', 'acos', 'acos_', @@ -1377,6 +1391,7 @@ def __dir__(self): 'sub', 'sub_', 'true_divide', + 'true_divide_', 'gammaln', 'gammaln_', 'ceil', @@ -1457,6 +1472,7 @@ def __dir__(self): 'set_default_tensor_type', 'disable_signal_handler', 'expand_as', + 'expand_copy', 'stack', 'hstack', 'vstack', @@ -1488,6 +1504,7 @@ def __dir__(self): 'cosh', 'log', 'log_', + 'logdet', 'log2', 'log2_', 'log10', diff --git a/python/paddle/_paddle_docs.py b/python/paddle/_paddle_docs.py index 6cc46cceff887..0767f25983322 100644 --- a/python/paddle/_paddle_docs.py +++ b/python/paddle/_paddle_docs.py @@ -3399,6 +3399,43 @@ def diag( ... +@add_doc_and_signature +def det( + x: Tensor, + name: str | None = None, + *, + out: Tensor | None = None, +) -> Tensor: + r""" + + Calculates determinant value of a square matrix or batches of square matrices. + + Args: + x (Tensor): the input matrix of size `(n, n)` or the + batch of matrices of size `(*, n, n)` where `*` is one or more + batch dimensions. Alias: ``input``. + name (str|None, optional): Name of the output.It's used to print debug info for + developers. Details: :ref:`api_guide_Name`. Default is None. + + Returns: + Tensor, the determinant value of a square matrix or batches of square matrices. + + Examples: + .. code-block:: pycon + + >>> import paddle + >>> paddle.seed(2023) + >>> x = paddle.randn([3, 3, 3]) + >>> A = paddle.linalg.det(x) + >>> print(A) + Tensor(shape=[3], dtype=float32, place=Place(cpu), stop_gradient=True, + [-1.29280925, 0.77832544, 0.89754158]) + + + """ + ... + + @add_doc_and_signature def diagonal( x: Tensor, @@ -5618,3 +5655,107 @@ def square_( Inplace version of ``square`` API, the output Tensor will be inplaced with input ``x``. """ ... + + +@add_doc_and_signature +def cholesky( + x: Tensor, + upper: bool = False, + name: str | None = None, + *, + out: Tensor | None = None, +) -> Tensor: + r""" + Computes the Cholesky decomposition of one symmetric positive-definite + matrix or batches of symmetric positive-definite matrices. + + If ``upper`` is ``True``, the decomposition has the form :math:`A = U^{T}U`, + and the returned matrix :math:`U` is upper-triangular. Otherwise, the + decomposition has the form :math:`A = LL^{T}`, and the returned matrix + :math:`L` is lower-triangular. + + Args: + x (Tensor): The input tensor. Its shape should be ``[*, M, M]``, + where ``*`` is zero or more batch dimensions, and matrices on the + inner-most 2 dimensions all should be symmetric positive-definite. + Its data type should be float32 or float64. Alias: ``input``. + upper (bool, optional): The flag indicating whether to return upper or lower + triangular matrices. Default: False. + name (str|None, optional): Name for the operation (optional, default is None). + For more information, please refer to :ref:`api_guide_Name`. + + Keyword Args: + out (Tensor|optional): The output tensor. Default: None. + + Returns: + Tensor: A Tensor with same shape and data type as ``x``. It represents + triangular matrices generated by Cholesky decomposition. + + Examples: + .. code-block:: pycon + + >>> import paddle + >>> paddle.seed(2023) + + >>> a = paddle.rand([3, 3], dtype="float32") + >>> a_t = paddle.transpose(a, [1, 0]) + >>> x = paddle.matmul(a, a_t) + 1e-03 + + >>> out = paddle.linalg.cholesky(x, upper=False) + >>> print(out) + Tensor(shape=[3, 3], dtype=float32, place=Place(cpu), stop_gradient=True, + [[1.04337060, 0. , 0. ], + [1.06467676, 0.17859183, 0. ], + [1.30602181, 0.08326342, 0.22790733]]) + """ + ... + + +@add_doc_and_signature +def eigh( + x: Tensor, + UPLO: Literal['L', 'U'] = 'L', + name: str | None = None, + *, + out: tuple[Tensor, Tensor] | None = None, +) -> tuple[Tensor, Tensor]: + r""" + Compute the eigenvalues and eigenvectors of a + complex Hermitian (conjugate symmetric) or a real symmetric matrix. + + Args: + x (Tensor): A tensor with shape ``[*, N, N]``. The data type of the input Tensor x + should be one of float32, float64, complex64, complex128. Alias: ``input``. + UPLO (str, optional): ``'L'`` represents the lower triangular matrix, + ``'U'`` represents the upper triangular matrix. Default: ``'L'``. + name (str|None, optional): The default value is None. Normally there is no need for + user to set this property. For more information, please refer to :ref:`api_guide_Name`. + + Keyword Args: + out (tuple(Tensor, Tensor)|optional): The output tensors. If provided, the eigenvalues + and eigenvectors will be stored in these tensors. Default: None. + + Returns: + 2-element tuple containing + + - out_value(Tensor): A Tensor with shape ``[*, N]`` and data type of float32 and float64. + The eigenvalues of eigh op. + - out_vector(Tensor): A Tensor with shape ``[*, N, N]`` and data type of float32, float64, + complex64 and complex128. The eigenvectors of eigh op. + + Examples: + .. code-block:: pycon + + >>> import paddle + + >>> x = paddle.to_tensor([[1, -2j], [2j, 5]]) + >>> out_value, out_vector = paddle.linalg.eigh(x, UPLO='L') + >>> print(out_value) + Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True, + [0.17157286, 5.82842731]) + >>> print(out_vector) + Tensor(shape=[2, 2], dtype=complex64, place=Place(cpu), stop_gradient=True, + [[(-0.92387950+0.00000000j), (-0.38268340+0.00000000j)], + [ (0.00000000+0.38268340j), (0.00000000-0.92387950j) ]]) + """ + ... diff --git a/python/paddle/amp/grad_scaler.py b/python/paddle/amp/grad_scaler.py index 5fd8ff20a2041..a3a6337b2a537 100644 --- a/python/paddle/amp/grad_scaler.py +++ b/python/paddle/amp/grad_scaler.py @@ -760,7 +760,7 @@ def __init__( enabled: bool = True, ) -> None: ... - @grad_scaler_decorator() + @grad_scaler_decorator def __init__( self, enable: bool = True, diff --git a/python/paddle/autograd/__init__.py b/python/paddle/autograd/__init__.py index 81f28a998375c..31b2c532fef73 100644 --- a/python/paddle/autograd/__init__.py +++ b/python/paddle/autograd/__init__.py @@ -23,6 +23,7 @@ from . import ( # noqa: F401 backward_mode, function, + grad_mode, ir_backward, ) from .autograd import hessian, jacobian diff --git a/python/paddle/autograd/grad_mode.py b/python/paddle/autograd/grad_mode.py new file mode 100644 index 0000000000000..d4005e4c26b2d --- /dev/null +++ b/python/paddle/autograd/grad_mode.py @@ -0,0 +1,15 @@ +# Copyright (c) 2025 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 paddle.base.dygraph.base import set_grad_enabled # noqa: F401 diff --git a/python/paddle/base/dygraph/math_op_patch.py b/python/paddle/base/dygraph/math_op_patch.py index 4e7c99f1c73c4..bd41464dc20f8 100644 --- a/python/paddle/base/dygraph/math_op_patch.py +++ b/python/paddle/base/dygraph/math_op_patch.py @@ -34,7 +34,13 @@ from numpy.typing import NDArray from paddle import Tensor - from paddle._typing import DTypeLike, PlaceLike, ShapeLike + from paddle._typing import ( + DTypeLike, + NestedNumericSequence, + PlaceLike, + ShapeLike, + TensorLike, + ) _supported_int_dtype_ = [ core.VarDesc.VarType.UINT8, @@ -344,6 +350,93 @@ def _mT_(var: Tensor) -> Tensor: 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, @@ -395,6 +488,37 @@ def _new_full_( 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, @@ -649,7 +773,10 @@ def _reduce_ex_(self: Tensor, proto): ('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_), diff --git a/python/paddle/base/dygraph/tensor_patch_methods.py b/python/paddle/base/dygraph/tensor_patch_methods.py index 1924ebeae9ee6..d5d34fbe594a8 100644 --- a/python/paddle/base/dygraph/tensor_patch_methods.py +++ b/python/paddle/base/dygraph/tensor_patch_methods.py @@ -126,7 +126,9 @@ def _to_static_var(self, to_parameter=False, **kwargs): attr_not_need_keys = [ 'grad', 'T', + 'H', 'mT', + 'mH', 'place', '_place_str', 'data', @@ -1130,7 +1132,7 @@ def cuda( ) -> Tensor: ... @framework.dygraph_only - @tensor_cuda_decorator() + @tensor_cuda_decorator def cuda( self: Tensor, device_id: DeviceLike = None, diff --git a/python/paddle/compat/nn/__init__.py b/python/paddle/compat/nn/__init__.py index c2b42414adc42..2b8e797e6dd3d 100644 --- a/python/paddle/compat/nn/__init__.py +++ b/python/paddle/compat/nn/__init__.py @@ -14,14 +14,13 @@ from __future__ import annotations -import collections import warnings -from itertools import repeat from math import sqrt from typing import TYPE_CHECKING import paddle from paddle import nn +from paddle.nn.modules.utils import _single from paddle.utils.decorator_utils import ForbidKeywordsDecorator from . import functional @@ -59,19 +58,6 @@ ] -def _ntuple(n, name="parse"): - def parse(x): - if isinstance(x, collections.abc.Iterable): - return tuple(x) - return tuple(repeat(x, n)) - - parse.__name__ = name - return parse - - -_single = _ntuple(1, "_single") - - class BatchNorm1D(nn.BatchNorm1D): def __init__( self, diff --git a/python/paddle/compat/nn/functional/__init__.py b/python/paddle/compat/nn/functional/__init__.py index 012985f2c5d59..5929b0e750f6e 100644 --- a/python/paddle/compat/nn/functional/__init__.py +++ b/python/paddle/compat/nn/functional/__init__.py @@ -51,6 +51,8 @@ 'scaled_dot_product_attention', 'unfold', 'smooth_l1_loss', + 'batch_norm', + 'instance_norm', ] @@ -470,3 +472,124 @@ def smooth_l1_loss( return paddle.nn.functional.smooth_l1_loss( input, target, reduction=reduction, delta=beta, is_huber=False ) + + +@ForbidKeywordsDecorator( + illegal_keys={"x", "epsilon", "data_format", "use_global_stats", "name"}, + func_name="paddle.compat.nn.functional.batch_norm", + correct_name="paddle.nn.functional.batch_norm", +) +def batch_norm( + input: Tensor, + running_mean: Tensor, + running_var: Tensor, + weight: Tensor | None = None, + bias: Tensor | None = None, + training: bool = False, + momentum: float = 0.1, + eps: float = 1e-05, +) -> Tensor: + r""" + + PyTorch compatible version of :ref:`api_paddle_nn_functional_batch_norm`. + Aligned with ``torch.nn.functional.batch_norm``. + + See :ref:`api_paddle_nn_functional_batch_norm` for more details. + + Args: + input (Tensor): Input tensor, the data type is float32 or float64. + running_mean (Tensor|None): Running mean. + running_var (Tensor|None): Running variance. + weight (Tensor|None, optional): The weight tensor. Default: None. + bias (Tensor|None, optional): The bias tensor. Default: None. + training (bool, optional): True means train mode. Default: False. + momentum (float, optional): The value used for the moving_mean and moving_var computation. Default: 0.1. + eps (float, optional): The small value added to variance to prevent division by zero. Default: 1e-05. + + Returns: + Tensor, the output of batch normalization. + + Examples: + .. code-block:: pycon + + >>> import paddle + + >>> x = paddle.arange(12, dtype="float32").reshape([2, 1, 2, 3]) + >>> running_mean = paddle.to_tensor([0], dtype="float32") + >>> running_var = paddle.to_tensor([1], dtype="float32") + >>> weight = paddle.to_tensor([2], dtype="float32") + >>> bias = paddle.to_tensor([1], dtype="float32") + >>> out = paddle.compat.nn.functional.batch_norm(x, running_mean, running_var, weight, bias) + >>> print(out) + Tensor(shape=[2, 1, 2, 3], dtype=float32, place=Place(cpu), stop_gradient=True, + [[[[1. , 2.99998999 , 4.99997997 ], + [6.99996996 , 8.99995995 , 10.99995041]]], + [[[12.99993992, 14.99992943, 16.99991989], + [18.99991035, 20.99990082, 22.99988937]]]]) + """ + return paddle.nn.functional.batch_norm( + x=input, + running_mean=running_mean, + running_var=running_var, + weight=weight, + bias=bias, + training=training, + momentum=1.0 - momentum, + epsilon=eps, + ) + + +@ForbidKeywordsDecorator( + illegal_keys={"x", "data_format", "name"}, + func_name="paddle.compat.nn.functional.instance_norm", + correct_name="paddle.nn.functional.instance_norm", +) +def instance_norm( + input: Tensor, + running_mean: Tensor | None = None, + running_var: Tensor | None = None, + weight: Tensor | None = None, + bias: Tensor | None = None, + use_input_stats: bool = True, + momentum: float = 0.1, + eps: float = 1e-05, +) -> Tensor: + r""" + + PyTorch compatible version of :ref:`api_paddle_nn_functional_instance_norm`. + Aligned with ``torch.nn.functional.instance_norm``. + + See :ref:`api_paddle_nn_functional_instance_norm` for more details. + + Args: + input (Tensor): Input tensor, the data type is float32 or float64. + running_mean (Tensor|None, optional): Running mean. Default: None. + running_var (Tensor|None, optional): Running variance. Default: None. + weight (Tensor|None, optional): The weight tensor. Default: None. + bias (Tensor|None, optional): The bias tensor. Default: None. + use_input_stats (bool, optional): Whether to use input statistics. Default: True. + momentum (float, optional): The value used for the moving_mean and moving_var computation. Default: 0.1. + eps (float, optional): The small value added to variance to prevent division by zero. Default: 1e-05. + + Returns: + Tensor, the output of instance normalization. + + Examples: + .. code-block:: pycon + + >>> import paddle + + >>> x = paddle.rand((2, 2, 2, 3)) + >>> out = paddle.compat.nn.functional.instance_norm(x) + >>> print(out) + """ + return paddle.nn.functional.instance_norm( + x=input, + running_mean=running_mean, + running_var=running_var, + weight=weight, + bias=bias, + use_input_stats=use_input_stats, + momentum=1.0 - momentum, + eps=eps, + ) diff --git a/python/paddle/device/__init__.py b/python/paddle/device/__init__.py index 067e4fa030f44..a77a4336962ae 100644 --- a/python/paddle/device/__init__.py +++ b/python/paddle/device/__init__.py @@ -731,15 +731,15 @@ def get_default_device() -> paddle.device: return paddle.device(dev) -def set_default_device(device: PlaceLike | int) -> None: +def set_default_device(device: PlaceLike | int | None = None) -> None: """ Paddle supports running calculations on various types of devices, including CPU, GPU, XPU, NPU and IPU. This function can specify the global device which the OP will run. Args: - device(str, Place or int): This parameter determines the specific running device. + device(str | Place | paddle.device | int, optional): This parameter determines the specific running device. It can be ``cpu``, ``gpu``, ``xpu``, ``npu``, ``gpu:x``, ``xpu:x``, ``npu:x`` and ``ipu``, - where ``x`` is the index of the GPUs, XPUs or NPUs. + where ``x`` is the index of the GPUs, XPUs or NPUs. Defaults is ``None``, which means current device. Examples: .. code-block:: pycon diff --git a/python/paddle/io/dataloader/batch_sampler.py b/python/paddle/io/dataloader/batch_sampler.py index 571ce330c8f8b..af0fd5fedb197 100644 --- a/python/paddle/io/dataloader/batch_sampler.py +++ b/python/paddle/io/dataloader/batch_sampler.py @@ -128,7 +128,7 @@ def __init__( drop_last: bool = False, ) -> None: ... - @batch_sampler_decorator() + @batch_sampler_decorator def __init__( self, dataset: Sized | None = None, diff --git a/python/paddle/linalg.py b/python/paddle/linalg.py index e94f3a0cf7e2e..c5cb829196da9 100644 --- a/python/paddle/linalg.py +++ b/python/paddle/linalg.py @@ -12,6 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from paddle import Tensor + from .tensor import inverse as inv from .tensor.linalg import ( cholesky, @@ -20,7 +27,6 @@ cond, corrcoef, cov, - cross, det, diagonal, eig, @@ -97,3 +103,32 @@ 'fp8_fp8_half_gemm_fused', 'diagonal', ] + + +def cross( + input: Tensor, + other: Tensor, + *, + dim: int = -1, + out: Tensor | None = None, +) -> Tensor: + """ + Computes the cross product of two 3-dimensional vectors along the specified dimension. + + Refer to :ref:`api_paddle_cross` for more detail. + + Args: + input (Tensor): The first input tensor. + other (Tensor): The second input tensor. + dim (int, optional): The dimension along which to compute the cross product. + Default: -1, which means the last dimension. + out (Tensor, optional): The output tensor. Default: None. + name (str, optional): Name for the operation. For more information, please + refer to :ref:`api_guide_Name`. Default: None. + + Returns: + Tensor: The cross product of ``input`` and ``other``. + """ + import paddle + + return paddle.cross(input, other, axis=dim, out=out) diff --git a/python/paddle/nn/functional/activation.py b/python/paddle/nn/functional/activation.py index a5c1b01ebcff0..8479e045e56c0 100644 --- a/python/paddle/nn/functional/activation.py +++ b/python/paddle/nn/functional/activation.py @@ -14,12 +14,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, overload import paddle from paddle import _C_ops, in_dynamic_mode from paddle.framework import core, in_dynamic_or_pir_mode from paddle.utils.decorator_utils import ( + gumbel_softmax_decorator, param_one_alias, param_two_alias, ) @@ -109,7 +110,13 @@ def celu( return out -def elu(x: Tensor, alpha: float = 1.0, name: str | None = None) -> Tensor: +@param_one_alias(["x", "input"]) +def elu( + x: Tensor, + alpha: float = 1.0, + inplace: bool = False, + name: str | None = None, +) -> Tensor: r""" elu activation. @@ -125,7 +132,9 @@ def elu(x: Tensor, alpha: float = 1.0, name: str | None = None) -> Tensor: Parameters: x (Tensor): The input Tensor with data type float32, float64. + Alias: ``input``. alpha (float, optional): The 'alpha' value of the ELU formulation. Default is 1.0. + inplace (bool, optional): Whether to use inplace operation. Default: False. name (str|None, optional): For details, please refer to :ref:`api_guide_Name`. Generally, no setting is required. Default: None. Returns: @@ -143,9 +152,20 @@ def elu(x: Tensor, alpha: float = 1.0, name: str | None = None) -> Tensor: Tensor(shape=[2, 2], dtype=float32, place=Place(cpu), stop_gradient=True, [[-0.12642412, 6. ], [ 1. , 15.60000038]]) + >>> out = F.elu(x, alpha=0.2, inplace=True) + >>> print(out) + Tensor(shape=[2, 2], dtype=float32, place=Place(cpu), stop_gradient=True, + [[-0.12642412, 6. ], + [ 1. , 15.60000038]]) + >>> print(x) + Tensor(shape=[2, 2], dtype=float32, place=Place(cpu), stop_gradient=True, + [[-0.12642412, 6. ], + [ 1. , 15.60000038]]) """ if in_dynamic_or_pir_mode(): + if inplace: + return _C_ops.elu_(x, alpha) return _C_ops.elu(x, alpha) else: @@ -164,6 +184,7 @@ def elu(x: Tensor, alpha: float = 1.0, name: str | None = None) -> Tensor: @inplace_apis_in_dygraph_only +@param_one_alias(["x", "input"]) def elu_(x: Tensor, alpha: float = 1.0, name: str | None = None) -> Tensor: r""" Inplace version of ``elu`` API, the output Tensor will be inplaced with input ``x``. @@ -523,6 +544,7 @@ def leaky_relu_( return _C_ops.leaky_relu_(x, negative_slope) +@param_one_alias(['x', 'input']) def prelu( x: Tensor, weight: Tensor, @@ -1879,6 +1901,27 @@ def glu(x: Tensor, axis: int = -1, name: str | None = None) -> Tensor: return out +@overload +def gumbel_softmax( + x: Tensor, + temperature: float = 1.0, + hard: bool = False, + axis: int = -1, + name: str | None = None, +) -> Tensor: ... + + +@overload +def gumbel_softmax( + logits: Tensor, + tau: float = 1.0, + hard: bool = False, + eps: float = 1e-10, + dim: int = -1, +) -> Tensor: ... + + +@gumbel_softmax_decorator def gumbel_softmax( x: Tensor, temperature: float = 1.0, @@ -1907,17 +1950,27 @@ def gumbel_softmax( .. math:: gumbel\_softmax(v_i)=\frac{e^{v_i/t}}{\sum_{j=1}^n{e^{v_j/t}}},i=1,2,3...n + Note: + This API has two signatures: + 1. ``paddle.nn.functional.gumbel_softmax(x, temperature=1.0, hard=False, axis=-1, name=None)`` (Paddle-style): + Standard Paddle API signature. + 2. ``paddle.nn.functional.gumbel_softmax(logits, tau=1.0, hard=False, eps=1e-10, dim=-1)`` (PyTorch-style): + PyTorch-compatible signature where ``logits`` is an alias for ``x``, + ``tau`` is an alias for ``temperature``, ``dim`` is an alias for ``axis``, + and ``eps`` is accepted but ignored (deprecated). + Parameters: x (Tensor): An N-D Tensor, the first N - 1 dimensions index into a batch of independent distributions and the last dimension represents a vector of probabilities with datatype float16, float32, float64. + Alias: ``logits``. temperature (float, optional): non-negative scalar temperature. - Default is 1.0. + Default is 1.0. Alias: ``tau``. hard (bool, optional): if True, the returned samples will be discretized as one-hot vectors, but will be differentiated as if it is the soft sample in autograd. Default is False. axis (int, optional): The axis along will be calculated softmax value. - Default is -1. + Default is -1. Alias: ``dim``. name (str|None, optional): For details, please refer to :ref:`api_guide_Name`. Generally, no setting is required. Default: None. Returns: diff --git a/python/paddle/nn/functional/norm.py b/python/paddle/nn/functional/norm.py index 43d0bb412ac26..8d32ca271bde2 100644 --- a/python/paddle/nn/functional/norm.py +++ b/python/paddle/nn/functional/norm.py @@ -30,6 +30,7 @@ ) from paddle.utils.decorator_utils import ( ParamAliasDecorator, + param_one_alias, param_two_alias, ) @@ -157,6 +158,7 @@ def normalize( return ret +@param_two_alias(["x", "input"], ["epsilon", "eps"]) def batch_norm( x, running_mean: Tensor, @@ -503,6 +505,7 @@ def rms_norm( return _C_ops.rms_norm(input, weight, normalized_shape, eps)[0] +@param_one_alias(["x", "input"]) def instance_norm( x: Tensor, running_mean: Tensor | None = None, diff --git a/python/paddle/nn/functional/pooling.py b/python/paddle/nn/functional/pooling.py index a77fdcfd0b6f5..ca9930acd0a50 100755 --- a/python/paddle/nn/functional/pooling.py +++ b/python/paddle/nn/functional/pooling.py @@ -574,7 +574,7 @@ def avg_pool3d( ) -@maxpool_decorator() +@maxpool_decorator def max_pool1d( x: Tensor, kernel_size: Size1, @@ -1168,7 +1168,7 @@ def max_unpool3d( return unpool_out -@maxpool_decorator() +@maxpool_decorator def max_pool2d( x: Tensor, kernel_size: Size2, @@ -1365,7 +1365,7 @@ def max_pool2d( return pool_out -@maxpool_decorator() +@maxpool_decorator def max_pool3d( x: Tensor, kernel_size: Size3, diff --git a/python/paddle/nn/layer/activation.py b/python/paddle/nn/layer/activation.py index ffe46072d7d02..06bffdad4e822 100644 --- a/python/paddle/nn/layer/activation.py +++ b/python/paddle/nn/layer/activation.py @@ -104,6 +104,7 @@ class ELU(Layer): Parameters: alpha (float, optional): The 'alpha' value of the ELU formulation. Default is 1.0. + inplace (bool, optional): Whether to use inplace operation. Default: False. name (str|None, optional): Name for the operation (optional, default is None). For more information, please refer to :ref:`api_guide_Name`. @@ -123,19 +124,35 @@ class ELU(Layer): Tensor(shape=[2, 2], dtype=float32, place=Place(cpu), stop_gradient=True, [[-0.12642412, 6. ], [ 1. , 15.60000038]]) + >>> m = paddle.nn.ELU(0.2, True) + >>> out = m(x) + >>> print(out) + Tensor(shape=[2, 2], dtype=float32, place=Place(cpu), stop_gradient=True, + [[-0.12642412, 6. ], + [ 1. , 15.60000038]]) + >>> print(x) + Tensor(shape=[2, 2], dtype=float32, place=Place(cpu), stop_gradient=True, + [[-0.12642412, 6. ], + [ 1. , 15.60000038]]) """ - def __init__(self, alpha: float = 1.0, name: str | None = None) -> None: + def __init__( + self, alpha: float = 1.0, inplace: bool = False, name: str | None = None + ) -> None: super().__init__() self._alpha = alpha + self._inplace = inplace self._name = name + @param_one_alias(["x", "input"]) def forward(self, x: Tensor) -> Tensor: - return F.elu(x, self._alpha, self._name) + return F.elu(x, self._alpha, self._inplace, self._name) def extra_repr(self) -> str: - name_str = f', name={self._name}' if self._name else '' - return f'alpha={self._alpha}{name_str}' + parts = [f'alpha={self._alpha}'] + parts.append(f'inplace={self._inplace}') if self._inplace else None + parts.append(f'name={self._name}') if self._name else None + return ', '.join(parts) class GLU(Layer): diff --git a/python/paddle/nn/layer/layers.py b/python/paddle/nn/layer/layers.py index 429dbd0d5463a..834e51fc04ca7 100644 --- a/python/paddle/nn/layer/layers.py +++ b/python/paddle/nn/layer/layers.py @@ -3932,5 +3932,38 @@ def zero_grad(self, set_to_none: bool = True) -> None: if p.grad is not None: p.clear_gradient(not set_to_none) + def to_empty( + self, device: PlaceLike | None = None, recurse: bool = True + ) -> Self: + """ + Move the parameters and buffers to the specified device without copying storage. + + Re-creates the parameters and buffers as empty tensors on the target device. + + Args: + device (PlaceLike, optional): The device to move parameters and buffers to. + If None, the current device is used. Default: None. + recurse (bool, optional): Whether to recursively process sublayers. + Default: True. + + Returns: + Layer: self + """ + if recurse: + for layer in self.children(): + layer.to_empty(device, recurse=True) + + for key, param in self._parameters.items(): + if param is not None: + with no_grad(): + empty_param = paddle.empty_like(param, device=device) + param._set_impl(empty_param) + + for key, buf in self._buffers.items(): + if buf is not None: + self._buffers[key] = paddle.empty_like(buf, device=device) + + return self + def _get_name(self): return self.__class__.__name__ diff --git a/python/paddle/nn/layer/loss.py b/python/paddle/nn/layer/loss.py index d64b875462b59..c2438017f55ce 100644 --- a/python/paddle/nn/layer/loss.py +++ b/python/paddle/nn/layer/loss.py @@ -14,6 +14,7 @@ from __future__ import annotations +import warnings from typing import TYPE_CHECKING import paddle @@ -39,6 +40,46 @@ __all__ = [] +class _Loss(Layer): + r""" + Base class for all loss functions. + + Parameters: + size_average (bool|None, optional): Deprecated (see ``reduction``). Default is ``None``. + reduce (bool|None, optional): Deprecated (see ``reduction``). Default is ``None``. + reduction (str, optional): Indicate how to calculate the loss, the candidates + are ``'none'`` | ``'mean'`` | ``'sum'``. Default is ``'mean'``. + """ + + reduction: _ReduceMode + + def __init__( + self, + size_average: bool | None = None, + reduce: bool | None = None, + reduction: str = 'mean', + ) -> None: + super().__init__() + if size_average is not None or reduce is not None: + reduction = ( + 'none' + if reduce is False + else ('sum' if size_average is False else 'mean') + ) + warnings.warn( + "'size_average' and 'reduce' args will be deprecated, " + f"please use reduction='{reduction}' instead.", + DeprecationWarning, + stacklevel=2, + ) + if reduction not in ['sum', 'mean', 'none']: + raise ValueError( + "'reduction' should be 'sum', 'mean' or 'none', " + f"but received {reduction}." + ) + self.reduction = reduction + + class BCEWithLogitsLoss(Layer): r""" diff --git a/python/paddle/nn/layer/pooling.py b/python/paddle/nn/layer/pooling.py index 2b5a9c2cf1cc4..28ee4dde8f542 100755 --- a/python/paddle/nn/layer/pooling.py +++ b/python/paddle/nn/layer/pooling.py @@ -680,7 +680,7 @@ class MaxPool1D(Layer): dilation: Size1 name: str | None - @maxpool_layer_decorator() + @maxpool_layer_decorator def __init__( self, kernel_size: Size1, @@ -812,7 +812,7 @@ class MaxPool2D(Layer): data_format: DataLayout2D name: str | None - @maxpool_layer_decorator() + @maxpool_layer_decorator def __init__( self, kernel_size: Size2, @@ -935,7 +935,7 @@ class MaxPool3D(Layer): data_format: DataLayout3D name: str | None - @maxpool_layer_decorator() + @maxpool_layer_decorator def __init__( self, kernel_size: Size3, diff --git a/python/paddle/nn/layer/rnn.py b/python/paddle/nn/layer/rnn.py index e2b20f6821865..82c131668702d 100644 --- a/python/paddle/nn/layer/rnn.py +++ b/python/paddle/nn/layer/rnn.py @@ -2271,7 +2271,7 @@ def __init__( dtype=None, ) -> None: ... - @gru_decorator() + @gru_decorator def __init__( self, input_size: int, diff --git a/python/paddle/nn/modules/__init__.py b/python/paddle/nn/modules/__init__.py index c1fafa5c9448e..c6d9f04d18f49 100644 --- a/python/paddle/nn/modules/__init__.py +++ b/python/paddle/nn/modules/__init__.py @@ -12,4 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from . import ( + loss, # noqa: F401 + utils, # noqa: F401 +) from .module import Module # noqa: F401 diff --git a/python/paddle/nn/modules/loss.py b/python/paddle/nn/modules/loss.py new file mode 100644 index 0000000000000..fdc927e873eaa --- /dev/null +++ b/python/paddle/nn/modules/loss.py @@ -0,0 +1,37 @@ +# Copyright (c) 2025 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 paddle.nn.layer.loss import ( # noqa: F401 + BCELoss, + BCEWithLogitsLoss, + CosineEmbeddingLoss, + CrossEntropyLoss, + CTCLoss, + GaussianNLLLoss, + HingeEmbeddingLoss, + KLDivLoss, + L1Loss, + MarginRankingLoss, + MSELoss, + MultiLabelMarginLoss, + MultiLabelSoftMarginLoss, + MultiMarginLoss, + NLLLoss, + PoissonNLLLoss, + SmoothL1Loss, + SoftMarginLoss, + TripletMarginLoss, + TripletMarginWithDistanceLoss, + _Loss, +) diff --git a/python/paddle/nn/modules/utils.py b/python/paddle/nn/modules/utils.py new file mode 100644 index 0000000000000..5c1ec4f10baa2 --- /dev/null +++ b/python/paddle/nn/modules/utils.py @@ -0,0 +1,34 @@ +# Copyright (c) 2025 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 collections +from itertools import repeat + + +def _ntuple(n, name="parse"): + def parse(x): + if isinstance(x, collections.abc.Iterable): + return tuple(x) + return tuple(repeat(x, n)) + + parse.__name__ = name + return parse + + +_single = _ntuple(1, "_single") +_pair = _ntuple(2, "_pair") +_triple = _ntuple(3, "_triple") +_quadruple = _ntuple(4, "_quadruple") diff --git a/python/paddle/optimizer/lr.py b/python/paddle/optimizer/lr.py index 80fb8b520a8f1..2c5f301d53f73 100644 --- a/python/paddle/optimizer/lr.py +++ b/python/paddle/optimizer/lr.py @@ -164,7 +164,7 @@ def __init__( verbose: bool = False, ) -> None: ... - @lr_scheduler_decorator() + @lr_scheduler_decorator def __init__( self, learning_rate: float = 0.1, @@ -1128,7 +1128,7 @@ def __init__( verbose: bool = False, ) -> None: ... - @lr_scheduler_decorator() + @lr_scheduler_decorator def __init__( self, learning_rate: float, @@ -1256,7 +1256,7 @@ def __init__( verbose: bool = False, ) -> None: ... - @lr_scheduler_decorator() + @lr_scheduler_decorator def __init__( self, learning_rate: float, @@ -1398,7 +1398,7 @@ def __init__( verbose: bool = False, ) -> None: ... - @lr_scheduler_decorator() + @lr_scheduler_decorator def __init__( self, learning_rate: float, @@ -1527,7 +1527,7 @@ def __init__( verbose: bool = False, ): ... - @lr_scheduler_decorator() + @lr_scheduler_decorator def __init__( self, learning_rate: float, @@ -1679,7 +1679,7 @@ def __init__( verbose: bool = False, ) -> None: ... - @lr_scheduler_decorator() + @lr_scheduler_decorator @param_one_alias(["epsilon", "eps"]) def __init__( self, @@ -1930,7 +1930,7 @@ def __init__( verbose: bool = False, ) -> None: ... - @lr_scheduler_decorator() + @lr_scheduler_decorator def __init__( self, learning_rate: float, @@ -2749,7 +2749,7 @@ def __init__( verbose: bool = False, ) -> None: ... - @lr_scheduler_decorator() + @lr_scheduler_decorator def __init__( self, learning_rate: float, diff --git a/python/paddle/pir/math_op_patch.py b/python/paddle/pir/math_op_patch.py index 1058961fc6b7f..eecb5a904f3f3 100644 --- a/python/paddle/pir/math_op_patch.py +++ b/python/paddle/pir/math_op_patch.py @@ -34,7 +34,13 @@ if TYPE_CHECKING: from paddle import Tensor - from paddle._typing import DTypeLike, PlaceLike, ShapeLike + from paddle._typing import ( + DTypeLike, + NestedNumericSequence, + PlaceLike, + ShapeLike, + TensorLike, + ) _already_patch_value = False @@ -709,6 +715,88 @@ def _mT_(self): 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, @@ -756,6 +844,50 @@ def _new_full_( 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, @@ -1560,7 +1692,10 @@ def get_device(self) -> None: ('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_), diff --git a/python/paddle/tensor/__init__.py b/python/paddle/tensor/__init__.py index b7333eff2492c..5a0c8b4c8012d 100644 --- a/python/paddle/tensor/__init__.py +++ b/python/paddle/tensor/__init__.py @@ -176,6 +176,7 @@ dstack, expand, expand_as, + expand_copy, flatten, flatten_, flip, @@ -523,6 +524,7 @@ sub = subtract sub_ = subtract_ clamp_ = clip_ +true_divide_ = divide_ movedim = moveaxis mod = remainder floor_mod = remainder @@ -667,6 +669,7 @@ 'sub', 'sub_', 'true_divide', + 'true_divide_', 'floor_divide', 'floor_divide_', 'remainder', @@ -755,6 +758,7 @@ 'expand', 'broadcast_to', 'expand_as', + 'expand_copy', 'ravel', 'flatten', 'flatten_', diff --git a/python/paddle/tensor/creation.py b/python/paddle/tensor/creation.py index 3ba080e6737c8..66d57751b54e6 100644 --- a/python/paddle/tensor/creation.py +++ b/python/paddle/tensor/creation.py @@ -4231,7 +4231,7 @@ def set_( return _C_ops.set_(x, source, shape, stride, offset) -@resize__decorator() +@resize__decorator @inplace_apis_in_dygraph_only def resize_( x: paddle.Tensor, diff --git a/python/paddle/tensor/linalg.py b/python/paddle/tensor/linalg.py index 5c3bd0e2f4330..ed6b33740f816 100644 --- a/python/paddle/tensor/linalg.py +++ b/python/paddle/tensor/linalg.py @@ -14,7 +14,7 @@ from __future__ import annotations import math -from typing import TYPE_CHECKING, Literal, TypeAlias +from typing import TYPE_CHECKING, Literal, NamedTuple, TypeAlias import numpy as np from typing_extensions import overload @@ -24,10 +24,13 @@ from paddle._C_ops import ( # noqa: F401 bincount, bmm, + cholesky, cross, + det, diagonal, dist, dot, + eigh, matmul, mv, ) @@ -38,6 +41,7 @@ VariableArgsDecorator, param_one_alias, param_two_alias, + qr_decorator, transpose_decorator, ) from paddle.utils.inplace_utils import inplace_apis_in_dygraph_only @@ -93,7 +97,7 @@ def transpose( ) -> Tensor: ... -@transpose_decorator() +@transpose_decorator def transpose( x: Tensor, perm: Sequence[int], name: str | None = None ) -> Tensor: @@ -222,7 +226,7 @@ def transpose( return out -@transpose_decorator() +@transpose_decorator @inplace_apis_in_dygraph_only def transpose_(x, perm, name=None): r""" @@ -1931,67 +1935,6 @@ def t_(input, name=None): return out -def cholesky(x: Tensor, upper: bool = False, name: str | None = None) -> Tensor: - r""" - Computes the Cholesky decomposition of one symmetric positive-definite - matrix or batches of symmetric positive-definite matrices. - - If `upper` is `True`, the decomposition has the form :math:`A = U^{T}U` , - and the returned matrix :math:`U` is upper-triangular. Otherwise, the - decomposition has the form :math:`A = LL^{T}` , and the returned matrix - :math:`L` is lower-triangular. - - Args: - x (Tensor): The input tensor. Its shape should be `[*, M, M]`, - where * is zero or more batch dimensions, and matrices on the - inner-most 2 dimensions all should be symmetric positive-definite. - Its data type should be float32 or float64. - upper (bool, optional): The flag indicating whether to return upper or lower - triangular matrices. Default: False. - name (str|None, optional): Name for the operation (optional, default is None). - For more information, please refer to :ref:`api_guide_Name`. - - Returns: - Tensor, A Tensor with same shape and data type as `x`. It represents - triangular matrices generated by Cholesky decomposition. - - Examples: - .. code-block:: pycon - - >>> import paddle - >>> paddle.seed(2023) - - >>> a = paddle.rand([3, 3], dtype="float32") - >>> a_t = paddle.transpose(a, [1, 0]) - >>> x = paddle.matmul(a, a_t) + 1e-03 - - >>> out = paddle.linalg.cholesky(x, upper=False) - >>> print(out) - Tensor(shape=[3, 3], dtype=float32, place=Place(cpu), stop_gradient=True, - [[1.04337060, 0. , 0. ], - [1.06467676, 0.17859183, 0. ], - [1.30602181, 0.08326342, 0.22790733]]) - """ - if in_dynamic_or_pir_mode(): - x_shape = x.shape - assert len(x_shape) >= 2 and x_shape[-1] == x_shape[-2], ( - "Shape must have at least 2 dimensions and last two dimensions must be equal." - ) - return _C_ops.cholesky(x, upper) - else: - check_variable_and_dtype(x, 'dtype', ['float32', 'float64'], 'cholesky') - check_type(upper, 'upper', bool, 'cholesky') - helper = LayerHelper('cholesky', **locals()) - out = helper.create_variable_for_type_inference(dtype=x.dtype) - helper.append_op( - type='cholesky', - inputs={'X': [x]}, - outputs={'Out': out}, - attrs={'upper': upper}, - ) - return out - - def matrix_rank( x: Tensor, tol: float | Tensor | None = None, @@ -2303,64 +2246,6 @@ def histogram_bin_edges( return paddle.linspace(min, max, bins + 1, name=name) -@param_one_alias(["x", "input"]) -def det(x: Tensor, name: str | None = None) -> Tensor: - """ - - Calculates determinant value of a square matrix or batches of square matrices. - - Args: - x (Tensor): the input matrix of size `(n, n)` or the - batch of matrices of size `(*, n, n)` where `*` is one or more - batch dimensions. Alias: ``input``. - name (str|None, optional): Name of the output.It's used to print debug info for - developers. Details: :ref:`api_guide_Name`. Default is None. - - Returns: - Tensor, the determinant value of a square matrix or batches of square matrices. - - Examples: - .. code-block:: pycon - - >>> import paddle - >>> paddle.seed(2023) - >>> x = paddle.randn([3, 3, 3]) - >>> A = paddle.linalg.det(x) - >>> print(A) - Tensor(shape=[3], dtype=float32, place=Place(cpu), stop_gradient=True, - [-1.29280925, 0.77832544, 0.89754158]) - - - """ - if in_dynamic_or_pir_mode(): - return _C_ops.det(x) - else: - check_dtype( - x.dtype, - 'Input', - ['float16', 'float32', 'float64', 'complex64', 'complex128'], - 'det', - ) - - input_shape = list(x.shape) - assert len(input_shape) >= 2, ( - "The x must be at least 2-dimensional, " - f"but received Input x's dimensional: {len(input_shape)}.\n" - ) - - assert input_shape[-1] == input_shape[-2], ( - "Expect squared input," - f"but received {input_shape[-2]} by {input_shape[-1]} matrix.\n" - ) - helper = LayerHelper('determinant', **locals()) - out = helper.create_variable_for_type_inference(dtype=x.dtype) - - helper.append_op( - type='determinant', inputs={'Input': [x]}, outputs={'Out': [out]} - ) - return out - - def slogdet(x: Tensor, name: str | None = None) -> Tensor: """ @@ -2430,6 +2315,25 @@ def slogdet(x: Tensor, name: str | None = None) -> Tensor: return out +def logdet(input: Tensor, name: str | None = None) -> Tensor: + """ + Computes the natural logarithm of the determinant of a square matrix or + batches of square matrices. + + For matrices with negative determinant, returns ``nan``. + For matrices with zero determinant, returns ``-inf``. + + Args: + input (Tensor): The input tensor of shape ``[*, n, n]`` where ``*`` + is zero or more batch dimensions. + name (str|None, optional): Name for the operation. Default: None. + + Returns: + Tensor: The log-determinant of ``input``, with shape ``[*]``. + """ + return det(input).log() + + def svd( x: Tensor, full_matrices: bool = False, @@ -2882,12 +2786,19 @@ def matrix_power( return out +class QrRetType(NamedTuple): + Q: Tensor + R: Tensor + + @overload def qr( x: Tensor, mode: Literal['reduced', 'complete'] = ..., name: str | None = ..., -) -> tuple[Tensor, Tensor]: ... + *, + out: tuple[Tensor, Tensor] | None = ..., +) -> QrRetType: ... @overload @@ -2895,37 +2806,66 @@ def qr( x: Tensor, mode: Literal['r'] = ..., name: str | None = ..., + *, + out: Tensor | None = ..., ) -> Tensor: ... +@overload +def qr( + input: Tensor, + some: bool = ..., + *, + out: tuple[Tensor, Tensor] | None = ..., +) -> QrRetType: ... + + +@qr_decorator def qr( x, mode="reduced", name=None, -) -> Tensor | tuple[Tensor, Tensor]: + *, + out=None, +) -> QrRetType | Tensor: r""" + Note: + This API supports two signatures: + 1. ``paddle.linalg.qr(x, mode='reduced', name=None, *, out=None)`` (Paddle-style): + Computes the QR decomposition with a ``mode`` string parameter. + 2. ``paddle.linalg.qr(input, some=True, *, out=None)`` (PyTorch-style): + Computes the QR decomposition with a ``some`` boolean parameter. + Computes the QR decomposition of one matrix or batches of matrices (backward is unsupported now). Args: x (Tensor): The input tensor. Its shape should be `[..., M, N]`, where ... is zero or more batch dimensions. M and N can be arbitrary positive number. The data type of x supports float, double, complex64, complex128. + Alias: ``input``, ``A``. mode (str, optional): A flag to control the behavior of qr. Suppose x's shape is `[..., M, N]` and denoting `K = min(M, N)`: If mode = "reduced", qr op will return reduced Q and R matrices, which means Q's shape is `[..., M, K]` and R's shape is `[..., K, N]`. If mode = "complete", qr op will return complete Q and R matrices, which means Q's shape is `[..., M, M]` and R's shape is `[..., M, N]`. - If mode = "r", qr op will only return reduced R matrix, which means - R's shape is `[..., K, N]`. Default: "reduced". + If mode = "r", qr op will only compute reduced R matrix, which means + R's shape is `[..., K, N]` and will not return Q. Default: "reduced". name (str|None, optional): Name for the operation (optional, default is None). For more information, please refer to :ref:`api_guide_Name`. + Keyword Args: + out (tuple[Tensor, Tensor]|Tensor|None, optional): The output tensor(s). + If mode is "r", out must be a single Tensor to store R. + Otherwise, out must be a tuple of (Q, R) tensors. + If set, the result will be stored in these Tensors. Default: None. + Returns: - If mode = "reduced" or mode = "complete", qr will return a two tensor-tuple, which represents Q and R. - If mode = "r", qr will return a tensor which represents R. + QrRetType | Tensor: If mode="r", returns a single Tensor R. + Otherwise, returns a QrRetType named tuple (Q, R). Examples: + .. code-block:: pycon >>> import paddle @@ -2946,10 +2886,6 @@ def qr( """ if in_dynamic_or_pir_mode(): q, r = _C_ops.qr(x, mode) - if mode == "r": - return r - else: - return q, r else: check_variable_and_dtype( x, 'dtype', ['float32', 'float64', 'complex64', 'complex128'], 'qr' @@ -2963,10 +2899,16 @@ def qr( helper.append_op( type='qr', inputs={'X': [x]}, outputs={'Q': q, 'R': r}, attrs=attrs ) - if mode == "r": - return r - else: - return q, r + if mode == "r": + if out is not None: + paddle.assign(r, out) + return out + return r + if out is not None: + paddle.assign(q, out[0]) + paddle.assign(r, out[1]) + return QrRetType(Q=out[0], R=out[1]) + return QrRetType(Q=q, R=r) @overload @@ -3517,89 +3459,6 @@ def multi_dot(x: list[Tensor], name: str | None = None) -> Tensor: return out -def eigh( - x: Tensor, UPLO: Literal['L', 'U'] = 'L', name: str | None = None -) -> tuple[Tensor, Tensor]: - """ - Compute the eigenvalues and eigenvectors of a - complex Hermitian (conjugate symmetric) or a real symmetric matrix. - - Args: - x (Tensor): A tensor with shape :math:`[*, N, N]` , The data type of the input Tensor x - should be one of float32, float64, complex64, complex128. - UPLO (str, optional): (string, default 'L'), 'L' represents the lower triangular matrix, - "'U' represents the upper triangular matrix.". Default: 'L'. - name (str|None, optional): The default value is None. Normally there is no need for user to set this - property. For more information, please refer to :ref:`api_guide_Name`. - - Returns: - 2-element tuple containing - - - out_value(Tensor): A Tensor with shape :math:`[*, N]` and data type of float32 and float64. - The eigenvalues of eigh op. - - out_vector(Tensor): A Tensor with shape :math:`[*, N, N]` and data type of float32, float64, - complex64 and complex128. The eigenvectors of eigh op. - - Examples: - .. code-block:: pycon - - >>> import paddle - - >>> x = paddle.to_tensor([[1, -2j], [2j, 5]]) - >>> out_value, out_vector = paddle.linalg.eigh(x, UPLO='L') - >>> print(out_value) - Tensor(shape=[2], dtype=float32, place=Place(cpu), stop_gradient=True, - [0.17157286, 5.82842731]) - >>> print(out_vector) - Tensor(shape=[2, 2], dtype=complex64, place=Place(cpu), stop_gradient=True, - [[(-0.92387950+0.00000000j), (-0.38268340+0.00000000j)], - [ (0.00000000+0.38268340j), (0.00000000-0.92387950j) ]]) - - """ - - def __check_input(x, UPLO): - x_shape = list(x.shape) - if len(x.shape) < 2: - raise ValueError( - "Input(input) only support >=2 tensor, but received " - f"length of Input(input) is {len(x.shape)}." - ) - if x_shape[-1] != x_shape[-2]: - raise ValueError( - f"The input matrix must be batches of square matrices. But received x's dimension: {x_shape}" - ) - if UPLO != 'L' and UPLO != 'U': - raise ValueError( - f"UPLO must be L or U. But received UPLO is: {UPLO}" - ) - - if in_dynamic_mode() or in_pir_mode(): - __check_input(x, UPLO) - return _C_ops.eigh(x, UPLO) - - else: - __check_input(x, UPLO) - - helper = LayerHelper('eigh', **locals()) - check_variable_and_dtype( - x, - 'dtype', - ['float32', 'float64', 'complex64', 'complex128'], - 'eigh', - ) - - out_value = helper.create_variable_for_type_inference(dtype=x.dtype) - out_vector = helper.create_variable_for_type_inference(dtype=x.dtype) - - helper.append_op( - type='eigh', - inputs={'X': x}, - outputs={'Eigenvalues': out_value, 'Eigenvectors': out_vector}, - attrs={'UPLO': UPLO}, - ) - return out_value, out_vector - - @param_one_alias(["x", "input", "A"]) def pinv( x: Tensor, diff --git a/python/paddle/tensor/logic.py b/python/paddle/tensor/logic.py index 2d382ea01d6d7..2e21eefca36ad 100755 --- a/python/paddle/tensor/logic.py +++ b/python/paddle/tensor/logic.py @@ -419,11 +419,14 @@ def greater_equal_(x: Tensor, y: Tensor, name: str | None = None) -> Tensor: @inplace_apis_in_dygraph_only +@param_two_alias(["x", "input"], ["y", "other"]) def greater_than_(x: Tensor, y: Tensor, name: str | None = None) -> Tensor: r""" Inplace version of ``greater_than`` API, the output Tensor will be inplaced with input ``x``. Please refer to :ref:`api_paddle_greater_than`. """ + if not isinstance(y, paddle.Tensor): + y = paddle.to_tensor(y, dtype=x.dtype) out_shape = broadcast_shape(x.shape, y.shape) if out_shape != x.shape: raise ValueError( diff --git a/python/paddle/tensor/manipulation.py b/python/paddle/tensor/manipulation.py index 7b87b5f7678ec..c60fd0551a38d 100644 --- a/python/paddle/tensor/manipulation.py +++ b/python/paddle/tensor/manipulation.py @@ -1219,7 +1219,7 @@ def fill_diagonal_( @dygraph_only -@fill_diagonal_inplace_decorator() +@fill_diagonal_inplace_decorator def fill_diagonal_( x: Tensor, value: float, @@ -2478,9 +2478,13 @@ def hstack( arrays = [arrays] if arrays and arrays[0].ndim == 1: - return paddle.concat(arrays, axis=0, name=name) + result = paddle.concat(arrays, axis=0, name=name) else: - return paddle.concat(arrays, axis=1, name=name) + result = paddle.concat(arrays, axis=1, name=name) + if out is not None: + paddle.assign(result, out) + return out + return result @param_one_alias(["x", "tensors"]) @@ -2559,7 +2563,11 @@ def vstack( if not isinstance(arrays, list): arrays = [arrays] - return paddle.concat(arrays, axis=0, name=name) + result = paddle.concat(arrays, axis=0, name=name) + if out is not None: + paddle.assign(result, out) + return out + return result @param_one_alias(["x", "tensors"]) @@ -2622,7 +2630,11 @@ def dstack( if not isinstance(arrays, list): arrays = [arrays] - return paddle.concat(arrays, axis=2, name=name) + result = paddle.concat(arrays, axis=2, name=name) + if out is not None: + paddle.assign(result, out) + return out + return result @param_one_alias(["x", "tensors"]) @@ -5000,7 +5012,7 @@ def tile( ) -> Tensor: ... -@tile_decorator() +@tile_decorator def tile( x: Tensor, repeat_times: TensorOrTensors | Sequence[int], @@ -5289,7 +5301,7 @@ def expand( ) -> Tensor: ... -@expand_decorator() +@expand_decorator def expand(x: Tensor, shape: ShapeLike, name: str | None = None) -> Tensor: """ @@ -5435,6 +5447,69 @@ def get_attr_expand_shape(list_expand_shape): return out +@overload +def expand_copy( + x: Tensor, + shape: ShapeLike, + name: str | None = None, +) -> Tensor: ... + + +@overload +def expand_copy( + input: Tensor, + *size: int, +) -> Tensor: ... + + +@expand_decorator +def expand_copy(x: Tensor, shape: ShapeLike, name: str | None = None) -> Tensor: + """ + Returns a new tensor with the expanded data, without memory sharing. + + This function is the copying version of :ref:`api_paddle_expand`, which always + returns a new tensor with the expanded data instead of a view. + + Note: + This API has two signatures: + 1. ``paddle.expand_copy(x, shape, name=None)`` (Paddle-style): + Returns a new tensor with expanded data following broadcast semantics. + 2. ``paddle.expand_copy(input, *size)`` (PyTorch-style): + Returns a new tensor with expanded data with variadic size arguments. + + Args: + x (Tensor): The input tensor. Alias: ``input``. + shape (list|tuple|Tensor): The target shape to expand to. The number of + dimensions must be greater than or equal to the number of dimensions of ``x``. + Alias: ``size``. + name (str|None, optional): Name for the operation (optional, default is None). + + Returns: + Tensor, A new tensor with the expanded data. + + Examples: + .. code-block:: pycon + + >>> import paddle + + >>> x = paddle.to_tensor([[1], [2], [3]], dtype='float32') + >>> out = paddle.expand_copy(x, shape=[3, 4]) + >>> print(out) + Tensor(shape=[3, 4], dtype=float32, place=Place(cpu), stop_gradient=True, + [[1., 1., 1., 1.], + [2., 2., 2., 2.], + [3., 3., 3., 3.]]) + >>> # verify it's a copy (not sharing memory) + >>> out[0] = 0 + >>> print(x) + Tensor(shape=[3, 1], dtype=float32, place=Place(cpu), stop_gradient=True, + [[1.], + [2.], + [3.]]) + """ + return expand(x, shape, name).clone() + + @overload def reshape(x: Tensor, shape: ShapeLike, name: str | None = None) -> Tensor: ... @@ -5443,7 +5518,7 @@ def reshape(x: Tensor, shape: ShapeLike, name: str | None = None) -> Tensor: ... def reshape(input: Tensor, *shape: int) -> Tensor: ... -@reshape_decorator() +@reshape_decorator def reshape(x: Tensor, shape: ShapeLike, name: str | None = None) -> Tensor: """ Changes the shape of ``x`` without changing its data. @@ -7867,7 +7942,7 @@ def index_add( ) -> Tensor: ... -@index_add_decorator() +@index_add_decorator def index_add( x: Tensor, index: Tensor, @@ -7975,7 +8050,7 @@ def index_add_( ) -> Tensor: ... -@index_add_decorator() +@index_add_decorator @inplace_apis_in_dygraph_only def index_add_( x: Tensor, @@ -8245,7 +8320,7 @@ def view( @dygraph_only -@view_decorator() +@view_decorator def view( x: Tensor, shape_or_dtype: Sequence[int] | DTypeLike, @@ -8501,7 +8576,7 @@ def index_fill( ) -> Tensor: ... -@index_fill_decorator() +@index_fill_decorator def index_fill( x: Tensor, index: Tensor, axis: int, value: float, name: str | None = None ): @@ -8568,7 +8643,7 @@ def index_fill_( @inplace_apis_in_dygraph_only -@index_fill_decorator() +@index_fill_decorator def index_fill_( x: Tensor, index: Tensor, axis: int, value: float, name: str | None = None ): @@ -8776,7 +8851,7 @@ def slice_scatter( ) -> Tensor: ... -@slice_scatter_decorator() +@slice_scatter_decorator def slice_scatter( x: Tensor, value: Tensor, diff --git a/python/paddle/tensor/math.py b/python/paddle/tensor/math.py index a4686cc62500c..bf40db5000009 100644 --- a/python/paddle/tensor/math.py +++ b/python/paddle/tensor/math.py @@ -1687,7 +1687,7 @@ def nansum( ) -> Tensor: ... -@nansum_decorator() +@nansum_decorator def nansum( x: Tensor, axis: int | Sequence[int] | None = None, @@ -3223,7 +3223,46 @@ def clip( return output +def clamp_max( + input: Tensor, max: float, *, out: Tensor | None = None +) -> Tensor: + """ + Clamps all elements in input into the range [min=None, max]. + + This is a wrapper around ``paddle.clip`` that only sets the upper bound. + + Args: + input (Tensor): The input Tensor. + max (float): The upper bound. + out (Tensor|None, optional): The output Tensor. Default: None. + + Returns: + Tensor: The clamped Tensor. + """ + return clip(input, min=None, max=max, out=out) + + +def clamp_min( + input: Tensor, min: float, *, out: Tensor | None = None +) -> Tensor: + """ + Clamps all elements in input into the range [min, max=None]. + + This is a wrapper around ``paddle.clip`` that only sets the lower bound. + + Args: + input (Tensor): The input Tensor. + min (float): The lower bound. + out (Tensor|None, optional): The output Tensor. Default: None. + + Returns: + Tensor: The clamped Tensor. + """ + return clip(input, min=min, max=None, out=out) + + @inplace_apis_in_dygraph_only +@param_one_alias(["x", "input"]) def clip_( x: Tensor, min: float | None = None, diff --git a/python/paddle/tensor/search.py b/python/paddle/tensor/search.py index 264fd1dc42d79..2a1fb67ea84c7 100755 --- a/python/paddle/tensor/search.py +++ b/python/paddle/tensor/search.py @@ -215,7 +215,7 @@ def index_select( ) -> Tensor: ... -@index_select_decorator() +@index_select_decorator def index_select( x: Tensor, index: Tensor, diff --git a/python/paddle/utils/decorator_utils.py b/python/paddle/utils/decorator_utils.py index aceb185cb8a46..4b58c0c429479 100644 --- a/python/paddle/utils/decorator_utils.py +++ b/python/paddle/utils/decorator_utils.py @@ -301,6 +301,49 @@ def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: return wrapper +def gumbel_softmax_decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: + """ + Decorator for ``gumbel_softmax`` that handles parameter aliases + between PyTorch and Paddle signatures. + + PyTorch: ``torch.nn.functional.gumbel_softmax(logits, tau=1, hard=False, eps=1e-10, dim=-1)`` + Paddle: ``paddle.nn.functional.gumbel_softmax(x, temperature=1.0, hard=False, axis=-1, name=None)`` + + This decorator handles: + - ``logits`` -> ``x`` + - ``tau`` -> ``temperature`` + - ``dim`` -> ``axis`` + - ``eps`` is stripped (deprecated no-op in PyTorch) + """ + + @functools.wraps(func) + def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: + # Strip eps (deprecated no-op parameter) + kwargs.pop("eps", None) + + # Parameter alias mapping + if "logits" in kwargs and "x" not in kwargs: + kwargs["x"] = kwargs.pop("logits") + if "tau" in kwargs and "temperature" not in kwargs: + kwargs["temperature"] = kwargs.pop("tau") + if "dim" in kwargs and "axis" not in kwargs: + kwargs["axis"] = kwargs.pop("dim") + + # Dispatch based on the type of the 4th positional arg (index 3). + # PyTorch: gumbel_softmax(logits, tau=1, hard=False, eps=1e-10, dim=-1) + # The 4th arg is either eps (float, deprecated no-op) or dim (int). + if len(args) >= 4 and not isinstance(args[3], int): + # The 4th arg is eps → strip it + return func(*(args[:3] + args[4:])) + + return func(*args, **kwargs) + + wrapper.__signature__ = inspect.signature(func) + return wrapper + + def param_two_alias_one_default( alias_list1: list[str], alias_list2: list[str], default_param: list[str] ) -> Callable[[Callable[_InputT, _RetT]], Callable[_InputT, _RetT]]: @@ -441,9 +484,9 @@ def process( return args, kwargs -def view_decorator() -> Callable[ - [Callable[_InputT, _RetT]], Callable[_InputT, _RetT] -]: +def view_decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: """ Usage Example: paddle.view(x=tensor_x, shape_or_dtype=[-1, 1, 3], name=None) @@ -454,24 +497,21 @@ def view_decorator() -> Callable[ tensor_x.view(size=[-1, 1, 3]) -> paddle.view(tensor_x, size=[-1, 1, 3]) """ - def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: - @functools.wraps(func) - def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: - if ("dtype" in kwargs) and ("shape_or_dtype" not in kwargs): - kwargs["shape_or_dtype"] = kwargs.pop("dtype") - elif ("size" in kwargs) and ("shape_or_dtype" not in kwargs): - kwargs["shape_or_dtype"] = kwargs.pop("size") - elif len(args) >= 2 and _is_int_or_scalar_tensor(args[1]): - if all(_is_int_or_scalar_tensor(arg) for arg in args[1:]): - kwargs["x"] = args[0] - kwargs['shape_or_dtype'] = list(args[1:]) - args = () - return func(*args, **kwargs) - - wrapper.__signature__ = inspect.signature(func) - return wrapper + @functools.wraps(func) + def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: + if ("dtype" in kwargs) and ("shape_or_dtype" not in kwargs): + kwargs["shape_or_dtype"] = kwargs.pop("dtype") + elif ("size" in kwargs) and ("shape_or_dtype" not in kwargs): + kwargs["shape_or_dtype"] = kwargs.pop("size") + elif len(args) >= 2 and _is_int_or_scalar_tensor(args[1]): + if all(_is_int_or_scalar_tensor(arg) for arg in args[1:]): + kwargs["x"] = args[0] + kwargs['shape_or_dtype'] = list(args[1:]) + args = () + return func(*args, **kwargs) - return decorator + wrapper.__signature__ = inspect.signature(func) + return wrapper class ForbidKeywordsDecorator(DecoratorBase): @@ -579,9 +619,9 @@ def process( return args, kwargs -def reshape_decorator() -> Callable[ - [Callable[_InputT, _RetT]], Callable[_InputT, _RetT] -]: +def reshape_decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: """ Usage Example: paddle.reshape(x=tensor_x, shape=[-1, 1, 3], name=None) @@ -590,27 +630,24 @@ def reshape_decorator() -> Callable[ tensor_x.reshape(-1, 1, 3) -> paddle.reshape(tensor_x, -1, 1, 3]) """ - def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: - @functools.wraps(func) - def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: - if ("input" in kwargs) and ("x" not in kwargs): - kwargs["x"] = kwargs.pop("input") - elif len(args) >= 2 and _is_int_or_scalar_tensor(args[1]): - if all(_is_int_or_scalar_tensor(arg) for arg in args[1:]): - kwargs["x"] = args[0] - kwargs['shape'] = list(args[1:]) - args = () - return func(*args, **kwargs) - - wrapper.__signature__ = inspect.signature(func) - return wrapper + @functools.wraps(func) + def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: + if ("input" in kwargs) and ("x" not in kwargs): + kwargs["x"] = kwargs.pop("input") + elif len(args) >= 2 and _is_int_or_scalar_tensor(args[1]): + if all(_is_int_or_scalar_tensor(arg) for arg in args[1:]): + kwargs["x"] = args[0] + kwargs['shape'] = list(args[1:]) + args = () + return func(*args, **kwargs) - return decorator + wrapper.__signature__ = inspect.signature(func) + return wrapper -def transpose_decorator() -> Callable[ - [Callable[_InputT, _RetT]], Callable[_InputT, _RetT] -]: +def transpose_decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: """ Usage Example: PyTorch: @@ -619,39 +656,36 @@ def transpose_decorator() -> Callable[ paddle.transpose(x, perm=[1, 0, 2]) """ - def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: - @functools.wraps(func) - def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: - if ("input" in kwargs) and ("x" not in kwargs): - kwargs["x"] = kwargs.pop("input") - - dim0 = kwargs.pop("dim0", kwargs.pop("axis0", None)) - dim1 = kwargs.pop("dim1", kwargs.pop("axis1", None)) + @functools.wraps(func) + def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: + if ("input" in kwargs) and ("x" not in kwargs): + kwargs["x"] = kwargs.pop("input") - if dim0 is None and len(args) > 1 and isinstance(args[1], int): - dim0 = args[1] - if dim1 is None and len(args) > 2 and isinstance(args[2], int): - dim1 = args[2] + dim0 = kwargs.pop("dim0", kwargs.pop("axis0", None)) + dim1 = kwargs.pop("dim1", kwargs.pop("axis1", None)) - if dim0 is not None and dim1 is not None: - ndim = kwargs["x"].ndim if "x" in kwargs else args[0].ndim - perm = list(range(ndim)) - perm[dim0], perm[dim1] = perm[dim1], perm[dim0] - kwargs["perm"] = perm - if len(args) > 1: - args = (args[0],) + if dim0 is None and len(args) > 1 and isinstance(args[1], int): + dim0 = args[1] + if dim1 is None and len(args) > 2 and isinstance(args[2], int): + dim1 = args[2] - return func(*args, **kwargs) + if dim0 is not None and dim1 is not None: + ndim = kwargs["x"].ndim if "x" in kwargs else args[0].ndim + perm = list(range(ndim)) + perm[dim0], perm[dim1] = perm[dim1], perm[dim0] + kwargs["perm"] = perm + if len(args) > 1: + args = (args[0],) - wrapper.__signature__ = inspect.signature(func) - return wrapper + return func(*args, **kwargs) - return decorator + wrapper.__signature__ = inspect.signature(func) + return wrapper -def expand_decorator() -> Callable[ - [Callable[_InputT, _RetT]], Callable[_InputT, _RetT] -]: +def expand_decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: """ Usage Example: paddle.expand(x=tensor_x, shape=[3, 4], name=None) @@ -660,29 +694,26 @@ def expand_decorator() -> Callable[ tensor_x.expand(size=[3, 4]) -> paddle.expand(tensor_x, size=[3, 4]) """ - def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: - @functools.wraps(func) - def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: - if ("input" in kwargs) and ("x" not in kwargs): - kwargs["x"] = kwargs.pop("input") - if ("size" in kwargs) and ("shape" not in kwargs): - kwargs["shape"] = kwargs.pop("size") - elif len(args) >= 2 and _is_int_or_scalar_tensor(args[1]): - if all(_is_int_or_scalar_tensor(arg) for arg in args[1:]): - kwargs["x"] = args[0] - kwargs['shape'] = list(args[1:]) - args = () - return func(*args, **kwargs) - - wrapper.__signature__ = inspect.signature(func) - return wrapper + @functools.wraps(func) + def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: + if ("input" in kwargs) and ("x" not in kwargs): + kwargs["x"] = kwargs.pop("input") + if ("size" in kwargs) and ("shape" not in kwargs): + kwargs["shape"] = kwargs.pop("size") + elif len(args) >= 2 and _is_int_or_scalar_tensor(args[1]): + if all(_is_int_or_scalar_tensor(arg) for arg in args[1:]): + kwargs["x"] = args[0] + kwargs['shape'] = list(args[1:]) + args = () + return func(*args, **kwargs) - return decorator + wrapper.__signature__ = inspect.signature(func) + return wrapper -def tile_decorator() -> Callable[ - [Callable[_InputT, _RetT]], Callable[_InputT, _RetT] -]: +def tile_decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: """ Usage Example: paddle.tile(x=tensor_x, repeat_times=[2, 3], name=None) @@ -691,73 +722,67 @@ def tile_decorator() -> Callable[ tensor_x.tile(2, 3) -> paddle.tile(tensor_x, 2, 3) """ - def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: - @functools.wraps(func) - def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: - if "input" in kwargs: - if "x" in kwargs: - raise ValueError( - "Cannot specify both 'x' and its alias 'input'" - ) - kwargs["x"] = kwargs.pop("input") - - if "dims" in kwargs: - if "repeat_times" in kwargs: - raise ValueError( - "Cannot specify both 'repeat_times' and its alias 'dims'" - ) - kwargs["repeat_times"] = kwargs.pop("dims") + @functools.wraps(func) + def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: + if "input" in kwargs: + if "x" in kwargs: + raise ValueError( + "Cannot specify both 'x' and its alias 'input'" + ) + kwargs["x"] = kwargs.pop("input") - if len(args) >= 2 and isinstance(args[1], int): - kwargs["x"] = args[0] - kwargs["repeat_times"] = list(args[1:]) - args = () - return func(*args, **kwargs) + if "dims" in kwargs: + if "repeat_times" in kwargs: + raise ValueError( + "Cannot specify both 'repeat_times' and its alias 'dims'" + ) + kwargs["repeat_times"] = kwargs.pop("dims") - wrapper.__signature__ = inspect.signature(func) - return wrapper + if len(args) >= 2 and isinstance(args[1], int): + kwargs["x"] = args[0] + kwargs["repeat_times"] = list(args[1:]) + args = () + return func(*args, **kwargs) - return decorator + wrapper.__signature__ = inspect.signature(func) + return wrapper -def index_select_decorator() -> Callable[ - [Callable[_InputT, _RetT]], Callable[_InputT, _RetT] -]: +def index_select_decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: """ Usage Example: PyTorch: torch.index_select(input, dim, index) Paddle: paddle.index_select(x, index, axis) """ - def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: - @functools.wraps(func) - def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: - if "input" in kwargs and "x" not in kwargs: - kwargs["x"] = kwargs.pop("input") - if "dim" in kwargs and "axis" not in kwargs: - kwargs["axis"] = kwargs.pop("dim") - if len(args) >= 2 and isinstance(args[1], int): - if len(args) < 3 and "index" not in kwargs: - raise TypeError( - "index_select() missing 1 required argument: 'index'" - ) - input_tensor = args[0] - dim_or_axis = args[1] - if "x" not in kwargs: - kwargs["x"] = input_tensor - if "axis" not in kwargs: - kwargs["axis"] = dim_or_axis - if len(args) > 2 and "index" not in kwargs: - kwargs["index"] = args[2] - args = args[3:] - else: - args = args[2:] - return func(*args, **kwargs) - - wrapper.__signature__ = inspect.signature(func) - return wrapper + @functools.wraps(func) + def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: + if "input" in kwargs and "x" not in kwargs: + kwargs["x"] = kwargs.pop("input") + if "dim" in kwargs and "axis" not in kwargs: + kwargs["axis"] = kwargs.pop("dim") + if len(args) >= 2 and isinstance(args[1], int): + if len(args) < 3 and "index" not in kwargs: + raise TypeError( + "index_select() missing 1 required argument: 'index'" + ) + input_tensor = args[0] + dim_or_axis = args[1] + if "x" not in kwargs: + kwargs["x"] = input_tensor + if "axis" not in kwargs: + kwargs["axis"] = dim_or_axis + if len(args) > 2 and "index" not in kwargs: + kwargs["index"] = args[2] + args = args[3:] + else: + args = args[2:] + return func(*args, **kwargs) - return decorator + wrapper.__signature__ = inspect.signature(func) + return wrapper def legacy_reduction_decorator( @@ -852,95 +877,86 @@ def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: return decorate -def index_add_decorator() -> Callable[ - [Callable[_InputT, _RetT]], Callable[_InputT, _RetT] -]: - def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: - @functools.wraps(func) - def wrapper(*args, **kwargs) -> _RetT: - if "input" in kwargs: - kwargs["x"] = kwargs.pop("input") - if "dim" in kwargs: - kwargs["axis"] = kwargs.pop("dim") - if "source" in kwargs: - kwargs["value"] = kwargs.pop("source") - - if len(args) >= 2 and isinstance(args[1], int): - kwargs["x"] = args[0] - kwargs["axis"] = args[1] - if len(args) > 2: - kwargs["index"] = args[2] - if len(args) > 3: - kwargs["value"] = args[3] - args = () - - return func(*args, **kwargs) - - wrapper.__signature__ = inspect.signature(func) - return wrapper - - return decorator +def index_add_decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: + @functools.wraps(func) + def wrapper(*args, **kwargs) -> _RetT: + if "input" in kwargs: + kwargs["x"] = kwargs.pop("input") + if "dim" in kwargs: + kwargs["axis"] = kwargs.pop("dim") + if "source" in kwargs: + kwargs["value"] = kwargs.pop("source") + if len(args) >= 2 and isinstance(args[1], int): + kwargs["x"] = args[0] + kwargs["axis"] = args[1] + if len(args) > 2: + kwargs["index"] = args[2] + if len(args) > 3: + kwargs["value"] = args[3] + args = () -def maxpool_decorator() -> Callable[ - [Callable[_InputT, _RetT]], Callable[_InputT, _RetT] -]: - def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: - @functools.wraps(func) - def wrapper(*args, **kwargs) -> _RetT: - if "input" in kwargs: - kwargs["x"] = kwargs.pop("input") - if "return_indices" in kwargs: - kwargs["return_mask"] = kwargs.pop("return_indices") + return func(*args, **kwargs) - if len(args) >= 5 and not isinstance(args[4], bool): - kwargs["x"] = args[0] - kwargs["kernel_size"] = args[1] - kwargs["stride"] = args[2] - kwargs["padding"] = args[3] - kwargs["dilation"] = args[4] - # The order of `ceil_mode` and `return_indices` is different from nn.MaxPool in PyTorch - if len(args) > 5: - kwargs["ceil_mode"] = args[5] - if len(args) > 6: - kwargs["return_mask"] = args[6] - args = () + wrapper.__signature__ = inspect.signature(func) + return wrapper - return func(*args, **kwargs) - wrapper.__signature__ = inspect.signature(func) - return wrapper +def maxpool_decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: + @functools.wraps(func) + def wrapper(*args, **kwargs) -> _RetT: + if "input" in kwargs: + kwargs["x"] = kwargs.pop("input") + if "return_indices" in kwargs: + kwargs["return_mask"] = kwargs.pop("return_indices") + + if len(args) >= 5 and not isinstance(args[4], bool): + kwargs["x"] = args[0] + kwargs["kernel_size"] = args[1] + kwargs["stride"] = args[2] + kwargs["padding"] = args[3] + kwargs["dilation"] = args[4] + # The order of `ceil_mode` and `return_indices` is different from nn.MaxPool in PyTorch + if len(args) > 5: + kwargs["ceil_mode"] = args[5] + if len(args) > 6: + kwargs["return_mask"] = args[6] + args = () - return decorator + return func(*args, **kwargs) + wrapper.__signature__ = inspect.signature(func) + return wrapper -def maxpool_layer_decorator() -> Callable[ - [Callable[_InputT, _RetT]], Callable[_InputT, _RetT] -]: - def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: - @functools.wraps(func) - def wrapper(*args, **kwargs) -> _RetT: - if "return_indices" in kwargs: - kwargs["return_mask"] = kwargs.pop("return_indices") - - if len(args) >= 5 and not isinstance(args[4], bool): - kwargs["kernel_size"] = args[1] - kwargs["stride"] = args[2] - kwargs["padding"] = args[3] - kwargs["dilation"] = args[4] - # The order of `ceil_mode` and `return_indices` is different from F.max_pool in PyTorch - if len(args) > 5: - kwargs["return_mask"] = args[5] - if len(args) > 6: - kwargs["ceil_mode"] = args[6] - args = (args[0],) - return func(*args, **kwargs) +def maxpool_layer_decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: + @functools.wraps(func) + def wrapper(*args, **kwargs) -> _RetT: + if "return_indices" in kwargs: + kwargs["return_mask"] = kwargs.pop("return_indices") + + if len(args) >= 5 and not isinstance(args[4], bool): + kwargs["kernel_size"] = args[1] + kwargs["stride"] = args[2] + kwargs["padding"] = args[3] + kwargs["dilation"] = args[4] + # The order of `ceil_mode` and `return_indices` is different from F.max_pool in PyTorch + if len(args) > 5: + kwargs["return_mask"] = args[5] + if len(args) > 6: + kwargs["ceil_mode"] = args[6] + args = (args[0],) - wrapper.__signature__ = inspect.signature(func) - return wrapper + return func(*args, **kwargs) - return decorator + wrapper.__signature__ = inspect.signature(func) + return wrapper def use_first_signature( @@ -991,9 +1007,9 @@ def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: return decorator -def grad_scaler_decorator() -> Callable[ - [Callable[_InputT, _RetT]], Callable[_InputT, _RetT] -]: +def grad_scaler_decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: """Decorator for GradScaler.__init__ to support three calling conventions: GradScaler(enable, init_loss_scaling, incr_ratio, decr_ratio, incr_every_n_steps, decr_every_n_nan_or_inf, use_dynamic_loss_scaling) @@ -1027,50 +1043,47 @@ def _remap_kwargs(kwargs: dict[str, Any]) -> None: f"Cannot specify both '{paddle_key}' and its alias '{torch_key}'" ) - def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: - @functools.wraps(func) - def wrapper(*args: Any, **kwargs: Any) -> _RetT: - # args[0] is always `self` for a bound __init__ call - real_args = args[1:] - - # Drop PyTorch-only 'device' kwarg (no Paddle equivalent) - kwargs.pop('device', None) - - # Remap PyTorch keyword aliases to Paddle names unconditionally - _remap_kwargs(kwargs) - - if real_args and isinstance(real_args[0], str): - # PyTorch with device prefix: GradScaler('cuda', init_scale=1024, ...) - # Strip device; remaining positional follow torch order - torch_pos = real_args[1:] - args = args[:1] # keep only self - for i, val in enumerate(torch_pos): - if i < len(_TORCH_POS_NAMES): - name = _TORCH_POS_NAMES[i] - if name not in kwargs: - kwargs[name] = val - elif real_args and not isinstance(real_args[0], bool): - # PyTorch positional without device: GradScaler(1024, 2.0, 0.5, ...) - # int/float but not bool: first arg is init_scale (PyTorch), not enable (Paddle) - args = args[:1] # keep only self - for i, val in enumerate(real_args): - if i < len(_TORCH_POS_NAMES): - name = _TORCH_POS_NAMES[i] - if name not in kwargs: - kwargs[name] = val - # else: Paddle call — pass through unchanged - - return func(*args, **kwargs) + @functools.wraps(func) + def wrapper(*args: Any, **kwargs: Any) -> _RetT: + # args[0] is always `self` for a bound __init__ call + real_args = args[1:] + + # Drop PyTorch-only 'device' kwarg (no Paddle equivalent) + kwargs.pop('device', None) + + # Remap PyTorch keyword aliases to Paddle names unconditionally + _remap_kwargs(kwargs) + + if real_args and isinstance(real_args[0], str): + # PyTorch with device prefix: GradScaler('cuda', init_scale=1024, ...) + # Strip device; remaining positional follow torch order + torch_pos = real_args[1:] + args = args[:1] # keep only self + for i, val in enumerate(torch_pos): + if i < len(_TORCH_POS_NAMES): + name = _TORCH_POS_NAMES[i] + if name not in kwargs: + kwargs[name] = val + elif real_args and not isinstance(real_args[0], bool): + # PyTorch positional without device: GradScaler(1024, 2.0, 0.5, ...) + # int/float but not bool: first arg is init_scale (PyTorch), not enable (Paddle) + args = args[:1] # keep only self + for i, val in enumerate(real_args): + if i < len(_TORCH_POS_NAMES): + name = _TORCH_POS_NAMES[i] + if name not in kwargs: + kwargs[name] = val + # else: Paddle call — pass through unchanged - wrapper.__signature__ = inspect.signature(func) - return wrapper + return func(*args, **kwargs) - return decorator + wrapper.__signature__ = inspect.signature(func) + return wrapper -def index_fill_decorator() -> Callable[ - [Callable[_InputT, _RetT]], Callable[_InputT, _RetT] -]: +def index_fill_decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: """ Decorator for index_fill API to handle parameter name and order differences. @@ -1079,232 +1092,208 @@ def index_fill_decorator() -> Callable[ Paddle: paddle.index_fill(x, index, axis, value) """ - def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: - @functools.wraps(func) - def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: - # Handle keyword argument aliases - if "input" in kwargs and "x" not in kwargs: - kwargs["x"] = kwargs.pop("input") - if "dim" in kwargs and "axis" not in kwargs: - kwargs["axis"] = kwargs.pop("dim") - - # Handle PyTorch positional argument order: (input, dim, index, value) - # Paddle order: (x, index, axis, value) - if len(args) >= 2 and isinstance(args[1], int): - # PyTorch order detected - kwargs["x"] = args[0] - kwargs["axis"] = args[1] - if len(args) > 2: - kwargs["index"] = args[2] - if len(args) > 3: - kwargs["value"] = args[3] - args = args[4:] if len(args) > 4 else () + @functools.wraps(func) + def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: + # Handle keyword argument aliases + if "input" in kwargs and "x" not in kwargs: + kwargs["x"] = kwargs.pop("input") + if "dim" in kwargs and "axis" not in kwargs: + kwargs["axis"] = kwargs.pop("dim") - return func(*args, **kwargs) + # Handle PyTorch positional argument order: (input, dim, index, value) + # Paddle order: (x, index, axis, value) + if len(args) >= 2 and isinstance(args[1], int): + # PyTorch order detected + kwargs["x"] = args[0] + kwargs["axis"] = args[1] + if len(args) > 2: + kwargs["index"] = args[2] + if len(args) > 3: + kwargs["value"] = args[3] + args = args[4:] if len(args) > 4 else () - wrapper.__signature__ = inspect.signature(func) - return wrapper + return func(*args, **kwargs) - return decorator + wrapper.__signature__ = inspect.signature(func) + return wrapper -def tensor_cuda_decorator() -> Callable[ - [Callable[_InputT, _RetT]], Callable[_InputT, _RetT] -]: +def tensor_cuda_decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: """ Usage Example: PyTorch: Tensor.cuda(device: DeviceLike, non_blocking: bool = False) Paddle: Tensor.cuda(device_id: DeviceLike, blocking: bool = True) """ - def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: - @functools.wraps(func) - def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: - if "device" in kwargs: - if "device_id" not in kwargs: - kwargs["device_id"] = kwargs.pop("device") - else: - raise ValueError( - "Cannot specify both 'device' and its alias 'device_id'." - ) - - if "non_blocking" in kwargs: - if "blocking" not in kwargs: - kwargs["blocking"] = not (kwargs.pop("non_blocking")) - else: - raise ValueError( - "Cannot specify both 'blocking' and 'non_blocking'." - ) + @functools.wraps(func) + def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: + if "device" in kwargs: + if "device_id" not in kwargs: + kwargs["device_id"] = kwargs.pop("device") + else: + raise ValueError( + "Cannot specify both 'device' and its alias 'device_id'." + ) - if len(args) >= 3 and isinstance(args[1], str): - # using pytorch signature - # args[0] is self - if "device_id" not in kwargs: - kwargs["device_id"] = args[1] - if "blocking" not in kwargs: - kwargs["blocking"] = not args[2] - if len(args) > 3: - raise ValueError("cuda() received too many arguments") - args = args[:1] - return func(*args, **kwargs) + if "non_blocking" in kwargs: + if "blocking" not in kwargs: + kwargs["blocking"] = not (kwargs.pop("non_blocking")) + else: + raise ValueError( + "Cannot specify both 'blocking' and 'non_blocking'." + ) - wrapper.__signature__ = inspect.signature(func) - return wrapper + if len(args) >= 3 and isinstance(args[1], str): + # using pytorch signature + # args[0] is self + if "device_id" not in kwargs: + kwargs["device_id"] = args[1] + if "blocking" not in kwargs: + kwargs["blocking"] = not args[2] + if len(args) > 3: + raise ValueError("cuda() received too many arguments") + args = args[:1] + return func(*args, **kwargs) - return decorator + wrapper.__signature__ = inspect.signature(func) + return wrapper -def batch_sampler_decorator() -> Callable[ - [Callable[_InputT, _RetT]], Callable[_InputT, _RetT] -]: +def batch_sampler_decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: """ Usage Example: PyTorch: torch.utils.data.BatchSampler(sampler, batch_size, drop_last) Paddle: paddle.utils.data.BatchSampler(dataset, sampler, shuffle, batch_size, drop_last) """ - def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: - @functools.wraps(func) - def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: - # args[0] is self - # args[1] is Sampler / Iterable, use torch signature - if len(args) >= 2 and isinstance( - args[1], (paddle.io.Sampler, Iterable) - ): - kwargs["sampler"] = args[1] - if len(args) >= 3: - kwargs["batch_size"] = args[2] - if len(args) == 4: - kwargs["drop_last"] = args[3] - if len(args) > 4: - raise TypeError( - "BatchSampler() received too many arguments" - ) - args = (args[0],) - return func(*args, **kwargs) - - wrapper.__signature__ = inspect.signature(func) - return wrapper + @functools.wraps(func) + def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: + # args[0] is self + # args[1] is Sampler / Iterable, use torch signature + if len(args) >= 2 and isinstance( + args[1], (paddle.io.Sampler, Iterable) + ): + kwargs["sampler"] = args[1] + if len(args) >= 3: + kwargs["batch_size"] = args[2] + if len(args) == 4: + kwargs["drop_last"] = args[3] + if len(args) > 4: + raise TypeError("BatchSampler() received too many arguments") + args = (args[0],) + return func(*args, **kwargs) - return decorator + wrapper.__signature__ = inspect.signature(func) + return wrapper -def lr_scheduler_decorator() -> Callable[ - [Callable[_InputT, _RetT]], Callable[_InputT, _RetT] -]: +def lr_scheduler_decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: """ Usage Example: PyTorch: __init__(self, optimizer, last_epoch) -> None: Paddle: __init__(self, learning_rate, last_epoch, verbose) -> None: """ - def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: - @functools.wraps(func) - def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: - opt = None - if "optimizer" in kwargs: - if "learning_rate" not in kwargs: - opt = kwargs.pop("optimizer") - kwargs["learning_rate"] = opt.get_lr() - else: - raise ValueError( - "Cannot specify both 'learning_rate' and 'optimizer'." - ) - elif len(args) > 1 and isinstance( - args[1], paddle.optimizer.Optimizer - ): - opt = args[1] - args_list = list(args) - args_list[1] = opt.get_lr() - args = tuple(args_list) - func(*args, **kwargs) - if opt is not None: - opt.set_lr_scheduler(args[0]) - - wrapper.__signature__ = inspect.signature(func) - return wrapper + @functools.wraps(func) + def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: + opt = None + if "optimizer" in kwargs: + if "learning_rate" not in kwargs: + opt = kwargs.pop("optimizer") + kwargs["learning_rate"] = opt.get_lr() + else: + raise ValueError( + "Cannot specify both 'learning_rate' and 'optimizer'." + ) + elif len(args) > 1 and isinstance(args[1], paddle.optimizer.Optimizer): + opt = args[1] + args_list = list(args) + args_list[1] = opt.get_lr() + args = tuple(args_list) + func(*args, **kwargs) + if opt is not None: + opt.set_lr_scheduler(args[0]) - return decorator + wrapper.__signature__ = inspect.signature(func) + return wrapper -def fill_diagonal_inplace_decorator() -> Callable[ - [Callable[_InputT, _RetT]], Callable[_InputT, _RetT] -]: +def fill_diagonal_inplace_decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: """ Usage Example: PyTorch: torch.Tensor.fill_diagonal_(fill_value, wrap=False) Paddle: paddle.Tensor.fill_diagonal_(value, offset, wrap) """ - def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: - @functools.wraps(func) - def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: - if "fill_value" in kwargs: - if "value" not in kwargs: - kwargs["value"] = kwargs.pop("fill_value") - else: - raise ValueError( - "Cannot specify both 'value' and its alias 'fill_value'." - ) - - # args[0] is x (tensor) - # args[1] is fill_value - # args[2] is wrap, use torch signature - if len(args) >= 3 and isinstance(args[2], bool): - kwargs["wrap"] = args[2] - if len(args) > 3: - raise TypeError( - "fill_diagonal_() received too many arguments" - ) - args = (args[0], args[1]) - return func(*args, **kwargs) + @functools.wraps(func) + def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: + if "fill_value" in kwargs: + if "value" not in kwargs: + kwargs["value"] = kwargs.pop("fill_value") + else: + raise ValueError( + "Cannot specify both 'value' and its alias 'fill_value'." + ) - wrapper.__signature__ = inspect.signature(func) - return wrapper + # args[0] is x (tensor) + # args[1] is fill_value + # args[2] is wrap, use torch signature + if len(args) >= 3 and isinstance(args[2], bool): + kwargs["wrap"] = args[2] + if len(args) > 3: + raise TypeError("fill_diagonal_() received too many arguments") + args = (args[0], args[1]) + return func(*args, **kwargs) - return decorator + wrapper.__signature__ = inspect.signature(func) + return wrapper -def nansum_decorator() -> Callable[ - [Callable[_InputT, _RetT]], Callable[_InputT, _RetT] -]: +def nansum_decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: """ Usage Example: PyTorch: torch.nansum(input, dim=None, keepdim=False, *, dtype=None, out=None) Paddle: paddle.nansum(x, axis=None, dtype=None, keepdim=False, name=None, *, out=None) """ - def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: - @functools.wraps(func) - def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: - if "input" in kwargs: - if "x" not in kwargs: - kwargs["x"] = kwargs.pop("input") - else: - raise ValueError( - "Cannot specify both 'x' and its alias 'input'." - ) - - if "dim" in kwargs: - if "axis" not in kwargs: - kwargs["axis"] = kwargs.pop("dim") - else: - raise ValueError( - "Cannot specify both 'axis' and its alias 'dim'." - ) + @functools.wraps(func) + def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: + if "input" in kwargs: + if "x" not in kwargs: + kwargs["x"] = kwargs.pop("input") + else: + raise ValueError( + "Cannot specify both 'x' and its alias 'input'." + ) - # args[0] is x - # args[1] is axis - # args[2] is keepdim, use torch signature - if len(args) == 3 and isinstance(args[2], bool): - kwargs["keepdim"] = args[2] - args = (args[0], args[1]) - return func(*args, **kwargs) + if "dim" in kwargs: + if "axis" not in kwargs: + kwargs["axis"] = kwargs.pop("dim") + else: + raise ValueError( + "Cannot specify both 'axis' and its alias 'dim'." + ) - wrapper.__signature__ = inspect.signature(func) - return wrapper + # args[0] is x + # args[1] is axis + # args[2] is keepdim, use torch signature + if len(args) == 3 and isinstance(args[2], bool): + kwargs["keepdim"] = args[2] + args = (args[0], args[1]) + return func(*args, **kwargs) - return decorator + wrapper.__signature__ = inspect.signature(func) + return wrapper def _calc_end_from_shapes(x, value, axes, starts, strides): @@ -1320,9 +1309,9 @@ def _calc_end_from_shapes(x, value, axes, starts, strides): return ends -def slice_scatter_decorator() -> Callable[ - [Callable[_InputT, _RetT]], Callable[_InputT, _RetT] -]: +def slice_scatter_decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: """ Decorator for slice_scatter to support PyTorch signature. @@ -1335,92 +1324,87 @@ def slice_scatter_decorator() -> Callable[ 3. Handle PyTorch style positional args """ - def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: - @functools.wraps(func) - def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: - # 1. Handle keyword argument aliases - if "input" in kwargs and "x" not in kwargs: - kwargs["x"] = kwargs.pop("input") - if "src" in kwargs and "value" not in kwargs: - kwargs["value"] = kwargs.pop("src") - if "dim" in kwargs and "axes" not in kwargs: - kwargs["axes"] = kwargs.pop("dim") - if "start" in kwargs and "starts" not in kwargs: - kwargs["starts"] = kwargs.pop("start") - if "end" in kwargs and "ends" not in kwargs: - kwargs["ends"] = kwargs.pop("end") - if "step" in kwargs and "strides" not in kwargs: - kwargs["strides"] = kwargs.pop("step") - - # 2. Handle positional arguments - # PyTorch: (input, src, dim, start, end, step) - dim is int - # Paddle: (x, value, axes, starts, ends, strides) - axes is list - if len(args) >= 2: - kwargs["x"] = args[0] - kwargs["value"] = args[1] - - if len(args) > 2: - # Check if Paddle style (axes is list) or PyTorch style (dim is int) - if isinstance(args[2], list): - # Paddle style - for i, key in enumerate( - ["axes", "starts", "ends", "strides"] - ): - if len(args) > i + 2: - kwargs[key] = args[i + 2] - else: - # PyTorch style: convert int to list - if len(args) > 2: - kwargs["axes"] = [args[2]] - if len(args) > 3: - kwargs["starts"] = [args[3]] - if len(args) > 4: - kwargs["ends"] = [args[4]] - if len(args) > 5: - kwargs["strides"] = [args[5]] - args = () - - # 3. Convert single int to list for keyword args - for key in ["axes", "starts", "ends", "strides"]: - if key in kwargs and isinstance(kwargs[key], int): - kwargs[key] = [kwargs[key]] + @functools.wraps(func) + def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: + # 1. Handle keyword argument aliases + if "input" in kwargs and "x" not in kwargs: + kwargs["x"] = kwargs.pop("input") + if "src" in kwargs and "value" not in kwargs: + kwargs["value"] = kwargs.pop("src") + if "dim" in kwargs and "axes" not in kwargs: + kwargs["axes"] = kwargs.pop("dim") + if "start" in kwargs and "starts" not in kwargs: + kwargs["starts"] = kwargs.pop("start") + if "end" in kwargs and "ends" not in kwargs: + kwargs["ends"] = kwargs.pop("end") + if "step" in kwargs and "strides" not in kwargs: + kwargs["strides"] = kwargs.pop("step") + + # 2. Handle positional arguments + # PyTorch: (input, src, dim, start, end, step) - dim is int + # Paddle: (x, value, axes, starts, ends, strides) - axes is list + if len(args) >= 2: + kwargs["x"] = args[0] + kwargs["value"] = args[1] + + if len(args) > 2: + # Check if Paddle style (axes is list) or PyTorch style (dim is int) + if isinstance(args[2], list): + # Paddle style + for i, key in enumerate( + ["axes", "starts", "ends", "strides"] + ): + if len(args) > i + 2: + kwargs[key] = args[i + 2] + else: + # PyTorch style: convert int to list + if len(args) > 2: + kwargs["axes"] = [args[2]] + if len(args) > 3: + kwargs["starts"] = [args[3]] + if len(args) > 4: + kwargs["ends"] = [args[4]] + if len(args) > 5: + kwargs["strides"] = [args[5]] + args = () - return func(*args, **kwargs) + # 3. Convert single int to list for keyword args + for key in ["axes", "starts", "ends", "strides"]: + if key in kwargs and isinstance(kwargs[key], int): + kwargs[key] = [kwargs[key]] - wrapper.__signature__ = inspect.signature(func) - return wrapper + return func(*args, **kwargs) - return decorator + wrapper.__signature__ = inspect.signature(func) + return wrapper -def resize__decorator() -> Callable[ - [Callable[_InputT, _RetT]], Callable[_InputT, _RetT] -]: +def resize__decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: """Decorator for resize_ to support PyTorch-style variable args (*sizes).""" - def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: - @functools.wraps(func) - def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: - # Handle PyTorch-style variable args: x.resize_(2, 3, 4) -> x.resize_([2, 3, 4]) - kwargs.pop('memory_format', None) - if len(args) >= 2: - # args[0] is self (x), args[1:] are the sizes - x = args[0] - sizes = args[1:] - # Check if all sizes are integers (variable args mode) - if all(isinstance(s, int) for s in sizes): - kwargs['shape'] = list(sizes) - args = (x,) - return func(*args, **kwargs) - - return wrapper + @functools.wraps(func) + def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: + # Handle PyTorch-style variable args: x.resize_(2, 3, 4) -> x.resize_([2, 3, 4]) + kwargs.pop('memory_format', None) + if len(args) >= 2: + # args[0] is self (x), args[1:] are the sizes + x = args[0] + sizes = args[1:] + # Check if all sizes are integers (variable args mode) + if all(isinstance(s, int) for s in sizes): + kwargs['shape'] = list(sizes) + args = (x,) + return func(*args, **kwargs) - return decorator + wrapper.__signature__ = inspect.signature(func) + return wrapper -def gru_decorator() -> Callable[ - [Callable[_InputT, _RetT]], Callable[_InputT, _RetT] -]: +def gru_decorator( + func: Callable[_InputT, _RetT], +) -> Callable[_InputT, _RetT]: """ Dispatch decorator for ``GRU.__init__``. @@ -1438,45 +1422,81 @@ def gru_decorator() -> Callable[ time_major=False, dropout=0, ...) """ - def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: - @functools.wraps(func) - def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: - # Detect PyTorch-style positional args: 4th param is bool (bias) - if len(args) >= 5 and isinstance(args[4], bool): - torch_names = ( - "bias", - "batch_first", - "dropout", - "bidirectional", - "device", - "dtype", - ) - for i, name in enumerate(torch_names): - pos = 4 + i - if pos >= len(args): - break - if name in kwargs: - raise TypeError( - f"__init__() got multiple values for argument '{name}'" - ) - kwargs[name] = args[pos] - args = args[:4] - - # Handle batch_first vs time_major (opposite meaning) - if "batch_first" in kwargs and "time_major" not in kwargs: - batch_first = kwargs.pop("batch_first") - kwargs["time_major"] = not batch_first - - # Handle bidirectional vs direction - if "bidirectional" in kwargs and "direction" not in kwargs: - bidirectional = kwargs.pop("bidirectional") - kwargs["direction"] = ( - "bidirectional" if bidirectional else "forward" - ) + @functools.wraps(func) + def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT: + # Detect PyTorch-style positional args: 4th param is bool (bias) + if len(args) >= 5 and isinstance(args[4], bool): + torch_names = ( + "bias", + "batch_first", + "dropout", + "bidirectional", + "device", + "dtype", + ) + for i, name in enumerate(torch_names): + pos = 4 + i + if pos >= len(args): + break + if name in kwargs: + raise TypeError( + f"__init__() got multiple values for argument '{name}'" + ) + kwargs[name] = args[pos] + args = args[:4] - return func(*args, **kwargs) + # Handle batch_first vs time_major (opposite meaning) + if "batch_first" in kwargs and "time_major" not in kwargs: + batch_first = kwargs.pop("batch_first") + kwargs["time_major"] = not batch_first - wrapper.__signature__ = inspect.signature(func) - return wrapper + # Handle bidirectional vs direction + if "bidirectional" in kwargs and "direction" not in kwargs: + bidirectional = kwargs.pop("bidirectional") + kwargs["direction"] = ( + "bidirectional" if bidirectional else "forward" + ) - return decorator + return func(*args, **kwargs) + + wrapper.__signature__ = inspect.signature(func) + return wrapper + + +def qr_decorator(func): + """ + Decorator for ``qr`` that handles parameter aliases and type-based dispatch + between PyTorch and Paddle signatures. + + This decorator handles: + - ``input`` -> ``x`` (parameter name alias) + - Type-based dispatch on the 2nd positional argument: + - If ``bool``, treat as ``some`` -> convert to ``mode`` (``True`` -> ``'reduced'``, ``False`` -> ``'complete'``) + - If ``str``, treat as ``mode`` (pass through) + - ``some`` keyword -> ``mode`` keyword conversion + """ + + @functools.wraps(func) + def wrapper(*args, **kwargs): + # Handle parameter aliases for x + if "input" in kwargs: + kwargs["x"] = kwargs.pop("input") + if "A" in kwargs: + kwargs["x"] = kwargs.pop("A") + + # Handle some -> mode keyword conversion + if "some" in kwargs: + some = kwargs.pop("some") + kwargs["mode"] = "reduced" if some else "complete" + + # Type-based dispatch on 2nd positional argument + if len(args) >= 2 and isinstance(args[1], bool): + # PyTorch-style: args = (input, some, ...) + some = args[1] + mode = "reduced" if some else "complete" + args = (args[0], mode, *args[2:]) + + return func(*args, **kwargs) + + wrapper.__signature__ = inspect.signature(func) + return wrapper diff --git a/test/legacy_test/test_api_compatibility_part1.py b/test/legacy_test/test_api_compatibility_part1.py index 0ee0637d4605d..523f4b5a617b4 100644 --- a/test/legacy_test/test_api_compatibility_part1.py +++ b/test/legacy_test/test_api_compatibility_part1.py @@ -2739,5 +2739,86 @@ def test_set_epoch(self): self.assertEqual(len(batches0), len(batches1)) +# Edit By AI Agent +# Test expand_copy compatibility +class TestExpandCopyAPI(unittest.TestCase): + def setUp(self): + paddle.disable_static() + self.x = paddle.to_tensor([1, 2, 3], dtype='int32') + + def test_dygraph(self): + paddle.disable_static() + # Test 1: positional arguments + out1 = paddle.expand_copy(self.x, shape=[2, 3]) + self.assertEqual(out1.shape, [2, 3]) + + # Test 2: keyword arguments (PyTorch alias) + out2 = paddle.expand_copy(x=self.x, shape=[2, 3]) + self.assertEqual(out2.shape, [2, 3]) + + # Test 3: Tensor method + out3 = self.x.expand_copy(shape=[2, 3]) + self.assertEqual(out3.shape, [2, 3]) + + # Test 4: expand_copy with -1 (keep dim) + out4 = paddle.expand_copy(self.x, shape=[2, -1]) + self.assertEqual(out4.shape, [2, 3]) + + # Test 5: expand_copy with same shape (no-op) + out5 = paddle.expand_copy(self.x, shape=[3]) + self.assertEqual(out5.shape, [3]) + + # Verify that result equals expand + ref = paddle.expand(self.x, shape=[2, 3]) + self.assertTrue(paddle.equal_all(out1, ref)) + + # Verify stop_gradient + x = paddle.to_tensor([1.0, 2.0, 3.0], stop_gradient=False) + out = paddle.expand_copy(x, shape=[2, 3]) + self.assertFalse(out.stop_gradient) + + # Test 6: expand_decorator alias: input -> x + out6 = paddle.expand_copy(input=self.x, shape=[2, 3]) + self.assertEqual(out6.shape, [2, 3]) + self.assertTrue(paddle.equal_all(out1, out6)) + + # Test 7: expand_decorator alias: size -> shape + out7 = paddle.expand_copy(self.x, size=[2, 3]) + self.assertEqual(out7.shape, [2, 3]) + self.assertTrue(paddle.equal_all(out1, out7)) + + # Test 8: expand_decorator alias: both input and size aliases + out8 = paddle.expand_copy(input=self.x, size=[2, 3]) + self.assertEqual(out8.shape, [2, 3]) + self.assertTrue(paddle.equal_all(out1, out8)) + + # Test 9: expand_decorator variable positional int args + out9 = paddle.expand_copy(self.x, 2, 3) + self.assertEqual(out9.shape, [2, 3]) + self.assertTrue(paddle.equal_all(out1, out9)) + + def test_static_Compatibility(self): + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data(name="x", shape=[3], dtype="int32") + + out1 = paddle.expand_copy(x, shape=[2, 3]) + out2 = paddle.expand_copy(input=x, shape=[2, 3]) + out3 = paddle.expand_copy(x, size=[2, 3]) + + exe = paddle.static.Executor() + np_x = np.array([1, 2, 3]).astype("int32") + fetches = exe.run( + main, + feed={"x": np_x}, + fetch_list=[out1, out2, out3], + ) + expected = np.broadcast_to(np_x, (2, 3)) + for out in fetches: + np.testing.assert_array_equal(out, expected) + + if __name__ == '__main__': unittest.main() diff --git a/test/legacy_test/test_api_compatibility_part2.py b/test/legacy_test/test_api_compatibility_part2.py index 85b498f44de23..59c6dc7c259e9 100644 --- a/test/legacy_test/test_api_compatibility_part2.py +++ b/test/legacy_test/test_api_compatibility_part2.py @@ -652,6 +652,85 @@ def test_static_Compatibility(self): np.testing.assert_allclose(out, ref_out, rtol=1e-5) +class TestLinalgCrossAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + # Shape [3, 2, 3] ensures default dim=-1 (last dim=2) is distinct from auto-axis (first len-3 dim=0) + # Both dim 0 and dim 2 have size 3, so cross is valid on both + self.np_x = np.random.rand(3, 2, 3).astype('float32') + self.np_y = np.random.rand(3, 2, 3).astype('float32') + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor(self.np_x) + y = paddle.to_tensor(self.np_y) + + # 1. linalg.cross with default dim=-1 + out1 = paddle.linalg.cross(x, y) + # 2. linalg.cross with explicit dim=-1 + out2 = paddle.linalg.cross(x, y, dim=-1) + # 3. linalg.cross using input/other/dim PyTorch-style keywords, dim=2 + out3 = paddle.linalg.cross(input=x, other=y, dim=2) + # 4. Mixed arguments + out4 = paddle.linalg.cross(x, other=y, dim=0) + + # Verify default is equivalent to dim=-1 + ref_out_neg1 = np.cross( + self.np_x, self.np_y, axisa=-1, axisb=-1, axisc=-1 + ) + np.testing.assert_allclose(out1.numpy(), ref_out_neg1, rtol=1e-5) + np.testing.assert_allclose(out2.numpy(), ref_out_neg1, rtol=1e-5) + + # Verify dim=2 is same as dim=-1 (last dim) + np.testing.assert_allclose(out3.numpy(), ref_out_neg1, rtol=1e-5) + + # Verify dim=0 gives different result + ref_out_0 = np.cross(self.np_x, self.np_y, axisa=0, axisb=0, axisc=0) + np.testing.assert_allclose(out4.numpy(), ref_out_0, rtol=1e-5) + + paddle.enable_static() + + def test_static_Compatibility(self): + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data(name="x", shape=[3, 2, 3], dtype='float32') + y = paddle.static.data(name="y", shape=[3, 2, 3], dtype='float32') + + # 1. linalg.cross with default dim=-1 + out1 = paddle.linalg.cross(x, y) + # 2. linalg.cross with explicit dim=0 + out2 = paddle.linalg.cross(x, y, dim=0) + # 3. linalg.cross using input/other/dim keywords with dim=2 + out3 = paddle.linalg.cross(input=x, other=y, dim=2) + + exe = paddle.static.Executor() + fetches = exe.run( + main, + feed={"x": self.np_x, "y": self.np_y}, + fetch_list=[out1, out2, out3], + ) + + # Verify default is equivalent to dim=-1 + ref_out_neg1 = np.cross( + self.np_x, self.np_y, axisa=-1, axisb=-1, axisc=-1 + ) + np.testing.assert_allclose(fetches[0], ref_out_neg1, rtol=1e-5) + + # Verify dim=0 + ref_out_0 = np.cross( + self.np_x, self.np_y, axisa=0, axisb=0, axisc=0 + ) + np.testing.assert_allclose(fetches[1], ref_out_0, rtol=1e-5) + + # Verify dim=2 + ref_out_2 = np.cross( + self.np_x, self.np_y, axisa=2, axisb=2, axisc=2 + ) + np.testing.assert_allclose(fetches[2], ref_out_2, rtol=1e-5) + + # Test dist compatibility class TestDistAPI(unittest.TestCase): def setUp(self): @@ -2992,6 +3071,86 @@ def test_static_Compatibility(self): np.testing.assert_allclose(out, expected, rtol=1e-6) +class TestELUAPI(unittest.TestCase): + def setUp(self): + self.np_x = np.array([-1.0, 0.0, 1.0, 2.0], dtype="float32") + + def _expected(self): + return np.where( + self.np_x > 0, self.np_x, 1.0 * (np.exp(self.np_x) - 1.0) + ) + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor(self.np_x) + + # 1. Paddle keyword arguments + out1 = paddle.nn.ELU()(x) + # 2. PyTorch positional arguments + out2 = paddle.nn.ELU(1.0)(x) + # 3. PyTorch keyword arguments (alias) + out3 = paddle.nn.ELU(alpha=1.0)(input=x) + # 4. Mixed arguments + out4 = paddle.nn.ELU(alpha=1.0)(x) + # 5. Functional Paddle positional arguments + out5 = paddle.nn.functional.elu(x) + # 6. Functional Paddle keyword arguments + out6 = paddle.nn.functional.elu(x=x, alpha=1.0) + # 7. Functional PyTorch keyword arguments (alias) + out7 = paddle.nn.functional.elu(input=x, alpha=1.0) + + expected = self._expected() + for out in [out1, out2, out3, out4, out5, out6, out7]: + np.testing.assert_allclose(out.numpy(), expected, rtol=1e-6) + + paddle.enable_static() + + def test_dygraph_inplace(self): + paddle.disable_static() + expected = self._expected() + + x = paddle.to_tensor(self.np_x) + out = paddle.nn.ELU(inplace=True)(x) + self.assertIs(out, x) + np.testing.assert_allclose(x.numpy(), expected, rtol=1e-6) + + x = paddle.to_tensor(self.np_x) + out = paddle.nn.functional.elu(x, inplace=True) + self.assertIs(out, x) + np.testing.assert_allclose(x.numpy(), expected, rtol=1e-6) + + paddle.enable_static() + + def test_static_Compatibility(self): + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data( + name="x", shape=self.np_x.shape, dtype=str(self.np_x.dtype) + ) + + # 1. Paddle keyword arguments + out1 = paddle.nn.ELU()(x) + # 2. PyTorch keyword arguments (alias) + out2 = paddle.nn.ELU(alpha=1.0)(input=x) + # 3. Functional Paddle positional arguments + out3 = paddle.nn.functional.elu(x) + # 4. Functional PyTorch keyword arguments (alias) + out4 = paddle.nn.functional.elu(input=x, alpha=1.0) + + exe = paddle.static.Executor() + fetches = exe.run( + main, + feed={"x": self.np_x}, + fetch_list=[out1, out2, out3, out4], + ) + + expected = self._expected() + for out in fetches: + np.testing.assert_allclose(out, expected, rtol=1e-6) + + class TestPReLUAPI(unittest.TestCase): def setUp(self): self.np_x = np.array( diff --git a/test/legacy_test/test_api_compatibility_part5.py b/test/legacy_test/test_api_compatibility_part5.py index 51c730a420282..9158ca6016c6e 100644 --- a/test/legacy_test/test_api_compatibility_part5.py +++ b/test/legacy_test/test_api_compatibility_part5.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys import unittest import numpy as np @@ -43,11 +44,41 @@ def test_dygraph_Compatibility(self): self.assertEqual(out1.dtype, paddle.float32) for out in [out1, out2, out3, out4]: self.assertEqual(out.dtype, paddle.float32) + # Verify numerical correctness + expected = np.histogram(self.np_x, bins=10, range=(0, 10))[ + 0 + ].astype("float32") + if out is out4: + np.testing.assert_allclose(out.numpy(), expected, rtol=1e-5) + else: + np.testing.assert_allclose(out.numpy(), expected, rtol=1e-5) + def test_static_Compatibility(self): paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data(name="x", shape=[100], dtype="float32") + + out1 = paddle.histc(x, bins=10, min=0, max=10) + out2 = paddle.histc(input=x, bins=10, min=0, max=10) + + exe = paddle.static.Executor() + fetches = exe.run( + main, + feed={"x": self.np_x}, + fetch_list=[out1, out2], + ) + expected = np.histogram(self.np_x, bins=10, range=(0, 10))[ + 0 + ].astype("float32") + for out in fetches: + np.testing.assert_allclose(out, expected, rtol=1e-5) + + # Test mvlgamma compatibility (alias for multigammaln) + paddle.disable_static() -# Test mvlgamma compatibility (alias for multigammaln) class TestMvlgammaAPI(unittest.TestCase): def setUp(self): np.random.seed(2025) @@ -68,8 +99,6 @@ def test_dygraph_Compatibility(self): for out in [out1, out2, out3]: self.assertEqual(out.shape, (3,)) - paddle.enable_static() - # Test mvlgamma_ compatibility (inplace) class TestMvlgamma_InplaceAPI(unittest.TestCase): @@ -87,8 +116,6 @@ def test_dygraph_Compatibility(self): # Verify shape unchanged self.assertEqual(x.shape, (3,)) - paddle.enable_static() - # Test negative_ compatibility (alias for neg_) class TestNegative_InplaceAPI(unittest.TestCase): @@ -106,16 +133,14 @@ def test_dygraph_Compatibility(self): expected = -self.np_x np.testing.assert_allclose(x.numpy(), expected, rtol=1e-5) - paddle.enable_static() - # Test to_sparse compatibility (alias for to_sparse_coo) class TestToSparseAPI(unittest.TestCase): def test_dygraph_Compatibility(self): + paddle.disable_static() if paddle.is_compiled_with_xpu(): self.skipTest("sparse ops are not supported on XPU") - paddle.disable_static() dense_x = paddle.to_tensor( [[0, 1, 0, 2], [0, 0, 3, 4]], dtype='float32' ) @@ -125,8 +150,6 @@ def test_dygraph_Compatibility(self): self.assertTrue(sparse_x.is_sparse_coo()) - paddle.enable_static() - # Test special.round compatibility class TestSpecialRoundAPI(unittest.TestCase): @@ -144,8 +167,6 @@ def test_dygraph_Compatibility(self): np.testing.assert_allclose(out1.numpy(), out2.numpy(), rtol=1e-5) - paddle.enable_static() - # Test autograd.enable_grad compatibility class TestAutogradEnableGradAPI(unittest.TestCase): @@ -163,17 +184,14 @@ def test_func(x): np.testing.assert_allclose(y.numpy(), [2.0, 4.0], rtol=1e-5) - paddle.enable_static() - # Test col_indices compatibility (alias for cols) class TestColIndicesAPI(unittest.TestCase): def test_dygraph_Compatibility(self): + paddle.disable_static() if paddle.is_compiled_with_xpu(): self.skipTest("sparse ops are not supported on XPU") - paddle.disable_static() - # Create a sparse CSR tensor crows = paddle.to_tensor([0, 2, 3, 5], dtype='int64') cols = paddle.to_tensor([1, 3, 2, 0, 1], dtype='int64') @@ -188,17 +206,14 @@ def test_dygraph_Compatibility(self): np.testing.assert_array_equal(result1.numpy(), result2.numpy()) - paddle.enable_static() - # Test crow_indices compatibility (alias for crows) class TestCrowIndicesAPI(unittest.TestCase): def test_dygraph_Compatibility(self): + paddle.disable_static() if paddle.is_compiled_with_xpu(): self.skipTest("sparse ops are not supported on XPU") - paddle.disable_static() - # Create a sparse CSR tensor crows = paddle.to_tensor([0, 2, 3, 5], dtype='int64') cols = paddle.to_tensor([1, 3, 2, 0, 1], dtype='int64') @@ -213,8 +228,6 @@ def test_dygraph_Compatibility(self): np.testing.assert_array_equal(result1.numpy(), result2.numpy()) - paddle.enable_static() - # Test take compatibility class TestTakeAPI(unittest.TestCase): @@ -248,8 +261,6 @@ def test_dygraph_Compatibility(self): for out in [out1, out2, out3, out4, out5]: np.testing.assert_allclose(out.numpy(), expected, rtol=1e-5) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -285,20 +296,20 @@ def test_static_Compatibility(self): for out in fetches: np.testing.assert_allclose(out, expected, rtol=1e-5) + # Test matrix_exp compatibility paddle.disable_static() -# Test matrix_exp compatibility class TestMatrixExpAPI(unittest.TestCase): def setUp(self): np.random.seed(2025) self.np_x = np.array([[1.0, 0.0], [0.0, 1.0]]).astype("float32") def test_dygraph_Compatibility(self): + paddle.disable_static() if paddle.is_compiled_with_rocm(): self.skipTest("Skip on DCU due to kernel issue") - paddle.disable_static() x = paddle.to_tensor(self.np_x) # 1. paddle.linalg.matrix_exp @@ -311,8 +322,6 @@ def test_dygraph_Compatibility(self): for out in [out1, out2]: np.testing.assert_allclose(out.numpy(), expected, rtol=1e-5) - paddle.enable_static() - def test_static_Compatibility(self): if paddle.is_compiled_with_rocm(): self.skipTest("Skip on DCU due to kernel issue") @@ -337,10 +346,10 @@ def test_static_Compatibility(self): expected = np.exp(1.0) * np.eye(2) np.testing.assert_allclose(fetches[0], expected, rtol=1e-5) + # Test retain_grad compatibility paddle.disable_static() -# Test retain_grad compatibility class TestRetainGradAPI(unittest.TestCase): def test_dygraph_Compatibility(self): paddle.disable_static() @@ -367,18 +376,15 @@ def test_dygraph_Compatibility(self): # b's gradient should be retained np.testing.assert_allclose(b.grad.numpy(), [2.0, 2.0], rtol=1e-5) - paddle.enable_static() - # Test sparse_mask compatibility class TestSparseMaskAPI(unittest.TestCase): def test_dygraph_Compatibility(self): + paddle.disable_static() # Skip on XPU as sparse_mask is not supported if paddle.is_compiled_with_xpu(): self.skipTest("sparse_mask is not supported on XPU") - paddle.disable_static() - # Create dense tensor x = paddle.to_tensor([[1.0, 2.0], [3.0, 4.0]]) @@ -395,8 +401,6 @@ def test_dygraph_Compatibility(self): result.values().numpy(), [1.0, 4.0], rtol=1e-5 ) - paddle.enable_static() - # Test ParameterList compatibility (values -> parameters alias) class TestParameterListAPI(unittest.TestCase): @@ -452,8 +456,6 @@ def test_dygraph_Compatibility(self): self.assertEqual(len(pl5), 1) self.assertEqual(len(pl6), 2) - paddle.enable_static() - # Test scatter_reduce_ compatibility (inplace) class TestScatterReduce_InplaceAPI(unittest.TestCase): @@ -497,8 +499,6 @@ def test_dygraph_Compatibility(self): self.assertEqual(out1.shape, [2, 3]) np.testing.assert_allclose(out1.numpy(), out2.numpy()) - paddle.enable_static() - # Test xavier_uniform compatibility (alias for xavier_uniform_) class TestXavierUniformAPI(unittest.TestCase): @@ -526,8 +526,6 @@ def test_dygraph_Compatibility(self): self.assertEqual(x1.shape, x3.shape) self.assertEqual(x1.shape, x4.shape) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -546,10 +544,10 @@ def test_static_Compatibility(self): self.assertIsNotNone(x1) self.assertIsNotNone(x2) + # Test sign_ compatibility (inplace) paddle.disable_static() -# Test sign_ compatibility (inplace) class TestSign_InplaceAPI(unittest.TestCase): def setUp(self): np.random.seed(2025) @@ -579,8 +577,6 @@ def test_dygraph_Compatibility(self): for out in [out1, out2, out3, out4]: np.testing.assert_allclose(out.numpy(), expected, rtol=1e-5) - paddle.enable_static() - def test_static_pir_infer_symbolic_shape(self): from paddle.base.libpaddle import pir @@ -662,8 +658,6 @@ def test_dygraph_Compatibility(self): for out in [out9, out10, out11]: np.testing.assert_allclose(out.numpy(), expected_sym, rtol=1e-5) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -747,10 +741,10 @@ def test_static_Compatibility(self): for out in fetches[9:]: np.testing.assert_allclose(out, expected_sym, rtol=1e-5) + # Test nll_loss compatibility (target -> label alias) paddle.disable_static() -# Test nll_loss compatibility (target -> label alias) class TestNllLossAPI(unittest.TestCase): def setUp(self): np.random.seed(2025) @@ -784,8 +778,6 @@ def test_dygraph_Compatibility(self): for out in [out1, out2, out3, out4]: np.testing.assert_allclose(out.numpy(), out1.numpy(), rtol=1e-5) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -815,10 +807,10 @@ def test_static_Compatibility(self): for out in fetches: self.assertEqual(out.shape, ()) + # Test bernoulli_ compatibility (inplace) paddle.disable_static() -# Test bernoulli_ compatibility (inplace) class TestBernoulli_InplaceAPI(unittest.TestCase): def setUp(self): np.random.seed(2025) @@ -853,8 +845,6 @@ def test_dygraph_Compatibility(self): for out in [out1, out2, out3, out4]: self.assertTrue(paddle.all((out == 0) | (out == 1)).item()) - paddle.enable_static() - # Test kl_div compatibility (target -> label alias) class TestKlDivAPI(unittest.TestCase): @@ -889,8 +879,6 @@ def test_dygraph_Compatibility(self): for out in [out1, out2, out3, out4]: np.testing.assert_allclose(out.numpy(), out1.numpy(), rtol=1e-5) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -920,10 +908,10 @@ def test_static_Compatibility(self): for out in fetches: self.assertEqual(out.shape, ()) + # Test hann_window compatibility paddle.disable_static() -# Test hann_window compatibility class TestHannWindowAPI(unittest.TestCase): def test_dygraph_Compatibility(self): paddle.disable_static() @@ -944,8 +932,6 @@ def test_dygraph_Compatibility(self): for out in [out1, out2, out3, out4]: self.assertEqual(out.shape, [512]) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -964,10 +950,10 @@ def test_static_Compatibility(self): for out in fetches: self.assertEqual(out.shape, (512,)) + # Test paddle.float compatibility (dtype alias) paddle.disable_static() -# Test paddle.float compatibility (dtype alias) class TestFloatDtypeAPI(unittest.TestCase): def test_dygraph_Compatibility(self): paddle.disable_static() @@ -983,8 +969,6 @@ def test_dygraph_Compatibility(self): param = paddle.create_parameter(shape=[2, 3], dtype=paddle.float) self.assertEqual(param.dtype, paddle.float32) - paddle.enable_static() - # Test fmod_ compatibility (inplace) class TestFmod_InplaceAPI(unittest.TestCase): @@ -1029,8 +1013,6 @@ def test_dygraph_Compatibility(self): for out in [out1, out2, out3, out4, out5]: np.testing.assert_allclose(out.numpy(), expected, rtol=1e-5) - paddle.enable_static() - # Test fill_diagonal_ compatibility (inplace) class TestFillDiagonal_InplaceAPI(unittest.TestCase): @@ -1064,8 +1046,6 @@ def test_dygraph_Compatibility(self): self.assertEqual(out[1, 1].item(), 1.0) self.assertEqual(out[2, 2].item(), 1.0) - paddle.enable_static() - # Test weight_norm compatibility (module -> layer alias) class TestWeightNormAPI(unittest.TestCase): @@ -1091,8 +1071,6 @@ def test_dygraph_Compatibility(self): self.assertIsNotNone(wn2.weight_g) self.assertIsNotNone(wn3.weight_g) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -1109,10 +1087,10 @@ def test_static_Compatibility(self): self.assertIsNotNone(wn1) self.assertIsNotNone(wn2) + # Test resize_ compatibility (variable args support) paddle.disable_static() -# Test resize_ compatibility (variable args support) class TestResize_InplaceAPI(unittest.TestCase): def setUp(self): np.random.seed(2025) @@ -1133,8 +1111,6 @@ def test_dygraph_Compatibility(self): self.assertEqual(out1.shape, [2, 3]) self.assertEqual(out2.shape, [2, 3]) - paddle.enable_static() - # Test Flatten compatibility (start_dim/end_dim -> start_axis/stop_axis) class TestFlattenAPI(unittest.TestCase): @@ -1167,8 +1143,6 @@ def test_dygraph_Compatibility(self): for out in [out1, out2, out3, out4]: self.assertEqual(out.shape, [2, 60]) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -1198,10 +1172,10 @@ def test_static_Compatibility(self): for out in fetches: self.assertEqual(out.shape, (2, 60)) + # Test L1Loss compatibility (size_average/reduce parameters) paddle.disable_static() -# Test L1Loss compatibility (size_average/reduce parameters) class TestL1LossAPI(unittest.TestCase): def setUp(self): np.random.seed(2025) @@ -1241,8 +1215,6 @@ def test_dygraph_Compatibility(self): self.assertEqual(out4.shape, [3, 5]) self.assertEqual(out5.shape, []) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -1273,10 +1245,10 @@ def test_static_Compatibility(self): for out in fetches: self.assertEqual(out.shape, ()) + # Test linalg.inv compatibility (A -> x alias) paddle.disable_static() -# Test linalg.inv compatibility (A -> x alias) class TestLinalgInvAPI(unittest.TestCase): def setUp(self): np.random.seed(2025) @@ -1317,8 +1289,6 @@ def test_dygraph_Compatibility(self): for out in [out1, out2, out3, out4, out5, out6, out7]: np.testing.assert_allclose(out.numpy(), expected, rtol=1e-5) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -1348,10 +1318,10 @@ def test_static_Compatibility(self): for out in fetches: np.testing.assert_allclose(out, expected, rtol=1e-5) + # Test det compatibility (paddle.det alias) paddle.disable_static() -# Test det compatibility (paddle.det alias) class TestDetAPI(unittest.TestCase): def setUp(self): np.random.seed(2025) @@ -1383,8 +1353,6 @@ def test_dygraph_Compatibility(self): for out in [out1, out2, out3, out4, out5]: np.testing.assert_allclose(out.numpy(), expected, rtol=1e-5) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -1412,10 +1380,10 @@ def test_static_Compatibility(self): for out in fetches: np.testing.assert_allclose(out, expected, rtol=1e-5) + # Test pinverse compatibility (paddle.pinverse alias) paddle.disable_static() -# Test pinverse compatibility (paddle.pinverse alias) class TestPinverseAPI(unittest.TestCase): def setUp(self): np.random.seed(2025) @@ -1472,8 +1440,6 @@ def test_dygraph_Compatibility(self): for out in [out9, out10, out11]: np.testing.assert_allclose(out.numpy(), expected_sym, rtol=1e-5) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -1548,10 +1514,10 @@ def test_static_Compatibility(self): for out in fetches[8:]: np.testing.assert_allclose(out, expected_sym, rtol=1e-5) + # Test addcdiv_ compatibility paddle.disable_static() -# Test addcdiv_ compatibility class TestAddcdiv_InplaceAPI(unittest.TestCase): def setUp(self): np.random.seed(2025) @@ -1598,8 +1564,6 @@ def test_dygraph_Compatibility(self): for out in [out1, out2, out3, out4, out5, out6, out7]: np.testing.assert_allclose(out.numpy(), expected, rtol=1e-5) - paddle.enable_static() - # Test imag compatibility (compat function) class TestImagAPI(unittest.TestCase): @@ -1628,8 +1592,6 @@ def test_dygraph_Compatibility(self): self.assertEqual(out.dtype, paddle.float32) np.testing.assert_allclose(out.numpy(), self.np_x.imag) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -1656,10 +1618,10 @@ def test_static_Compatibility(self): for out in fetches: np.testing.assert_allclose(out, self.np_x.imag) + # Test real compatibility (compat function) paddle.disable_static() -# Test real compatibility (compat function) class TestRealAPI(unittest.TestCase): def setUp(self): np.random.seed(2025) @@ -1684,8 +1646,6 @@ def test_dygraph_Compatibility(self): for out in [out1, out2, out3]: np.testing.assert_allclose(out.numpy(), self.np_x.real) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -1712,10 +1672,10 @@ def test_static_Compatibility(self): for out in fetches: np.testing.assert_allclose(out, self.np_x.real) + # Test nan_to_num compatibility (PyTorch parameter alias) paddle.disable_static() -# Test nan_to_num compatibility (PyTorch parameter alias) class TestNanToNumAPI(unittest.TestCase): def setUp(self): np.random.seed(2025) @@ -1753,8 +1713,6 @@ def test_dygraph_Compatibility(self): for out in [out1, out2, out3, out4, out5, out6, out7]: np.testing.assert_allclose(out.numpy(), out1.numpy()) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -1783,10 +1741,10 @@ def test_static_Compatibility(self): for out in fetches: np.testing.assert_allclose(out, fetches[0]) + # Test randint_like compatibility (PyTorch parameter alias and new params) paddle.disable_static() -# Test randint_like compatibility (PyTorch parameter alias and new params) class TestRandintLikeAPI(unittest.TestCase): def setUp(self): np.random.seed(2025) @@ -1824,8 +1782,6 @@ def test_dygraph_Compatibility(self): for out in [out1, out2, out3, out4, out5, out6, out7]: self.assertEqual(out.shape, x.shape) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -1850,10 +1806,10 @@ def test_static_Compatibility(self): for out in fetches: self.assertEqual(out.shape, tuple(self.shape)) + # Test resize_as_ compatibility paddle.disable_static() -# Test resize_as_ compatibility class TestResizeAsAPI(unittest.TestCase): def setUp(self): np.random.seed(2025) @@ -1884,8 +1840,6 @@ def test_dygraph_Compatibility(self): for out in [out1, out2, out3, out4]: self.assertEqual(out.shape, y.shape) - paddle.enable_static() - # Test huber_loss compatibility (alias for smooth_l1_loss) class TestHuberLossAPI(unittest.TestCase): @@ -1921,8 +1875,6 @@ def test_dygraph_Compatibility(self): np.testing.assert_allclose(out1.numpy(), out4.numpy()) np.testing.assert_allclose(out1.numpy(), out5.numpy()) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -1958,10 +1910,10 @@ def test_static_Compatibility(self): for out in fetches: self.assertEqual(out.shape, (3, 5)) + # Test fmod compatibility (alias for remainder/mod) paddle.disable_static() -# Test fmod compatibility (alias for remainder/mod) class TestFmodAPI(unittest.TestCase): def setUp(self): np.random.seed(2025) @@ -2001,8 +1953,6 @@ def test_dygraph_Compatibility(self): for out in [out1, out2, out3, out4, out5, out6, out7]: np.testing.assert_allclose(out.numpy(), out1.numpy()) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -2032,10 +1982,10 @@ def test_static_Compatibility(self): for out in fetches: np.testing.assert_allclose(out, fetches[0]) + # Test absolute compatibility (alias for abs) paddle.disable_static() -# Test absolute compatibility (alias for abs) class TestAbsoluteAPI(unittest.TestCase): def setUp(self): np.random.seed(2025) @@ -2074,8 +2024,6 @@ def test_dygraph_Compatibility(self): for out in [out1, out2, out3, out4, out5, out6, out7]: np.testing.assert_allclose(out.numpy(), expected) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -2101,10 +2049,10 @@ def test_static_Compatibility(self): for out in fetches: np.testing.assert_allclose(out, expected) + # Test assert_allclose compatibility paddle.disable_static() -# Test assert_allclose compatibility class TestAssertAllcloseAPI(unittest.TestCase): def test_dygraph_Compatibility(self): paddle.disable_static() @@ -2138,8 +2086,6 @@ def test_dygraph_Compatibility(self): with self.assertRaises(AssertionError): paddle.testing.assert_allclose(x, z) - paddle.enable_static() - # Test GRU compatibility class TestGRUAPI(unittest.TestCase): @@ -2234,8 +2180,6 @@ def test_dygraph_Compatibility(self): # 7. dtype parameter test (constructor only) paddle.nn.GRU(self.input_size, self.hidden_size, dtype="float32") - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -2286,10 +2230,10 @@ def test_static_Compatibility(self): (self.batch_size, self.seq_len, self.hidden_size), ) + # Test set_default_tensor_type compatibility paddle.disable_static() -# Test set_default_tensor_type compatibility class TestSetDefaultTensorTypeAPI(unittest.TestCase): def test_dygraph_Compatibility(self): paddle.disable_static() @@ -2373,8 +2317,6 @@ def test_dygraph_Compatibility(self): # Restore original dtype paddle.set_default_dtype(original_dtype) - paddle.enable_static() - # Test PackedSequence compatibility class TestPackedSequenceAPI(unittest.TestCase): @@ -2475,8 +2417,6 @@ def test_dygraph_Compatibility(self): packed_pinned, paddle.nn.utils.rnn.PackedSequence ) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -2503,10 +2443,10 @@ def test_static_Compatibility(self): self.assertEqual(packed2.data.name, data.name) self.assertEqual(packed2.batch_sizes.name, batch_sizes.name) + # Test invert_permutation compatibility paddle.disable_static() -# Test invert_permutation compatibility class TestInvertPermutationAPI(unittest.TestCase): def setUp(self): np.random.seed(2025) @@ -2532,8 +2472,6 @@ def test_dygraph_Compatibility(self): inv_perm3 = paddle.nn.utils.rnn.invert_permutation(perm2) np.testing.assert_array_equal(inv_perm3.numpy(), [0, 1, 2, 3]) - paddle.enable_static() - def test_static_Compatibility(self): paddle.enable_static() main = paddle.static.Program() @@ -2554,10 +2492,10 @@ def test_static_Compatibility(self): for out in fetches: np.testing.assert_array_equal(out, [1, 2, 0]) + # Test pack_padded_sequence compatibility paddle.disable_static() -# Test pack_padded_sequence compatibility class TestPackPaddedSequenceAPI(unittest.TestCase): def setUp(self): np.random.seed(2025) @@ -2642,8 +2580,6 @@ def test_dygraph_Compatibility(self): packed6.batch_sizes.numpy(), expected_batch_sizes ) - paddle.enable_static() - # Test pad_packed_sequence compatibility class TestPadPackedSequenceAPI(unittest.TestCase): @@ -2751,8 +2687,6 @@ def test_dygraph_Compatibility(self): self.assertEqual(padded8.shape, [3, 5, 10]) np.testing.assert_allclose(padded8.numpy(), expected_padded_bf) - paddle.enable_static() - # Test pad_sequence compatibility class TestPadSequenceAPI(unittest.TestCase): @@ -2838,8 +2772,6 @@ def test_dygraph_Compatibility(self): np.testing.assert_allclose(padded5.numpy()[10:, 2, :], self.c) np.testing.assert_allclose(padded5.numpy()[:10, 2, :], 0.0) - paddle.enable_static() - # Test unpad_sequence compatibility class TestUnpadSequenceAPI(unittest.TestCase): @@ -2894,8 +2826,6 @@ def test_dygraph_Compatibility(self): for i, (original, unpadded) in enumerate(zip(sequences, unpadded4)): np.testing.assert_allclose(original.numpy(), unpadded.numpy()) - paddle.enable_static() - # Test pack_sequence compatibility class TestPackSequenceAPI(unittest.TestCase): @@ -2955,8 +2885,6 @@ def test_dygraph_Compatibility(self): ) np.testing.assert_array_equal(packed4.batch_sizes.numpy(), [2, 2, 1]) - paddle.enable_static() - # Test unpack_sequence compatibility class TestUnpackSequenceAPI(unittest.TestCase): @@ -2994,8 +2922,1490 @@ def test_dygraph_Compatibility(self): for i, (original, unpacked) in enumerate(zip(sequences, unpacked3)): np.testing.assert_array_equal(original.numpy(), unpacked.numpy()) + +# Test vstack compatibility +class TestVstackAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + self.np_x1 = np.array([1, 2, 3]).astype("float32") + self.np_x2 = np.array([4, 5, 6]).astype("float32") + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x1 = paddle.to_tensor(self.np_x1) + x2 = paddle.to_tensor(self.np_x2) + + # 1. Paddle Positional arguments + out1 = paddle.vstack([x1, x2]) + # 2. Paddle keyword arguments + out2 = paddle.vstack(x=[x1, x2]) + # 3. PyTorch keyword arguments (alias) + out3 = paddle.vstack(tensors=[x1, x2]) + + expected = np.array([[1, 2, 3], [4, 5, 6]]) + for out in [out1, out2, out3]: + np.testing.assert_allclose(out.numpy(), expected) + + # 4. out parameter test + out4 = paddle.empty([2, 3], dtype="float32") + paddle.vstack([x1, x2], out=out4) + np.testing.assert_allclose(out4.numpy(), expected) + + +# Test batch_norm compatibility (compat version) +class TestBatchNormFnAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + self.np_x = np.random.rand(2, 3, 4, 4).astype("float32") + self.np_running_mean = np.zeros(3).astype("float32") + self.np_running_var = np.ones(3).astype("float32") + self.np_weight = np.ones(3).astype("float32") + self.np_bias = np.zeros(3).astype("float32") + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor(self.np_x) + running_mean = paddle.to_tensor(self.np_running_mean) + running_var = paddle.to_tensor(self.np_running_var) + weight = paddle.to_tensor(self.np_weight) + bias = paddle.to_tensor(self.np_bias) + + compat_bn = paddle.compat.nn.functional.batch_norm + + # 1. PyTorch-style positional arguments + out1 = compat_bn(x, running_mean, running_var, weight, bias) + # 2. PyTorch-style keyword arguments + out2 = compat_bn( + input=x, + running_mean=running_mean, + running_var=running_var, + weight=weight, + bias=bias, + ) + # 3. Paddle-style positional (should also work via compat, as it is + # a wrapper calling paddle.nn.functional.batch_norm internally) + out3 = paddle.nn.functional.batch_norm( + x, running_mean, running_var, weight, bias + ) + + for out in [out1, out2]: + self.assertEqual(out.shape, x.shape) + self.assertEqual(out.dtype, paddle.float32) + + # Verify compat output is consistent with paddle's native result + np.testing.assert_allclose( + out1.numpy(), out3.numpy(), rtol=1e-5, atol=1e-5 + ) + + # 4. Test momentum conversion: torch momentum=0.1 -> paddle momentum=0.9 + # This verifies the compat wrapper correctly transforms the parameter. + out_torch_momentum = compat_bn( + input=x, + running_mean=running_mean, + running_var=running_var, + weight=weight, + bias=bias, + momentum=0.1, + ) + out_paddle_momentum = paddle.nn.functional.batch_norm( + x, + running_mean, + running_var, + weight, + bias, + momentum=0.9, + ) + np.testing.assert_allclose( + out_torch_momentum.numpy(), + out_paddle_momentum.numpy(), + rtol=1e-5, + atol=1e-5, + ) + + # 5. Verify result matches numerical expectation in eval mode + # batch_norm(x) = (x - running_mean) / sqrt(running_var + eps) * weight + bias + # With running_mean=0, running_var=1, weight=1, bias=0: y ≈ x + np.testing.assert_allclose( + out1.numpy(), x.numpy(), rtol=1e-4, atol=1e-4 + ) + + def test_static_Compatibility(self): + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data( + name="x", shape=[2, 3, 4, 4], dtype="float32" + ) + running_mean = paddle.static.data( + name="running_mean", shape=[3], dtype="float32" + ) + running_var = paddle.static.data( + name="running_var", shape=[3], dtype="float32" + ) + weight = paddle.static.data( + name="weight", shape=[3], dtype="float32" + ) + bias = paddle.static.data(name="bias", shape=[3], dtype="float32") + + compat_bn = paddle.compat.nn.functional.batch_norm + + out1 = compat_bn(x, running_mean, running_var, weight, bias) + out2 = compat_bn( + input=x, + running_mean=running_mean, + running_var=running_var, + weight=weight, + bias=bias, + ) + + exe = paddle.static.Executor() + fetches = exe.run( + main, + feed={ + "x": self.np_x, + "running_mean": self.np_running_mean, + "running_var": self.np_running_var, + "weight": self.np_weight, + "bias": self.np_bias, + }, + fetch_list=[out1, out2], + ) + for out in fetches: + self.assertEqual(out.shape, (2, 3, 4, 4)) + # In eval mode with running_mean=0, running_var=1, weight=1, bias=0 + # output ≈ input + np.testing.assert_allclose(out, self.np_x, rtol=1e-4, atol=1e-4) + + # Test gumbel_softmax compatibility + paddle.disable_static() + + +class TestGumbelSoftmaxAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + self.np_x = np.random.randn(4, 6).astype("float32") + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor(self.np_x) + + # 1. Paddle Positional arguments + out1 = paddle.nn.functional.gumbel_softmax(x, temperature=1.0) + + # 2. Paddle keyword arguments + out2 = paddle.nn.functional.gumbel_softmax( + x=x, temperature=1.0, hard=False, axis=-1 + ) + + # 3. PyTorch keyword arguments (logits alias, tau alias, dim alias) + out3 = paddle.nn.functional.gumbel_softmax( + logits=x, tau=1.0, hard=False, dim=-1 + ) + + # 4. hard=True test + out4 = paddle.nn.functional.gumbel_softmax(x, hard=True) + + # 5. PyTorch 4 positional args: (logits, tau, hard, eps) + out5 = paddle.nn.functional.gumbel_softmax(x, 1.0, False, 1e-10) + + # 6. PyTorch 4 positional args: (logits, tau, hard, dim) + out6 = paddle.nn.functional.gumbel_softmax(x, 1.0, False, 0) + + # Verify outputs + for out in [out1, out2, out3, out5, out6]: + self.assertEqual(out.shape, x.shape) + self.assertEqual(out.dtype, paddle.float32) + + # Verify hard=True returns one-hot + self.assertTrue((out4.sum(axis=-1) == 1.0).all()) + + +# Test set_default_device compatibility +class TestSetDefaultDeviceAPI(unittest.TestCase): + def test_dygraph_Compatibility(self): + paddle.disable_static() + + # Save original device + original_device = paddle.get_default_device() + + # Test with string device + paddle.set_default_device("cpu") + self.assertEqual( + paddle.get_default_device(), paddle.device.Device("cpu") + ) + + # Test with None (reset to CPU) + paddle.set_default_device(None) + self.assertEqual( + paddle.get_default_device(), paddle.device.Device("cpu") + ) + + # Restore original device + if original_device is not None: + paddle.set_device(str(original_device)) + + +# Test set_grad_enabled compatibility +class TestSetGradEnabledAPI(unittest.TestCase): + def test_dygraph_Compatibility(self): + paddle.disable_static() + + x = paddle.to_tensor([1.0, 2.0], stop_gradient=False) + + # Test via autograd.grad_mode.set_grad_enabled + with paddle.autograd.grad_mode.set_grad_enabled(False): + y = x * 2 + self.assertTrue(y.stop_gradient) + + with paddle.autograd.grad_mode.set_grad_enabled(True): + z = x * 2 + self.assertFalse(z.stop_gradient) + + +# Test new_tensor compatibility +class TestNewTensorAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + self.np_data = np.array([[1, 2, 3], [4, 5, 6]], dtype="float32") + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor([1, 2, 3], dtype="float32") + + # Test new_tensor with data + out = x.new_tensor(self.np_data) + self.assertEqual(out.shape, [2, 3]) + self.assertEqual(out.dtype, x.dtype) + np.testing.assert_allclose(out.numpy(), self.np_data, rtol=1e-5) + + # Test new_tensor with requires_grad=False + out2 = x.new_tensor(self.np_data, requires_grad=False) + self.assertTrue(out2.stop_gradient) + + def test_static_Compatibility(self): + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data(name="x", shape=[3], dtype="float32") + + # Test new_tensor with data + out = x.new_tensor(self.np_data) + out2 = x.new_tensor(self.np_data, dtype="float64") + + exe = paddle.static.Executor() + fetches = exe.run( + feed={"x": np.array([1, 2, 3], dtype="float32")}, + fetch_list=[out, out2], + ) + self.assertEqual(fetches[0].shape, (2, 3)) + self.assertEqual(fetches[0].dtype, np.float32) + np.testing.assert_allclose(fetches[0], self.np_data, rtol=1e-5) + + self.assertEqual(fetches[1].dtype, np.float64) paddle.enable_static() + # Test to_empty compatibility + paddle.disable_static() + + +class TestToEmptyAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + self.np_x = np.random.randn(3, 4).astype("float32") + + def test_dygraph_Compatibility(self): + paddle.disable_static() + + # Test single layer with Parameters + layer = paddle.nn.Linear(4, 2) + layer.to_empty(device="cpu") + + # Verify parameters are on the correct device + for param in layer.parameters(): + self.assertTrue("cpu" in str(param.place).lower()) + + # Test with recurse=False + layer.to_empty(device="cpu", recurse=False) + + # Test multi-layer (nested sublayers) + class NestedLayer(paddle.nn.Layer): + def __init__(self): + super().__init__() + self.fc1 = paddle.nn.Linear(4, 4) + self.fc2 = paddle.nn.Linear(4, 2) + + def forward(self, x): + return self.fc2(self.fc1(x)) + + nested = NestedLayer() + nested.to_empty(device="cpu") + for param in nested.parameters(): + self.assertTrue("cpu" in str(param.place).lower()) + # Verify sublayer parameters are also moved + for param in nested.fc1.parameters(): + self.assertTrue("cpu" in str(param.place).lower()) + for param in nested.fc2.parameters(): + self.assertTrue("cpu" in str(param.place).lower()) + + # Test with ordinary buffers (non-Parameter tensors) + class LayerWithBuf(paddle.nn.Layer): + def __init__(self): + super().__init__() + self.fc = paddle.nn.Linear(4, 2) + self.register_buffer( + "my_buf", paddle.zeros([2, 3], dtype="float32") + ) + + layer_buf = LayerWithBuf() + layer_buf.to_empty(device="cpu") + self.assertTrue("cpu" in str(layer_buf.my_buf.place).lower()) + for param in layer_buf.fc.parameters(): + self.assertTrue("cpu" in str(param.place).lower()) + + +# Test _Loss base class compatibility +class TestLossBaseAPI(unittest.TestCase): + def test_dygraph_Compatibility(self): + paddle.disable_static() + + # Verify _Loss is importable from paddle.nn.modules.loss + from paddle.nn.modules.loss import _Loss + + self.assertTrue(issubclass(_Loss, paddle.nn.Layer)) + + # Test creating a _Loss instance with reduction + loss_base = _Loss(reduction='mean') + self.assertEqual(loss_base.reduction, 'mean') + + loss_base_sum = _Loss(reduction='sum') + self.assertEqual(loss_base_sum.reduction, 'sum') + + # Test _Loss with size_average/reduce (PyTorch compatibility kwargs) + loss_sa = _Loss(size_average=True, reduce=True) + self.assertEqual(loss_sa.reduction, 'mean') + + loss_sa_false = _Loss(size_average=False, reduce=True) + self.assertEqual(loss_sa_false.reduction, 'sum') + + loss_reduce_false = _Loss(size_average=True, reduce=False) + self.assertEqual(loss_reduce_false.reduction, 'none') + + +# Test _pair compatibility +class TestPairAPI(unittest.TestCase): + def test_dygraph_Compatibility(self): + paddle.disable_static() + # Test _pair import from paddle.nn.modules.utils + from paddle.nn.modules.utils import _pair + + # Test with int + result = _pair(3) + self.assertEqual(result, (3, 3)) + + # Test with tuple + result2 = _pair((4, 5)) + self.assertEqual(result2, (4, 5)) + + # Test with list + result3 = _pair([6, 7]) + self.assertEqual(result3, (6, 7)) + + +# Test GradScaler compatibility (already aligned) +class TestGradScalerAPI(unittest.TestCase): + def test_dygraph_Compatibility(self): + paddle.disable_static() + + # Test Paddle-style constructor + scaler1 = paddle.cuda.amp.GradScaler( + enable=True, init_loss_scaling=65536.0 + ) + self.assertIsNotNone(scaler1) + + # Test PyTorch-style constructor + scaler2 = paddle.cuda.amp.GradScaler(enabled=True, init_scale=65536.0) + self.assertIsNotNone(scaler2) + + # Test PyTorch-style constructor with growth params + scaler3 = paddle.cuda.amp.GradScaler( + init_scale=1024.0, + growth_factor=2.0, + backoff_factor=0.5, + growth_interval=1000, + enabled=True, + ) + self.assertIsNotNone(scaler3) + + +# Test hstack compatibility (out parameter fix) +class TestHstackAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + self.np_x = np.random.rand(2, 3).astype("float32") + self.np_y = np.random.rand(2, 3).astype("float32") + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor(self.np_x) + y = paddle.to_tensor(self.np_y) + + # 1. Paddle Positional arguments + out1 = paddle.hstack([x, y]) + # 2. Paddle keyword arguments with alias + out2 = paddle.hstack(tensors=[x, y]) + # 3. out parameter test + out3 = paddle.empty_like(out1) + paddle.hstack([x, y], out=out3) + + # Verify all outputs + np.testing.assert_allclose(out1.numpy(), out2.numpy()) + np.testing.assert_allclose(out1.numpy(), out3.numpy()) + + def test_static_Compatibility(self): + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data(name="x", shape=[2, 3], dtype="float32") + y = paddle.static.data(name="y", shape=[2, 3], dtype="float32") + + out1 = paddle.hstack([x, y]) + + exe = paddle.static.Executor() + fetches = exe.run( + main, + feed={"x": self.np_x, "y": self.np_y}, + fetch_list=[out1], + ) + self.assertEqual(fetches[0].shape, (2, 6)) + + # Test nn.ELU compatibility (inplace parameter) + paddle.disable_static() + + +class TestELUAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + # Use data with both positive and negative values to test ELU + self.np_x = np.random.randn(2, 3).astype("float32") + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor(self.np_x) + + # 1. Paddle position arguments + elu = paddle.nn.ELU(alpha=1.0) + out1 = elu(x) + # 2. Inplace=False + elu2 = paddle.nn.ELU(alpha=1.0, inplace=False) + out2 = elu2(x) + # 3. Inplace=True + x3 = paddle.to_tensor(self.np_x.copy()) + elu3 = paddle.nn.ELU(alpha=1.0, inplace=True) + out3 = elu3(x3) + + # Reference: ELU(x) = max(0,x) + min(0, alpha*(exp(x)-1)) + expected = np.where( + self.np_x > 0, self.np_x, 1.0 * (np.exp(self.np_x) - 1) + ) + + # Verify non-inplace outputs + np.testing.assert_allclose(out1.numpy(), expected, rtol=1e-5, atol=1e-5) + np.testing.assert_allclose(out2.numpy(), expected, rtol=1e-5, atol=1e-5) + # Verify inplace modifies input + np.testing.assert_allclose( + out3.numpy(), x3.numpy(), rtol=1e-5, atol=1e-5 + ) + np.testing.assert_allclose(out3.numpy(), expected, rtol=1e-5, atol=1e-5) + + def test_static_Compatibility(self): + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data(name="x", shape=[2, 3], dtype="float32") + + elu = paddle.nn.ELU(alpha=1.0) + out = elu(x) + + exe = paddle.static.Executor() + fetches = exe.run( + main, + feed={"x": self.np_x}, + fetch_list=[out], + ) + expected = np.where( + self.np_x > 0, self.np_x, 1.0 * (np.exp(self.np_x) - 1) + ) + np.testing.assert_allclose( + fetches[0], expected, rtol=1e-5, atol=1e-5 + ) + + # Test linalg.cross compatibility (parameter aliases, out) + paddle.disable_static() + + +class TestLinalgCrossAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + # cross product requires last dimension = 3 + self.np_x = np.random.rand(5, 3).astype("float32") + self.np_y = np.random.rand(5, 3).astype("float32") + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor(self.np_x) + y = paddle.to_tensor(self.np_y) + + # 1. Paddle Positional arguments + out1 = paddle.linalg.cross(x, y) + # 2. PyTorch keyword arguments + out2 = paddle.linalg.cross(input=x, other=y) + # 3. PyTorch keyword arguments with dim alias + out3 = paddle.linalg.cross(x, y, dim=1) + # 4. out parameter test + out4 = paddle.empty_like(out1) + paddle.linalg.cross(x, y, out=out4) + # 5. Tensor method + out5 = x.cross(y) + + # Verify all outputs + expected_np = np.cross(self.np_x, self.np_y, axisa=1, axisb=1, axisc=1) + for out in [out1, out2, out3, out4, out5]: + np.testing.assert_allclose( + out.numpy(), expected_np, rtol=1e-5, atol=1e-5 + ) + + def test_static_Compatibility(self): + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data(name="x", shape=[5, 3], dtype="float32") + y = paddle.static.data(name="y", shape=[5, 3], dtype="float32") + + out1 = paddle.linalg.cross(input=x, other=y) + out2 = paddle.linalg.cross(x, y, dim=1) + + exe = paddle.static.Executor() + fetches = exe.run( + main, + feed={"x": self.np_x, "y": self.np_y}, + fetch_list=[out1, out2], + ) + expected_np = np.cross( + self.np_x, self.np_y, axisa=1, axisb=1, axisc=1 + ) + for out in fetches: + np.testing.assert_allclose( + out, expected_np, rtol=1e-5, atol=1e-5 + ) + + # Test Tensor.true_divide_ compatibility (alias for divide_) + paddle.disable_static() + + +class TestTrueDivide_InplaceAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + self.np_x = np.array([1.0, 2.0, 3.0]).astype("float32") + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor(self.np_x.copy()) + y = paddle.to_tensor([2.0, 4.0, 6.0]) + + # PyTorch-style keyword arguments + x.true_divide_(other=y) + expected = self.np_x / np.array([2.0, 4.0, 6.0]) + np.testing.assert_allclose(x.numpy(), expected, rtol=1e-5) + + +# Test Tensor.H/mH/T compatibility (new properties) +class TestTensorHAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + self.np_2d = np.array([[1.0, 2.0], [3.0, 4.0]]).astype("float32") + self.np_3d = np.arange(24).reshape(2, 3, 4).astype("float32") + self.np_complex = np.array([[1 + 2j, 3 + 4j], [5 + 6j, 7 + 8j]]).astype( + "complex64" + ) + + def test_dygraph_Compatibility(self): + paddle.disable_static() + + # Test .H on real 2D tensor + x = paddle.to_tensor(self.np_2d) + h = x.H + expected = self.np_2d.transpose() + np.testing.assert_allclose(h.numpy(), expected, rtol=1e-5) + + # Test .H on complex 2D tensor + x_c = paddle.to_tensor(self.np_complex) + h_c = x_c.H + expected_c = self.np_complex.transpose().conj() + np.testing.assert_allclose(h_c.numpy(), expected_c, rtol=1e-5) + + # Test .mH on 2D real tensor + mh = x.mH + expected_mh = self.np_2d.transpose().conj() + np.testing.assert_allclose(mh.numpy(), expected_mh, rtol=1e-5) + + # Test .mH on 3D real tensor (last two dims swap + conj) + x_3d = paddle.to_tensor(self.np_3d) + mh_3d = x_3d.mH + expected_mh_3d = self.np_3d.transpose(0, 2, 1).conj() + np.testing.assert_allclose(mh_3d.numpy(), expected_mh_3d, rtol=1e-5) + + # Test .H on 0D real tensor (returns self) + x_0d = paddle.to_tensor(np.array(5.0).astype("float32")) + h_0d = x_0d.H + self.assertEqual(h_0d.shape, []) + np.testing.assert_allclose(h_0d.numpy(), np.array(5.0), rtol=1e-5) + + # Test .H on 0D complex tensor (returns self) + x_0d_c = paddle.to_tensor(np.array(1 + 2j).astype("complex64")) + h_0d_c = x_0d_c.H + self.assertEqual(h_0d_c.shape, []) + np.testing.assert_allclose(h_0d_c.numpy(), np.array(1 - 2j), rtol=1e-5) + + # Test .mH on 0D tensor (returns self) + mh_0d = x_0d.mH + self.assertEqual(mh_0d.shape, []) + np.testing.assert_allclose(mh_0d.numpy(), np.array(5.0), rtol=1e-5) + + # Test .T on 2D real tensor + t = x.T + expected_t = self.np_2d.T + np.testing.assert_allclose(t.numpy(), expected_t, rtol=1e-5) + + # Test .T on 3D real tensor (reverses all dims) + t_3d = x_3d.T + expected_t_3d = self.np_3d.transpose(2, 1, 0) + np.testing.assert_allclose(t_3d.numpy(), expected_t_3d, rtol=1e-5) + + def test_static_Compatibility(self): + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data(name="x", shape=[2, 2], dtype="float32") + x_c = paddle.static.data( + name="x_c", shape=[2, 2], dtype="complex64" + ) + x_3d = paddle.static.data( + name="x_3d", shape=[2, 3, 4], dtype="float32" + ) + x_0d = paddle.static.data(name="x_0d", shape=[], dtype="float32") + + # Test .H on 2D real tensor + h = x.H + # Test .H on 2D complex tensor + h_c = x_c.H + # Test .mH on 2D real tensor + mh = x.mH + # Test .mH on 3D real tensor (last two dims swap + conj) + mh_3d = x_3d.mH + # Test .H on 0D tensor (returns self) + h_0d = x_0d.H + # Test .mH on 0D tensor (returns self) + mh_0d = x_0d.mH + + exe = paddle.static.Executor() + fetches = exe.run( + main, + feed={ + "x": self.np_2d, + "x_c": self.np_complex, + "x_3d": self.np_3d, + "x_0d": np.array(5.0).astype("float32"), + }, + fetch_list=[h, h_c, mh, mh_3d, h_0d, mh_0d], + ) + np.testing.assert_allclose( + fetches[0], self.np_2d.transpose(), rtol=1e-5 + ) + np.testing.assert_allclose( + fetches[1], self.np_complex.transpose().conj(), rtol=1e-5 + ) + np.testing.assert_allclose( + fetches[2], self.np_2d.transpose().conj(), rtol=1e-5 + ) + np.testing.assert_allclose( + fetches[3], self.np_3d.transpose(0, 2, 1).conj(), rtol=1e-5 + ) + np.testing.assert_allclose(fetches[4], np.array(5.0), rtol=1e-5) + np.testing.assert_allclose(fetches[5], np.array(5.0), rtol=1e-5) + + paddle.disable_static() + + # Test clamp_max compatibility (new API) + paddle.disable_static() + + +class TestClampMaxAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + self.np_x = np.array([1.0, 5.0, 3.0, 8.0, 2.0]).astype("float32") + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor(self.np_x) + + # 1. Paddle Positional arguments + out1 = paddle.clamp_max(x, 4.0) + # 2. Paddle keyword arguments + out2 = paddle.clamp_max(input=x, max=4.0) + # 3. out parameter test + out3 = paddle.empty_like(x) + paddle.clamp_max(x, 4.0, out=out3) + + expected = np.minimum(self.np_x, 4.0) + for out in [out1, out2, out3]: + np.testing.assert_allclose(out.numpy(), expected, rtol=1e-5) + + def test_static_Compatibility(self): + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data(name="x", shape=[5], dtype="float32") + + out1 = paddle.clamp_max(x, 4.0) + out2 = paddle.clamp_max(input=x, max=4.0) + + exe = paddle.static.Executor() + fetches = exe.run( + main, + feed={"x": self.np_x}, + fetch_list=[out1, out2], + ) + expected = np.minimum(self.np_x, 4.0) + for out in fetches: + np.testing.assert_allclose(out, expected, rtol=1e-5) + + # Test clamp_min compatibility (new API) + paddle.disable_static() + + +class TestClampMinAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + self.np_x = np.array([1.0, 5.0, 3.0, 8.0, 2.0]).astype("float32") + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor(self.np_x) + + # 1. Paddle Positional arguments + out1 = paddle.clamp_min(x, 3.0) + # 2. Paddle keyword arguments + out2 = paddle.clamp_min(input=x, min=3.0) + # 3. out parameter test + out3 = paddle.empty_like(x) + paddle.clamp_min(x, 3.0, out=out3) + + expected = np.maximum(self.np_x, 3.0) + for out in [out1, out2, out3]: + np.testing.assert_allclose(out.numpy(), expected, rtol=1e-5) + + def test_static_Compatibility(self): + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data(name="x", shape=[5], dtype="float32") + + out1 = paddle.clamp_min(x, 3.0) + out2 = paddle.clamp_min(input=x, min=3.0) + + exe = paddle.static.Executor() + fetches = exe.run( + main, + feed={"x": self.np_x}, + fetch_list=[out1, out2], + ) + expected = np.maximum(self.np_x, 3.0) + for out in fetches: + np.testing.assert_allclose(out, expected, rtol=1e-5) + + # Test qr compatibility (new API) + paddle.disable_static() + + +class TestQrAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + self.np_x = np.random.rand(4, 3).astype("float32") + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor(self.np_x) + + # 1. Paddle Positional arguments (some=True by default) + Q1, R1 = paddle.qr(x) + Q2, R2 = paddle.qr(input=x, some=True) + Q3, R3 = paddle.qr(x, some=False) + # 4. test mode keyword + Q5, R5 = paddle.linalg.qr(x, mode='reduced') + # 5. some as positional bool (type-based dispatch) + Q6, R6 = paddle.qr(x, True) + Q7, R7 = paddle.qr(x, False) + # 6. A alias + Q8, R8 = paddle.qr(A=x, some=True) + # 7. mode='r' returns single Tensor R + R9 = paddle.qr(x, mode='r') + # 8. mode='r' with out parameter + R10 = paddle.empty(shape=[4, 3], dtype=x.dtype) + paddle.qr(x, mode='r', out=R10) + + # Verify some=True matches reduced mode + np.testing.assert_allclose(Q1.numpy(), Q5.numpy(), rtol=1e-5, atol=1e-5) + np.testing.assert_allclose(R1.numpy(), R5.numpy(), rtol=1e-5, atol=1e-5) + # Verify some=True and positional True match + np.testing.assert_allclose(Q1.numpy(), Q6.numpy(), rtol=1e-5, atol=1e-5) + # Verify some=False and positional False match + np.testing.assert_allclose(Q3.numpy(), Q7.numpy(), rtol=1e-5, atol=1e-5) + # Verify A alias + np.testing.assert_allclose(Q1.numpy(), Q8.numpy(), rtol=1e-5, atol=1e-5) + # Verify reconstruction + reconstr = Q1 @ R1 + np.testing.assert_allclose( + reconstr.numpy(), self.np_x, rtol=1e-5, atol=1e-5 + ) + + # some=False gives complete QR + self.assertEqual(Q3.shape, (4, 4)) + # mode='r' returns single Tensor + self.assertEqual(len(R9.shape), 2) + # Verify mode='r' with out parameter + np.testing.assert_allclose( + R9.numpy(), R10.numpy(), rtol=1e-5, atol=1e-5 + ) + + def test_static_Compatibility(self): + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data(name="x", shape=[4, 3], dtype="float32") + r_out = paddle.static.data( + name="r_out", shape=[4, 3], dtype="float32" + ) + + Q1, R1 = paddle.qr(x) + Q2, R2 = paddle.qr(x, mode='reduced') + R3 = paddle.qr(x, mode='r') + # mode='r' with out parameter + paddle.qr(x, mode='r', out=r_out) + + exe = paddle.static.Executor() + fetches = exe.run( + main, + feed={ + "x": self.np_x, + "r_out": np.zeros([4, 3], dtype="float32"), + }, + fetch_list=[Q1, R1, Q2, R2, R3, r_out], + ) + # Verify default and mode='reduced' match + np.testing.assert_allclose( + fetches[0], fetches[2], rtol=1e-5, atol=1e-5 + ) + np.testing.assert_allclose( + fetches[1], fetches[3], rtol=1e-5, atol=1e-5 + ) + # Verify mode='r' returns R only + np.testing.assert_allclose( + fetches[1], fetches[4], rtol=1e-5, atol=1e-5 + ) + # Verify mode='r' with out parameter + np.testing.assert_allclose( + fetches[4], fetches[5], rtol=1e-5, atol=1e-5 + ) + + # Test logdet compatibility (new API) + paddle.disable_static() + + +class TestLogdetAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + # Positive definite matrix + A = np.random.rand(3, 3).astype("float32") + self.np_x = (A @ A.T + np.eye(3) * 0.1).astype("float32") + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor(self.np_x) + + out1 = paddle.logdet(x) + expected = np.log(np.linalg.det(self.np_x)) + np.testing.assert_allclose(out1.numpy(), expected, rtol=1e-5, atol=1e-5) + + def test_static_Compatibility(self): + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data(name="x", shape=[3, 3], dtype="float32") + + out = paddle.logdet(x) + + exe = paddle.static.Executor() + fetches = exe.run( + main, + feed={"x": self.np_x}, + fetch_list=[out], + ) + expected = np.log(np.linalg.det(self.np_x)) + np.testing.assert_allclose( + fetches[0], expected, rtol=1e-5, atol=1e-5 + ) + + # Test linalg.eigh compatibility (out parameter, input alias) + paddle.disable_static() + + +class TestEighAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + A = np.random.rand(3, 3).astype("float32") + self.np_x = (A + A.T) / 2 + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor(self.np_x) + + # 1. Paddle positional arguments + w1, v1 = paddle.linalg.eigh(x) + # 2. PyTorch keyword arguments + w2, v2 = paddle.linalg.eigh(input=x, UPLO='L') + + # Verify eigenvalues match + expected_w = np.linalg.eigh(self.np_x)[0] + np.testing.assert_allclose(w1.numpy(), expected_w, rtol=1e-5, atol=1e-5) + np.testing.assert_allclose(w2.numpy(), expected_w, rtol=1e-5, atol=1e-5) + + def test_static_Compatibility(self): + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data(name="x", shape=[3, 3], dtype="float32") + + w, v = paddle.linalg.eigh(x) + + exe = paddle.static.Executor() + fetches = exe.run( + main, + feed={"x": self.np_x}, + fetch_list=[w, v], + ) + expected_w = np.linalg.eigh(self.np_x)[0] + np.testing.assert_allclose( + fetches[0], expected_w, rtol=1e-5, atol=1e-5 + ) + + # Test linalg.cholesky compatibility (out parameter, input alias) + paddle.disable_static() + + +class TestLinalgCholeskyAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + A = np.random.rand(3, 3).astype("float64") + self.np_x = (A @ A.T + np.eye(3) * 0.1).astype("float64") + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor(self.np_x) + + # 1. Paddle positional arguments + out1 = paddle.linalg.cholesky(x) + # 2. PyTorch keyword arguments + out2 = paddle.linalg.cholesky(input=x, upper=False) + # 3. Upper triangular + out3 = paddle.linalg.cholesky(x, upper=True) + + expected = np.linalg.cholesky(self.np_x) + np.testing.assert_allclose(out1.numpy(), expected, rtol=1e-5, atol=1e-5) + np.testing.assert_allclose(out2.numpy(), expected, rtol=1e-5, atol=1e-5) + expected_upper = np.linalg.cholesky(self.np_x).T + np.testing.assert_allclose( + out3.numpy(), expected_upper, rtol=1e-5, atol=1e-5 + ) + + def test_static_Compatibility(self): + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data(name="x", shape=[3, 3], dtype="float64") + + out1 = paddle.linalg.cholesky(x) + out2 = paddle.linalg.cholesky(input=x, upper=False) + + exe = paddle.static.Executor() + fetches = exe.run( + main, + feed={"x": self.np_x}, + fetch_list=[out1, out2], + ) + expected = np.linalg.cholesky(self.np_x) + for out in fetches: + np.testing.assert_allclose(out, expected, rtol=1e-5, atol=1e-5) + + # Test nn.functional.prelu compatibility (input alias for x) + paddle.disable_static() + + +class TestPreluAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + self.np_x = np.random.rand(1, 2, 3).astype("float32") + self.np_weight = np.array([0.25], dtype="float32") + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor(self.np_x) + w = paddle.to_tensor(self.np_weight) + + # 1. Paddle positional arguments + out1 = paddle.nn.functional.prelu(x, w) + # 2. Paddle keyword arguments + out2 = paddle.nn.functional.prelu(x=x, weight=w) + # 3. PyTorch keyword arguments (input alias) + out3 = paddle.nn.functional.prelu(input=x, weight=w) + + expected = out1.numpy() + np.testing.assert_allclose(out1.numpy(), expected) + np.testing.assert_allclose(out2.numpy(), expected) + np.testing.assert_allclose(out3.numpy(), expected) + + def test_static_Compatibility(self): + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data(name="x", shape=[1, 2, 3], dtype="float32") + w = paddle.static.data(name="w", shape=[1], dtype="float32") + + out1 = paddle.nn.functional.prelu(x, w) + out2 = paddle.nn.functional.prelu(x=x, weight=w) + out3 = paddle.nn.functional.prelu(input=x, weight=w) + + exe = paddle.static.Executor() + fetches = exe.run( + main, + feed={"x": self.np_x, "w": self.np_weight}, + fetch_list=[out1, out2, out3], + ) + expected = fetches[0] + for out in fetches: + np.testing.assert_allclose(out, expected) + + # Test linalg.qr compatibility (A alias for x) + paddle.disable_static() + + +class TestLinalgQrAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + self.np_x = np.random.rand(3, 3).astype("float64") + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor(self.np_x) + + # 1. Paddle positional arguments + q1, r1 = paddle.linalg.qr(x, mode='reduced') + # 2. Paddle keyword arguments + q2, r2 = paddle.linalg.qr(x=x, mode='reduced') + # 3. PyTorch keyword arguments (input alias) + q3, r3 = paddle.linalg.qr(input=x, mode='reduced') + # 4. PyTorch keyword arguments (A alias) + q4, r4 = paddle.linalg.qr(A=x, mode='reduced') + # 5. out parameter + q5 = paddle.empty([3, 3], dtype='float64') + r5 = paddle.empty([3, 3], dtype='float64') + q_out, r_out = paddle.linalg.qr(x, mode='reduced', out=(q5, r5)) + # 6. mode='r' returns single Tensor R + r6 = paddle.linalg.qr(x, mode='r') + # 7. mode='r' with out parameter (single tensor) + r7_out = paddle.empty([3, 3], dtype='float64') + paddle.linalg.qr(x, mode='r', out=r7_out) + + np.testing.assert_allclose(q1.numpy(), q2.numpy()) + np.testing.assert_allclose(q1.numpy(), q3.numpy()) + np.testing.assert_allclose(q1.numpy(), q4.numpy()) + np.testing.assert_allclose(q1.numpy(), q_out.numpy()) + np.testing.assert_allclose(r1.numpy(), r2.numpy()) + np.testing.assert_allclose(r1.numpy(), r3.numpy()) + np.testing.assert_allclose(r1.numpy(), r4.numpy()) + np.testing.assert_allclose(r1.numpy(), r_out.numpy()) + # Verify mode='r' returns matching R + np.testing.assert_allclose(r1.numpy(), r6.numpy()) + # Verify mode='r' with out parameter + np.testing.assert_allclose(r6.numpy(), r7_out.numpy()) + + # 8. Tensor method - positional + q6, r6 = x.qr('reduced') + # 7. Tensor method - kwargs + q7, r7 = x.qr(mode='reduced') + + np.testing.assert_allclose(q1.numpy(), q6.numpy()) + np.testing.assert_allclose(q1.numpy(), q7.numpy()) + np.testing.assert_allclose(r1.numpy(), r6.numpy()) + np.testing.assert_allclose(r1.numpy(), r7.numpy()) + + def test_static_Compatibility(self): + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data(name="x", shape=[3, 3], dtype="float64") + + # 1. Paddle positional arguments + q1, r1 = paddle.linalg.qr(x, mode='reduced') + # 2. Paddle keyword arguments + q2, r2 = paddle.linalg.qr(x=x, mode='reduced') + # 3. PyTorch keyword arguments (input alias) + q3, r3 = paddle.linalg.qr(input=x, mode='reduced') + # 4. PyTorch keyword arguments (A alias) + q4, r4 = paddle.linalg.qr(A=x, mode='reduced') + # 5. Tensor method + q5, r5 = x.qr(mode='reduced') + # 6. mode='r' returns single Tensor R + r6 = paddle.linalg.qr(x, mode='r') + # 7. mode='r' with out parameter + r7_out = paddle.static.data( + name="r7_out", shape=[3, 3], dtype="float64" + ) + paddle.linalg.qr(x, mode='r', out=r7_out) + + exe = paddle.static.Executor() + fetches = exe.run( + main, + feed={ + "x": self.np_x, + "r7_out": np.zeros([3, 3], dtype="float64"), + }, + fetch_list=[q1, r1, q2, r2, q3, r3, q4, r4, q5, r5, r6, r7_out], + ) + # Verify Q matrices match + for i in range(0, 10, 2): + np.testing.assert_allclose(fetches[0], fetches[i]) + # Verify R matrices match + for i in range(1, 10, 2): + np.testing.assert_allclose(fetches[1], fetches[i]) + # Verify mode='r' returns matching R + np.testing.assert_allclose(fetches[1], fetches[10]) + # Verify mode='r' with out parameter + np.testing.assert_allclose(fetches[10], fetches[11]) + + # Test clamp_ compatibility (functional inplace) + paddle.disable_static() + + +class TestClamp_API(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + self.np_x = np.array([-1.0, 0.5, 2.0, 3.5]).astype("float32") + + def test_dygraph_Compatibility(self): + paddle.disable_static() + # 1. Paddle positional arguments + x1 = paddle.to_tensor(self.np_x) + out1 = paddle.clamp_(x1, min=0.0, max=2.0) + + # 2. Paddle keyword arguments + x2 = paddle.to_tensor(self.np_x) + out2 = paddle.clamp_(x=x2, min=0.0, max=2.0) + + # 3. PyTorch keyword arguments (input alias) + x3 = paddle.to_tensor(self.np_x) + out3 = paddle.clamp_(input=x3, min=0.0, max=2.0) + + # 4. Tensor method + x4 = paddle.to_tensor(self.np_x) + out4 = x4.clamp_(min=0.0, max=2.0) + + expected = np.clip(self.np_x, 0.0, 2.0) + for out in [out1, out2, out3, out4]: + np.testing.assert_allclose(out.numpy(), expected) + # Verify inplace modification + np.testing.assert_allclose(x1.numpy(), expected) + np.testing.assert_allclose(x2.numpy(), expected) + np.testing.assert_allclose(x3.numpy(), expected) + np.testing.assert_allclose(x4.numpy(), expected) + + # 5. Paddle positional args without keyword + x5 = paddle.to_tensor(self.np_x * 2) + expected5 = np.clip(self.np_x * 2, 0.0, 2.0) + out5 = paddle.clamp_(x5, 0.0, 2.0) + np.testing.assert_allclose(out5.numpy(), expected5) + np.testing.assert_allclose(x5.numpy(), expected5) + + # 6. Mixed arguments + x6 = paddle.to_tensor(self.np_x * 2) + out6 = paddle.clamp_(x6, min=0.0, max=2.0) + np.testing.assert_allclose(out6.numpy(), expected5) + np.testing.assert_allclose(x6.numpy(), expected5) + + paddle.enable_static() + + # Inplace API no static graph test + + +# Test rms_norm compatibility +class TestRmsNormFnAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + self.np_x = np.random.rand(2, 3, 4).astype("float16") + self.np_weight = np.ones(4).astype("float16") + self.np_x_fp32 = self.np_x.astype("float32") + self.np_weight_fp32 = self.np_weight.astype("float32") + + def test_dygraph_Compatibility(self): + if sys.platform == "win32": + return + if not paddle.device.is_compiled_with_cuda(): + return + paddle.disable_static() + x = paddle.to_tensor(self.np_x) + weight = paddle.to_tensor(self.np_weight) + + # 1. Paddle Positional arguments + out1 = paddle.nn.functional.rms_norm(x, [4], weight) + # 2. Paddle keyword arguments + out2 = paddle.nn.functional.rms_norm( + input=x, normalized_shape=[4], weight=weight + ) + # 3. PyTorch keyword arguments (alias) + out3 = paddle.nn.functional.rms_norm( + input=x, weight=weight, normalized_shape=[4] + ) + + for out in [out1, out2, out3]: + self.assertEqual(out.shape, x.shape) + self.assertEqual(out.dtype, paddle.float16) + + # Numerical verification: rms_norm(x) = x / sqrt(mean(x^2) + eps) * weight + np_weight = self.np_weight_fp32.reshape(1, 1, 4) + np_rms = np.sqrt( + np.mean(self.np_x_fp32**2, axis=2, keepdims=True) + 1e-5 + ) + expected = self.np_x_fp32 / np_rms * np_weight + for out in [out1, out2, out3]: + np.testing.assert_allclose( + out.numpy(), expected, rtol=1e-2, atol=1e-2 + ) + + def test_static_Compatibility(self): + if sys.platform == "win32": + return + if not paddle.device.is_compiled_with_cuda(): + return + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data(name="x", shape=[2, 3, 4], dtype="float16") + weight = paddle.static.data( + name="weight", shape=[4], dtype="float16" + ) + + out1 = paddle.nn.functional.rms_norm(x, [4], weight) + out2 = paddle.nn.functional.rms_norm( + input=x, normalized_shape=[4], weight=weight + ) + + exe = paddle.static.Executor() + fetches = exe.run( + main, + feed={"x": self.np_x, "weight": self.np_weight}, + fetch_list=[out1, out2], + ) + np_weight = self.np_weight_fp32.reshape(1, 1, 4) + np_rms = np.sqrt( + np.mean(self.np_x_fp32**2, axis=2, keepdims=True) + 1e-5 + ) + expected = self.np_x_fp32 / np_rms * np_weight + for out in fetches: + np.testing.assert_allclose(out, expected, rtol=1e-2, atol=1e-2) + + paddle.disable_static() + + +class TestInstanceNormFnAPI(unittest.TestCase): + def setUp(self): + np.random.seed(2025) + self.np_x = np.random.rand(2, 3, 4, 4).astype("float32") + self.np_weight = np.ones(3).astype("float32") + self.np_bias = np.zeros(3).astype("float32") + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor(self.np_x) + weight = paddle.to_tensor(self.np_weight) + bias = paddle.to_tensor(self.np_bias) + + compat_in = paddle.compat.nn.functional.instance_norm + + # 1. PyTorch-style positional arguments + out1 = compat_in(x, weight=weight, bias=bias) + # 2. PyTorch-style keyword arguments + out2 = compat_in(input=x, weight=weight, bias=bias) + # 3. Paddle-style positional + out3 = paddle.nn.functional.instance_norm(x, weight=weight, bias=bias) + + for out in [out1, out2]: + self.assertEqual(out.shape, x.shape) + self.assertEqual(out.dtype, paddle.float32) + + # Verify compat output matches native paddle + np.testing.assert_allclose( + out1.numpy(), out3.numpy(), rtol=1e-5, atol=1e-5 + ) + + # 4. Verify result matches PyTorch numerical expectation + # Instance norm on [N,C,H,W]: compute mean/var per (N,C) plane + mean = x.mean(axis=(2, 3), keepdim=True) + var = x.var(axis=(2, 3), keepdim=True, unbiased=False) + expected = (x - mean) / (var + 1e-5).sqrt() * weight.reshape( + [1, 3, 1, 1] + ) + bias.reshape([1, 3, 1, 1]) + np.testing.assert_allclose( + out1.numpy(), expected.numpy(), rtol=1e-4, atol=1e-4 + ) + + # 5. Test momentum conversion: torch momentum=0.1 -> paddle momentum=0.9 + out_torch_momentum = compat_in( + input=x, + weight=weight, + bias=bias, + momentum=0.1, + ) + out_paddle_momentum = paddle.nn.functional.instance_norm( + x, + weight=weight, + bias=bias, + momentum=0.9, + ) + np.testing.assert_allclose( + out_torch_momentum.numpy(), + out_paddle_momentum.numpy(), + rtol=1e-5, + atol=1e-5, + ) + + def test_static_Compatibility(self): + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data( + name="x", shape=self.np_x.shape, dtype="float32" + ) + weight = paddle.static.data( + name="weight", shape=[3], dtype="float32" + ) + bias = paddle.static.data(name="bias", shape=[3], dtype="float32") + + # 1. Paddle positional arguments + out1 = paddle.nn.functional.instance_norm( + x, weight=weight, bias=bias + ) + # 2. Paddle keyword arguments + out2 = paddle.nn.functional.instance_norm( + x=x, weight=weight, bias=bias + ) + + exe = paddle.static.Executor() + fetches = exe.run( + feed={ + "x": self.np_x, + "weight": self.np_weight, + "bias": self.np_bias, + }, + fetch_list=[out1, out2], + ) + for f in fetches: + self.assertEqual(f.shape, self.np_x.shape) + self.assertEqual(f.dtype, np.float32) + + paddle.enable_static() + + paddle.disable_static() + + +class TestQrAPICompatibility(unittest.TestCase): + def test_dygraph_compatibility(self): + paddle.disable_static() + x = paddle.to_tensor([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]).astype( + 'float64' + ) + + # 1. Default mode (reduced) + q1, r1 = paddle.linalg.qr(x) + # 2. Paddle keyword arguments + q2, r2 = paddle.linalg.qr(x=x, mode='reduced') + # 3. PyTorch keyword arguments (alias) + q3, r3 = paddle.linalg.qr(input=x) + # 4. mode='r' returns single Tensor R + r4 = paddle.linalg.qr(x, mode='r') + self.assertEqual(r4.shape, [2, 2]) + # 5. mode='complete' + q5, r5 = paddle.linalg.qr(x, mode='complete') + self.assertEqual(q5.shape, [3, 3]) + self.assertEqual(r5.shape, [3, 2]) + # 6. Tensor method + q6, r6 = x.qr() + # 7. Tensor method with mode='r' returns single Tensor R + r7 = x.qr('r') + self.assertEqual(r7.shape, [2, 2]) + # 8. out parameter + q_out = paddle.empty([3, 2], dtype='float64') + r_out = paddle.empty([2, 2], dtype='float64') + result = paddle.linalg.qr(x, out=(q_out, r_out)) + self.assertIs(result[0], q_out) + self.assertIs(result[1], r_out) + + def test_static_Compatibility(self): + paddle.enable_static() + main = paddle.static.Program() + startup = paddle.static.Program() + with paddle.static.program_guard(main, startup): + x = paddle.static.data(name="x", shape=[3, 2], dtype="float64") + + q1, r1 = paddle.linalg.qr(x) + q2, r2 = paddle.linalg.qr(x, mode='reduced') + q3, r3 = paddle.linalg.qr(input=x) + r4 = paddle.linalg.qr(x, mode='r') + + exe = paddle.static.Executor() + np_x = np.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]).astype( + "float64" + ) + fetches = exe.run( + main, + feed={"x": np_x}, + fetch_list=[q1, r1, q2, r2, q3, r3, r4], + ) + # Verify all Q match + for i in range(0, 6, 2): + np.testing.assert_allclose(fetches[0], fetches[i]) + # Verify all R match + for i in range(1, 6, 2): + np.testing.assert_allclose(fetches[1], fetches[i]) + # Verify mode='r' gives R + np.testing.assert_allclose(fetches[1], fetches[6]) + + paddle.disable_static() + if __name__ == "__main__": unittest.main() diff --git a/test/legacy_test/test_cholesky_op.py b/test/legacy_test/test_cholesky_op.py index ca19fbb58e0e3..763b228c6fca1 100644 --- a/test/legacy_test/test_cholesky_op.py +++ b/test/legacy_test/test_cholesky_op.py @@ -240,7 +240,7 @@ def _test_case(self): paddle.linalg.cholesky(paddle.randn([0, 5])) def test_error(self): - self.assertRaises(AssertionError, self._test_case) + self.assertRaises(ValueError, self._test_case) if __name__ == "__main__": diff --git a/test/legacy_test/test_cross_op.py b/test/legacy_test/test_cross_op.py index d8b0322747be8..f2c1ee62df646 100644 --- a/test/legacy_test/test_cross_op.py +++ b/test/legacy_test/test_cross_op.py @@ -359,6 +359,41 @@ def init_output(self): self.outputs = {'Out': np.array(z_list).reshape(self.shape)} +class TestLinalgCrossDefaultDim(unittest.TestCase): + def test_linalg_cross_default_dim(self): + # Test that paddle.linalg.cross defaults to dim=-1, not axis=9 auto + # Using shape [3, 2, 3] where auto-axis picks dim 0, but dim=-1 picks dim 2 + paddle.disable_static() + np_x = np.random.randn(3, 2, 3).astype('float32') + np_y = np.random.randn(3, 2, 3).astype('float32') + + x = paddle.to_tensor(np_x) + y = paddle.to_tensor(np_y) + + # linalg.cross with default (should use dim=-1) + out_default = paddle.linalg.cross(x, y) + # linalg.cross with explicit dim=-1 + out_neg1 = paddle.linalg.cross(x, y, dim=-1) + # linalg.cross with explicit dim=2 + out_dim2 = paddle.linalg.cross(x, y, dim=2) + # linalg.cross with explicit dim=0 + out_dim0 = paddle.linalg.cross(x, y, dim=0) + + np.testing.assert_allclose( + out_default.numpy(), out_neg1.numpy(), rtol=1e-5 + ) + np.testing.assert_allclose( + out_default.numpy(), out_dim2.numpy(), rtol=1e-5 + ) + # dim=0 should give different result when shape is [3, 2, 3] + with self.assertRaises(AssertionError): + np.testing.assert_allclose( + out_default.numpy(), out_dim0.numpy(), rtol=1e-5 + ) + + paddle.enable_static() + + if __name__ == '__main__': paddle.enable_static() unittest.main()