diff --git a/python/paddle/nn/functional/input.py b/python/paddle/nn/functional/input.py index 602f8df38300f7..c51e48ebfe1a8c 100644 --- a/python/paddle/nn/functional/input.py +++ b/python/paddle/nn/functional/input.py @@ -17,7 +17,7 @@ import paddle from paddle import _C_ops -from paddle.utils.decorator_utils import ParamAliasDecorator +from paddle.utils.decorator_utils import param_one_alias from ...base.data_feeder import check_variable_and_dtype from ...base.layer_helper import LayerHelper @@ -162,7 +162,7 @@ def embedding_renorm_( return weight -@ParamAliasDecorator({"x": ["input"]}) +@param_one_alias(["x", "input"]) def embedding( x: Tensor, weight: Tensor, diff --git a/python/paddle/sparse/unary.py b/python/paddle/sparse/unary.py index 572d50089a1bf3..82a4688fdbd669 100644 --- a/python/paddle/sparse/unary.py +++ b/python/paddle/sparse/unary.py @@ -882,7 +882,7 @@ def expm1(x: Tensor, name: str | None = None) -> Tensor: return _C_ops.sparse_expm1(x) -@param_one_alias({"x": "input"}) +@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. diff --git a/python/paddle/tensor/logic.py b/python/paddle/tensor/logic.py index 01ead4a064bc6e..6e02ce0d548385 100755 --- a/python/paddle/tensor/logic.py +++ b/python/paddle/tensor/logic.py @@ -22,7 +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.decorator_utils import ParamAliasDecorator, param_two_alias from paddle.utils.inplace_utils import inplace_apis_in_dygraph_only from ..base.data_feeder import check_type, check_variable_and_dtype @@ -1330,7 +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"]}) +@param_two_alias(["x", "input"], ["y", "other"]) def bitwise_or( x: Tensor, y: Tensor, out: Tensor | None = None, name: str | None = None ) -> Tensor: diff --git a/python/paddle/tensor/manipulation.py b/python/paddle/tensor/manipulation.py index d99da47a1301d2..5dbbfdea0fe0ea 100644 --- a/python/paddle/tensor/manipulation.py +++ b/python/paddle/tensor/manipulation.py @@ -27,6 +27,7 @@ from paddle.utils.decorator_utils import ( ParamAliasDecorator, param_one_alias, + param_two_alias, view_decorator, ) from paddle.utils.inplace_utils import inplace_apis_in_dygraph_only @@ -3471,7 +3472,7 @@ def squeeze_( return _C_ops.squeeze_(input, axes) -@ParamAliasDecorator({"x": ["input"], "axis": ["dim"]}) +@param_two_alias(["x", "input"], ["axis", "dim"]) def unique_consecutive( x: Tensor, return_inverse: bool = False, @@ -4988,7 +4989,7 @@ def get_attr_expand_shape(list_expand_shape): return out -@param_one_alias({"x": "input"}) +@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. diff --git a/python/paddle/tensor/math.py b/python/paddle/tensor/math.py index c64cfc2f4b6e8e..72a73ab931ab00 100644 --- a/python/paddle/tensor/math.py +++ b/python/paddle/tensor/math.py @@ -25,7 +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.decorator_utils import ParamAliasDecorator, param_two_alias from paddle.utils.inplace_utils import inplace_apis_in_dygraph_only from ..base.data_feeder import ( @@ -4963,7 +4963,7 @@ def isnan(x: Tensor, name: str | None = None) -> Tensor: return out -@ParamAliasDecorator({"x": ["input"], "axis": ["dim"]}) +@param_two_alias(["x", "input"], ["axis", "dim"]) def prod( x: Tensor, axis: int | Sequence[int] | None = None, @@ -6628,6 +6628,7 @@ def lcm_(x: Tensor, y: Tensor, name: str | None = None) -> Tensor: return out +@ParamAliasDecorator({"x": ["input"], "axis": ["dim"]}) def diff( x: Tensor, n: int = 1, diff --git a/python/paddle/tensor/random.py b/python/paddle/tensor/random.py index 04db34f8709c28..939432af4a5490 100644 --- a/python/paddle/tensor/random.py +++ b/python/paddle/tensor/random.py @@ -29,6 +29,7 @@ in_pir_mode, use_pir_api, ) +from paddle.utils.decorator_utils import param_one_alias from ..base.data_feeder import ( check_dtype, @@ -442,6 +443,7 @@ def log_normal_( return normal_(x, mean=mean, std=std).exp_() +@param_one_alias(["x", "input"]) def multinomial( x: Tensor, num_samples: int = 1, @@ -1949,6 +1951,7 @@ def rand( return uniform(shape, dtype, min=0.0, max=1.0, name=name) +@param_one_alias(["lam", "lambd"]) def exponential_( x: Tensor, lam: float = 1.0, name: str | None = None ) -> Tensor: diff --git a/python/paddle/tensor/stat.py b/python/paddle/tensor/stat.py index 4505d22e1261d1..f180978da8e97c 100644 --- a/python/paddle/tensor/stat.py +++ b/python/paddle/tensor/stat.py @@ -25,7 +25,10 @@ in_dynamic_mode, in_dynamic_or_pir_mode, ) -from paddle.utils.decorator_utils import ParamAliasDecorator +from paddle.utils.decorator_utils import ( + ParamAliasDecorator, + param_two_alias_one_default, +) from ..base.data_feeder import check_type, check_variable_and_dtype from ..common_ops_import import Variable @@ -473,6 +476,7 @@ def nanmedian( @overload +@param_two_alias_one_default(["x", "input"], ["axis", "dim"], ["mode", 'min']) def median( x: Tensor, axis: int = ..., @@ -483,6 +487,7 @@ def median( @overload +@param_two_alias_one_default(["x", "input"], ["axis", "dim"], ["mode", 'min']) def median( x: Tensor, axis: int | None = ..., @@ -492,6 +497,7 @@ def median( ) -> Tensor: ... +@param_two_alias_one_default(["x", "input"], ["axis", "dim"], ["mode", 'min']) def median( x, axis=None, diff --git a/python/paddle/utils/decorator_utils.py b/python/paddle/utils/decorator_utils.py index 35152f365f2125..bf870a73ff6dd5 100644 --- a/python/paddle/utils/decorator_utils.py +++ b/python/paddle/utils/decorator_utils.py @@ -14,6 +14,7 @@ import functools import inspect +import warnings from collections.abc import Iterable from typing import Any, Callable, TypeVar, cast @@ -92,13 +93,120 @@ def process( return args, processed_kwargs -def param_one_alias(alias_mapping): +class SetDefaultParaAliasDecorator(DecoratorBase): + """Support default parameter settings, implementation of parameter alias processing decorator""" + + def __init__( + self, + alias_mapping: dict[str, Iterable[str]], + default_params: dict[str, Any], + ) -> 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") + + # 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 + + self.default_params = default_params + warnings.simplefilter("always", category=Warning) + + 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 + + is_torch_call = False + + # Directly modify kwargs based on alias mapping (only modify if necessary) + for alias, original in self.alias_mapping.items(): + if alias in kwargs: + if original not in kwargs: + kwargs[original] = kwargs.pop(alias) + is_torch_call = True + else: + raise ValueError( + f"Cannot specify both '{original}' and its alias '{alias}'" + ) + + if is_torch_call: + warnings.warn( + "Set default parameters " + str(self.default_params), + category=Warning, + ) + for key, value in self.default_params.items(): + if key not in kwargs: + kwargs[key] = value + + return args, kwargs + + +def param_one_alias(alias_list): def decorator(func): + @functools.wraps(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") + if (alias_list[0] not in kwargs) and (alias_list[1] in kwargs): + kwargs[alias_list[0]] = kwargs.pop(alias_list[1]) + return func(*args, **kwargs) + + wrapper.__signature__ = inspect.signature(func) + return wrapper + + return decorator + + +def param_two_alias(alias_list1, alias_list2): + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + if not kwargs: + return func(*args, **kwargs) + if (alias_list1[0] not in kwargs) and (alias_list1[1] in kwargs): + kwargs[alias_list1[0]] = kwargs.pop(alias_list1[1]) + if (alias_list2[0] not in kwargs) and (alias_list2[1] in kwargs): + kwargs[alias_list2[0]] = kwargs.pop(alias_list2[1]) + return func(*args, **kwargs) + + wrapper.__signature__ = inspect.signature(func) + return wrapper + + return decorator + + +def param_two_alias_one_default(alias_list1, alias_list2, default_param): + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + if not kwargs: + return func(*args, **kwargs) + + is_torch_call = False + + if (alias_list1[0] not in kwargs) and (alias_list1[1] in kwargs): + kwargs[alias_list1[0]] = kwargs.pop(alias_list1[1]) + is_torch_call = True + if (alias_list2[0] not in kwargs) and (alias_list2[1] in kwargs): + kwargs[alias_list2[0]] = kwargs.pop(alias_list2[1]) + is_torch_call = True + + if is_torch_call: + warnings.warn( + "Set default parameters " + str(default_param), + category=Warning, + ) + if default_param[0] not in kwargs: + kwargs[default_param[0]] = default_param[1] return func(*args, **kwargs) wrapper.__signature__ = inspect.signature(func) diff --git a/test/legacy_test/test_diff_op.py b/test/legacy_test/test_diff_op.py index cff2a731bfa4dd..71bda9175d2192 100644 --- a/test/legacy_test/test_diff_op.py +++ b/test/legacy_test/test_diff_op.py @@ -344,6 +344,35 @@ def set_args(self): self.append = None +class TestDiffOpFp16_TorchAlias(TestDiffOp): + def test_fp16_with_gpu(self): + paddle.enable_static() + if paddle.base.core.is_compiled_with_cuda(): + place = paddle.CUDAPlace(0) + with paddle.static.program_guard( + paddle.static.Program(), paddle.static.Program() + ): + input = np.random.random([4, 4]).astype("float16") + x = paddle.static.data( + name="input", shape=[4, 4], dtype="float16" + ) + exe = paddle.static.Executor(place) + out = paddle.diff( + x, + n=self.n, + dim=self.axis, + prepend=self.prepend, + append=self.append, + ) + fetches = exe.run( + feed={ + "input": input, + }, + fetch_list=[out], + ) + paddle.disable_static() + + if __name__ == '__main__': paddle.enable_static() unittest.main() diff --git a/test/legacy_test/test_exponential_op.py b/test/legacy_test/test_exponential_op.py index 1df9276590a0f2..08df9fd24b6263 100644 --- a/test/legacy_test/test_exponential_op.py +++ b/test/legacy_test/test_exponential_op.py @@ -344,6 +344,72 @@ def test_fixed_random_number(self): paddle.enable_static() + def test_fixed_random_number_torch_alias(self): + # Test GPU Fixed random number, which is generated by 'curandStatePhilox4_32_10_t' + if not paddle.is_compiled_with_cuda(): + return + + # Different GPU generatte different random value. Only test V100 here. + if "V100" not in paddle.device.cuda.get_device_name(): + return + + paddle.disable_static() + paddle.set_device('gpu') + paddle.seed(2021) + + x = paddle.empty([64, 3, 1024, 1024], dtype="float32") + x.exponential_(lambd=1.0) + x_np = x.numpy() + expect = [ + 0.80073667, + 0.2249291, + 0.07734892, + 1.25392, + 0.14013891, + 0.45736602, + 1.9735607, + 0.30490234, + 0.57100505, + 0.8115938, + ] + np.testing.assert_allclose(x_np[0, 0, 0, 0:10], expect, rtol=1e-05) + + x = paddle.empty([10, 10], dtype="float32") + x.exponential_(lambd=3.0) + x_np = x.numpy() + expect = [ + 0.02831675, + 0.1691551, + 0.6798956, + 0.69347525, + 0.0243443, + 0.22180498, + 0.30574575, + 0.9839696, + 0.2834912, + 0.59420055, + ] + np.testing.assert_allclose(x_np[5, 0:10], expect, rtol=1e-05) + + x = paddle.empty([16, 2, 1024, 768], dtype="float64") + x.exponential_(lambd=0.25) + x_np = x.numpy() + expect = [ + 10.0541229, + 12.67860643, + 1.09850734, + 7.35289643, + 2.65471225, + 3.86217432, + 2.97902086, + 2.92744479, + 2.67927152, + 0.19667352, + ] + np.testing.assert_allclose(x_np[0, 0, 0, 100:110], expect, rtol=1e-05) + + paddle.enable_static() + class TestExponentialFP16Op(OpTest): def setUp(self): diff --git a/test/legacy_test/test_median.py b/test/legacy_test/test_median.py index 77a9145f9205c7..0fc6008625bb4c 100644 --- a/test/legacy_test/test_median.py +++ b/test/legacy_test/test_median.py @@ -419,5 +419,63 @@ def test_median_dygraph(self): self.dygraph_single_test_median([x, 1, False]) +class TestMedianAlias(unittest.TestCase): + def static_single_test_median(self, lis_test): + paddle.enable_static() + x, axis, keepdims = lis_test + res_np = np_median_min_axis(x, axis=axis, keepdims=keepdims) + main_program = paddle.static.Program() + startup_program = paddle.static.Program() + exe = paddle.static.Executor() + with paddle.static.program_guard(main_program, startup_program): + x_in = paddle.static.data(shape=x.shape, dtype=x.dtype, name='x') + y = paddle.median(x_in, dim=axis, keepdim=keepdims) + [res_pd, _] = exe.run(feed={'x': x}, fetch_list=[y]) + np.testing.assert_allclose(res_pd, res_np) + paddle.disable_static() + + def dygraph_single_test_median(self, lis_test): + x, axis, keepdims = lis_test + res_np = np_median_min_axis(x, axis=axis, keepdims=keepdims) + if axis is None: + res_pd = paddle.median( + paddle.to_tensor(x), dim=axis, keepdim=keepdims + ) + else: + res_pd, _ = paddle.median( + paddle.to_tensor(x), dim=axis, keepdim=keepdims + ) + np.testing.assert_allclose(res_pd.numpy(False), res_np) + + def test_median_static(self): + h = 3 + w = 4 + l = 2 + x = np.arange(h * w * l).reshape([h, w, l]).astype("float32") + lis_tests = [ + [x.astype(dtype), axis, keepdims] + for axis in [-1, 0, 1, 2] + for keepdims in [False, True] + for dtype in ['float32', 'float64', 'int32', 'int64'] + ] + for lis_test in lis_tests: + self.static_single_test_median(lis_test) + + def test_median_dygraph(self): + paddle.disable_static() + h = 3 + w = 4 + l = 2 + x = np.arange(h * w * l).reshape([h, w, l]).astype("float32") + lis_tests = [ + [x.astype(dtype), axis, keepdims] + for axis in [-1, 0, 1, 2] + for keepdims in [False, True] + for dtype in ['float32', 'float64', 'int32', 'int64'] + ] + for lis_test in lis_tests: + self.dygraph_single_test_median(lis_test) + + if __name__ == '__main__': unittest.main() diff --git a/test/legacy_test/test_multinomial_op.py b/test/legacy_test/test_multinomial_op.py index c863bffad3b763..8f8bf75be5e3be 100644 --- a/test/legacy_test/test_multinomial_op.py +++ b/test/legacy_test/test_multinomial_op.py @@ -348,6 +348,53 @@ def test_alias(self): paddle.tensor.multinomial(x, num_samples=10, replacement=True) paddle.tensor.random.multinomial(x, num_samples=10, replacement=True) + def test_alias_torch(self): + if not paddle.is_compiled_with_cuda(): + return + + if "V100" not in paddle.device.cuda.get_device_name(): + return + + paddle.disable_static() + paddle.set_device('gpu') + paddle.seed(100) + + x = paddle.randint(0, 100, [1024, 10000]).astype('float32') + y = paddle.multinomial( + input=x, num_samples=1, replacement=False + ).numpy() + self.assertEqual(np.sum(y), 5187793) + self.assertEqual(np.mean(y), 5066.2041015625) + expect = [9982, 1655, 4741, 1323, 9319, 3298, 6473, 7477, 2507, 2628] + np.testing.assert_array_equal(y[100:110, :].flatten(), expect) + + y = paddle.multinomial( + input=x, num_samples=5000, replacement=False + ).numpy() + self.assertEqual(np.sum(y), 25603962316) + self.assertEqual(np.mean(y), 5000.77388984375) + expect = [7300, 6055, 8714, 5401, 7360, 161, 5035, 7002, 6788, 2916] + np.testing.assert_array_equal(y[100, 1000:1010], expect) + + y = paddle.multinomial( + input=x, num_samples=5000, replacement=False + ).numpy() + self.assertEqual(np.sum(y), 25592855710) + self.assertEqual(np.mean(y), 4998.604630859375) + expect = [5700, 6567, 4399, 5688, 7472, 545, 6894, 526, 2124, 385] + np.testing.assert_array_equal(y[300, 3000:3010], expect) + + y = paddle.multinomial( + input=x, num_samples=20000, replacement=True + ).numpy() + self.assertEqual(np.sum(y), 102371362581) + self.assertEqual(np.mean(y), 4998.60168852539) + self.assertEqual(np.std(y), 2886.316308500771) + expect = [7630, 8235, 8445, 3275, 5580, 4591, 1331, 342, 1662, 7156] + np.testing.assert_array_equal(y[100, 0:10], expect) + + paddle.enable_static() + class TestMultinomialError(unittest.TestCase): def setUp(self):