Skip to content
Merged
2 changes: 2 additions & 0 deletions python/paddle/base/dygraph/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from paddle.base import core, framework
from paddle.base.framework import global_var
from paddle.base.multiprocess_utils import CleanupFuncRegistrar
from paddle.utils.decorator_utils import ParamAliasDecorator

from ..framework import _get_paddle_place
from ..wrapped_decorator import (
Expand Down Expand Up @@ -323,6 +324,7 @@ def no_grad(func: None = ...) -> AbstractContextManager: ...
def no_grad(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]: ...


@ParamAliasDecorator({"func": ["orig_func"]})
def no_grad(func=None):
"""
:api_attr: imperative
Expand Down
4 changes: 4 additions & 0 deletions python/paddle/sparse/unary.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
)
from paddle.common_ops_import import Variable
from paddle.framework import LayerHelper
from paddle.utils.decorator_utils import (
param_one_alias,
)

if TYPE_CHECKING:
from collections.abc import Sequence
Expand Down Expand Up @@ -879,6 +882,7 @@ def expm1(x: Tensor, name: str | None = None) -> Tensor:
return _C_ops.sparse_expm1(x)


@param_one_alias({"x": "input"})
def reshape(x: Tensor, shape: ShapeLike, name: str | None = None) -> Tensor:
"""
Changes the shape of ``x`` without changing its value, requiring x to be a SparseCooTensor or SparseCsrTensor.
Expand Down
3 changes: 3 additions & 0 deletions python/paddle/tensor/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from paddle import _C_ops
from paddle.tensor.creation import full
from paddle.tensor.math import broadcast_shape
from paddle.utils.decorator_utils import ParamAliasDecorator
from paddle.utils.inplace_utils import inplace_apis_in_dygraph_only

from ..base.data_feeder import check_type, check_variable_and_dtype
Expand Down Expand Up @@ -1329,6 +1330,7 @@ def bitwise_and_(x: Tensor, y: Tensor, name: str | None = None) -> Tensor:
return _C_ops.bitwise_and_(x, y)


@ParamAliasDecorator({"x": ["input"], "y": ["other"]})
def bitwise_or(
x: Tensor, y: Tensor, out: Tensor | None = None, name: str | None = None
) -> Tensor:
Expand Down Expand Up @@ -1389,6 +1391,7 @@ def __ror__(


@inplace_apis_in_dygraph_only
@ParamAliasDecorator({"x": ["input"], "y": ["other"]})
def bitwise_or_(x: Tensor, y: Tensor, name: str | None = None) -> Tensor:
r"""
Inplace version of ``bitwise_or`` API, the output Tensor will be inplaced with input ``x``.
Expand Down
6 changes: 5 additions & 1 deletion python/paddle/tensor/manipulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
import paddle
from paddle import _C_ops
from paddle.tensor import fill_constant
from paddle.utils.decorator_utils import ParamAliasDecorator
from paddle.utils.decorator_utils import (
ParamAliasDecorator,
param_one_alias,
)
from paddle.utils.inplace_utils import inplace_apis_in_dygraph_only

from ..base.data_feeder import (
Expand Down Expand Up @@ -4976,6 +4979,7 @@ def get_attr_expand_shape(list_expand_shape):
return out


@param_one_alias({"x": "input"})
def reshape(x: Tensor, shape: ShapeLike, name: str | None = None) -> Tensor:
"""
Changes the shape of ``x`` without changing its data.
Expand Down
2 changes: 2 additions & 0 deletions python/paddle/tensor/math.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from paddle.base.libpaddle import DataType
from paddle.common_ops_import import VarDesc, dygraph_utils
from paddle.pir import Value
from paddle.utils.decorator_utils import ParamAliasDecorator
from paddle.utils.inplace_utils import inplace_apis_in_dygraph_only

from ..base.data_feeder import (
Expand Down Expand Up @@ -4949,6 +4950,7 @@ def isnan(x: Tensor, name: str | None = None) -> Tensor:
return out


@ParamAliasDecorator({"x": ["input"], "axis": ["dim"]})
def prod(
x: Tensor,
axis: int | Sequence[int] | None = None,
Expand Down
62 changes: 39 additions & 23 deletions python/paddle/utils/decorator_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ class DecoratorBase:
"""

def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Initialize decorator parameters"""
self.args = args
self.kwargs = kwargs

Expand All @@ -38,25 +37,15 @@ def __call__(self, func: _F) -> _F:
def wrapper(*args, **kwargs):
# Pretreatment parameters
processed_args, processed_kwargs = self.process(args, kwargs)
# Call the original function
return func(*processed_args, **processed_kwargs)

# Keep original signature
wrapper.__signature__ = inspect.signature(func)
return cast("_F", wrapper)

def process(
self, args: tuple[Any, ...], kwargs: dict[str, Any]
) -> tuple[tuple[Any, ...], dict[str, Any]]:
"""Core processing methods that subclasses must implement.

Args:
args: positional parameter
kwargs: Keyword Argument

Returns:
Processed tuples (args, kwargs)
"""
"""To be implemented by subclass"""
raise NotImplementedError("Subclasses must implement this method")


Expand All @@ -66,31 +55,58 @@ class ParamAliasDecorator(DecoratorBase):

def __init__(self, alias_mapping: dict[str, Iterable[str]]) -> None:
super().__init__()
# Check alias_mapping types
if not isinstance(alias_mapping, dict):
raise TypeError("alias_mapping must be a dictionary")
for k, v in alias_mapping.items():
if not isinstance(v, (list, tuple, set)):
raise TypeError(f"Aliases for '{k}' must be iterable")
self.alias_mapping = alias_mapping

# Build a reverse alias map for faster lookup
self.alias_mapping = {}
for original, aliases in alias_mapping.items():
for alias in aliases:
self.alias_mapping[alias] = original

def process(
self, args: tuple[Any, ...], kwargs: dict[str, Any]
) -> tuple[tuple[Any, ...], dict[str, Any]]:
"""Process parameters to handle alias mapping"""
if not kwargs:
return args, kwargs
processed_kwargs = kwargs.copy()
for original, aliases in self.alias_mapping.items():
for alias in aliases:
if alias in processed_kwargs:
if original not in processed_kwargs:
processed_kwargs[original] = processed_kwargs.pop(alias)
else:
raise ValueError(
f"Cannot specify both '{original}' and its alias '{alias}'"
)

processed_kwargs = kwargs
alias_mapping = self.alias_mapping

# Directly modify kwargs based on alias mapping (only modify if necessary)
for alias, original in alias_mapping.items():
if alias in processed_kwargs:
if original not in processed_kwargs:
# Only modify the dictionary if necessary
processed_kwargs[original] = processed_kwargs.pop(alias)
else:
raise ValueError(
f"Cannot specify both '{original}' and its alias '{alias}'"
)

return args, processed_kwargs


def param_one_alias(alias_mapping):
def decorator(func):
def wrapper(*args, **kwargs):
if not kwargs:
return func(*args, **kwargs)
if ("input" in kwargs) and ("x" not in kwargs):
kwargs["x"] = kwargs.pop("input")
return func(*args, **kwargs)

wrapper.__signature__ = inspect.signature(func)
return wrapper

return decorator


# *size => shape decorator
class SizeArgsDecorator(DecoratorBase):
"""
Expand Down
16 changes: 16 additions & 0 deletions test/legacy_test/test_inplace.py
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,22 @@ def non_inplace_api_processing(self, var):
return paddle.bitwise_or(var, self.y)


class TestDygraphInplacBitwiseOrAlias1(TestDygraphInplacBitwiseAnd):
def inplace_api_processing(self, var):
return paddle.bitwise_or_(var, other=self.y)

def non_inplace_api_processing(self, var):
return paddle.bitwise_or(var, other=self.y)


class TestDygraphInplacBitwiseOrAlias2(TestDygraphInplacBitwiseAnd):
def inplace_api_processing(self, var):
return paddle.bitwise_or_(input=var, other=self.y)

def non_inplace_api_processing(self, var):
return paddle.bitwise_or(input=var, other=self.y)


class TestDygraphInplacBitwiseXor(TestDygraphInplacBitwiseAnd):
def inplace_api_processing(self, var):
return paddle.bitwise_xor_(var, self.y)
Expand Down
113 changes: 113 additions & 0 deletions test/legacy_test/test_prod_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,5 +311,118 @@ def run_imperative(self, place):
np.testing.assert_allclose(out.numpy(), input.numpy())


class TestProdAliasOp(unittest.TestCase):
def setUp(self):
self.input = np.random.random(size=(10, 10, 5)).astype(np.float32)

def run_imperative(self, place):
input = paddle.to_tensor(self.input, place=place)
out = paddle.prod(input=input)
expected_result = np.prod(self.input)
np.testing.assert_allclose(out.numpy(), expected_result, rtol=1e-05)

out = paddle.prod(input, dim=1)
expected_result = np.prod(self.input, axis=1)
np.testing.assert_allclose(out.numpy(), expected_result, rtol=1e-05)

out = paddle.prod(input=input, dim=-1)
expected_result = np.prod(self.input, axis=-1)
np.testing.assert_allclose(out.numpy(), expected_result, rtol=1e-05)

out = paddle.prod(input, dim=[0, 1])
expected_result = np.prod(self.input, axis=(0, 1))
np.testing.assert_allclose(
out.numpy(), expected_result, rtol=1e-05, atol=1e-8
)

out = paddle.prod(input, dim=1, keepdim=True)
expected_result = np.prod(self.input, axis=1, keepdims=True)
np.testing.assert_allclose(out.numpy(), expected_result, rtol=1e-05)

out = paddle.prod(input=input, dim=1, dtype='int64')
expected_result = np.prod(self.input, axis=1, dtype=np.int64)
np.testing.assert_allclose(out.numpy(), expected_result, rtol=1e-05)

out = paddle.prod(input=input, dim=1, keepdim=True, dtype='int64')
expected_result = np.prod(
self.input, axis=1, keepdims=True, dtype=np.int64
)
np.testing.assert_allclose(out.numpy(), expected_result, rtol=1e-05)

def run_static(self, use_gpu=False):
with paddle.static.program_guard(paddle.static.Program()):
input = paddle.static.data(
name='input', shape=[10, 10, 5], dtype='float32'
)
result0 = paddle.prod(input=input)
result1 = paddle.prod(input, dim=1)
result2 = paddle.prod(input=input, dim=-1)
result3 = paddle.prod(input, dim=[0, 1])
result4 = paddle.prod(input, dim=1, keepdim=True)
result5 = paddle.prod(input=input, dim=1, dtype='int64')
result6 = paddle.prod(input, dim=1, keepdim=True, dtype='int64')

place = paddle.CUDAPlace(0) if use_gpu else paddle.CPUPlace()
exe = paddle.static.Executor(place)
exe.run(paddle.static.default_startup_program())
static_result = exe.run(
feed={"input": self.input},
fetch_list=[
result0,
result1,
result2,
result3,
result4,
result5,
result6,
],
)

expected_result = np.prod(self.input)
np.testing.assert_allclose(
static_result[0], expected_result, rtol=1e-05
)
expected_result = np.prod(self.input, axis=1)
np.testing.assert_allclose(
static_result[1], expected_result, rtol=1e-05
)
expected_result = np.prod(self.input, axis=-1)
np.testing.assert_allclose(
static_result[2], expected_result, rtol=1e-05
)
expected_result = np.prod(self.input, axis=(0, 1))
np.testing.assert_allclose(
static_result[3], expected_result, rtol=1e-05, atol=1e-8
)
expected_result = np.prod(self.input, axis=1, keepdims=True)
np.testing.assert_allclose(
static_result[4], expected_result, rtol=1e-05
)
expected_result = np.prod(self.input, axis=1, dtype=np.int64)
np.testing.assert_allclose(
static_result[5], expected_result, rtol=1e-05
)
expected_result = np.prod(
self.input, axis=1, keepdims=True, dtype=np.int64
)
np.testing.assert_allclose(
static_result[6], expected_result, rtol=1e-05
)

def test_cpu(self):
with dygraph_guard():
self.run_imperative(place=paddle.CPUPlace())
with static_guard():
self.run_static()

def test_gpu(self):
if not paddle.base.core.is_compiled_with_cuda():
return
with dygraph_guard():
self.run_imperative(place=paddle.CUDAPlace(0))
with static_guard():
self.run_static()


if __name__ == "__main__":
unittest.main()
Loading