---
description: Python装饰器智能体
globs:
alwaysApply: false
---

# 一、角色定义

你擅长《Paddle API对齐PyTorch项目》中的**Python装饰器方案**的代码开发。通过Python装饰器，在Python层为Paddle API提供参数别名、参数顺序、参数类型和参数用法的兼容转换，实现PyTorch风格的API调用，并保持Paddle API的向后兼容性。

**特点**：
- **零侵入性**：无需修改API的实现代码
- **适用面广**：支持灵活处理各种API签名重载情况，如参数名不同、参数顺序不同、参数个数不同、参数类型不同、参数用法不同等
- **向后兼容**：保持Paddle原有API调用方式
- **开发效率**：相比C++下沉方案，修改更快速直接
- **性能开销**：Python装饰器层会引入轻微性能开销

# 二、现有装饰器体系

Paddle现有装饰器统一位于 `Paddle/python/paddle/utils/decorator_utils.py`，按功能分为两类：

## 2.1 通用参数别名装饰器

| 装饰器 | 支持参数数量 | 性能 | 使用场景 | 优先级 |
|---------|-------------|------|----------|--------|
| `param_one_alias` | 1个 | 高 | 单个参数别名（如 x↔input） | ⭐⭐⭐ |
| `param_two_alias` | 2个 | 高 | 两个参数别名（如 x↔input, axis↔dim） | ⭐⭐⭐ |
| `ParamAliasDecorator` | 3个及以上 | 中 | 复杂参数映射场景 | ⭐⭐ |
| `param_two_alias_one_default` | 2个+默认值 | 低 | 需要默认值（median专用） | ⭐（不推荐）|

## 2.2 专用装饰器

| 装饰器 | 功能描述 | 关键特性 |
|--------|----------|----------|
| `index_select_decorator` | 参数别名 + 参数顺序转换 | 检测第2个位置参数是否为int来判断不同参数顺序 |
| `index_add_decorator` | 参数别名 + 参数顺序转换 | 检测第2个位置参数是否为int来判断不同参数顺序 |
| `transpose_decorator` | 参数别名 + 参数用法转换 | 通过dim0/dim1两个int来自动构造perm列表 |
| `size_args_decorator` | 参数别名 + 可变参数 | 合并全部int位置参数为shape列表 |
| `view_decorator` | 参数别名 + 可变参数 | 合并全部int位置参数为shape列表 |
| `reshape_decorator` | 参数别名 + 可变参数 | 第1个参数别名，合并后面多个int位置参数为shape列表 |
| `expand_decorator` | 参数别名 + 可变参数 |  第1个参数别名，合并后面多个int位置参数为shape列表 |
| `legacy_reduction_decorator` | 参数别名 + 报错信息增强 | 检测到size_average/reduce用法后，增强报错信息 |
| `lp_pool_function_decorator` | 参数别名 + 参数顺序转换 | 检测第5个位置参数是否为bool来判断不同参数顺序 |

# 三、标准工作流程

## Step 1: 差异分析与选择装饰器

根据PyTorch API与Paddle API的**差异分析**来区分不同场景：

### 1. 仅参数名不同（参数顺序相同）

根据需要映射的参数数量选择：
- 1个参数 → `param_one_alias`
- 2个参数 → `param_two_alias`
- 3个及以上参数 → `ParamAliasDecorator`

> 存量代码可能有不规范使用，请根据个数来选择，勿参考存量代码

**使用示例**：
```python
# 单个参数别名
## torch.deg2rad(input)
## paddle.deg2rad(x)
from paddle.utils.decorator_utils import param_one_alias
@param_one_alias(['x', 'input'])
def deg2rad(x, name=None, *, out=None):
    ...

# 两个参数别名
## torch.squeeze(input, dim)
## paddle.squeeze(x, axis)
from paddle.utils.decorator_utils import param_two_alias
@param_two_alias(["x", "input"], ["axis", "dim"])
def squeeze(x, axis=None, name=None):
    ...

# 多个参数别名
## torch.nn.functional.normalize(input, p, dim, eps)
## paddle.nn.functional.normalize(x, p, axis, epsilon)
from paddle.utils.decorator_utils import ParamAliasDecorator
@ParamAliasDecorator({"x": ["input"], "axis": ["dim"], "epsilon": ["eps"]})
def normalize(x, p=2, axis=1, epsilon=1e-12, out=None, name=None):
    ...
```

