From 26e05e4c2e8f360e516b024fcbb69611757253c6 Mon Sep 17 00:00:00 2001 From: Ayakouji Date: Fri, 8 Aug 2025 14:57:42 +0800 Subject: [PATCH 01/10] add permute api --- python/paddle/__init__.py | 2 + python/paddle/tensor/__init__.py | 1 + python/paddle/tensor/linalg.py | 44 ++++++++++++++++ test/legacy_test/test_permute_op.py | 79 +++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+) create mode 100644 test/legacy_test/test_permute_op.py diff --git a/python/paddle/__init__.py b/python/paddle/__init__.py index 9111fe8eda5af1..dd374b8934a289 100644 --- a/python/paddle/__init__.py +++ b/python/paddle/__init__.py @@ -242,6 +242,7 @@ matrix_transpose, mv, norm, + permute, t, t_, transpose, @@ -1079,6 +1080,7 @@ 'tanh_', 'transpose', 'transpose_', + 'permute', 'cauchy_', 'geometric_', 'randn', diff --git a/python/paddle/tensor/__init__.py b/python/paddle/tensor/__init__.py index 75d2882a04006f..829568c80fdf9f 100644 --- a/python/paddle/tensor/__init__.py +++ b/python/paddle/tensor/__init__.py @@ -98,6 +98,7 @@ norm, ormqr, pca_lowrank, + permute, pinv, qr, solve, diff --git a/python/paddle/tensor/linalg.py b/python/paddle/tensor/linalg.py index d253b31bb04708..419767931506e6 100644 --- a/python/paddle/tensor/linalg.py +++ b/python/paddle/tensor/linalg.py @@ -190,6 +190,50 @@ def transpose_(x, perm, name=None): return _C_ops.transpose_(x, perm) +@overload +def permute( + input: Tensor, dims: Sequence[int], name: str | None = None +) -> Tensor: ... + + +@overload +def permute(input: Tensor, *dims: int, name: str | None = None) -> Tensor: ... + + +def permute(input: Tensor, *dims: int, name: str | None = None) -> Tensor: + """ + Permute the dimensions of a tensor. + + Args: + input (Tensor): the input tensor. + dims (tuple of int): The desired ordering of dimensions. Supports passing as variable-length + arguments (e.g., permute(x, 1, 0, 2)) or as a single list/tuple (e.g., permute(x, [1, 0, 2])). + + Returns: + Tensor: A tensor with permuted dimensions. + + Examples: + .. code-block:: python + + >>> import paddle + + >>> x = paddle.randn([2, 3, 4]) + >>> y = paddle.permute(x, (1, 0, 2)) + >>> print(y.shape) + [3, 2, 4] + + >>> y = paddle.permute(x, 1, 0, 2) + >>> print(y.shape) + [3, 2, 4] + """ + if len(dims) == 1 and isinstance(dims[0], (list, tuple)): + perm = list(dims[0]) + else: + perm = list(dims) + + return paddle.transpose(x=input, perm=perm, name=name) + + def matrix_transpose( x: paddle.Tensor, name: str | None = None, diff --git a/test/legacy_test/test_permute_op.py b/test/legacy_test/test_permute_op.py new file mode 100644 index 00000000000000..6f2c06e041e7e0 --- /dev/null +++ b/test/legacy_test/test_permute_op.py @@ -0,0 +1,79 @@ +# 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. + +import unittest + +import numpy as np + +import paddle + + +class TestPermuteApi(unittest.TestCase): + def test_static(self): + paddle.enable_static() + with paddle.static.program_guard( + paddle.static.Program(), paddle.static.Program() + ): + x = paddle.static.data(name='x', shape=[2, 3, 4], dtype='float32') + + # function: list / tuple / varargs + y1 = paddle.permute(x, [1, 0, 2]) + y2 = paddle.permute(x, (2, 1, 0)) + y3 = paddle.permute(x, 1, 2, 0) + + place = paddle.CPUPlace() + exe = paddle.static.Executor(place) + x_np = np.random.random([2, 3, 4]).astype("float32") + out1, out2, out3 = exe.run( + feed={"x": x_np}, fetch_list=[y1, y2, y3] + ) + + expected1 = np.transpose(x_np, [1, 0, 2]) + expected2 = np.transpose(x_np, (2, 1, 0)) + expected3 = np.transpose(x_np, [1, 2, 0]) + + np.testing.assert_array_equal(out1, expected1) + np.testing.assert_array_equal(out2, expected2) + np.testing.assert_array_equal(out3, expected3) + + def test_dygraph(self): + paddle.disable_static() + x = paddle.randn([2, 3, 4]) + x_np = x.numpy() + + y1 = paddle.permute(x, [1, 0, 2]) + y2 = paddle.permute(x, (2, 1, 0)) + y3 = paddle.permute(x, 1, 2, 0) + + m1 = x.permute([1, 0, 2]) + m2 = x.permute((2, 1, 0)) + m3 = x.permute(1, 2, 0) + + expected1 = np.transpose(x_np, [1, 0, 2]) + expected2 = np.transpose(x_np, (2, 1, 0)) + expected3 = np.transpose(x_np, [1, 2, 0]) + + np.testing.assert_array_equal(y1.numpy(), expected1) + np.testing.assert_array_equal(y2.numpy(), expected2) + np.testing.assert_array_equal(y3.numpy(), expected3) + + np.testing.assert_array_equal(m1.numpy(), expected1) + np.testing.assert_array_equal(m2.numpy(), expected2) + np.testing.assert_array_equal(m3.numpy(), expected3) + + paddle.enable_static() + + +if __name__ == '__main__': + unittest.main() From 723b7d749cdd2eaafb08be4fe789a25617c5a50a Mon Sep 17 00:00:00 2001 From: aquagull Date: Mon, 11 Aug 2025 11:22:11 +0800 Subject: [PATCH 02/10] fix --- python/paddle/tensor/__init__.py | 1 + python/paddle/tensor/linalg.py | 18 ++++++++++-------- test/legacy_test/test_permute_op.py | 10 ++++++++-- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/python/paddle/tensor/__init__.py b/python/paddle/tensor/__init__.py index 829568c80fdf9f..2f4bc4576eb355 100644 --- a/python/paddle/tensor/__init__.py +++ b/python/paddle/tensor/__init__.py @@ -703,6 +703,7 @@ 'strided_slice', 'transpose', 'transpose_', + 'permute', 'cauchy_', 'geometric_', 'tan_', diff --git a/python/paddle/tensor/linalg.py b/python/paddle/tensor/linalg.py index 419767931506e6..abc94830fa1ab3 100644 --- a/python/paddle/tensor/linalg.py +++ b/python/paddle/tensor/linalg.py @@ -195,12 +195,12 @@ def permute( input: Tensor, dims: Sequence[int], name: str | None = None ) -> Tensor: ... - @overload -def permute(input: Tensor, *dims: int, name: str | None = None) -> Tensor: ... - +def permute( + input: Tensor, *dims: int, name: str | None = None +) -> Tensor: ... -def permute(input: Tensor, *dims: int, name: str | None = None) -> Tensor: +def permute(input: Tensor, dims=None, *args, name: str | None = None) -> Tensor: """ Permute the dimensions of a tensor. @@ -226,11 +226,13 @@ def permute(input: Tensor, *dims: int, name: str | None = None) -> Tensor: >>> print(y.shape) [3, 2, 4] """ - if len(dims) == 1 and isinstance(dims[0], (list, tuple)): - perm = list(dims[0]) + if dims is not None and not args: + if isinstance(dims, (list, tuple)): + perm = dims + else: + perm = [dims] else: - perm = list(dims) - + perm = (dims,) + args if dims is not None else args return paddle.transpose(x=input, perm=perm, name=name) diff --git a/test/legacy_test/test_permute_op.py b/test/legacy_test/test_permute_op.py index 6f2c06e041e7e0..c1305992decbf5 100644 --- a/test/legacy_test/test_permute_op.py +++ b/test/legacy_test/test_permute_op.py @@ -31,12 +31,13 @@ def test_static(self): y1 = paddle.permute(x, [1, 0, 2]) y2 = paddle.permute(x, (2, 1, 0)) y3 = paddle.permute(x, 1, 2, 0) + y4 = paddle.permute(x, dims=[1, 2, 0]) place = paddle.CPUPlace() exe = paddle.static.Executor(place) x_np = np.random.random([2, 3, 4]).astype("float32") - out1, out2, out3 = exe.run( - feed={"x": x_np}, fetch_list=[y1, y2, y3] + out1, out2, out3, out4 = exe.run( + feed={"x": x_np}, fetch_list=[y1, y2, y3, y4] ) expected1 = np.transpose(x_np, [1, 0, 2]) @@ -46,6 +47,7 @@ def test_static(self): np.testing.assert_array_equal(out1, expected1) np.testing.assert_array_equal(out2, expected2) np.testing.assert_array_equal(out3, expected3) + np.testing.assert_array_equal(out4, expected3) def test_dygraph(self): paddle.disable_static() @@ -55,10 +57,12 @@ def test_dygraph(self): y1 = paddle.permute(x, [1, 0, 2]) y2 = paddle.permute(x, (2, 1, 0)) y3 = paddle.permute(x, 1, 2, 0) + y4 = paddle.permute(x, dims=[1, 2, 0]) m1 = x.permute([1, 0, 2]) m2 = x.permute((2, 1, 0)) m3 = x.permute(1, 2, 0) + m4 = x.permute(dims=[1, 2, 0]) expected1 = np.transpose(x_np, [1, 0, 2]) expected2 = np.transpose(x_np, (2, 1, 0)) @@ -67,10 +71,12 @@ def test_dygraph(self): np.testing.assert_array_equal(y1.numpy(), expected1) np.testing.assert_array_equal(y2.numpy(), expected2) np.testing.assert_array_equal(y3.numpy(), expected3) + np.testing.assert_array_equal(y4.numpy(), expected3) np.testing.assert_array_equal(m1.numpy(), expected1) np.testing.assert_array_equal(m2.numpy(), expected2) np.testing.assert_array_equal(m3.numpy(), expected3) + np.testing.assert_array_equal(m4.numpy(), expected3) paddle.enable_static() From 8b2a62880e527c6cbb05e9d0c82f6b7b0603a8a5 Mon Sep 17 00:00:00 2001 From: aquagull Date: Mon, 11 Aug 2025 11:36:55 +0800 Subject: [PATCH 03/10] code-style --- python/paddle/tensor/linalg.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/paddle/tensor/linalg.py b/python/paddle/tensor/linalg.py index abc94830fa1ab3..3f17586e5725f1 100644 --- a/python/paddle/tensor/linalg.py +++ b/python/paddle/tensor/linalg.py @@ -195,10 +195,10 @@ def permute( input: Tensor, dims: Sequence[int], name: str | None = None ) -> Tensor: ... + @overload -def permute( - input: Tensor, *dims: int, name: str | None = None -) -> Tensor: ... +def permute(input: Tensor, *dims: int, name: str | None = None) -> Tensor: ... + def permute(input: Tensor, dims=None, *args, name: str | None = None) -> Tensor: """ @@ -232,7 +232,7 @@ def permute(input: Tensor, dims=None, *args, name: str | None = None) -> Tensor: else: perm = [dims] else: - perm = (dims,) + args if dims is not None else args + perm = (dims, *args) if dims is not None else args return paddle.transpose(x=input, perm=perm, name=name) From 5ce1511f7aa31619875f4166dfb41a2ae16aa2fa Mon Sep 17 00:00:00 2001 From: aquagull Date: Tue, 12 Aug 2025 11:18:58 +0800 Subject: [PATCH 04/10] fix --- python/paddle/tensor/linalg.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/python/paddle/tensor/linalg.py b/python/paddle/tensor/linalg.py index 3f17586e5725f1..000b0776190baa 100644 --- a/python/paddle/tensor/linalg.py +++ b/python/paddle/tensor/linalg.py @@ -190,17 +190,7 @@ def transpose_(x, perm, name=None): return _C_ops.transpose_(x, perm) -@overload -def permute( - input: Tensor, dims: Sequence[int], name: str | None = None -) -> Tensor: ... - - -@overload -def permute(input: Tensor, *dims: int, name: str | None = None) -> Tensor: ... - - -def permute(input: Tensor, dims=None, *args, name: str | None = None) -> Tensor: +def permute(input: Tensor, dims: Sequence[int], *args) -> Tensor: """ Permute the dimensions of a tensor. @@ -222,8 +212,8 @@ def permute(input: Tensor, dims=None, *args, name: str | None = None) -> Tensor: >>> print(y.shape) [3, 2, 4] - >>> y = paddle.permute(x, 1, 0, 2) - >>> print(y.shape) + >>> x.permute(1, 0, 2) + >>> print(x.shape) [3, 2, 4] """ if dims is not None and not args: @@ -233,7 +223,8 @@ def permute(input: Tensor, dims=None, *args, name: str | None = None) -> Tensor: perm = [dims] else: perm = (dims, *args) if dims is not None else args - return paddle.transpose(x=input, perm=perm, name=name) + + return transpose(x=input, perm=perm) def matrix_transpose( From 8c2e37fffe3fc5fb67bad2133d173462d57561d3 Mon Sep 17 00:00:00 2001 From: Ayakouji Date: Tue, 12 Aug 2025 18:01:06 +0800 Subject: [PATCH 05/10] update --- python/paddle/tensor/linalg.py | 14 ++++---------- python/paddle/utils/decorator_utils.py | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/python/paddle/tensor/linalg.py b/python/paddle/tensor/linalg.py index 000b0776190baa..3baca6e42c546f 100644 --- a/python/paddle/tensor/linalg.py +++ b/python/paddle/tensor/linalg.py @@ -24,6 +24,7 @@ from paddle.base.libpaddle import DataType from paddle.common_ops_import import VarDesc from paddle.tensor.math import broadcast_shape +from paddle.utils.decorator_utils import VariableArgsDecorator from paddle.utils.inplace_utils import inplace_apis_in_dygraph_only from ..base.data_feeder import ( @@ -190,7 +191,8 @@ def transpose_(x, perm, name=None): return _C_ops.transpose_(x, perm) -def permute(input: Tensor, dims: Sequence[int], *args) -> Tensor: +@VariableArgsDecorator('dims') +def permute(input: Tensor, dims: Sequence[int]) -> Tensor: """ Permute the dimensions of a tensor. @@ -216,15 +218,7 @@ def permute(input: Tensor, dims: Sequence[int], *args) -> Tensor: >>> print(x.shape) [3, 2, 4] """ - if dims is not None and not args: - if isinstance(dims, (list, tuple)): - perm = dims - else: - perm = [dims] - else: - perm = (dims, *args) if dims is not None else args - - return transpose(x=input, perm=perm) + return transpose(x=input, perm=dims) def matrix_transpose( diff --git a/python/paddle/utils/decorator_utils.py b/python/paddle/utils/decorator_utils.py index 79ec73937ec8c1..f4a00bc9723312 100644 --- a/python/paddle/utils/decorator_utils.py +++ b/python/paddle/utils/decorator_utils.py @@ -89,3 +89,19 @@ def process( f"Cannot specify both '{original}' and its alias '{alias}'" ) return args, processed_kwargs + + +class VariableArgsDecorator(DecoratorBase): + def __init__(self, var: str) -> None: + super().__init__() + if not isinstance(var, str): + raise TypeError("var must be a string") + self.var = var + + def process( + self, args: tuple[Any, ...], kwargs: dict[str, Any] + ) -> tuple[tuple[Any, ...], dict[str, Any]]: + if len(args) >= 2 and isinstance(args[1], int): + kwargs[self.var] = list(args[1:]) + args = args[:1] + return args, kwargs From 1dd5e843e5fc785a58ec92b536a1bff7fd80df3d Mon Sep 17 00:00:00 2001 From: Ayakouji Date: Tue, 12 Aug 2025 18:04:45 +0800 Subject: [PATCH 06/10] update --- python/paddle/utils/decorator_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/paddle/utils/decorator_utils.py b/python/paddle/utils/decorator_utils.py index f4a00bc9723312..3c7ae4156c383d 100644 --- a/python/paddle/utils/decorator_utils.py +++ b/python/paddle/utils/decorator_utils.py @@ -103,5 +103,5 @@ def process( ) -> tuple[tuple[Any, ...], dict[str, Any]]: if len(args) >= 2 and isinstance(args[1], int): kwargs[self.var] = list(args[1:]) - args = args[:1] + args = args[0] return args, kwargs From f9285772370182f23470a632927e9abe2746aa21 Mon Sep 17 00:00:00 2001 From: Ayakouji Date: Tue, 12 Aug 2025 20:32:08 +0800 Subject: [PATCH 07/10] update --- python/paddle/utils/decorator_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/paddle/utils/decorator_utils.py b/python/paddle/utils/decorator_utils.py index 7af5c65cf8831e..f26948db8bacd4 100644 --- a/python/paddle/utils/decorator_utils.py +++ b/python/paddle/utils/decorator_utils.py @@ -145,5 +145,5 @@ def process( ) -> tuple[tuple[Any, ...], dict[str, Any]]: if len(args) >= 2 and isinstance(args[1], int): kwargs[self.var] = list(args[1:]) - args = args[0] + args = args[:1] return args, kwargs From 403d3474a237694666b300e75cac6c86092bc6ff Mon Sep 17 00:00:00 2001 From: Ayakouji Date: Thu, 14 Aug 2025 11:24:03 +0800 Subject: [PATCH 08/10] code style --- python/paddle/tensor/linalg.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/paddle/tensor/linalg.py b/python/paddle/tensor/linalg.py index 43ee174579bca5..17dfda9f88efb1 100644 --- a/python/paddle/tensor/linalg.py +++ b/python/paddle/tensor/linalg.py @@ -24,7 +24,10 @@ from paddle.base.libpaddle import DataType from paddle.common_ops_import import VarDesc from paddle.tensor.math import broadcast_shape -from paddle.utils.decorator_utils import ParamAliasDecorator, VariableArgsDecorator +from paddle.utils.decorator_utils import ( + ParamAliasDecorator, + VariableArgsDecorator, +) from paddle.utils.inplace_utils import inplace_apis_in_dygraph_only from ..base.data_feeder import ( From 53fee7089ccfeccb986bd881ffb8dab29c803836 Mon Sep 17 00:00:00 2001 From: Ayakouji Date: Fri, 15 Aug 2025 21:13:46 +0800 Subject: [PATCH 09/10] fix doc --- python/paddle/tensor/linalg.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/paddle/tensor/linalg.py b/python/paddle/tensor/linalg.py index 17dfda9f88efb1..4774b226cdb36f 100644 --- a/python/paddle/tensor/linalg.py +++ b/python/paddle/tensor/linalg.py @@ -201,7 +201,7 @@ def permute(input: Tensor, dims: Sequence[int]) -> Tensor: Args: input (Tensor): the input tensor. - dims (tuple of int): The desired ordering of dimensions. Supports passing as variable-length + dims (tuple|list|int): The desired ordering of dimensions. Supports passing as variable-length arguments (e.g., permute(x, 1, 0, 2)) or as a single list/tuple (e.g., permute(x, [1, 0, 2])). Returns: @@ -217,8 +217,8 @@ def permute(input: Tensor, dims: Sequence[int]) -> Tensor: >>> print(y.shape) [3, 2, 4] - >>> x.permute(1, 0, 2) - >>> print(x.shape) + >>> y = x.permute(1, 0, 2) + >>> print(y.shape) [3, 2, 4] """ return transpose(x=input, perm=dims) From fd3bd1bd82b8cfb7a660b826b3c1a1651c11ad82 Mon Sep 17 00:00:00 2001 From: Ayakouji Date: Fri, 15 Aug 2025 22:27:50 +0800 Subject: [PATCH 10/10] fix --- python/paddle/tensor/linalg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/paddle/tensor/linalg.py b/python/paddle/tensor/linalg.py index 4774b226cdb36f..45b2d48e002f41 100644 --- a/python/paddle/tensor/linalg.py +++ b/python/paddle/tensor/linalg.py @@ -201,7 +201,7 @@ def permute(input: Tensor, dims: Sequence[int]) -> Tensor: Args: input (Tensor): the input tensor. - dims (tuple|list|int): The desired ordering of dimensions. Supports passing as variable-length + *dims (tuple|list|int): The desired ordering of dimensions. Supports passing as variable-length arguments (e.g., permute(x, 1, 0, 2)) or as a single list/tuple (e.g., permute(x, [1, 0, 2])). Returns: @@ -217,7 +217,7 @@ def permute(input: Tensor, dims: Sequence[int]) -> Tensor: >>> print(y.shape) [3, 2, 4] - >>> y = x.permute(1, 0, 2) + >>> y = x.permute([1, 0, 2]) >>> print(y.shape) [3, 2, 4] """