1503 lines
54 KiB
Python
1503 lines
54 KiB
Python
# 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 functools
|
|
import inspect
|
|
import warnings
|
|
from collections.abc import Callable, Iterable
|
|
from typing import Any, TypeVar, cast
|
|
|
|
from typing_extensions import ParamSpec, get_overloads
|
|
|
|
import paddle
|
|
|
|
_InputT = ParamSpec("_InputT")
|
|
_RetT = TypeVar("_RetT")
|
|
_SENTINEL = object()
|
|
|
|
|
|
def _is_int_or_scalar_tensor(x):
|
|
if isinstance(x, int):
|
|
return True
|
|
if isinstance(x, (paddle.Tensor, paddle.pir.Value)):
|
|
return x.ndim == 0
|
|
return False
|
|
|
|
|
|
class DecoratorBase:
|
|
"""Decorative base class, providing a universal decorative framework.
|
|
|
|
Subclass only needs to implement the 'process' method to define the core logic.
|
|
"""
|
|
|
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
self.args = args
|
|
self.kwargs = kwargs
|
|
|
|
def __call__(
|
|
self, func: Callable[_InputT, _RetT]
|
|
) -> Callable[_InputT, _RetT]:
|
|
"""As an entry point for decorative applications"""
|
|
|
|
@functools.wraps(func)
|
|
def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT:
|
|
# Pretreatment parameters
|
|
processed_args, processed_kwargs = self.process(args, kwargs)
|
|
return func(*processed_args, **processed_kwargs)
|
|
|
|
wrapper.__signature__ = inspect.signature(func)
|
|
return cast("Callable[_InputT, _RetT]", wrapper)
|
|
|
|
def process(
|
|
self, args: tuple[Any, ...], kwargs: dict[str, Any]
|
|
) -> tuple[tuple[Any, ...], dict[str, Any]]:
|
|
"""To be implemented by subclass"""
|
|
raise NotImplementedError("Subclasses must implement this method")
|
|
|
|
|
|
# Example implementation: Parameter alias decorator
|
|
class ParamAliasDecorator(DecoratorBase):
|
|
"""Implementation of Decorator for Parameter Alias Processing"""
|
|
|
|
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")
|
|
|
|
# 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
|
|
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
|
|
|
|
|
|
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,
|
|
) -> Callable[[Callable[_InputT, _RetT]], Callable[_InputT, _RetT]]:
|
|
def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]:
|
|
@functools.wraps(func)
|
|
def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT:
|
|
if not kwargs:
|
|
return func(*args, **kwargs)
|
|
if alias_list[1] in kwargs:
|
|
if alias_list[0] not in kwargs:
|
|
kwargs[alias_list[0]] = kwargs.pop(alias_list[1])
|
|
else:
|
|
raise ValueError(
|
|
f"Cannot specify both '{alias_list[0]}' and its alias '{alias_list[1]}'"
|
|
)
|
|
return func(*args, **kwargs)
|
|
|
|
wrapper.__signature__ = inspect.signature(func)
|
|
return wrapper
|
|
|
|
return decorator
|
|
|
|
|
|
def param_two_alias(
|
|
alias_list1: list[str], alias_list2: list[str]
|
|
) -> Callable[[Callable[_InputT, _RetT]], Callable[_InputT, _RetT]]:
|
|
def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]:
|
|
@functools.wraps(func)
|
|
def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT:
|
|
if not kwargs:
|
|
return func(*args, **kwargs)
|
|
if alias_list1[1] in kwargs:
|
|
if alias_list1[0] not in kwargs:
|
|
kwargs[alias_list1[0]] = kwargs.pop(alias_list1[1])
|
|
else:
|
|
raise ValueError(
|
|
f"Cannot specify both '{alias_list1[0]}' and its alias '{alias_list1[1]}'"
|
|
)
|
|
if alias_list2[1] in kwargs:
|
|
if alias_list2[0] not in kwargs:
|
|
kwargs[alias_list2[0]] = kwargs.pop(alias_list2[1])
|
|
else:
|
|
raise ValueError(
|
|
f"Cannot specify both '{alias_list2[0]}' and its alias '{alias_list2[1]}'"
|
|
)
|
|
return func(*args, **kwargs)
|
|
|
|
wrapper.__signature__ = inspect.signature(func)
|
|
return wrapper
|
|
|
|
return decorator
|
|
|
|
|
|
def lp_pool_layer_decorator(
|
|
func: Callable[_InputT, _RetT],
|
|
) -> Callable[_InputT, _RetT]:
|
|
@functools.wraps(func)
|
|
def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT:
|
|
if len(args) == 5 and isinstance(args[4], bool):
|
|
warnings.warn(
|
|
"The 5th positional argument in '__init__' method is a boolean value, which is being interpreted as 'ceil_mode'.",
|
|
category=Warning,
|
|
stacklevel=2,
|
|
)
|
|
kwargs["ceil_mode"] = args[4]
|
|
args = args[:4]
|
|
return func(*args, **kwargs)
|
|
|
|
wrapper.__signature__ = inspect.signature(func)
|
|
return wrapper
|
|
|
|
|
|
def lp_pool_function_decorator(
|
|
func: Callable[_InputT, _RetT],
|
|
) -> Callable[_InputT, _RetT]:
|
|
@functools.wraps(func)
|
|
def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT:
|
|
if "input" in kwargs:
|
|
kwargs["x"] = kwargs.pop("input")
|
|
if len(args) == 5 and isinstance(args[4], bool):
|
|
warnings.warn(
|
|
"The 5th positional argument is a boolean value, which is being interpreted as 'ceil_mode'.",
|
|
category=Warning,
|
|
stacklevel=2,
|
|
)
|
|
kwargs["ceil_mode"] = args[4]
|
|
args = args[:4]
|
|
return func(*args, **kwargs)
|
|
|
|
wrapper.__signature__ = inspect.signature(func)
|
|
return wrapper
|
|
|
|
|
|
def conv_transpose_layer_decorator(
|
|
func: Callable[_InputT, _RetT],
|
|
) -> Callable[_InputT, _RetT]:
|
|
"""Dispatch decorator for ``Conv{1,3}DTranspose.__init__``.
|
|
|
|
PyTorch's ``ConvTranspose{1,2,3}d`` places ``bias`` (``bool``) at the 9th
|
|
positional argument (index 8 when ``self`` is counted), while Paddle's
|
|
native signature places ``dilation`` (``int``) at the same position.
|
|
When ``args[8]`` is a ``bool`` we interpret the call as the PyTorch
|
|
convention and remap positional arguments ``args[8:13]`` to keyword
|
|
arguments ``bias``, ``dilation``, ``padding_mode``, ``device``, ``dtype``
|
|
so the call succeeds against Paddle's signature.
|
|
"""
|
|
|
|
@functools.wraps(func)
|
|
def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT:
|
|
if len(args) >= 9 and isinstance(args[8], bool):
|
|
torch_names = (
|
|
"bias",
|
|
"dilation",
|
|
"padding_mode",
|
|
"device",
|
|
"dtype",
|
|
)
|
|
for i, name in enumerate(torch_names):
|
|
pos = 8 + 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[:8]
|
|
return func(*args, **kwargs)
|
|
|
|
wrapper.__signature__ = inspect.signature(func)
|
|
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]]:
|
|
def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]:
|
|
@functools.wraps(func)
|
|
def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT:
|
|
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)
|
|
return wrapper
|
|
|
|
return decorator
|
|
|
|
|
|
# *size => shape decorator
|
|
class SizeArgsDecorator(DecoratorBase):
|
|
"""
|
|
Usage Example:
|
|
|
|
paddle.ones(1, dtype=paddle.float32)
|
|
paddle.ones(1, 2, 3, dtype=paddle.float32)
|
|
paddle.ones([1, 2, 3], dtype=paddle.float32)
|
|
paddle.ones(size=[1, 2, 3], dtype=paddle.float32)
|
|
|
|
paddle.ones([1, 2, 3], paddle.float32)
|
|
paddle.ones(shape=[1, 2, 3], dtype=paddle.float32)
|
|
"""
|
|
|
|
def process(
|
|
self, args: tuple[Any, ...], kwargs: dict[str, Any]
|
|
) -> tuple[tuple[Any, ...], dict[str, Any]]:
|
|
if 'size' in kwargs:
|
|
kwargs['shape'] = kwargs.pop('size')
|
|
elif len(args) >= 1 and isinstance(args[0], int):
|
|
kwargs['shape'] = list(args)
|
|
args = ()
|
|
|
|
return args, kwargs
|
|
|
|
|
|
def size_args_decorator(
|
|
func: Callable[_InputT, _RetT],
|
|
) -> Callable[_InputT, _RetT]:
|
|
"""
|
|
A decorator that normalizes the 'size' argument to 'shape'.
|
|
|
|
Usage Example:
|
|
|
|
paddle.ones(1, dtype=paddle.float32)
|
|
paddle.ones(1, 2, 3, dtype=paddle.float32)
|
|
paddle.ones([1, 2, 3], dtype=paddle.float32)
|
|
paddle.ones(size=[1, 2, 3], dtype=paddle.float32)
|
|
paddle.ones([1, 2, 3], paddle.float32)
|
|
paddle.ones(shape=[1, 2, 3], dtype=paddle.float32)
|
|
"""
|
|
|
|
@functools.wraps(func)
|
|
def wrapped_func(*args: Any, **kwargs: Any) -> Any:
|
|
if 'size' in kwargs:
|
|
kwargs['shape'] = kwargs.pop('size')
|
|
elif len(args) >= 1 and isinstance(args[0], int):
|
|
kwargs['shape'] = list(args)
|
|
args = ()
|
|
|
|
if 'shape' in kwargs and isinstance(kwargs['shape'], int):
|
|
kwargs['shape'] = [kwargs['shape']]
|
|
|
|
return func(*args, **kwargs)
|
|
|
|
wrapped_func.__signature__ = inspect.signature(func)
|
|
|
|
return wrapped_func
|
|
|
|
|
|
def size_args_decorator_patch(
|
|
method: Callable[_InputT, _RetT],
|
|
) -> Callable[_InputT, _RetT]:
|
|
"""
|
|
A decorator that allow *size for patching method to Tensor.
|
|
e.g. Tensor.method(*size, *, ...).
|
|
|
|
Usage Example:
|
|
|
|
paddle.randn([]).new_ones(1, dtype=paddle.float32)
|
|
paddle.randn([]).new_ones(1, 2, 3, dtype=paddle.float32)
|
|
paddle.randn([]).new_ones([1, 2, 3], dtype=paddle.float32)
|
|
paddle.randn([]).new_ones(size=[1, 2, 3], dtype=paddle.float32)
|
|
paddle.randn([]).new_ones([1, 2, 3], paddle.float32)
|
|
"""
|
|
|
|
@functools.wraps(method)
|
|
def wrapped_func(*args: Any, **kwargs: Any) -> Any:
|
|
if len(args) >= 2 and isinstance(args[1], int):
|
|
# args[0]: Tensor
|
|
# args[1:]: *size
|
|
kwargs['size'] = list(args[1:])
|
|
args = (args[0],)
|
|
|
|
return method(*args, **kwargs)
|
|
|
|
wrapped_func.__signature__ = inspect.signature(method)
|
|
|
|
return wrapped_func
|
|
|
|
|
|
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
|
|
|
|
|
|
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)
|
|
tensor_x.view(paddle.float32) -> paddle.view(tensor_x, paddle.float32)
|
|
tensor_x.view(dtype=paddle.float32) -> paddle.view(tensor_x, dtype=paddle.float32)
|
|
tensor_x.view([-1, 1, 3]) -> paddle.view(tensor_x, [-1, 1, 3])
|
|
tensor_x.view(-1, 1, 3) -> paddle.view(tensor_x, -1, 1, 3)
|
|
tensor_x.view(size=[-1, 1, 3]) -> paddle.view(tensor_x, size=[-1, 1, 3])
|
|
"""
|
|
|
|
@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
|
|
|
|
|
|
class ForbidKeywordsDecorator(DecoratorBase):
|
|
"""A decorator that hints users to use the correct `compat` functions, when erroneous keyword arguments are detected"""
|
|
|
|
def __init__(
|
|
self,
|
|
illegal_keys: set[str],
|
|
func_name: str,
|
|
correct_name: str,
|
|
url_suffix: str = "",
|
|
) -> None:
|
|
"""
|
|
Args:
|
|
illegal_keys (set[str]): the keywords to reject
|
|
func_name (str): the name of the function being decorated (should incorporate module name, like paddle.nn.Unfold)
|
|
correct_name (str): the user hint that points to the correct function
|
|
url_suffix (str, optional): Only specified in non paddle.compat functions. If specified, the function being decorated
|
|
will emit a warning upon the first call, warning the users about the API difference and points to Docs.
|
|
Please correctly specifying the `url_suffix`, this should be the suffix of the api-difference doc. For example:
|
|
|
|
(prefix omitted)/docs/zh/develop/guides/model_convert/convert_from_pytorch/api_difference/invok_only_diff/**torch.nn.Unfold**.html
|
|
|
|
In this example, the correct `url_suffix` should be 'torch/torch.nn.Unfold'. Defaults to an empty str.
|
|
"""
|
|
super().__init__()
|
|
self.illegal_keys = illegal_keys
|
|
self.func_name = func_name
|
|
self.correct_name = correct_name
|
|
self.warn_msg = None
|
|
if url_suffix:
|
|
self.warn_msg = (
|
|
f"The API '{func_name}' may behave differently from its PyTorch counterpart. "
|
|
"Refer to the compatibility guide for details:\n"
|
|
"https://www.paddlepaddle.org.cn/documentation/docs/en/develop/guides/model_convert/"
|
|
f"convert_from_pytorch/api_difference/invok_only_diff/{url_suffix}.html"
|
|
)
|
|
|
|
def process(
|
|
self, args: tuple[Any, ...], kwargs: dict[str, Any]
|
|
) -> tuple[tuple[Any, ...], dict[str, Any]]:
|
|
found_keys = [key for key in self.illegal_keys if key in kwargs]
|
|
|
|
if found_keys:
|
|
found_keys.sort()
|
|
keys_str = ", ".join(f"'{key}'" for key in found_keys)
|
|
plural = "s" if len(found_keys) > 1 else ""
|
|
|
|
if (
|
|
self.warn_msg is not None
|
|
): # warn the users only when the API is mis-used
|
|
warnings.warn(
|
|
self.warn_msg,
|
|
category=UserWarning,
|
|
stacklevel=3,
|
|
)
|
|
self.warn_msg = None
|
|
raise TypeError(
|
|
f"{self.func_name}() received unexpected keyword argument{plural} {keys_str}. "
|
|
f"\nDid you mean to use {self.correct_name}() instead?"
|
|
)
|
|
return args, kwargs
|
|
|
|
|
|
class ForbidKeywordsIgnoreOneParamDecorator(ForbidKeywordsDecorator):
|
|
"""A decorator that hints users to use the correct `compat` functions, when erroneous keyword arguments are detected and one argument is ignored"""
|
|
|
|
def __init__(
|
|
self,
|
|
illegal_keys: set[str],
|
|
ignore_param: tuple[str, int, type[Any]],
|
|
func_name: str,
|
|
correct_name: str,
|
|
url_suffix: str = "",
|
|
) -> None:
|
|
"""
|
|
Args:
|
|
illegal_keys (set[str]): the keywords to reject
|
|
ignore_param: (tuple[str, int, type[Any]]): A tuple of (parameter_name, index, type) to ignore by name, position and type
|
|
func_name (str): the name of the function being decorated (should incorporate module name, like paddle.nn.Unfold)
|
|
correct_name (str): the user hint that points to the correct function
|
|
url_suffix (str, optional): Only specified in non paddle.compat functions. If specified, the function being decorated
|
|
will emit a warning upon the first call, warning the users about the API difference and points to Docs.
|
|
Please correctly specifying the `url_suffix`, this should be the suffix of the api-difference doc. For example:
|
|
|
|
(prefix omitted)/docs/zh/develop/guides/model_convert/convert_from_pytorch/api_difference/invok_only_diff/**torch.nn.Unfold**.html
|
|
|
|
In this example, the correct `url_suffix` should be 'torch/torch.nn.Unfold'. Defaults to an empty str.
|
|
"""
|
|
super().__init__(illegal_keys, func_name, correct_name, url_suffix)
|
|
self.ignore_param = ignore_param
|
|
|
|
def process(
|
|
self, args: tuple[Any, ...], kwargs: dict[str, Any]
|
|
) -> tuple[tuple[Any, ...], dict[str, Any]]:
|
|
args, kwargs = super().process(args, kwargs)
|
|
|
|
if self.ignore_param:
|
|
name, index, typ = self.ignore_param
|
|
if index < len(args) and isinstance(args[index], typ):
|
|
args = args[:index] + args[index + 1 :]
|
|
else:
|
|
kwargs.pop(name, None)
|
|
|
|
return args, kwargs
|
|
|
|
|
|
def reshape_decorator(
|
|
func: Callable[_InputT, _RetT],
|
|
) -> Callable[_InputT, _RetT]:
|
|
"""
|
|
Usage Example:
|
|
paddle.reshape(x=tensor_x, shape=[-1, 1, 3], name=None)
|
|
paddle.reshape(input=tensor_x, shape=[-1, 1, 3], name=None)
|
|
tensor_x.reshape([-1, 1, 3]) -> paddle.reshape(tensor_x, [-1, 1, 3])
|
|
tensor_x.reshape(-1, 1, 3) -> paddle.reshape(tensor_x, -1, 1, 3])
|
|
"""
|
|
|
|
@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
|
|
|
|
|
|
def transpose_decorator(
|
|
func: Callable[_InputT, _RetT],
|
|
) -> Callable[_InputT, _RetT]:
|
|
"""
|
|
Usage Example:
|
|
PyTorch:
|
|
torch.transpose(x, dim0=0, dim1=1)
|
|
Paddle:
|
|
paddle.transpose(x, perm=[1, 0, 2])
|
|
"""
|
|
|
|
@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))
|
|
|
|
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]
|
|
|
|
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],)
|
|
|
|
return func(*args, **kwargs)
|
|
|
|
wrapper.__signature__ = inspect.signature(func)
|
|
return wrapper
|
|
|
|
|
|
def expand_decorator(
|
|
func: Callable[_InputT, _RetT],
|
|
) -> Callable[_InputT, _RetT]:
|
|
"""
|
|
Usage Example:
|
|
paddle.expand(x=tensor_x, shape=[3, 4], name=None)
|
|
tensor_x.expand([3, 4]) -> paddle.expand(tensor_x, [3, 4])
|
|
tensor_x.expand(3, 4) -> paddle.expand(tensor_x, 3, 4)
|
|
tensor_x.expand(size=[3, 4]) -> paddle.expand(tensor_x, size=[3, 4])
|
|
"""
|
|
|
|
@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
|
|
|
|
|
|
def tile_decorator(
|
|
func: Callable[_InputT, _RetT],
|
|
) -> Callable[_InputT, _RetT]:
|
|
"""
|
|
Usage Example:
|
|
paddle.tile(x=tensor_x, repeat_times=[2, 3], name=None)
|
|
paddle.tile(input=tensor_x, dims=[2, 3])
|
|
tensor_x.tile([2, 3]) -> paddle.tile(tensor_x, [2, 3])
|
|
tensor_x.tile(2, 3) -> paddle.tile(tensor_x, 2, 3)
|
|
"""
|
|
|
|
@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")
|
|
|
|
if len(args) >= 2 and isinstance(args[1], int):
|
|
kwargs["x"] = args[0]
|
|
kwargs["repeat_times"] = list(args[1:])
|
|
args = ()
|
|
return func(*args, **kwargs)
|
|
|
|
wrapper.__signature__ = inspect.signature(func)
|
|
return wrapper
|
|
|
|
|
|
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)
|
|
"""
|
|
|
|
@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
|
|
|
|
|
|
def legacy_reduction_decorator(
|
|
*,
|
|
overload_args_list: list[str] | None = None,
|
|
alias_mapping: dict[str, str] | None = None,
|
|
is_method: bool = False,
|
|
):
|
|
"""One-shot PyTorch compatibility wrapper for a loss API.
|
|
|
|
Each loss API declares its own PyTorch positional layout
|
|
(``overload_args_list``) and PyTorch-to-Paddle kwarg renames
|
|
(``alias_mapping``). When the call matches PyTorch's positional
|
|
layout, positional args are bound to their PyTorch names; the
|
|
deprecated ``size_average`` / ``reduce`` pair is translated into
|
|
Paddle's ``reduction`` with a ``DeprecationWarning``; remaining
|
|
PyTorch kwargs are renamed to their Paddle equivalents. Mirrors
|
|
PaConvert's ``SizeAverageMatcher`` so that one matcher / one
|
|
decorator covers the whole loss family.
|
|
|
|
Args:
|
|
overload_args_list: PyTorch positional names (after ``self``
|
|
when ``is_method=True``). Positional translation is only
|
|
triggered when the ``size_average`` slot contains
|
|
``bool`` / ``None`` -- a ``str`` reduction or non-bool
|
|
value there means the caller is using Paddle's positional
|
|
layout and we leave the call alone.
|
|
alias_mapping: ``{pytorch_name: paddle_name}`` kwarg renames.
|
|
is_method: ``True`` for class ``__init__``.
|
|
"""
|
|
alias_mapping = alias_mapping or {}
|
|
sa_idx = (
|
|
overload_args_list.index('size_average')
|
|
if overload_args_list and 'size_average' in overload_args_list
|
|
else -1
|
|
)
|
|
|
|
def decorate(f):
|
|
name = f.__qualname__.split(".")[0] if is_method else f.__name__
|
|
|
|
@functools.wraps(f)
|
|
def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT:
|
|
if is_method:
|
|
self_args, use_args = args[:1], list(args[1:])
|
|
else:
|
|
self_args, use_args = (), list(args)
|
|
|
|
# PyTorch positional layout: bool/None at the size_average
|
|
# slot is the unambiguous fingerprint; bind positional args
|
|
# to PyTorch names. Anything else (str reduction, int
|
|
# ignore_index, float eps) is the Paddle-positional shape.
|
|
if (
|
|
sa_idx >= 0
|
|
and len(use_args) > sa_idx
|
|
and (type(use_args[sa_idx]) is bool or use_args[sa_idx] is None)
|
|
):
|
|
for i, val in enumerate(use_args):
|
|
if i < len(overload_args_list):
|
|
kwargs.setdefault(overload_args_list[i], val)
|
|
use_args = []
|
|
|
|
sa = kwargs.pop('size_average', None)
|
|
rd = kwargs.pop('reduce', None)
|
|
if sa is not None or rd is not None:
|
|
suggested = (
|
|
'none'
|
|
if rd is False
|
|
else ('sum' if sa is False else 'mean')
|
|
)
|
|
kwargs['reduction'] = suggested
|
|
warnings.warn(
|
|
f"'size_average' and 'reduce' args of '{name}' will be "
|
|
f"deprecated, please use reduction='{suggested}' instead.",
|
|
DeprecationWarning,
|
|
stacklevel=3,
|
|
)
|
|
|
|
for torch_name, paddle_name in alias_mapping.items():
|
|
if torch_name in kwargs:
|
|
if paddle_name in kwargs:
|
|
raise ValueError(
|
|
f"Cannot specify both '{paddle_name}' and "
|
|
f"its alias '{torch_name}'"
|
|
)
|
|
kwargs[paddle_name] = kwargs.pop(torch_name)
|
|
|
|
return f(*self_args, *use_args, **kwargs)
|
|
|
|
wrapper.__signature__ = inspect.signature(f)
|
|
return wrapper
|
|
|
|
return decorate
|
|
|
|
|
|
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 = ()
|
|
|
|
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 func(*args, **kwargs)
|
|
|
|
wrapper.__signature__ = inspect.signature(func)
|
|
return wrapper
|
|
|
|
|
|
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],)
|
|
|
|
return func(*args, **kwargs)
|
|
|
|
wrapper.__signature__ = inspect.signature(func)
|
|
return wrapper
|
|
|
|
|
|
def use_first_signature(
|
|
func: Callable[_InputT, _RetT],
|
|
) -> Callable[_InputT, _RetT]:
|
|
overloads = get_overloads(func)
|
|
if not overloads:
|
|
return func
|
|
first_overload = overloads[0]
|
|
sig = inspect.signature(first_overload)
|
|
func.__signature__ = sig
|
|
return func
|
|
|
|
|
|
def variadic_tensor_decorator(
|
|
param_name: str,
|
|
param_pos: int = 0,
|
|
) -> Callable[[Callable[_InputT, _RetT]], Callable[_InputT, _RetT]]:
|
|
"""
|
|
Decorator to handle variadic tensor arguments.
|
|
|
|
Usage Example:
|
|
PyTorch: torch.block_diag(x, y, z)
|
|
Paddle: paddle.block_diag([x, y, z])
|
|
|
|
Args:
|
|
param_name: The parameter name to use for the list (e.g., 'inputs', 'input', 'x')
|
|
"""
|
|
|
|
def decorator(func: Callable[_InputT, _RetT]) -> Callable[_InputT, _RetT]:
|
|
@functools.wraps(func)
|
|
def wrapper(*args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT:
|
|
# PyTorch usage: variadic tensor arguments
|
|
if len(args) >= 1 and isinstance(
|
|
args[param_pos], (paddle.Tensor, paddle.pir.Value)
|
|
):
|
|
kwargs[param_name] = list(args[param_pos:])
|
|
args = args[:param_pos]
|
|
# Paddle usage: list/tuple argument
|
|
elif len(args) >= 1 and isinstance(args[param_pos], (list, tuple)):
|
|
kwargs[param_name] = args[param_pos]
|
|
args = args[:param_pos]
|
|
return func(*args, **kwargs)
|
|
|
|
wrapper.__signature__ = inspect.signature(func)
|
|
return wrapper
|
|
|
|
return decorator
|
|
|
|
|
|
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)
|
|
GradScaler(device, init_scale, growth_factor, backoff_factor, growth_interval, enabled)
|
|
GradScaler(init_scale, growth_factor, backoff_factor, growth_interval, enabled)
|
|
"""
|
|
|
|
_ALIAS_MAP = {
|
|
'enabled': 'enable',
|
|
'init_scale': 'init_loss_scaling',
|
|
'growth_factor': 'incr_ratio',
|
|
'backoff_factor': 'decr_ratio',
|
|
'growth_interval': 'incr_every_n_steps',
|
|
}
|
|
# PyTorch positional order (device already stripped): init_scale, growth_factor, backoff_factor, growth_interval, enabled
|
|
_TORCH_POS_NAMES = [
|
|
'init_loss_scaling',
|
|
'incr_ratio',
|
|
'decr_ratio',
|
|
'incr_every_n_steps',
|
|
'enable',
|
|
]
|
|
|
|
def _remap_kwargs(kwargs: dict[str, Any]) -> None:
|
|
for torch_key, paddle_key in _ALIAS_MAP.items():
|
|
if torch_key in kwargs:
|
|
if paddle_key not in kwargs:
|
|
kwargs[paddle_key] = kwargs.pop(torch_key)
|
|
else:
|
|
raise ValueError(
|
|
f"Cannot specify both '{paddle_key}' and its alias '{torch_key}'"
|
|
)
|
|
|
|
@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)
|
|
|
|
wrapper.__signature__ = inspect.signature(func)
|
|
return wrapper
|
|
|
|
|
|
def index_fill_decorator(
|
|
func: Callable[_InputT, _RetT],
|
|
) -> Callable[_InputT, _RetT]:
|
|
"""
|
|
Decorator for index_fill API to handle parameter name and order differences.
|
|
|
|
Usage Example:
|
|
PyTorch: torch.index_fill(input, dim, index, value)
|
|
Paddle: paddle.index_fill(x, index, axis, value)
|
|
"""
|
|
|
|
@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 ()
|
|
|
|
return func(*args, **kwargs)
|
|
|
|
wrapper.__signature__ = inspect.signature(func)
|
|
return wrapper
|
|
|
|
|
|
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)
|
|
"""
|
|
|
|
@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'."
|
|
)
|
|
|
|
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)
|
|
|
|
wrapper.__signature__ = inspect.signature(func)
|
|
return wrapper
|
|
|
|
|
|
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)
|
|
"""
|
|
|
|
@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
|
|
|
|
|
|
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:
|
|
"""
|
|
|
|
@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
|
|
|
|
|
|
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)
|
|
"""
|
|
|
|
@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)
|
|
|
|
wrapper.__signature__ = inspect.signature(func)
|
|
return wrapper
|
|
|
|
|
|
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)
|
|
"""
|
|
|
|
@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'."
|
|
)
|
|
|
|
# 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)
|
|
|
|
wrapper.__signature__ = inspect.signature(func)
|
|
return wrapper
|
|
|
|
|
|
def _calc_end_from_shapes(x, value, axes, starts, strides):
|
|
"""Calculate end values for slice_scatter from tensor shapes.
|
|
|
|
Supports multi-axis by calculating end for each axis.
|
|
"""
|
|
ends = []
|
|
for i, ax in enumerate(axes):
|
|
dim_idx = ax if ax >= 0 else len(x.shape) + ax
|
|
value_size = value.shape[dim_idx] if dim_idx < len(value.shape) else 1
|
|
ends.append(starts[i] + value_size * strides[i])
|
|
return ends
|
|
|
|
|
|
def slice_scatter_decorator(
|
|
func: Callable[_InputT, _RetT],
|
|
) -> Callable[_InputT, _RetT]:
|
|
"""
|
|
Decorator for slice_scatter to support PyTorch signature.
|
|
|
|
PyTorch: torch.slice_scatter(input, src, dim=0, start=None, end=None, step=1)
|
|
Paddle: paddle.slice_scatter(x, value, axes, starts, ends, strides)
|
|
|
|
This decorator handles:
|
|
1. Parameter aliases: input->x, src->value, dim->axes, start->starts, end->ends, step->strides
|
|
2. Convert single int to list: PyTorch uses int, Paddle uses list
|
|
3. Handle PyTorch style positional args
|
|
"""
|
|
|
|
@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]]
|
|
|
|
return func(*args, **kwargs)
|
|
|
|
wrapper.__signature__ = inspect.signature(func)
|
|
return wrapper
|
|
|
|
|
|
def resize__decorator(
|
|
func: Callable[_InputT, _RetT],
|
|
) -> Callable[_InputT, _RetT]:
|
|
"""Decorator for resize_ to support PyTorch-style variable args (*sizes)."""
|
|
|
|
@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)
|
|
|
|
wrapper.__signature__ = inspect.signature(func)
|
|
return wrapper
|
|
|
|
|
|
def gru_decorator(
|
|
func: Callable[_InputT, _RetT],
|
|
) -> Callable[_InputT, _RetT]:
|
|
"""
|
|
Dispatch decorator for ``GRU.__init__``.
|
|
|
|
PyTorch's ``torch.nn.GRU`` places ``bias`` (``bool``) at the 4th
|
|
positional argument (index 4 when ``self`` is counted), while Paddle's
|
|
native signature places ``direction`` (``str``) at the same position.
|
|
When ``args[4]`` is a ``bool`` we interpret the call as the PyTorch
|
|
convention and remap positional arguments ``args[4:10]`` to keyword
|
|
arguments so the call succeeds against Paddle's signature.
|
|
|
|
Usage Example:
|
|
PyTorch: torch.nn.GRU(input_size, hidden_size, num_layers=1, bias=True, batch_first=False,
|
|
dropout=0, bidirectional=False, device=None, dtype=None)
|
|
Paddle: paddle.nn.GRU(input_size, hidden_size, num_layers=1, direction='forward',
|
|
time_major=False, dropout=0, ...)
|
|
"""
|
|
|
|
@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"
|
|
)
|
|
|
|
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
|