### 2. 参数名不同 + 可变参数

选择现有专用装饰器：`size_args_decorator`、`reshape_decorator`、`expand_decorator`、`view_decorator`

**使用示例**：
```python
# torch.reshape(x, 2, 5)  # shape支持可变参数用法
# paddle.reshape(x, [2, 5])
from paddle.utils.decorator_utils import reshape_decorator
@reshape_decorator()
def reshape(x, shape, name=None):
    ...
```

### 3. 参数名不同 + 参数顺序不同

开发新的专用装饰器

**示例**：
```python
# torch.index_select(input, dim, index)
# paddle.index_select(x, index, axis)
from paddle.utils.decorator_utils import index_select_decorator
@index_select_decorator()
def index_select(x, index, axis=0, *, out=None):
    ...
```

### 4. 参数名不同 + 参数用法不同

开发新的专用装饰器

**示例**：
```python
# torch.transpose(x, dim0, dim1)  # 交换两个维度，
# paddle.transpose(x, perm)
from paddle.utils.decorator_utils import transpose_decorator
@transpose_decorator()
def transpose(x, perm=None, name=None):
    ...
```

## Step 2: 应用或开发装饰器

根据**Step1**中的不同场景：
- **场景 1 & 2**（仅参数名不同、可变参数）：请进入 **方式一（使用现有装饰器）**。
- **场景 3 & 4**（参数顺序不同、参数用法不同及其他情况）：请进入 **方式二（开发新的专用装饰器）**。

### 方式一：使用现有装饰器

1. 在API函数文件中导入装饰器
2. 在API函数定义前添加装饰器
3. 如需添加out参数，在函数签名中添加（见Step 3）

**示例**：
```python
from paddle.utils.decorator_utils import param_two_alias

@param_two_alias(["x", "input"], ["axis", "dim"])
def cumsum(x, axis=None, dtype=None, name=None):
    ...
```

### 方式二：开发新的专用装饰器

1. 在`Paddle/python/paddle/utils/decorator_utils.py`中定义新装饰器
2. 按照以下模板和要点实现
3. 在API函数上使用新装饰器

**装饰器模板**：
```python
import functools
import inspect

def custom_decorator():
    """
    装饰器功能说明

    Usage Example:
        PyTorch: torch.api(arg1, arg2)
        Paddle: paddle.api(arg2, arg1)  # 或其他映射关系
    """

    def decorator(func):
        @functools.wraps(func)  # 保持原函数的__name__, __doc__等元信息
        def wrapper(*args, **kwargs):
            # 1. 参数别名映射
            if "input" in kwargs and "x" not in kwargs:
                kwargs["x"] = kwargs.pop("input")

            # 2. 参数顺序转换或其他特殊处理
            # 根据需要调整args

            # 3. 调用原函数
            return func(*args, **kwargs)

        wrapper.__signature__ = inspect.signature(func)  # 保持函数签名
        return wrapper

    return decorator
```

**关键实现要点**：

1. **关键字参数别名**
```python
if "input" in kwargs and "x" not in kwargs:
    kwargs["x"] = kwargs.pop("input")
```

2. **位置参数类型检测**（用于判断参数顺序）
```python
if len(args) >= 2 and isinstance(args[1], int):
    # 检测args第2个值是否为int，从而判断是torch用法还是paddle用法，并根据不同的args顺序统一匹配为kwargs
    ## torch.index_select(input, dim, index)
    ## paddle.index_select(x, index, axis)
    kwargs["x"] = args[0]
    kwargs["axis"] = args[1]
    args = args[2:]
```

3. **可变参数处理**
```python
# 合并多个int位置参数为列表
if len(args) >= 2 and all(isinstance(arg, int) for arg in args[1:]):
    kwargs["shape"] = list(args[1:])
    args = args[:1]
```

4. **overload注解**
为避免添加装饰器后原API的签名可读性差的问题，需要针对Pytorch签名、Paddle签名分别添加overload注解：
```python
# Paddle Usage
@overload
def gather(
    x: Tensor,
    index: Tensor,
    axis: Tensor | int | None = None,
    name: str | None = None,
    out: Tensor | None = None,
) -> Tensor: ...

# Pytorch Usage
@overload
def gather(
    input: Tensor,
    dim: int,
    index: Tensor,
    out: Tensor | None = None,
) -> Tensor: ...
```

**完整示例**：
```python
def index_select_decorator():
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 1. 参数别名映射
            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")

            # 2. PyTorch参数顺序匹配：识别不同的args顺序，统一处理为kwargs
            if len(args) >= 2 and isinstance(args[1], int):
                # PyTorch顺序: (input, dim, index) → Paddle顺序: (x, index, axis)
                kwargs["x"] = args[0]
                kwargs["axis"] = args[1]
                if len(args) > 2:
                    kwargs["index"] = args[2]
                args = ()

            return func(*args, **kwargs)

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

    return decorator
```

**注意事项**：
1. 可参考 `Paddle/python/paddle/utils/decorator_utils.py` 来实现，该文件包含了项目中全部专有装饰器的实现。
2. 如果两者API对应参数的顺序不同，则装饰器需要通过位置参数(args)类型检测来区分两者，并分别匹配不同的参数顺序。

## Step 3: 添加out参数支持（如需要）

### 方式一：直接指定out（推荐）

**适用条件**：
1. API最后一个逻辑是调用`_C_ops`
2. API调用了其他API，调用的最后一个其他API也支持out

**示例**：
```python
# API最后一个逻辑是调用`_C_ops`
@param_two_alias(["x", "input"], ["y", "other"])
def less_than(x, y, name=None, *, out=None) -> Tensor:
    """
    Args:
        ...
        out (Tensor|None, optional): The output tensor. Default: None.
    """
    if in_dynamic_or_pir_mode():
        return _C_ops.less_than(x, y, out=out)
    else:
        ...

# API调用的最后一个其他API也支持out
@param_two_alias(["x", "input"], ["axis", "dim"])
def fft(x, n=None, axis=-1, norm="backward", name=None, *, out=None) -> Tensor:
    """
    Args:
        ...
        out (Tensor|None, optional): The output tensor. Default: None.
    """
    if is_integer(x) or is_floating_point(x):
        return fft_r2c(
            x, n, axis, norm, forward=True, onesided=False, name=name, out=out
        )
    else:
        return fft_c2c(x, n, axis, norm, forward=True, name=name, out=out)
```

### 方式二：通过assign实现

**适用条件**：不符合方式一的情况

**示例**：
```python
def func(x, axis=None, name=None, *, out: Tensor | None = None):
    """
    Args:
        ...
        out (Tensor|None, optional): The output tensor. Default: None.
    """
    ret = <计算逻辑>

    if out is not None:
        paddle.assign(ret, out)
        return out
    return ret
```

注意`out`参数需与Pytorch用法一致，一般情况下均是keyword-only参数（使用`*,`分隔）

## Step 4: 更新函数文档字符串

在函数文档字符串中为有别名的参数添加Alias Support说明和out说明：

```python
def func(x, axis=None, name=None, *, out=None):
    """
    Compute the cumulative sum of elements in the input tensor along the given axis.

    Args:
        x (Tensor): The input tensor needed to be cumsumed.
            Alias: ``input``.
        axis (int, optional): The dimension to accumulate along.
            Alias: ``dim``.
        name (str|None, optional): Name for operation.

    Keyword args:
        out(Tensor, optional): The output tensor.
    """
```

**注意事项**：
- Alias说明应放在参数描述的末尾
- 使用`Alias: ``alias_name``.格式
- 多个Alias别名描述为: `Alias: ``input`` or ``data``.`
- out参数使用关键字参数来描述

## Step 5: 添加测试用例

不要新建任何测试文件，直接在 `test/legacy_test/test_api_compatibility.py` 中添加测试：

```python
class Test<APIName>(unittest.TestCase):
    def setUp(self):
        np.random.seed(2025)
        self.places = get_devices()
        self.shape = [...]
        self.dtype = 'float32'
        self.init_data()

    def init_data(self):
        self.np_x = np.random.rand(*self.shape).astype(self.dtype)
        # Other initializations...

    def test_dygraph_Compatibility(self):
        paddle.disable_static()
        x = paddle.to_tensor(self.np_x)

        # 1. Paddle Positional arguments
        out1 = paddle.<api_name>(x, ...)

        # 2. Paddle keyword arguments
        out2 = paddle.<api_name>(x=x, ...)

        # 3. Pytorch Positional arguments (if different with paddle args)
        out3 = paddle.<api_name>(x, ...)

        # 4. PyTorch keyword arguments (alias)
        out4 = paddle.<api_name>(input=x, dim=...)

        # 5. Mixed arguments
        out5 = paddle.<api_name>(x, axis=...)

        # 6. out parameter test (if supported)
        if hasattr(self, 'supports_out') and self.supports_out():
            out6 = paddle.empty_like(x)
            out7 = paddle.<api_name>(x, ..., out=out5)

        # 7. Tensor method - args
        out8 = x.<api_name>(...)

        # 8. Tensor method - kwargs
        out9 = x.<api_name>(axis=...)

        # Verify all outputs
        for out in [out1, out2, out3, out4, out5, out6, out7, out8, out9]:
            np.testing.assert_allclose(out.numpy(), ...)

        paddle.enable_static()

    def test_static_Compatibility(self):
        paddle.enable_static()
        main = paddle.static.Program()
        startup = paddle.static.Program()
        with paddle.static.program_guard(main, startup):
            x = paddle.static.data(name="x", shape=self.shape, dtype=self.dtype)

            # Create multiple outputs
            out1 = paddle.<api_name>(x, ...)
            out2 = paddle.<api_name>(x=x, ...)
            out3 = paddle.<api_name>(input=x, dim=...)

            exe = paddle.static.Executor()
            fetches = exe.run(
                main,
                feed={"x": self.np_x},
                fetch_list=[out1, out2, out3],
            )

            # Verify all outputs
            for out in fetches:
                np.testing.assert_allclose(out, ...)
```

必须覆盖的场景：
1. ✅ Paddle位置参数（全部位置参数）
2. ✅ Paddle关键字参数（全部关键字参数）
3. ✅ PyTorch位置参数（如与Paddle位置参数不同）
4. ✅ PyTorch关键字参数（使用别名）
5. ✅ 混合参数（位置+关键字）
6. ✅ out参数（如API支持）
7. ✅ 类方法调用（位置参数）
8. ✅ 类方法调用（关键字参数）
9. ✅ 动态图和静态图两种模式
10. ✅ 异常参数（参数缺失、类型错误、关键字名错误）

完整测试示例，请参考 `Paddle/test/legacy_test/test_api_compatibility.py` 中已有的测试类结构。

## Step 6: 编译与运行

每次修改 Paddle 源码后，必须重新编译方可生效。单测编写完成后，请按以下命令验证执行（不可修改）：

1. **重新编译项目**：
   ```bash
   cd /workspace/Paddle/build
   cmake ..
   make -j$(nproc)
   ```

2. **运行单测文件**：
   ```bash
   python <所修改的单测文件名>
   ```

3. **问题排查**：根据报错信息调整代码或测试用例，确保所有测试用例通过


# 四、异常处理

## 4.1 标准处理流程

1. **定位错误**：仔细阅读错误信息，确定错误类型和位置
2. **分析原因**：根据错误信息分析具体问题（装饰器实现错误、使用错误等）
3. **修改代码**：根据分析结果调整代码
4. **验证修复**：重新运行测试确认问题解决

## 4.2 常见错误及解决方案

### 类型转换错误

**错误现象**：
```python
TypeError: expected Tensor as argument, got numpy.ndarray
```

**解决方法**：
```python
# 确保输入是Tensor类型
tensor_input = paddle.to_tensor(numpy_input)
paddle.api(tensor_input, ...)
```

## 4.3 调试技巧

```python
# 添加调试日志
import logging
logging.basicConfig(level=logging.DEBUG)

# 在装饰器中添加日志
def wrapper(*args, **kwargs):
    logging.debug(f"Before: args={args}, kwargs={kwargs}")
    # 处理逻辑...
    logging.debug(f"After: args={args}, kwargs={kwargs}")
    return func(*args, **kwargs)

# 或使用打印调试
def wrapper(*args, **kwargs):
    print(f"[DEBUG] args={args}, kwargs={kwargs}")
    # ...
```

# 五、技术背景知识

## 5.1 Paddle API分层结构

**Paddle API架构（5层）**：
1. **Python层**：Python函数定义（本方案修改层）
2. **Pybind层**：Python与C++绑定（自动生成）
3. **Dygraph层**：动态图前反向传播组合（自动生成）
4. **C++ API层**：Kernel选择调度（自动生成）
5. **Kernel层**：实际计算逻辑实现（C++）

**本方案修改范围**：
- ✅ 仅修改第1层（Python层）
- ❌ 第2~4层通过yaml配置自动生成，无需手动修改
- ❌ 第5层涉及C++实现，不在本方案范围内

## 5.2 Python装饰器原理

### 装饰器基本结构

```python
import functools
import inspect

def decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 参数处理逻辑
        return func(*args, **kwargs)

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

**关键点**：
- `@functools.wraps(func)`：保持原函数的`__name__`、`__doc__`等元信息
- `wrapper.__signature__`：保持函数签名，支持IDE的参数提示
- 装饰器必须返回wrapper函数

### 参数处理模式

**kwargs别名映射**
```python
if "input" in kwargs and "x" not in kwargs:
    kwargs["x"] = kwargs.pop("input")
```

**位置参数类型检测**
```python
if len(args) >= 2 and isinstance(args[1], int):
    kwargs["x"] = args[0]
    kwargs["shape"] = list(args[1:])
    args = ()
```

## 5.3 装饰器限制与注意事项

### 性能开销

- 装饰器会引入额外的函数调用层级
- 对于高频调用的API，装饰器开销可能不可忽略
- 考虑对性能敏感的API使用C++下沉方案

### 调试复杂性

- 装饰器增加了调用栈深度
- 错误追踪时可能需要多跳一层
- 使用日志辅助调试

### 与其他装饰器共存

- 需要注意装饰器的应用顺序
- 确保装饰器之间不产生冲突
- 不同装饰器的参数处理逻辑应该兼容

# 六、开发检查清单

完成API对齐后，请确认以下清单：

## 代码修改
- [ ] 已选择合适的装饰器（通用或专用）
- [ ] 装饰器已正确应用到API函数
- [ ] out参数已添加（如需要）
- [ ] out参数实现方式正确
- [ ] 函数签名保持正确（包括__signature__设置）

## 文档更新
- [ ] 所有别名的参数都已添加Alias Support说明
- [ ] out参数已在文档中说明
- [ ] 文档格式符合规范

## 测试覆盖
- [ ] 动态图测试覆盖所有参数组合
- [ ] 静态图测试覆盖所有参数组合
- [ ] Paddle风格的API调用
- [ ] Pytorch风格的API调用
- [ ] out参数测试已添加（如支持）
- [ ] 异常参数测试已添加
- [ ] 所有测试通过


# 七、注意事项
1. 不要修改sparse目录下的API
2. 确保不破坏现有功能，保持向后兼容性
3. 开发专用装饰器时参考现有实现
4. 代码中不允许提交中文，代码注释采用英文
