diff --git a/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/README.md b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/README.md new file mode 100644 index 00000000000..22281169c5d --- /dev/null +++ b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/README.md @@ -0,0 +1,74 @@ +# 《Paddle API 对齐 PyTorch 项目》完整 SKILL 体系 + +AI Agent 自动对齐 Paddle API 与 PyTorch API。包含 1 个总 skill 和 6 个分阶段 skill。 + +## 两种使用方式 + +### 方式一:一步到位(推荐) + +使用总 skill **`/api-compatibility`**,传入待对齐的 PyTorch API 列表: + +``` +/api-compatibility torch.atan torch.asinh torch.abs_ +``` + +总 skill 会自动执行完整流程(Step1→Step2→Step3→Step4→Step5),返回对齐结果统计表。 + +### 方式二:分步操作 + +针对具体任务,单独调用对应 skill: + +| 阶段 | Skill | 用途 | +|------|-------|------| +| Step1 | `/api-change-decider` | 分析 API 差异,决策改动方案 | +| Step2 | `/python-decorator` | 用 Python 装饰器修改 API | +| Step2 | `/cpp-sink` | 用 C++下沉修改 API(性能更优) | +| Step3 | `/pytorch-alignment-validator` | 验证 API 对齐 | +| Step4 | `/api-docs-updater` | 更新中文文档 | +| Step5 | `/create-pr` | 提交 PR 到三个仓库 | + +## 工作流程 + +``` +输入:待对齐 API 列表 + ↓ +Step1:方案决策(api-change-decider) + ├─ 分析 API 差异 + └─ 决策改动方案(方案 1-6) + ↓ +Step2:代码修改(python-decorator 或 cpp-sink) + ├─ 方案 1:Python 装饰器 + ├─ 方案 2:C++下沉 + └─ 其他方案:跳过 + ↓ +Step3:对齐验证(pytorch-alignment-validator) + ├─ 更新 API 映射 + ├─ 补充测试用例 + └─ 运行单元测试 + ↓ +Step4:文档更新(api-docs-updater) + └─ 更新中文 API 文档 + ↓ +Step5:代码提交(create-pr) + └─ 提交 PR 到 Paddle/PaConvert/docs + ↓ +输出:对齐结果统计表 +``` + +## 使用示例 + +**完整对齐一个 API**(推荐方式): +``` +/api-compatibility torch.atan +``` +自动完成全流程,得到对齐结果。 + +**调试单个 API**(需要逐步修改): +``` +/api-change-decider torch.atan # 确定方案 +/cpp-sink torch.atan # 指定方案修改 +/python-decorator torch.atan # 指定方案修改 +/pytorch-alignment-validator torch.atan # 验证 +/api-docs-updater torch.atan # 更新文档 +/create-pr torch.atan # 提交 PR +``` diff --git a/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/api-change-decider/SKILL.md b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/api-change-decider/SKILL.md new file mode 100644 index 00000000000..51250e43d43 --- /dev/null +++ b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/api-change-decider/SKILL.md @@ -0,0 +1,254 @@ +--- +name: api-change-decider +description: 仅用于《Paddle API 对齐 PyTorch 项目》,负责 Step1:方案决策,分析 PyTorch API 与 Paddle API 之间的差异,制定合适的 API 改动方案 +allowed-tools: Read Grep‌ Glob‌ WebSearch +disable-model-invocation: false +--- + +# 一、输入输出规范 + +## 1.1 输入 +需要对齐的 PyTorch API 列表(如 `torch.atan`、`torch.asinh`) + +## 1.2 输出 +方案类型、对应 Paddle API、差异分类、决策依据(以表格形式展示) + +## 1.3 输出内容 +输出应包含如下内容,以表格式形式展示: +1. **方案类型**:从方案 1~6 中选择合适的方案(可组合多种方案,例如方案 3+方案 1) +2. **对应 Paddle API**:需改动的 Paddle API 完整路径(如 `paddle.nn.functional.dropout`) +3. **差异分类**:差异分类是什么 +4. **决策依据**:总结差异分析过程和选择理由 + - API 相对引用路径是否一致 + - 为什么选择该方案 + - 为什么不选择其他方案 + - 该方案是否会影响后向兼容 + +## 1.4 输出格式示例 +```markdown +# 决策结果 +|Pytorch API|方案类型|Paddle API|差异分类|决策依据| +|-|-|-|-|-| +|torch.atan|方案 2|paddle.atan|torch 参数更多|仅参数名不同(input→x)+仅多 out 参数,Python 实现仅有一次`_C_ops.atan(x)`调用,满足 C++下沉条件,性能最优| +|torch.frexp|方案 3+方案 1|paddle.frexp|torch 参数更多|API 相对引用路径一致,差异为:1)仅参数名不同(input→x);2)仅多 out 参数。当前 Python 实现包含多个 paddle 操作调用(abs、floor、log2 等),逻辑较复杂,不满足 C++下沉条件。选择方案 3 修改 API,在末尾添加 out 参数(带默认值 None),保持后向兼容。同时方案 1 支持参数名不同的重载情况。| +``` + +# 二、候选方案 + +## 方案 1:Python 装饰器 +**适用场景**: +- 在 Python 层添加装饰器实现对齐 +- 可对输入 `(*args, **kwargs)` 进行操作 +- 支持多种重载情况:参数名不同、参数顺序不同、参数个数不同、参数类型不同 +- 同时支持 torch 和 paddle 两套参数签名 + +**工作原理**: +- 根据输入参数的名称、类型、个数的不同来判断是 torch 签名还是 paddle 签名 +- 分别针对两套签名进行不同的功能适配,从而既保留了原本的 paddle 功能,也新增了 torch 功能 +- 这是方案 3(修改 API)的升级版,在保持后向兼容性的前提下实现对齐 + +**核心要求**: +- **必须能够区分**:能够根据输入的参数类型、名称的不同来区分 torch 签名还是 paddle 签名 +- **无法区分则不适用**:如果无法通过输入参数特征区分两套签名,则方案 1 不适用 + +**优点**:灵活性强,兼容性好 +**缺点**:性能低于 C++下沉实现 + +## 方案 2:C++下沉 +**适用场景**: +- 将 API 直接下沉到 C++层 +- Paddle 机制支持在 C++层配置参数映射 +- **支持仅参数名不同的重载情况**(不涉及参数顺序或个数差异) +- **支持仅多 out 参数的情况** +- 方案 2 需要判断 API 的 Python 实现,需满足以下两个条件: + - 只有一次 `_C_ops.xxx` 调用 + - `_C_ops.xxx` 前面没有**前处理逻辑**,或虽有**前处理逻辑**但逻辑简单且不涉及其他 API 调用(如 x.flatten 等),容易改写为 C++ + - 注:分析时忽略静态图部分(LayerHelper 分支代码),该分支不再维护 + +**方案 2 不适用场景(满足其一则不适用)**: +- ❌ API 差异涉及参数顺序或个数差异 +- ❌ Python 实现里调用了其他**Paddle API**,如 x.flatten、paddle.flatten(x)等 +- ❌ 包含多个 `_C_ops.xxx` 调用 +- ❌ `_C_ops.xxx` 前面的**前处理逻辑**较为复杂,不容易被改写为 C++ + +**优点**:性能最优 +**缺点**:仅支持参数名不同的情况 + +## 方案 3:修改 API +**适用场景**: +- 直接修改现有 API 实现对齐 +- 新增参数或功能 +- 修改原有参数或功能 + +**以下修改不会导致后向兼容问题,可开展**: +- ✅ 在 API 参数末尾添加参数,且参数具有默认值 +- ✅ 对已有 API 参数扩展新功能,保留原有功能 + +**以下修改会导致后向兼容问题,需禁止**: +- ❌ 改变已有参数顺序 +- ❌ 改变已有参数名称 +- ❌ 修改返回值类型 + +**优点**:直接对齐,实现简单 +**缺点**:可能导致后向不兼容 + +## 方案 4:新增 API +**适用场景**: +- API 相对引用路径不一致 +- 新增的 API 需要与 Pytorch 完全一致,包括 API 相对引用路径、输入参数与返回值(名称、个数、功能均一致) + +**优点**:完全对齐,无后向兼容问题 + +## 方案 5:新增 compat 类型 API +**适用场景**: +- 在 `paddle/compat/__init__.py` 下新增 API +- 既无法原地修改(后向兼容性问题严重) +- 也无法新增 API(API 相对引用路径已被占用) + +**优点**:无后向兼容问题 +**缺点**:多一级 compat 路径,无法真正实现与 Pytorch 完全一致,实现之后差异分类将成为『仅 API 调用方式不一致』,在无其他方案时可以退而求其次选择 + +## 方案 6:无需改动 +**适用场景**: +- API 完全一致 + +# 三、标准工作流程 + +## Step 1: 分析差异文档 + +### 1.1 获取差异文档 +务必查阅每个 PyTorch API 对应的 API 差异文档(`torch.{api_name}.md`,位于 docs/docs/guides/model_convert/convert_from_pytorch/api_difference/目录下),如果实在无法找到差异文档,则说明该 API 已经实现了对齐一致,差异分类直接视作『API 完全一致』,无需再查询其他内容。 + +### 1.2 差异分类定义 +差异分类共 13 类,具体如下: + +| 序号 | 差异分类 | 说明 | +|:---:|:---|:---| +| 1 | API 完全一致 | 无差异,无需改动 | +| 2 | 仅 API 调用方式不一致 | API 相对引用路径不一致,但参数完全相同 | +| 3 | 仅参数名不一致 | 参数功能相同但参数名称不同 | +| 4 | paddle 参数更多 | Paddle 提供更多可选参数 | +| 5 | 参数默认值不一致 | 参数默认值不同 | +| 6 | torch 参数更多 | PyTorch 提供更多参数 | +| 7 | 输入参数用法不一致 | 参数处理方式不同 | +| 8 | 输入参数类型不一致 | 参数类型要求不同 | +| 9 | 返回参数类型不一致 | 返回值类型或结构不同 | +| 10 | 组合替代实现 | PyTorch API 需多个 Paddle API 组合实现 | +| 11 | 可删除 | PyTorch API 在 Paddle 中可直接删除 | +| 12 | API 别名 | PyTorch API 是其他 API 的别名 | +| 13 | 功能缺失 | Paddle 暂无等效实现 | + +### 1.3 提取差异信息 + +提取相关差异信息,提供给 Step2 进行方案决策: + +| 项目 | 内容 | +|------|------| +| API 映射 | PyTorch API vs 对应的 Paddle API(可能为空)| +| 参数映射 | 参数对应关系和差异说明 | +| 转写示例 | 代码转换示例 | + +注意以下参数直接忽略,不视作差异信息: +1. 忽略第 1 列的 generator、memory_format、layout 参数 +1. 忽略第 2 列的 name 参数 + +## Step 2: 方案决策 + +### 2.1 关键概念 + +**API 相对引用路径**:API 完整路径在去掉框架导入模块(torch/paddle)后剩余的部分 +- 示例:`torch.nn.functional.dropout` 的 API 相对引用路径是 `nn.functional.dropout` +- 示例:`paddle.nn.functional.dropout` 的 API 相对引用路径是 `nn.functional.dropout` +- 示例:`torch.Tensor.tile` 的 API 相对引用路径是 `Tensor.tile` + +### 2.2 关键原则 + +> **⚠️ 严格遵循**:决策时必须严格遵守以下原则,不得主观臆断: +> +> 1. **API 相对引用路径不一致 → 必须方案 4(新增 API)** +> - 只要 API 相对引用路径不一致,直接选择方案 4,无需再分析其他因素 +> - 对于一方为空的情况下,也视作不一致(无对应 Paddle API 情况下) +> +> 2. **严格按流程图和规则判断** +> - 必须按照决策流程图的路径执行 +> - 严格按照各方案的适用条件判断 +> - 不得因"可以"、"应该"等主观判断偏离规则定义 + +### 2.3 决策流程图 + +``` +开始 + │ + ├───→ API 相对引用路径是否一致? ──────┐ + │ │ + │是 │否 + ↓ ↓ +具体有哪些差异? 方案 4(新增 API)→结束 + │ + ├──→ API 完全一致 → 方案 6(无需改动)→结束 + │ + ├──→ 仅参数名不一致 → 方案 2(C++下沉)→ 不适用则方案 1→结束 + │ + ├──→ paddle 参数更多 → 是否影响对齐?─┬→否→方案 6(无需改动)→结束 + │ └→是→方案 3(修改 API)→存在兼容性则方案 5→结束 + │ + ├──→ 参数默认值不一致 → 方案 3(修改 API)→存在兼容性则方案 5→结束 + │ + ├──→ torch 参数更多 → 仅多 out 参数?─┬→是→方案 2(C++下沉)→不适用则方案 3→存在兼容性则方案 1→无法区分则方案 5→结束 + │ └→否→方案 3(修改 API)→存在兼容性则方案 1→无法区分则方案 5→结束 + │ + ├──→ 输入参数用法/类型不一致 → 方案 3(修改 API)→存在兼容性则方案 1→无法区分则方案 5→结束 + │ + └──→ 返回参数类型不一致 → 方案 5(新增 compat 类型 API)→结束 +``` + +### 2.4 详细决策规则 + +**前置判断**:以下规则均基于**API 相对引用路径一致**的前提。只要 API 相对引用路径不一致,直接选择**方案 4(新增 API)**,无需进入后续判断。 + +#### 1. API 完全一致 +- **决策**:方案 6(无需改动) + +#### 2. 仅参数名不一致 +- **优先级 1**:方案 2(C++下沉) + - **适用条件**:满足方案 2 适用条件(见第三部分定义) + - **优势**:性能最优 +- **优先级 2**:方案 1(Python 装饰器) + - **适用条件**:不满足方案 2 适用条件 + +#### 3. paddle 参数更多 +- **判断**:额外参数是否影响对齐 + - **否**(如默认参数,Paddle 保持默认即可)→ 方案 6(无需改动) + - **是** → 方案 3(修改 API) + - **存在兼容性** → 方案 5(新增 compat 类型 API) + +#### 4. 参数默认值不一致 +- **优先级 1**:方案 3(修改 API) +- **回退**:存在兼容性 → 方案 5(新增 compat 类型 API) + +#### 5. torch 参数更多 +- **子判断**:是否仅多 out 参数 + - **是** → 方案 2(C++下沉) + - **不适用** → 方案 3(修改 API)→ 存在兼容性则方案 1→ 无法区分则方案 5 + - **否** → 方案 3(修改 API)→ 存在兼容性则方案 1→ 无法区分则方案 5 + +#### 6. 输入参数用法/类型不一致 +- **优先级 1**:方案 3(修改 API) + - **存在兼容性** → 方案 1(Python 装饰器) + - **无法区分两套签名** → 方案 5(新增 compat 类型 API) + +#### 7. 返回参数类型不一致 +- **唯一决策**:方案 5(新增 compat 类型 API) +- **原因**: + - ❌ 方案 3:修改返回值必然不兼容 + - ❌ 方案 1:装饰器只能根据输入区分,无法区分返回值差异 + - ✅ 方案 5:在 compat 路径下新增,不影响原 API + +### 2.5 方案组合说明 + +> **重要提醒**:一个 API 可能涉及多种差异分类,需要综合分析所有差异点,可以组合多种方案来消除所有差异点。 + +**示例**:`torch.frexp` 存在"仅参数名不同"和"仅多 out 参数"两个差异点 +- **组合方案**:方案 3 + 方案 1 + - 方案 3:在末尾添加 out 参数(带默认值 None),消除"仅多 out 参数"差异 + - 方案 1:支持参数名不同的重载,消除"仅参数名不同"差异 diff --git a/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/api-compatibility/SKILL.md b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/api-compatibility/SKILL.md new file mode 100644 index 00000000000..b77209e5933 --- /dev/null +++ b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/api-compatibility/SKILL.md @@ -0,0 +1,375 @@ +--- +name: api-compatibility +description: 开展《Paddle API 对齐 PyTorch 项目》,负责项目整体统筹规划,调用多个 skill,完成输入的 API 对齐 +disable-model-invocation: true +--- + +# 一、项目目标 + +**使 Paddle API 与 PyTorch API 完全对齐**,实现: + +- 对于任意 PyTorch API 用法,只需将 `torch.*` 替换为 `paddle.*` +- 计算结果完全一致(数值精度、行为逻辑) + +**流程概览**: +1. 接收待对齐的 Pytorch API 列表:$ARGUMENTS +2. 为对应的 Paddle API 决策改动方案 +3. 执行具体的 Paddle API 修改 +4. 验证修改后的 Paddle API 是否对齐 Pytorch +5. 更新修改后的 Paddle API 中文文档 +6. 提交代码到相应的仓库 +7. 给出 Pytorch API 列表的对齐结果统计 + +# 二、输入输出规范 + +## 2.1 输入 +用户提供待对齐的 Pytorch API 列表,示例: +```markdown +torch.argmax +torch.log2 +torch.logsumexp +``` + +## 2.2 输出 + +用户输入列表的对齐情况,格式示例: +```markdown +# API 对齐结果统计 + +|API 名称|对齐状态|改动方案|备注| +|-|-|-|-| +|torch.argmax|已对齐|方案 2|-| +|torch.log2|已对齐|无需改动|<简介为何无需改动>例如:未查询到差异文件,两者 API 完全一致| +|torch.logsumexp|未对齐|方案 1|<简介失败原因>例如:方案 1 暂不支持修改| +``` + +# 三、技术背景知识 + +### 3.1 根目录说明 + +| 目录名称 | 内容说明 | 对应步骤 | +|---------|---------|---------------| +| Paddle | 包含所有 Paddle API 的实现 | Step2:代码修改 | +| PaConvert | 包含所有 Pytorch 单元测试 | Step3:对齐验证 | +| docs | 包含所有 Paddle API 中文文档 | Step4:文档更新 | +| Paddle、PaConvert、docs | 代码提交对象 | Step5:代码提交 | + +### 3.2 相关文件位置 + +|**功能模块**|**检索关键字**|**文件路径**|**举例**|**注意**| +|-|-|-|-|-| +|API 中文文档|`{api_name}_cn.rst`|docs/docs/api/paddle/|tan_cn.rst|| +|API 差异文档|`torch.{api_name}.md`|docs/docs/guides/model_convert/convert_from_pytorch/api_difference/|torch.tan.md|| +|C++下沉使用|`python_api_info.yaml`、`ops.yaml`|Paddle/paddle/phi/ops/yaml/|python_api_info.yamlops.yaml|| +|C++下沉使用|`_paddle_docs.py`|Paddle/python/paddle/|_paddle_docs.py|| +|Paddle API 实现位置|`def {api_name}` 或 `class {api_name}`|Paddle/python/paddle/|Paddle/python/paddle/tensor/ops.py|不要误检索到 sparse 目录下(稀疏 API 位置),本项目与稀疏无关,所有 sparse 相关文件直接忽略| +|Paddle API 兼容性单测位置|`test_api_compatibility.py`|Paddle/test/legacy_test/test_api_compatibility.py|| +|Pytorch API 单测位置|`test_{api_name}.py`|PaConvert/tests/|PaConvert/tests/test_tan.py|| + +### 3.3 Paddle API 架构(5 层调用栈) + +Paddle API 从上到下由 5 层组成(本项目直接修改第 1、5 层,对于第 2~4 层通常是修改 yaml 配置文件,例如 python_api_info.yaml): + +| 层级 | 名称 | 语言 | 文件位置 | 功能说明 | 是否修改 | +|------|------|------|----------|----------|----------| +| 1 | Python 层 | Python | `*.py` | API 的 Python 接口定义 | ✅ **修改** | +| 2 | Pybind 层 | C++ | 根据`*.yaml`自动生成(`paddle/fluid/pybind/eager_op_function.cc`)| Python 与 C++的绑定层 | ✅ **修改 yaml 配置来实现修改** | +| 3 | Dygraph 层 | C++ | 根据`*.yaml`自动生成(`paddle/fluid/eager/.../dygraph_functions.cc`)| 前反向传播组合 | ❌ 通常不改 | +| 4 | C++ API 层 | C++ | 根据`*.yaml`自动生成(`paddle/phi/api/lib/api.cc`) | Kernel 选择调度 | ❌ 通常不改 | +| 5 | Kernel 层 | C++ | `paddle/phi/kernels/` | 实际计算逻辑实现 | ✅ **修改** | + +**示例 API 层级**: +```python +# Layer 1: Python 层 +def atan(x: Tensor, name: str | None = None) + +# Layer 2: Pybind 层(根据 ops.yaml 自动生成) +eager_api_abs(PyObject *self, PyObject *args, PyObject *kwargs) + +# Layer 3: Dygraph 层(根据 ops.yaml 自动生成) +paddle::Tensor atan_ad_func(const paddle::Tensor& x, ...) + +# Layer 4: C++ API 层(根据 ops.yaml 自动生成) +Tensor atan(const Tensor& x, ...) + +# Layer 5: Kernel 层 +void AtanKernel(const Context& dev_ctx, const DenseTensor& x, DenseTensor* out) +``` + +### 3.4 专业术语表 + +| 术语 | 定义 | 备注 | +|------|------|------| +| PyTorch | 深度学习框架,导入模块为`torch` | - | +| Paddle | 飞桨深度学习框架,导入模块为`paddle` | - | +| API | 应用程序接口 | 既可以是一个 Python 函数,也可以是一个 Python 类 | +| API 完整路径 | API 完整路径 | 如`torch.nn.functional.dropout`、`paddle.nn.functional.dropout`| +| API 相对引用路径 | API 完整路径在去掉框架导入模块(torch/paddle)后剩余的部分| 如`nn.functional.dropout` | +| PyTorch API | `torch.*` 系列接口 | 约 2000+个 API,是本项目的**对齐标准**| +| Paddle API | `paddle.*` 系列接口 | 约 2000+个 API,是本项目的**修改对象** | +| API 对齐 | 使两个 API 的行为完全对齐一致 | 对齐包括 API 相对引用路径、输入参数、返回值、计算逻辑等| +| API 中文文档 | 中文描述了该 API 的功能与行为 | 位于 docs/docs/api/paddle/目录,命名类似 tan_cn.rst | +| API 差异文档 | 中文描述了 Pytorch API 与 Paddle API 两者的行为差异 | 位于 docs/docs/guides/model_convert/convert_from_pytorch/api_difference/下的一级子目录,命名类似 torch.tan.md | +| compat 类型 API | 兼容性 API | 为保持后向兼容而添加的 API,能实现除 API 相对引用路径之外的完全对齐,实现之后差异分类将成为『仅 API 调用方式不一致』| + +### 3.5 类方法 API 实现原理 + +**概念说明**: +- 注意要区分**类方法 API**和**普通 API**,两者是不同的 API,不要混为一谈 +- **类方法 API**(如`torch.Tensor.abs`):`torch.Tensor`类方法 +- **普通 API**(如`torch.abs`):普通方法 + +Paddle 的 Tensor 类方法通过**patch 机制**实现,即将普通方法动态添加到`paddle.Tensor`(即`core.eager.Tensor`)类上成为类方法。 + +**实现机制**(参考`Paddle/python/paddle/base/dygraph/math_op_patch.py`): + +```python +# 从 paddle.tensor 模块获取方法定义 +import paddle.tensor + +# 将普通方法 patch 到 core.eager.Tensor 类上 +for method_name in paddle.tensor.tensor_method_func: + if hasattr(core.eager.Tensor, method_name): + continue + method_impl = getattr(paddle.tensor, method_name, None) + if method_impl: + setattr(core.eager.Tensor, method_name, method_impl) +``` + +**查找类方法实现时的注意事项**: +- ✅ **正确做法**:直接搜索对应的普通方法实现,如搜索`def abs(`或`def atan(` +- ❌ **错误做法**:不要搜索`class Tensor`或在 Tensor 类定义中查找方法 +- 原因:Tensor 类方法是通过`setattr`动态添加的,不在类定义的源码中直接体现 + +### 3.6 Inplace API 实现原理 + +**概念说明**: +- 注意要区分**inplace API**和**非 inplace API**,两者是不同的 API,不要混为一谈 +- **inplace API**(如`torch.abs_`):原地操作,直接修改输入 Tensor,其不应有 out 参数,如有 out 需删除 +- **非 inplace API**(如`torch.abs`):返回新 Tensor,不修改输入 Tensor +- Inplace API 无需测试静态图,只需测试动态图 + +**示例对比**: +```python +y = paddle.abs(x) # 非 inplace:返回新 Tensor,x 不变 +x.abs_() # inplace:原地修改 x +``` + +**自动生成机制**: +Paddle 支持自动生成 inplace API,无需在`ops.yaml`中单独配置。当定义了`inplace: (x -> out)`字段后,系统自动生成对应的 inplace 版本,复用原 API 的 Kernel 实现。 + +**配置示例**: +1. **OP 配置**(`ops.yaml`,第 10-22 行): +```yaml +- op : abs + args : (Tensor x) + output : Tensor(out) + inplace: (x -> out) # 关键字段:指定 x 和 out 可以 in-place + backward : abs_grad +``` + +2. **Python API 配置**(`python_api_info.yaml`,第 6-9 行): + - ⚠️ **仅在 C++下沉(方案 2)时需要配置** +```yaml +- op : abs_ + name : [paddle.abs_, paddle.Tensor.abs_] + args_alias : + use_default_mapping : True +``` + +# 四、标准工作流程 + +## 4.1 流程概览 + +``` +输入 API 列表 → Step1:所有 API 方案决策 → Step2:所有 API 代码修改 → Step3:所有 API 对齐验证 → Step4:所有 API 文档更新 → Step5:代码提交 → 全部完成(流程全自动推进,不用询问) +``` + +具体如下: +### Step 1:方案决策(调用 `/api-change-decider` skill) + Step 1.1: 分析差异文档 + Step 1.2: 方案决策 + +### Step 2:代码修改 +#### 方案 1:Python 装饰器(调用 `/python-decorator` skill) + Step 2.1: 差异分析与选择装饰器 + Step 2.2: 应用或开发装饰器 + Step 2.3: 添加 out 参数支持 + Step 2.4: 更新函数文档字符串 + Step 2.5: 添加测试用例 + Step 2.6: 编译并运行 +#### 方案 2:C++下沉(调用 `/cpp-sink` skill) + Step 2.1: 配置 python_api_info.yaml + Step 2.2: 迁移文档到_paddle_docs.py + Step 2.3: 替换 Python 实现 + Step 2.4: 添加测试用例 + Step 2.5: 编译并运行 + +### Step 3:对齐验证(调用 `/pytorch-alignment-validator` skill) + Step 3.1: 标记已对齐的 API + Step 3.2: 补充测试用例 + Step 3.3: 运行单元测试 + +### Step 4:文档更新(调用 `/api-docs-updater` skill) + Step 4.1: 获取代码变更信息 + Step 4.2: 更新 API 中文文档 + +### Step 5:代码提交(调用 `/create-pr` skill) + +**执行逻辑**: +1. 接收用户输入的待对齐 API 列表(如 `torch.argmax`, `torch.log2`, `torch.logsumexp`) +2. **批量处理模式**:按 Step 顺序依次执行,每个 Step 处理完所有 API 后才进入下一个 Step + - Step1:对**所有 API**进行方案决策,记录每个 API 的方案类型 + - Step2:对**所有 API**进行代码修改 + - Step3:对**所有 API**进行对齐验证 + - Step4:对**所有 API**更新文档 + - Step5:对**所有 API**进行代码提交 +3. **流程推进的豁免与放弃条件**: + - **豁免条件**: + * 每一个执行步骤均需调用相应的 skill 来执行 + * 当前仅支持方案 1/2,若决策为其他方案,则该 API 跳过,只需记录决策结果即可,不要自行处理 + - **放弃策略**(合理分配精力,最大化成功率): + * **放弃判断标准**: + - 当某个 API 在 Step2 或 Step3 经过多次尝试(建议 3 次)仍无法通过验证 + - 经分析判断短期内难以解决,继续投入时间成本过高 + * **放弃执行要求(必须严格遵守)**: + - ⚠️ 必须完整回退该 API 在 Step2 和 Step3 中的所有代码修改 + - ⚠️ 确保项目处于干净状态,不得保留任何"修改了但没改对"的中间状态 + - 在最终的对齐结果统计表中标记该 API 为"未对齐",并简要说明放弃原因 + * **整体策略原则**: + - 目标是最大化 API 列表的整体对齐成功率,而非执着于单个 API + - 优先处理更可能成功的 API,避免在困难 API 上消耗过多时间 + - 放弃是为了提高整体效率的理性决策,不是逃避问题 +4. 所有 API 都完成 5 个步骤(除被豁免或放弃外)后,任务结束 + + +## 4.2 详细步骤 + +### Step 1: 方案决策 ⚙️ + +**目标**:确定每个 API 的改动方案 + +**执行步骤**: +1. 输入:需要对齐的 PyTorch API 列表(如 `torch.atan`、`torch.asinh`) +2. 调用 `/api-change-decider` skill +3. 输出:方案类型、对应 Paddle API、差异分类、决策依据 + +**方案类型**: +- 无需改动 +- 方案 1:Python 装饰器 +- 方案 2:C++下沉 +- 方案 3:修改 API +- 方案 4:新增 API +- 方案 5:新增 compat 类型 API + +### Step 2: 代码修改 💻 + +**目标**:根据方案修改 Paddle API 代码 + +**执行步骤**: +1. 输入:方案类型、对应 Paddle API(如 `paddle.atan`、`paddle.asinh`)、差异分类、决策依据 +2. 根据方案类型,调用对应的子智能体,每个子智能体批量处理其所负责的 API: + - 方案 1 → 调用 `/python-decorator` skill + - 方案 2 → 调用 `/cpp-sink` skill + - 方案 3 → `/python-decorator`和`/cpp-sink`skill 支持新增 out 参数,其他修改无对应 skill 支持,豁免 + - 方案 4 → 无对应 skill 支持,豁免 + - 方案 5 → 无对应 skill 支持,豁免 +3. 输出:是否代码修改无误(即单测运行通过) + +**异常处理**: +- 本步骤多次调试仍异常时,主控智能体根据报错信息评估,是否需要回退到前序步骤: + - 是否 Step1 中有 API 的方案决策错误? + + +### Step 3: 对齐验证 ✅ **(金标准)** + +**目标**:验证修改后的 Paddle API 能与 PyTorch API 完全对齐 + +**执行步骤**: +1. 输入:PyTorch API 列表(如 `torch.atan`、`torch.asinh`) +2. 调用 `/pytorch-alignment-validator` skill +3. 输出:是否通过对齐验证(即单测运行通过) + +**异常处理**: +- 本步骤多次调试仍异常时,主控智能体根据报错信息评估,是否需要回退到前序步骤: + - 是否 Step1 中有 API 的方案决策错误? + - 是否 Step2 中有 API 的代码实现有误? + +### Step 4: 文档更新 📝 + +**目标**:更新 Paddle API 中文文档 + +**执行步骤**: +1. 调用 `/api-docs-updater` skill + +**异常处理**: +- 文档更新必须与 Step2 中的代码修改保持同步 +- 确保文档准确反映代码的最新行为 + +### Step 5: 代码提交 📤 + +**目标**:提交修改后的代码到 Paddle、PaConvert、Docs 仓库 + +**执行步骤**: +1. 调用 `/create-pr` skill +2. 在对应的三个仓库分别创建或更新 Pull Request +3. 包含代码修改、测试用例和文档更新 + +## 4.3 重要约束 ⚠️ + +1. **流程正向推进原则** + - 正常情况下必须遵循 Step1 → Step2 → Step3 → Step4 → Step5 的顺序 + - 每个步骤完成并验证通过后,才能进入下一步骤 + - 禁止跳过任何步骤(特别是 Step3 对齐验证步骤和 Step5 代码提交步骤) + +2. **异常回溯调整原则** + - 当 Step2(代码修改)或 Step3(对齐验证)多次尝试仍无法通过时 + - 主智能体需要根据错误信息诊断问题根源: + * 若判断为方案选择错误 → 返回 Step1 重新决策 + * 若判断为代码实现有误 → 在 Step2 调整实现方式 + - 回溯后需从该步骤重新按流程向前推进,例如回溯到 Step2,则重新 Step2 → Step3 → Step4 → Step5 + +3. **完整性保障** + - 每个 API 的 5 个步骤都必须完整执行(除被豁免或放弃外) + - 即使需要回溯调整,最终也要确保走完全流程 + - 所有步骤的产出物(代码、测试、文档、PR)必须齐全 + + +## 4.4 工作示例 + +假设待对齐 API 为 `torch.argmax`: + +``` +1. Step1: 方案决策 → 得到『方案 2:C++下沉』 +2. Step2: 代码修改 → 修改 Paddle 目录,将 paddle.argmax 下沉到 C++ +3. Step3: 对齐验证 → 修改 PaConvert 目录,编写 Pytorch 单元测试,对比测试,验证对齐 +4. Step4: 文档更新 → 修改 docs 目录,更新 paddle.argmax 文档 +5. Step5: 代码提交 → 调用 /create-pr skill,在 Paddle、PaConvert、Docs 三个仓库分别创建或更新 PR +``` + + +# 五、代码修改规范 + +## 5.1 代码注释规范 +- ✅ 改动位置如果方便注释,可以注释`# Edit by AI Agent`,但只能注释 1 次 + +## 5.2 代码质量规范 +- ✅ 保持代码风格与项目一致 +- ✅ 不破坏现有功能 +- ✅ 确保向后兼容性 + +## 5.3 测试规范 +- ✅ 修改后必须通过原有测试 +- ✅ 必须添加新的单元测试 +- ✅ 必须通过 PyTorch 对齐验证 + +## 5.4 文档规范 + +- ✅ 同步更新中文文档 +- ✅ 文档格式符合 Paddle 规范 +- ✅ 准确描述 API 功能和参数 + +# 六、注意事项 + +1. 复盘记忆中的历史易错点,避免重复犯错 +2. 严格按标准工作流程执行,杜绝自行臆断和跳过步骤 diff --git a/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/api-docs-updater/SKILL.md b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/api-docs-updater/SKILL.md new file mode 100644 index 00000000000..0a182208901 --- /dev/null +++ b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/api-docs-updater/SKILL.md @@ -0,0 +1,286 @@ +--- +name: api-docs-updater +description: 仅用于《Paddle API 对齐 PyTorch 项目》,负责 Step4,在 API 代码修改完成后,同步更新中文 API 文档,确保文档准确反映 API 的最新行为 +allowed-tools: Read Grep Glob‌ Write‌ Edit +context: fork +background: true +verbose: true +disable-model-invocation: false +--- + +# 一、工作目录说明 + +涉及以下文档的修改: + +| 文档类型 | 文件命名 | 改动点 | +|----------|----------|----------| +| API 概览文档 | `docs/api/paddle/Overview_cn.rst` | API 索引目录,新增 API 时需要更新 | +| API 中文文档 | `{api_name}_cn.rst` | 针对 API 功能改动点,修改文档 | + +# 二、工作流程概述 + +## 基本流程 + +1. **查找 API 英文文档** - 在两个位置查找: + - 直接存储在 API 实现代码中的 `__doc__` 文档字符串 + - 集中存储在 `Paddle/python/paddle/_paddle_docs.py` 文件中 + +2. **对比英文和中文文档** - 识别不一致之处 + +3. **根据代码修改方案选择对应模式** - 见第三章 + +4. **按照格式规范更新中文文档** - 见第四章 + +# 三、常见修改模式 + +根据代码修改方案的不同,文档需要采用不同的修改模式。本章覆盖所有常见场景,并提供完整的实战示例。 + +## 模式 1:参数别名(通用装饰器) + +**适用场景**: +- 使用 `@param_one_alias` / `@param_two_alias` / `@ParamAliasDecorator` 装饰器 +- 仅参数名不同,参数顺序相同 +- 常见别名映射:x↔input,y↔other,axis↔dim,keepdim↔keepdims + +**修改内容**: +1. 函数签名:无需修改(除非新增 out 参数) +2. 参数部分:每个参数末尾添加别名说明 +3. 如果新增 out 参数,添加关键字参数小节 + +**英文文档要求**(代码 docstring): +- 格式:`Alias: ` + `` ``别名`` `` +- 多个别名:`Alias: ` `` ``input`` ` or ` `` ``other`` `` +- 位置:参数描述末尾 + +```python +Args: + x (Tensor): Input tensor. Alias: ``input``. + axis (int, optional): Axis. Alias: ``dim``. +``` + +**中文文档要求**(`.rst` 文件): +- 格式:`别名 ` + `` ``别名`` ``(与英文格式保持一致) +- 多个别名:`别名 ` `` ``input`` ` ` 或 ` `` ``other`` `` +- 位置:参数描述末尾,句号前 + +**完整实战示例 - paddle.atan2(参数别名+out 参数)** + +```rst +.. py:function:: paddle.atan2(x, y, name=None, *, out=None) + +参数 +::::::::: + - **x** (Tensor) - 输入的 Tensor。别名 ``input``。 + - **y** (Tensor) - 输入的 Tensor。别名 ``other``。 + - **name** (str,可选) - 操作的名称。 + +关键字参数 +::::::::: + - **out** (Tensor,可选) - 输出 Tensor,若不为 ``None``,计算结果将保存在该 Tensor 中,默认值为 ``None``。 + +返回 +::::::::: + + Tensor:计算结果 Tensor。 +``` + +--- + +## 模式 2:Overload 重载(专用装饰器) + +**适用场景**: +- 使用专用装饰器实现参数顺序转换、参数用法转换、可变参数等 +- API 支持两种完全不同的签名(Paddle 风格和 PyTorch 风格) +- 典型场景:参数顺序不同、可变参数、参数用法转换 + +**修改内容**: +1. 函数签名:保留实现签名(Paddle 风格) +2. 文档正文:明确说明有两种调用方式 +3. 参数部分:仍以 Paddle 风格为准 +4. 别名说明:对应两种签名的参数 + +**文档要求**: +- 在正文开头阐述两种签名(Paddle 在前,PyTorch 在后) +- Args/Returns 部分仍以 Paddle 风格签名为准 +- 对有别名的参数添加别名说明 + +**完整实战示例 - paddle.broadcast_tensors** + +```rst +.. py:function:: paddle.broadcast_tensors(input, name=None) + +此 API 有两种调用方式: + +1. ``paddle.broadcast_tensors(input, name=None)`` (Paddle 风格): + 接收一个 Tensor 序列作为参数 + +2. ``paddle.broadcast_tensors(*tensors)`` (PyTorch 风格): + 接收可变个 Tensor 参数 + +参数 +::::::::: + - **input** (Sequence[Tensor]) - 待广播的 Tensor 序列。别名 ``*tensors``。 + - **name** (str,可选) - 操作名称。 + +返回 +::::::::: + list[Tensor]:广播后的 Tensor 列表。 +``` + +--- + +## 模式 3:out 参数支持 + +**适用场景**: +- 新增 out 参数支持(keyword-only 或位置参数) +- out 为单个 Tensor 或元组 + +**修改内容**: +1. 函数签名:添加 `, *, out=None`(keyword-only)或 `, out=None`(位置参数) +2. 新增部分:添加关键字参数或参数小节描述 out + +**英文文档要求**(代码 docstring): + +keyword-only 形式: +```python +Keyword Args: + out (Tensor|None, optional): The output tensor. Default: None. +``` + +位置参数形式: +```python +Args: + ... + out (Tensor|None, optional): The output Tensor. Default: None. +``` + +**中文文档要求**(`.rst` 文件): + +keyword-only 形式: +```rst +.. py:function:: paddle.api(x, name=None, *, out=None) + +参数 +::::: + - **x** (Tensor) - 输入 Tensor。 + - **name** (str,可选) - 操作名称。 + +关键字参数 +:::::::::: + - **out** (Tensor,可选) - 输出 Tensor,若不为 ``None``,计算结果将保存在该 Tensor 中,默认值为 ``None``。 +``` + +位置参数形式: +```rst +.. py:function:: paddle.api(x, out=None, name=None) + +参数 +::::: + - **x** (Tensor) - 输入 Tensor。 + - **out** (Tensor,可选) - 输出 Tensor,若不为 ``None``,计算结果将保存在该 Tensor 中,默认值为 ``None``。 + - **name** (str,可选) - 操作名称。 +``` + +--- + +## 模式 4:返回值为元组的 out 参数 + +**适用场景**: +- API 返回多个值(如 `frexp` 返回 mantissa 和 exponent) +- out 参数也是元组类型 + +**修改内容**: +1. 函数签名:添加 `, *, out=None` +2. 关键字参数:out 类型标注为 `tuple[Tensor, Tensor]` + +**完整实战示例 - paddle.frexp** + +```rst +.. py:function:: paddle.frexp(x, name=None, *, out=None) + +用于把一个浮点数分解为尾数和指数的函数,返回一个尾数 Tensor 和一个指数 Tensor。 + +参数 +::::::::: + - **x** (Tensor) - 输入是一个多维的 Tensor,数据类型为 float32、float64。别名 ``input``。 + - **name** (str,可选) - 具体用法请参见 :ref:`api_guide_Name`,默认值为 None。 + +关键字参数 +:::::::::: + - **out** (tuple[Tensor, Tensor],可选) - 输出 Tensor 元组,若不为 ``None``,计算结果将保存在该 Tensor 元组中,默认值为 ``None``。 + +返回 +::::::::: +mantissa(Tensor):分解后的尾数,形状和原输入一致。 +exponent(Tensor):分解后的指数,形状和原输入一致。 +``` + +--- + +## 模式 5:Inplace API 的别名说明 + +**适用场景**: +- Inplace API(函数名以 `_` 结尾,如 `paddle.abs_`、`paddle.floor_divide_`) +- 通过 Python 装饰器添加参数别名支持 +- 仅需补充别名说明,无需修改功能描述 + +**修改内容**: +1. 函数签名:原样保留,不添加任何修改 +2. 新增部分:在 Inplace 说明之后添加 `.. note::` 块描述别名 + +**中文文档要求**(`.rst` 文件): + +```rst +.. py:function:: paddle.floor_divide_(x, y, name=None) + +Inplace 版本的 :ref:`cn_api_paddle_floor_divide` API,对输入 `x` 采用 Inplace 策略。 + +更多关于 inplace 操作的介绍请参考 `3.1.3 原位(Inplace)操作和非原位操作的区别`_ 了解详情。 + +.. _3.1.3 原位(Inplace)操作和非原位操作的区别: https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/guides/beginner/tensor_cn.html#id3 + +.. note:: + + 别名支持:参数名 ``input`` 可替代 ``x``,参数名 ``other`` 可替代 ``y``,如 ``floor_divide_(input=tensor_x, other=tensor_y)`` 等价于 ``floor_divide_(x=tensor_x, y=tensor_y)``。 +``` + + +# 四、格式规范与注意事项 + +## 格式规范 + +| 项目 | 规范 | 示例 | +|------|------|------| +| **别名说明位置** | 参数描述末尾,句号前 | `- **x** (Tensor) - 输入的 Tensor。别名 ` ``input``` | +| **别名格式** | 2 个反单引号+别名+2 个反单引号 | `` ``input`` `` 或 `` ``dim`` `` | +| **多个别名** | 用"或"连接 | `别名 ` ``input`` ` 或 ` ``other``` | +| **参数类型** | 可选参数用管道符 | `(float\|None,可选)` 或 `(str\|None,可选)` | +| **关键字参数标题** | "关键字参数"后跟冒号行 | `关键字参数` + 换行 + `:::::::::` | +| **关键字参数缩进** | 4 个空格对齐 | ` ` ` ` `- **out** (Tensor,可选) - ...` | +| **out 参数模板** | 统一说明 | `输出 Tensor,若不为 ``None``,计算结果将保存在该 Tensor 中,默认值为 ``None``。` | + +**rst 格式**: +- 代码块使用 `::` +- 列表项使用 `-` 开头 +- 参数类型用 `()` 包裹 +- 别名用反引号包裹:`` ``input`` `` + +## 注意事项 + +1. **Tensor 类方法**(如 `paddle.Tensor.abs`) + - 没有独立文档,无需处理 + - 勿与普通方法(如 `paddle.abs`)混淆 + +2. **Inplace 方法**(如 `paddle.abs_`) + - 仅更新代码签名,不需修改文档 + - 参数别名支持与原方法一致 + +3. **文档内容保持** + - 保留原有的文档风格和格式 + - 不要大面积删除文档原内容 + - 示例代码采用 COPY-FROM: 格式,不要修改 + +4. **英文文档与中文文档必须对应** + - 别名格式完全相同 + - Overload 说明内容对应 + - out 参数描述对齐 diff --git a/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/cpp-sink/SKILL.md b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/cpp-sink/SKILL.md new file mode 100644 index 00000000000..524d6cf16ee --- /dev/null +++ b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/cpp-sink/SKILL.md @@ -0,0 +1,727 @@ +--- +name: cpp-sink +description: 仅用于《Paddle API 对齐 PyTorch 项目》,负责 Step2:API 代码修改,实施 C++下沉的代码开发。通过将 Python API 下沉至 C++层,可以减少 Python 装饰器带来的性能开销,提升 API 调度效率。 +allowed-tools: Read Grep‌ Glob‌ Edit Bash +disable-model-invocation: false +--- + +# 一、标准工作流程 + +根据 API 的复杂度,将下沉场景分为三类: + +## 场景一: 仅参数名不一致(最简单) + +**适用条件(需全部满足):** +- Paddle Python API 与`ops.yaml`中参数**类型或数量一致** +- Paddle API 和 PyTorch API 仅参数名称不一致 +- Python 端调用`_C_ops`前**无任何前处理逻辑** + +**典型示例:** `paddle.log2` + +请严格按以下步骤依次执行,不要自行修改或跳过步骤: + +### Step 1:配置 `python_api_info.yaml` + +添加参数别名映射(按 op name 的字典序添加): + +```yaml +- op : log2 + name : [paddle.log2, paddle.Tensor.log2] + args_alias : + use_default_mapping : True # 启用默认映射:x->input +``` + +**说明:** +- `name`:指定对应的 Python API 名称(函数 API 和 Tensor 方法) +- `use_default_mapping : True`:自动配置如下字段映射: +```yaml +- op : op_name + name : [paddle.op_name, paddle.Tensor.op_name] + args_alias : + x: [input] + y: [other] + axis: [dim] + keepdims: [keepdim] +``` +- 不在上述字段映射范围里,允许配置自定义映射,格式: +```yaml +- op : op_name + name : [paddle.op_name, paddle.Tensor.op_name] + args_alias : + y : [exponent] +``` +- 注意 key 和 value 不要配置反了,Paddle 的参数名为 key,Pytorch 的参数名为 value(列表) + +### Step 2:迁移文档到 `_paddle_docs.py` + +注意要更新函数文档字符,在文档的 Args 部分为有别名的参数添加 Alias Support 说明,如下: +> 注:Alias 说明应放在该参数描述的末尾,格式为: Alias: ``alias_name`` ,多个 Alias 描述为: Alias: ``alias_name1`` or ``alias_name2`` + +```python +add_doc_and_signature( + "log2", + r""" + Calculates the log to the base 2 of the given input tensor, element-wise. + + .. math:: + + Out = \log_2x + + Args: + x (Tensor): Input tensor must be one of the following types: int32, int64, float16, bfloat16, float32, float64, complex64, complex128. Alias: ``input``. + name (str|None, optional): Name for the operation (optional, default is None). For more information, please refer to :ref:`api_guide_Name`. + out (Tensor, optional): The output Tensor. If set, the result will be stored in this Tensor. Default: None. + + Keyword args: + out(Tensor, optional): The output tensor. + + Returns: + Tensor: The log to the base 2 of the input Tensor computed element-wise. + + Examples: + + .. code-block:: pycon + + >>> import paddle + + >>> # example 1: x is a float + >>> x_i = paddle.to_tensor([[1.0], [2.0]]) + >>> res = paddle.log2(x_i) + >>> res + Tensor(shape=[2, 1], dtype=float32, place=Place(cpu), stop_gradient=True, + [[0.], + [1.]]) + + >>> # example 2: x is float32 + >>> x_i = paddle.full(shape=[1], fill_value=2, dtype='float32') + >>> paddle.to_tensor(x_i) + >>> res = paddle.log2(x_i) + >>> res + Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True, + [1.]) + + >>> # example 3: x is float64 + >>> x_i = paddle.full(shape=[1], fill_value=2, dtype='float64') + >>> paddle.to_tensor(x_i) + >>> res = paddle.log2(x_i) + >>> res + Tensor(shape=[1], dtype=float64, place=Place(cpu), stop_gradient=True, + [1.]) +""", + """ +def log2( + x: Tensor, + name: str | None = None, + *, + out: Tensor | None = None, +) -> Tensor +""", +) +``` + +如果支持了 out 参数,必须在 API 文档中描述 out 参数,out 为 keyword-only 参数(*后面)时注意增加`Keyword Args:`,并在此部分描述。out 为位置参数时直接在 Args 部分描述。如下: + +```python +# out 为 keyword-only 参数 +""" +Args: + ... + +Keyword Args: + out (Tensor|optional): The output tensor. Default: None. + +Returns: + ... +""" + +# out 为位置参数 +""" +Args: + out (Tensor, optional): The output Tensor. Default: None. + +Returns: + ... +""" +``` + +**注意事项**: +- Tensor 类方法(如 paddle.Tensor.abs)没有文档,无需处理,请勿与普通方法(如 paddle.abs)混淆 +- Inplace 方法(如 paddle.abs_等下划线 API),只需要更新 API 签名,不需要修改文档 +- 注意文档格式规范,需与 `_paddle_docs.py` 现有文档格式保持一致 + +### Step 3:替换 Python 实现 + +找到 API 的 Python 实现位置(如 `python/paddle/tensor/math.py`,注意不要误检索到稀疏 API 位置): + +1. 在文件上方合适位置**导入**C++实现 +2. 直接**删除**原有的 Python 函数实现 + +```python +# 在文件最上方合适位置导入(不要添加注释) +from paddle._C_ops import log2 # noqa: F401 + +# 以下内容全部删除(不要添加注释) +# def log2(x: Tensor, name: str | None = None) -> Tensor: +# ... +``` + +### Step 4:添加测试用例 + +不要新建任何测试文件,直接在 `test/legacy_test/test_api_compatibility[1-9]\.py(数字最大的)` 中添加测试。严格按以下模板来编写: + +**测试模板**: +```python +class TestAPI(unittest.TestCase): + def setUp(self): + # If not use random seed, remove setUp + np.random.seed(2025) + self.np_x = np.random.rand(...).astype(...) + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor(self.np_x) + + # 1. Paddle Positional arguments + out1 = paddle.(x, ...) + + # 2. Paddle keyword arguments + out2 = paddle.(x=x, ...) + + # 3. Pytorch Positional arguments (only if order different with paddle args) + out3 = paddle.(x, ...) + + # 4. PyTorch keyword arguments (alias) + out4 = paddle.(input=x, dim=...) + + # 5. Mixed arguments + out5 = paddle.(x, axis=...) + + # 6. out parameter test (only if supported) + out6 = paddle.empty_like(x) + out7 = paddle.(x, ..., out=out6) + + # 7. Tensor method - args (only if supported) + out8 = x.(...) + + # 8. Tensor method - kwargs (only if supported) + out9 = x.(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.(x, ...) + out2 = paddle.(x=x, ...) + out3 = paddle.(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 位置参数(如果 Pytorch 与 Paddle 参数顺序不同) +4. ✅ PyTorch 关键字参数(使用参数别名) +5. ✅ 混合参数(如果参数量>=2,位置+关键字) +6. ✅ out 参数(如果 API 支持,inplace 无需测) +7. ✅ 类方法 Pytorch 位置参数(如果有类方法) +8. ✅ 类方法 Pytorch 关键字参数(如果有类方法) + +静态图模式:(inplace 无需测) +1. ✅ Paddle 位置参数(全部位置参数) +2. ✅ Paddle 关键字参数(全部关键字参数) +3. ✅ PyTorch 位置参数(如果 Pytorch 与 Paddle 参数顺序不同) +4. ✅ PyTorch 关键字参数(使用参数别名) +5. ✅ 类方法 Pytorch 位置参数(如果有类方法) +6. ✅ 类方法 Pytorch 关键字参数(如果有类方法) + +注意: +1. 有些测试项是可选的,需要自行判断是否需要添加。 +2. 添加测试项需要遵循上述顺序,不要打乱。 +3. 输出结果序号需要保持连贯,每一个输出结果均需要检验,尽可能循环检验减少行数。 +3. 比对测试项,对于内容相同的测试项,不要重复添加。 + +完整测试示例,请参考 `Paddle/test/legacy_test/test_api_compatibility[1-9]\.py` 中已有的测试类结构。 + +### Step 5:编译并运行 + +单测编写完成后,按以下命令验证执行(不可修改): + +1. **重新编译项目**: + ```bash + cd /workspace/Paddle/build + cmake .. + make -j$(nproc) + ``` + +2. **运行单测文件**: + ```bash + python <所修改的单测文件名> + ``` + +3. **问题排查**:根据报错信息调整代码或测试用例,确保所有测试用例通过。注意每次修改 Paddle 源码后,必须重新编译方可生效。 + +编译注意事项: +- 编译完成后不需要重新安装,无需执行 setup/install 等任何安装操作,直接可生效 +- 编译不要删除 build 目录,否则会导致增量编译失效,编译时间极长 + +## 场景二: 具有前处理逻辑(中等复杂度) + +**适用条件(全部满足):** +- Paddle Python API 与`ops.yaml`中参数**类型或数量一致** +- Paddle API 和 PyTorch API 仅参数名称不一致 +- Python 端在调用`_C_ops`前**有其他前处理逻辑** + +**典型示例:** `paddle.logsumexp` + +请严格按以下步骤依次执行,不要自行修改或跳过步骤: + +### Step 1:配置 `python_api_info.yaml` + +```yaml +- op : logsumexp + name : [paddle.logsumexp, paddle.Tensor.logsumexp] + args_alias: + use_default_mapping : True # x->input, axis->dim + pre_process: + func : LogsumexpPreProcess(x, axis, reduce_all) # 前处理函数 +``` + +**关键点:** +- `name`:指定对应的 Python API 名称 +- `args_alias.use_default_mapping`:启用默认参数映射 +- `pre_process.func`:指定前处理函数名称及其参数列表 + + +### Step 2:实现前处理函数 + +将 Python 端在调用`_C_ops`前的**其他前处理逻辑**,修改为 C++的实现。尽可能参考 `arg_pre_process.cc` 中已有的预处理函数来实现,在风格和逻辑上保持尽可能一致。 + +在 `paddle/fluid/pybind/arg_pre_process.h` 声明: + +```cpp +namespace paddle { +namespace pybind { + +// 动态图版本 +void LogsumexpPreProcess(Tensor *x, std::vector *axis, bool *reduce_all); + +// 静态图版本 +void LogsumexpPreProcess(pir::Value *x, std::vector *axis, bool *reduce_all); + +} // namespace pybind +} // namespace paddle +``` + +在 `paddle/fluid/pybind/arg_pre_process.cc` 实现: + +```cpp +#include "paddle/fluid/pybind/arg_pre_process.h" +#include "paddle/fluid/eager/utils.h" +#include "paddle/fluid/pir/utils/general_functions.h" + +namespace paddle { +namespace pybind { + +// 动态图实现 +void LogsumexpPreProcess(Tensor *x, std::vector *axis, bool *reduce_all) { + // Python 原逻辑: + // if axis == [] or len(axis) == len(x.shape): + // reduce_all = True + // else: + // reduce_all = False + + if (axis->empty() || axis->size() == x->dims().size()) { + *reduce_all = true; + } else { + *reduce_all = false; + } +} + +// 静态图实现 +void LogsumexpPreProcess(pir::Value *x, std::vector *axis, bool *reduce_all) { + std::vector x_shape = pir::GetShapeFromValue(*x); + if (axis->empty() || axis->size() == x_shape.size()) { + *reduce_all = true; + } else { + *reduce_all = false; + } +} + +} // namespace pybind +} // namespace paddle +``` + +**注意事项:** +- 必须同时实现动态图和静态图版本 +- 函数通过指针修改参数值 +- 直接翻译 Python 前处理逻辑到 C++ + +### Step 3~6:参考 Step 1 的 Step 2~5 + +## 场景三: 复杂参数映射(最复杂) + +**适用条件(全部满足):** +- Paddle Python API 与`ops.yaml`中参数**类型或数量不一致** +- 需要复杂的自定义参数解析逻辑,来匹配 Paddle Python API 与`ops.yaml`的参数差异 + +**典型示例:** `paddle.argmax`/`paddle.argmin` + +请严格按以下步骤依次执行,不要自行修改或跳过步骤: + +### Step 1:配置 `python_api_info.yaml` + +```yaml +- op : argmax + name : [paddle.argmax, paddle.Tensor.argmax] + args_mapper : + func : ArgMaxMinMapper # 自定义参数映射函数 +``` + +**关键点:** +- `name`:指定对应的 Python API 名称 +- `args_mapper.func`:指定自定义 Mapper 函数名称 +- 当使用`args_mapper`时,**不会生成默认参数解析代码** +- Mapper 需要手动解析所有参数,包括参数别名支持 + +### Step 2:实现自定义 Mapper + +将 Python API 与`ops.yaml`中参数**类型或数量不一致**的差异,通过 C++自定义 Mapper 来进行匹配与转换。尽可能参考 `args_mapper.cc` 中已有的 Mapper 来实现,在风格和逻辑上保持尽可能一致。 + +在 `paddle/fluid/pybind/args_mapper.h` 声明: + +```cpp +namespace paddle { +namespace pybind { + +// 动态图版本 +void ArgMaxMinMapper(PyObject* args, + PyObject* kwargs, + Tensor* x, + paddle::experimental::Scalar* axis, + bool* keepdims, + bool* flatten, + phi::DataType* dtype); + +// 静态图版本 +void ArgMaxMinMapper(PyObject* args, + PyObject* kwargs, + pir::Value* x, + pir::Value* axis, + bool* keepdims, + bool* flatten, + phi::DataType* dtype); + +} // namespace pybind +} // namespace paddle +``` + +在 `paddle/fluid/pybind/args_mapper.cc` 实现: + +```cpp +#include "paddle/fluid/pybind/args_mapper.h" +#include "paddle/fluid/eager/utils.h" +#include "paddle/fluid/pir/dialect/operator/ir/pd_api.h" +#include "paddle/fluid/pybind/eager_utils.h" +#include "paddle/fluid/pybind/op_function_common.h" + +namespace paddle { +namespace pybind { + +// 动态图实现 +void ArgMaxMinMapper(PyObject* args, + PyObject* kwargs, + Tensor* x, + paddle::experimental::Scalar* axis, + bool* keepdims, + bool* flatten, + phi::DataType* dtype) { + // Python 参数:(x, axis, keepdim, dtype, name) + // C++ _C_ops 参数:(x, axis, keepdim, flatten, dtype) + + int nargs = args ? static_cast(PyTuple_Size(args)) : 0; + int remaining_kwargs = kwargs ? static_cast(PyDict_Size(kwargs)) : 0; + const int max_args = 4; // 不包括 name 参数 + CheckParamsCount(nargs, remaining_kwargs, max_args); + + // 1. 解析 x 参数(支持多个别名) + *x = GetTensorFromArgsOrKWArgs("argmax", + "x", + args, + 0, + kwargs, + {"x", "input"}, // 别名列表 + nargs, + &remaining_kwargs, + false); + + // 2. 解析 axis 参数并处理特殊逻辑 + PyObject* axis_obj = GetItemFromArgsOrKWArgs( + args, 1, kwargs, {"axis", "dim"}, nargs, &remaining_kwargs); + + // Python 逻辑: + // flatten = False + // if axis is None: + // flatten = True + // axis = 0 + *flatten = false; + if (axis_obj == Py_None || axis_obj == nullptr) { + *flatten = true; + *axis = 0; + } else { + *axis = CastPyArg2Scalar(axis_obj, "argmax", 1); + } + + // 3. 解析 keepdims 参数(支持多个别名) + PyObject* keepdims_obj = GetItemFromArgsOrKWArgs( + args, 2, kwargs, {"keepdim", "keepdims"}, nargs, &remaining_kwargs); + *keepdims = CastPyArg2Boolean(keepdims_obj, "argmax", 2, false); + + // 4. 解析 dtype 参数并验证 + PyObject* dtype_obj = GetItemFromArgsOrKWArgs( + args, 3, kwargs, {"dtype"}, nargs, &remaining_kwargs); + + PADDLE_ENFORCE_NE( + dtype_obj, + Py_None, + phi::errors::InvalidArgument("the value of 'dtype' in argmax and argmin " + "could not be None, but received None")); + *dtype = CastPyArg2DataType(dtype_obj, "argmax", 3, phi::DataType::INT64); + + // 5. 检查剩余参数有效性 + CheckRemainingParamsValidity(args, kwargs, remaining_kwargs, nargs); +} + +// 静态图实现 +void ArgMaxMinMapper(PyObject* args, + PyObject* kwargs, + pir::Value* x, + pir::Value* axis, + bool* keepdims, + bool* flatten, + phi::DataType* dtype) { + int nargs = args ? static_cast(PyTuple_Size(args)) : 0; + int remaining_kwargs = kwargs ? static_cast(PyDict_Size(kwargs)) : 0; + const int max_args = 4; + CheckParamsCount(nargs, remaining_kwargs, max_args); + + // 1. 解析 Value 类型的 x + PyObject* x_obj = GetItemFromArgsOrKWArgs( + args, 0, kwargs, {"x", "input"}, nargs, &remaining_kwargs); + *x = CastPyArg2Value(x_obj, "argmax", 0, false); + + // 2. 解析 axis 并转换为 Value 类型 + PyObject* axis_obj = GetItemFromArgsOrKWArgs( + args, 1, kwargs, {"axis", "dim"}, nargs, &remaining_kwargs); + + *flatten = false; + if (axis_obj == Py_None || axis_obj == nullptr) { + *flatten = true; + // 静态图中 axis 需要是 Value 类型 + *axis = paddle::dialect::full( + std::vector{1}, 0, phi::DataType::INT64, phi::CPUPlace()); + } else if (PyObject_CheckIRValue(axis_obj)) { + *axis = CastPyArg2Value(axis_obj, "argmax", 1); + } else { + int64_t axis_tmp = CastPyArg2Long(axis_obj, "argmax", 1); + *axis = paddle::dialect::full(std::vector{1}, + axis_tmp, + phi::DataType::INT64, + phi::CPUPlace()); + } + + // 3-5. 解析其他参数(同动态图) + PyObject* keepdims_obj = GetItemFromArgsOrKWArgs( + args, 2, kwargs, {"keepdim", "keepdims"}, nargs, &remaining_kwargs); + *keepdims = CastPyArg2Boolean(keepdims_obj, "argmax", 2, false); + + PyObject* dtype_obj = GetItemFromArgsOrKWArgs( + args, 3, kwargs, {"dtype"}, nargs, &remaining_kwargs); + + PADDLE_ENFORCE_NE( + dtype_obj, + Py_None, + phi::errors::InvalidArgument("the value of 'dtype' in argmax and argmin " + "could not be None, but received None")); + *dtype = CastPyArg2DataType(dtype_obj, "argmax", 3, phi::DataType::INT64); + + CheckRemainingParamsValidity(args, kwargs, remaining_kwargs, nargs); +} + +} // namespace pybind +} // namespace paddle +``` + +**关键技术点:** + +1. **参数解析工具函数:** + - `GetTensorFromArgsOrKWArgs`:解析 Tensor 参数,支持多个别名 + - `GetItemFromArgsOrKWArgs`:获取通用 Python 对象 + - `CastPyArg2Scalar`:转换为 Scalar 类型 + - `CastPyArg2Boolean`:转换为 bool 类型 + - `CastPyArg2DataType`:转换为 DataType 枚举 + - `CheckParamsCount`:检查参数数量 + - `CheckRemainingParamsValidity`:检查是否有未处理参数 + +2. **静态图特殊处理:** + - 使用`pir::Value`代替`Tensor` + - 常量值需转换为`Value`类型:使用`paddle::dialect::full` + +3. **参数别名支持:** + - 通过传入别名列表`{"x", "input"}`同时支持 Paddle 和 Torch 风格 + +### Step 3: 迁移文档到 `_paddle_docs.py` + +除了完成场景一的文档迁移操作之外,场景三文档迁移还需要额外注意: + +如果使用了自定义 Mapper,则 API 有可能支持了签名重载,需要分别描述两种签名,如下: +> 注:只需在文档正文中阐述两种签名(Paddle 在前,Pytorch 在后),文档其他位置如 Args/Returns 仍以 Paddle 风格签名为准 + +```python +""" +This API has two signatures: + +1. ``paddle.sum(x, axis=None, dtype=None, keepdim=False, name=None, *, out=None)`` (Paddle-style) +2. ``paddle.sum(input, dim=None, keepdim=False, dtype=None, *, out=None)`` (PyTorch-style) + +Args: + ... + +Returns: + ... +""" +``` + +### Step 4~6:参考场景一的 Step 3~5 + +## 不同场景对比 + +| 项目 | Step 1: 仅参数名不同 | Step 2: 有前处理逻辑 | Step 3: 复杂参数映射 | +|------|---------------------|---------------------|---------------------| +| **YAML 配置** | `args_alias` + `use_default_mapping` | `args_alias` + `pre_process` | `args_mapper` | +| **C++实现** | 无需额外 C++代码 | `arg_pre_process.h/cc` | `args_mapper.h/cc` | +| **实现难度** | ⭐ 简单 | ⭐⭐ 中等 | ⭐⭐⭐ 复杂 | +| **参数解析** | 自动生成 | 自动生成 + 前处理 | 完全手动 | +| **适用情况** | 仅参数名不一致 | 仅参数名不一致+前处理逻辑 | 参数类型/数量不一致 | +| **示例 API** | log2 | logsumexp | argmax, argmin | + + +# 二、异常处理 + +## 2.1 处理流程 +当遇到错误时,建议按照以下步骤处理,确保代码能运行通过: +1. **定位错误**:仔细阅读错误信息,确定错误类型和位置 +2. **分析原因**:根据错误信息分析具体问题,例如参数错误、类型不匹配 +3. **修改代码**:根据错误信息与分析结果,调整代码 +4. **验证修复**:重新运行测试确认问题解决 + +## 2.2 常见错误及解决方案 + +### 静态图兼容性问题 +**错误现象**: +```python +TypeError: (InvalidType) all(): argument (position 1) must be Value, but got Variable +``` + +**问题原因**: +API 下沉后使用了新的 Value 类型系统,但测试代码仍在使用旧的 Variable 类型 + +**解决方法**: +1. 删除过时的测试文件: + ```bash + rm -rf test/deprecated/test_xxx.py + ``` +2. 删除`CMakeLists.txt`中涉及的单测配置: + ```cmake + # 删除相关单测配置 + # set_tests_properties(test_lbfgs_deprecated PROPERTIES TIMEOUT 100) + ``` +3. 检查并更新所有引用这些测试的代码 + +### 参数解析错误 +**错误现象**: +```python +TypeError: argmax() got an unexpected keyword argument 'invalid_param' +``` + +**解决方法**: +1. 检查参数名称拼写 +2. 确认是否支持该参数 + +### 类型转换错误 +**错误现象**: +```python +TypeError: expected Tensor as argument, got numpy.ndarray +``` + +**解决方法**: +1. 确保输入数据是 Tensor 类型: + ```python + tensor_input = paddle.to_tensor(numpy_input) + ``` +2. 检查数据类型是否匹配 + + +# 三、技术背景知识 + +## 3.1 工具函数速查 + +```cpp +// 获取 Tensor 参数(支持别名) +Tensor GetTensorFromArgsOrKWArgs( + const std::string& op_name, + const std::string& param_name, + PyObject* args, int arg_idx, + PyObject* kwargs, const std::vector& aliases, + int nargs, int* remaining_kwargs, bool required); + +// 获取通用 Python 对象 +PyObject* GetItemFromArgsOrKWArgs( + PyObject* args, int arg_idx, + PyObject* kwargs, const std::vector& aliases, + int nargs, int* remaining_kwargs); + +// 类型转换 +paddle::experimental::Scalar CastPyArg2Scalar(PyObject* obj, const std::string& op_name, int arg_idx); +bool CastPyArg2Boolean(PyObject* obj, const std::string& op_name, int arg_idx, bool default_value); +phi::DataType CastPyArg2DataType(PyObject* obj, const std::string& op_name, int arg_idx, phi::DataType default_value); +std::vector CastPyArg2Ints(PyObject* obj, const std::string& op_name, int arg_idx); + +// 检查函数 +void CheckParamsCount(int nargs, int remaining_kwargs, int max_args); +void CheckRemainingParamsValidity(PyObject* args, PyObject* kwargs, int remaining_kwargs, int nargs); +``` + +# 四、注意事项 + +1. 若 Python API 参数顺序与`_C_ops` API 不同,属于特殊情况,Cpp 下沉方案无法实现,需要使用 Python 装饰器方案。 +2. 代码中不允许提交中文,代码注释采用英文 +3. 若 API 需支持`out`参数,必须修改`add_doc_and_signature`中的字符串,增加 out 参数 +4. 不要修改`generated_tensor_methods_patch.py`,该文件是自动生成的,修改没有意义,如无法对齐可考虑放弃 C++下沉方案而不是改动该文件 +5. 示例代码若涉及多种数据类型,可能触发类型检查误报,添加注释忽略: +```python + .. code-block:: pycon + >>> # type: ignore + >>> import paddle + >>> x = paddle.to_tensor([1.0, 2.0]) +``` diff --git a/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/create-pr/SKILL.md b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/create-pr/SKILL.md new file mode 100644 index 00000000000..a76b6f4ab7e --- /dev/null +++ b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/create-pr/SKILL.md @@ -0,0 +1,145 @@ +--- +name: create-pr +description: 仅用于《Paddle API 对齐 PyTorch 项目》,负责 Step5:代码提交,负责在前序步骤都完成后,分别对 Paddle、PaConvert、Docs 三个仓库创建或更新 Pull Request +allowed-tools: Bash(git *) +disable-model-invocation: false +--- + +# 一、标准工作流程 + +该 skill 负责将前序步骤的成果提交到三个代码库: +1. **PaddlePaddle/Paddle** - API 代码实现(base: develop) +2. **PaddlePaddle/PaConvert** - PyTorch 兼容性测试(base: master) +3. **PaddlePaddle/docs** - 中文 API 文档(base: develop) + +## Step 1:检查三个仓库的改动状态 + +验证三个仓库都已有需要提交的改动: + +```bash +cd /path/to/Paddle && git status +cd /path/to/PaConvert && git status +cd /path/to/docs && git status +``` + +## Step 2:添加改动并提交 + +对每个仓库执行以下操作(顺序:Paddle → Docs → PaConvert): + +```bash +# Paddle 仓库 +cd /path/to/Paddle +git add -A +git commit -m "[API Compatibility] api_name_1/api_name_2/api_name_3/... Edit By AI Agent" +# 等待 pre-commit hook 完成 +# 如果 pre-commit 失败,修复问题后重新 git add 和 commit + +# docs 仓库 +cd /path/to/docs +git add -A +git commit -m "[API Compatibility] api_name_1/api_name_2/api_name_3/... Edit By AI Agent" +# 等待 pre-commit hook 完成 +# 如果 pre-commit 失败,修复问题后重新 git add 和 commit + +# PaConvert 仓库 +cd /path/to/PaConvert +git add -A +git commit -m "[API Compatibility] api_name_1/api_name_2/api_name_3/... Edit By AI Agent" +# 等待 pre-commit hook 完成 +# 如果 pre-commit 失败,修复问题后重新 git add 和 commit +``` + +## Step 3:推送代码到 upstream claude 分支 + +```bash +# Paddle 仓库 +cd /path/to/Paddle +git push upstream HEAD:claude -f + +# docs 仓库 +cd /path/to/docs +git push upstream HEAD:claude -f + +# PaConvert 仓库 +cd /path/to/PaConvert +git push upstream HEAD:claude -f +``` + +## Step 4:创建 PR + +根据自动获取的 PyTorch API 名单生成 PR,执行以下命令创建 PR(顺序:Paddle → Docs → PaConvert): + +```bash +# Paddle PR +gh pr create --repo PaddlePaddle/Paddle --base develop --head zhwesky2010:claude \ + --title "[API Compatibility] api_name_1/api_name_2/api_name_3/... Edit By AI Agent" \ + --body "$(cat <<'EOF' +### PR Category +User Experience + +### PR Types +Improvements + +### Description +**API Compatibility Edit By AI Agent:** +\`\`\` +torch.api_name_1 +torch.api_name_2 +... +\`\`\` + +### 是否引起精度变化 +否 +EOF +)" + +# Docs PR +gh pr create --repo PaddlePaddle/docs --base develop --head zhwesky2010:claude \ + --title "[API Compatibility] api_name_1/api_name_2/api_name_3/... Edit By AI Agent" \ + --body "$(cat <<'EOF' +**API Compatibility Edit By AI Agent:** + +\`\`\` +torch.api_name_1 +torch.api_name_2 +... +\`\`\` + +- https://github.com/PaddlePaddle/Paddle/pull/{paddle_pr_number} + +EOF +)" + +# PaConvert PR +gh pr create --repo PaddlePaddle/PaConvert --base master --head zhwesky2010:claude \ + --title "[API Compatibility] api_name_1/api_name_2/api_name_3/... Edit By AI Agent" \ + --body "$(cat <<'EOF' +### PR Docs +- https://github.com/PaddlePaddle/docs/pull/{docs_pr_number} + +### PR APIs +**API Compatibility Edit By AI Agent:** +\`\`\` +torch.api_name_1 +torch.api_name_2 +... +\`\`\` + +- https://github.com/PaddlePaddle/Paddle/pull/{paddle_pr_number} + +EOF +)" +``` + +其中: +- 将 `api_name_1/api_name_2/...` 替换为实际的 API 名单 +- 将 `{paddle_pr_number}` 和 `{docs_pr_number}` 替换为实际创建的 PR 号 + + +# 二、注意事项 + +- 从前序步骤的上下文中自动获取 PyTorch API 名单,无需用户输入 +- 三个 PR 的 API 名单必须完全一致 +- 如果 pre-commit hook 失败,修复问题后重新提交 +- 所有改动必须推送到 upstream 的 claude 分支 +- 确保 PR 创建成功,如果失败需要继续修正直到成功 diff --git a/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/python-decorator/SKILL.md b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/python-decorator/SKILL.md new file mode 100644 index 00000000000..2fdce2a457a --- /dev/null +++ b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/python-decorator/SKILL.md @@ -0,0 +1,685 @@ +--- +name: python-decorator +description: 仅用于《Paddle API 对齐 PyTorch 项目》,负责 Step2:API 代码修改,实施 Python 装饰器的代码开发。通过 Python 装饰器,在 Python 层为 Paddle API 提供参数别名、参数顺序、参数类型和参数用法的兼容转换,实现 PyTorch 风格的 API 调用,并保持 Paddle API 的向后兼容性。 +allowed-tools: Read Grep‌ Glob‌ Edit Bash +disable-model-invocation: false +--- + +# 一、现有装饰器体系 + +Paddle 现有装饰器统一位于 `Paddle/python/paddle/utils/decorator_utils.py`,按功能分为两类: + +## 1.1 通用别名装饰器 + +| 装饰器 | 支持参数数量 | 性能 | 使用场景 | 优先级 | +|---------|-------------|------|----------|--------| +| `param_one_alias` | 1 个 | 高 | 单个参数别名(如 x↔input) | ⭐⭐⭐ | +| `param_two_alias` | 2 个 | 高 | 两个参数别名(如 x↔input, axis↔dim) | ⭐⭐⭐ | +| `ParamAliasDecorator` | 3 个及以上 | 中 | 复杂参数映射场景 | ⭐⭐ | +| `param_two_alias_one_default` | 2 个+默认值 | 低 | 需要默认值(median 专用) | ⭐(不推荐)| + +## 1.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 函数定义前添加装饰器 + +**示例**: +```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] +``` + + +**完整示例**: +```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)类型检测来区分两者,并分别匹配不同的参数顺序 +3. 专用装饰器应该尽可能逻辑简单,只假定存在 Paddle 签名+Pytorch 签名两种用法,其他情况无需判断,提升性能 +4. overload 注解:专有装饰器需添加 overload 注解(通用别名装饰器无需注解),需针对 Paddle 签名、Pytorch 签名分别添加 overload 注解(Paddle 在前,Pytorch 在后) +```python +@overload +def gather( + x: Tensor, + index: Tensor, + axis: Tensor | int | None = None, + name: str | None = None, + out: Tensor | None = None, +) -> Tensor: ... + +@overload +def gather( + input: Tensor, + dim: int, + index: Tensor, + out: Tensor | None = None, +) -> Tensor: ... +``` + +## Step 3: 添加 out 参数支持 + +仅支持新增 out 参数,新增其他参数则需方案 3(修改 API 智能体)来开展。 + +### 方式一:直接指定 out(推荐) + +**适用条件**: +1. 情况 1:API 最后一个逻辑是调用`_C_ops`;情况 2:API 调用了其他 API,调用的最后一个其他 API 也支持 out +2. out 参数只有一个 Tensor + +**示例**: +```python +# 情况 1:API 最后一个逻辑是调用`_C_ops` +@param_two_alias(["x", "input"], ["y", "other"]) +def less_than(x, y, name=None, *, out=None) -> Tensor: + """ + Keyword 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: + ... + +# 情况 2: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: + ... + + Keyword 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: + ... + + Keyword args: + out (Tensor|None, optional): The output tensor. Default: None. + """ + # case1: 只有 1 个 out 的情况 + ret = <计算逻辑> + if out is not None: + paddle.assign(ret, out) + return out + return ret + + # case2: 有多个 out 的情况 + ret1, ret2 = <计算逻辑> + if out is not None: + paddle.assign(ret1, out[0]) + paddle.assign(ret2, out[1]) + return out + return ret1, ret2 +``` + +注意: +1. 需在 API 签名中增加 out 参数,`out`参数需与 Pytorch 用法一致,一般情况下 out 均是 keyword-only 参数(使用`*,`分隔),少数情况下 out 是位置参数。 +2. 处理 out 参数时,仅需处理 in_dynamic_or_pir_mode()分支下的逻辑,老静态图(LayerHelper)分支无需处理 out 参数。 + +## Step 4: 更新函数文档字符串 + +如果使用的是通用别名装饰器,则在文档的 Args 部分为有别名的参数添加 Alias Support 说明,如下: +> 注:Alias 说明应放在该参数描述的末尾,格式为: Alias: ``alias_name`` ,多个 Alias 描述为: Alias: ``alias_name1`` or ``alias_name2`` + +```python +@param_two_alias(["x", "input"], ["axis", "dim"]) +def fft( + x: Tensor, + n: int | None = None, + axis: int = -1, + norm: _NormalizeMode = "backward", + name: str | None = None, + *, + out: Tensor | None = None, +) -> Tensor: + """ + Calculate one-dimensional discrete Fourier transform. + + Args: + x (Tensor): The input data. It's a Tensor type. It's a complex. + Alias: ``input``. + n (int|None, optional): The length of the output transform axis. + axis (int, optional): Axis used to calculate FFT. + Alias: ``dim``. + norm (str, optional): Indicates which direction to scale the `forward` or `backward` transform + pair and what normalization factor to use. + name (str|None, optional): The default value is None. + + Keyword args: + out(Tensor, optional): The output tensor. + """ +``` + +如果使用的是专用装饰器,则表明 API 支持了签名重载,需要分别描述两种签名,可以参考代码中的@overload 注解,如下: +> 注:只需在文档正文中阐述两种签名(Paddle 在前,Pytorch 在后),文档其他位置如 Args/Returns 仍以 Paddle 风格签名为准 + +```python +@overload +def broadcast_tensors(input: Sequence[Tensor], name: str | None = None) -> list[Tensor]: ... + +@overload +def broadcast_tensors(*tensors: Tensor) -> list[Tensor]: ... + +@variadic_tensor_decorator('input') +def broadcast_tensors(input: Sequence[Tensor], name: str | None = None) -> list[Tensor]: + """ + This API has two signatures: + + 1. ``paddle.broadcast_tensors(input, name=None)`` (Paddle-style): + Broadcast a list of tensors following broadcast semantics. + + 2. ``paddle.broadcast_tensors(*tensors)`` (PyTorch-style): + Broadcast variadic tensor arguments following broadcast semantics. + + Args: + ... + + Returns: + ... + """ +``` + +如果支持了 out 参数,必须在 API 文档中描述 out 参数,out 为 keyword-only 参数(*后面)时注意增加`Keyword Args:`,并在此部分描述。out 为位置参数时直接在 Args 部分描述。如下: + +```python +# out 为 keyword-only 参数 +def func(x, name=None, *, out=None): + """ + ... + + Args: + ... + + Keyword Args: + out (Tensor|optional): The output tensor. Default: None. + + Returns: + ... + """ + +# out 为位置参数 +def func(x, out=None, name=None): + """ + ... + + Args: + x (Tensor): Input of Atan operator. + out (Tensor, optional): The output Tensor. Default: None. + name (str|None, optional): Name for the operation. + + Returns: + ... + """ +``` + +**注意事项**: +- Tensor 类方法(如 paddle.Tensor.abs)没有文档,无需处理,请勿与普通方法(如 paddle.abs)混淆 +- Inplace 方法(如 paddle.abs_等下划线 API),只需要更新 API 签名,不需要修改文档 + +## Step 5: 添加测试用例 + +不要新建任何测试文件,直接在 `test/legacy_test/test_api_compatibility[1-9]\.py(数字最大的)` 中添加测试。严格按以下模板来编写: + +**测试模板**: +```python +class TestAPI(unittest.TestCase): + def setUp(self): + # If not use random seed, remove setUp + np.random.seed(2025) + self.np_x = np.random.rand(...).astype(...) + + def test_dygraph_Compatibility(self): + paddle.disable_static() + x = paddle.to_tensor(self.np_x) + + # 1. Paddle Positional arguments + out1 = paddle.(x, ...) + + # 2. Paddle keyword arguments + out2 = paddle.(x=x, ...) + + # 3. Pytorch Positional arguments (only if order different with paddle args) + out3 = paddle.(x, ...) + + # 4. PyTorch keyword arguments (alias) + out4 = paddle.(input=x, dim=...) + + # 5. Mixed arguments + out5 = paddle.(x, axis=...) + + # 6. out parameter test (only if supported) + out6 = paddle.empty_like(x) + out7 = paddle.(x, ..., out=out6) + + # 7. Tensor method - args (only if supported) + out8 = x.(...) + + # 8. Tensor method - kwargs (only if supported) + out9 = x.(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.(x, ...) + out2 = paddle.(x=x, ...) + out3 = paddle.(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 位置参数(如果 Pytorch 与 Paddle 参数顺序不同) +4. ✅ PyTorch 关键字参数(使用参数别名) +5. ✅ 混合参数(如果参数量>=2,位置+关键字) +6. ✅ out 参数(如果 API 支持,inplace 无需测) +7. ✅ 类方法 Pytorch 位置参数(如果有类方法) +8. ✅ 类方法 Pytorch 关键字参数(如果有类方法) + +静态图模式:(inplace 无需测) +1. ✅ Paddle 位置参数(全部位置参数) +2. ✅ Paddle 关键字参数(全部关键字参数) +3. ✅ PyTorch 位置参数(如果 Pytorch 与 Paddle 参数顺序不同) +4. ✅ PyTorch 关键字参数(使用参数别名) +5. ✅ 类方法 Pytorch 位置参数(如果有类方法) +6. ✅ 类方法 Pytorch 关键字参数(如果有类方法) + +注意: +1. 有些测试项是可选的,需要自行判断是否需要添加。 +2. 添加测试项需要遵循上述顺序,不要打乱。 +3. 输出结果序号需要保持连贯,每一个输出结果均需要检验,尽可能循环检验减少行数。 +3. 比对测试项,对于内容相同的测试项,不要重复添加。 + +完整测试示例,请参考 `Paddle/test/legacy_test/test_api_compatibility[1-9]\.py` 中已有的测试类结构。 + +## Step 6: 编译与运行 + +单测编写完成后,按以下命令验证执行(不可修改): + +1. **重新编译项目**: + ```bash + cd /workspace/Paddle/build + cmake .. + make -j$(nproc) + ``` + +2. **运行单测文件**: + ```bash + python <所修改的单测文件名> + ``` + +3. **问题排查**:根据报错信息调整代码或测试用例,确保所有测试用例通过。注意每次修改 Paddle 源码后,必须重新编译方可生效。 + +编译注意事项: +- 编译完成后不需要重新安装,无需执行 setup/install 等任何安装操作,直接可生效 +- 编译不要删除 build 目录,否则会导致增量编译失效,编译时间极长 + +# 三、异常处理 + +## 3.1 标准处理流程 +1. **定位错误**:仔细阅读错误信息,确定错误类型和位置 +2. **分析原因**:根据错误信息分析具体问题(装饰器实现错误、使用错误等) +3. **修改代码**:根据分析结果调整代码 +4. **验证修复**:重新运行测试确认问题解决 + +## 3.2 常见错误及解决方案 + +### 类型转换错误 + +**错误现象**: +```python +TypeError: expected Tensor as argument, got numpy.ndarray +``` + +**解决方法**: +```python +# 确保输入是 Tensor 类型 +tensor_input = paddle.to_tensor(numpy_input) +paddle.api(tensor_input, ...) +``` + +## 3.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}") + # ... +``` + +# 四、技术背景知识 + +## 4.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++实现,不在本方案范围内 + +## 4.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 = () +``` + +## 4.3 装饰器特点与注意事项 + +### 装饰器特点 +- **零侵入性**:无需修改 API 的实现代码 +- **适用面广**:支持灵活处理各种 API 签名重载情况,如参数名不同、参数顺序不同、参数个数不同、参数类型不同、参数用法不同等 +- **向后兼容**:保持 Paddle 原有 API 调用方式 +- **开发效率**:相比 C++下沉方案,修改更快速直接 +- **性能开销**:Python 装饰器层会引入轻微性能开销 + +### 性能开销 + +- 装饰器会引入额外的函数调用层级 +- 对于高频调用的 API,装饰器开销可能不可忽略 +- 考虑对性能敏感的 API 使用 C++下沉方案 + +### 调试复杂性 + +- 装饰器增加了调用栈深度 +- 错误追踪时可能需要多跳一层 +- 使用日志辅助调试 + +### 与其他装饰器共存 + +- 需要注意装饰器的应用顺序 +- 确保装饰器之间不产生冲突 +- 不同装饰器的参数处理逻辑应该兼容 + +# 五、注意事项 + +1. 不要修改 sparse 目录下的 API +2. 确保不破坏现有功能,保持向后兼容性 +3. 开发专用装饰器时参考现有实现 +4. 代码中不允许提交中文,代码注释采用英文 diff --git a/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/pytorch-alignment-validator/SKILL.md b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/pytorch-alignment-validator/SKILL.md new file mode 100644 index 00000000000..ff14844b7bd --- /dev/null +++ b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/pytorch-alignment-validator/SKILL.md @@ -0,0 +1,213 @@ +--- +name: pytorch-alignment-validator +description: 仅用于《Paddle API 对齐 PyTorch 项目》,负责 Step3:Pytorch 对齐验证,基于 PaConvert 工具验证 Paddle API 与 PyTorch API 是否用法完全对齐一致 +allowed-tools: Read Grep‌ Glob‌ Write‌ Edit Bash(python *) +disable-model-invocation: false +--- + +# 一、标准工作流程 + +> **PaConvert 介绍**:Pytorch->Paddle 代码转换工具,可以搭建起 Pytorch-Paddle API 之间的桥梁。 + +请严格按以下 Step 依次执行,不要自行修改或跳过 Step: + +## Step 1: 标记已对齐的 API +1. 定位文件:`PaConvert/paconvert/api_mapping.json` +2. 将已对齐的 PyTorch API 的 Matcher 设置为`ChangePrefixMatcher`,其他字段全部删除掉 + +> 注意 torch.abs、torch.abs_、torch.Tensor.abs、torch.Tensor.abs_是四个不同的 API + +## Step 2: 增加测试用例 +**目的:** 判断是否满足如下测试规范,如不满足,则需增加测试用例使之符合规范 + +**修改位置:** +- Pytorch 单测路径: `PaConvert/tests/` +- Pytorch 单测文件名: `test_.py` +- **测试文件命名规范**:API 名称转换为下划线命名法,并在文件名中体现完整的模块路径层级: + - `torch.argmax` → `test_argmax.py`(顶层函数) + - `torch.Tensor.argmax` → `test_Tensor_argmax.py`(类方法,用大写 T 表示类名) + - `torch.linalg.inv` → `test_linalg_inv.py`(子模块函数,用下划线连接模块名) + - 从左到右依次将 API 路径中的`.`替换为`_`,类名保留首字母大写,最终加上`test_`前缀和`.py`后缀 +- 注意新增的测试 case 需要放到之前测试 case 的后面,不要删除之前的测试 case + +**测试规范:** +1. **参数覆盖要全面** + 测试所有可能的 Pytorch 参数用法: + - 全部位置参数:`func(a, b, c)` + - 全部关键字参数:`func(x=a, y=b, z=c)` + - 混合参数:`func(a, y=b, z=c)` + - 乱序关键字:`func(z=c, x=a, y=b)` + - 指定所有默认参数:`func(a, dim=0,offset=1)` + - 缺省所有默认参数:`func(a)` + - 变量传参:`args=(a,b); func(*args)` + - 参数别名:`func(input=a, other=b)` + +2. **输入数据要有效** + - 不能使用全零张量等无效输入 + - 需要包含不同维度的输入(1D、2D、3D 等) + - 覆盖不同数据类型(float32、float64、int 等) + +3. **测试数量要充分** + - 涉及 N 个新参数时,包含各种排列组合的用法 + - 测试用例越多越好,充分验证功能正确性 + +**测试用例模板:** +```python +import textwrap +import pytest +from apibase import APIBase + +obj = APIBase("torch.target_api") + +def test_case_1(): + """Basic usage""" + pytorch_code = textwrap.dedent(""" + import torch + result = torch.target_api(torch.tensor([1, 2, 3])) + """) + obj.run(pytorch_code, ["result"]) + +def test_case_2(): + """Positional arguments test""" + pytorch_code = textwrap.dedent(""" + import torch + x = torch.tensor([1.0, 2.0]) + result = torch.target_api(x, 2, 1) + """) + obj.run(pytorch_code, ["result"]) + +def test_case_3(): + """Keyword arguments test""" + pytorch_code = textwrap.dedent(""" + import torch + x = torch.tensor([1.0, 2.0]) + result = torch.target_api(input=x, dim=1, dtype=torch.float32) + """) + obj.run(pytorch_code, ["result"]) + +def test_case_4(): + """Keyword arguments out of order test""" + pytorch_code = textwrap.dedent(""" + import torch + x = torch.tensor([1.0, 2.0]) + result = torch.target_api(dtype=torch.float32, dim=1, input=x) + """) + obj.run(pytorch_code, ["result"]) + +def test_case_5(): + """Gradient computation test""" + pytorch_code = textwrap.dedent(""" + import torch + x = torch.tensor([1., 2.], requires_grad=True) + y = torch.target_api(x) + y.sum().backward() + x_grad = x.grad + """) + # Skip gradient attribute check because Paddle's stop_gradient mechanism differs from PyTorch's requires_grad mechanism at the framework level. + obj.run(pytorch_code, ["y", "x_grad"], check_stop_gradient=False) + +def test_case_6(): + """Edge case test""" + pytorch_code = textwrap.dedent(""" + import torch + result = torch.target_api(torch.empty(0)) + """) + obj.run(pytorch_code, ["result"]) + +def test_case_7(): + """Expression argument test""" + pytorch_code = textwrap.dedent(""" + import torch + result = torch.target_api(torch.tensor([1, 2, 3]), 1 + 1) + """) + obj.run(pytorch_code, ["result"]) +``` + +**特殊场景处理:** + +1. **GPU 环境测试**: +```python +import paddle +import pytest + +should_skip = not paddle.device.is_compiled_with_cuda() +skip_reason = "Test requires CUDA" + +@pytest.mark.skipif(condition=should_skip, reason=skip_reason) +def test_case_with_cuda(): + ... +``` + +2. **不支持功能标记**: +```python +@pytest.mark.skip(reason="Not supported") +def test_unsupported_case(): + ... +``` + +3. **自定义比较逻辑**(通常不需要): +```python +class CustomAPIBase(APIBase): + def compare(self, name, pytorch_result, paddle_result, ...): + # 自定义比较逻辑 + pytorch_str = str(pytorch_result).removeprefix("torch.") + assert pytorch_str == paddle_result +``` + +## Step 3: 运行单元测试 + +在补充完测试用例后,确保能通过所有用例,请按以下命令验证执行(不可修改): + +1. 本地执行以下命令: + ```bash + cd /workspace/PaConvert/ + python -m pytest tests/test_.py + ``` + +2. 根据报错信息,修改代码或测试用例(禁止通过修改 api_mapping.json 来使单测通过),确保所有测试用例通过。注意每次修改 Paddle 源码后,必须重新编译方可生效: +```bash +cd /workspace/Paddle/build +cmake .. +make -j$(nproc) +``` + +编译注意事项: +- 编译完成后不需要重新安装,无需执行 setup/install 等任何安装操作,直接可生效 +- 编译不要删除 build 目录,否则会导致增量编译失效,编译时间极长 + +# 二、异常处理 + +## 2.1 异常处理策略 + +在 PyTorch 对齐验证过程中,需要对不同异常情况进行分类处理。以下是异常处理策略: + +### a. PyTorch 代码执行失败 +- **错误标识**:`Failed to execute pytorch code` +- **根本原因**:PyTorch 单元测试代码存在问题,无法正常执行 +- **处理策略**:修改 PyTorch 单元测试代码,确保能正确执行 Pytorch 代码 +- **验证标准**:测试代码应该能够在标准的 PyTorch 环境中正常运行 + +### b. Paddle 代码执行失败 +- **错误标识**:`Failed to execute paddle code` +- **根本原因**:Pytorch 单测正确,但修改后的 Paddle API 实现存在问题,导致代码无法执行 +- **处理策略**:需要返回到前序 Step 修改,因此结束本 Step,将报错信息返回给主控智能体分析 +- **关联任务**:Paddle API 需要进一步修改以兼容 PyTorch 接口 + +### c. 计算结果不一致 +- **错误标识**:`Unable to align results` +- **根本原因**:Pytorch 单测正确,但 Paddle API 与 PyTorch API 计算结果存在差异 +- **处理策略**:需要返回到前序 Step 修改,因此结束本 Step,将报错信息返回给主控智能体分析 +- **验证要求**:Paddle API 需要进一步修改以兼容 PyTorch 接口,确保数值精度、数据类型、形状等完全一致 + +> 禁止通过配置 api_mapping.json 为非`ChangePrefixMatcher`来使单测通过,本步骤的通过标准为:Matcher 配置为`ChangePrefixMatcher` + 单测运行通过。 + +## 2.2 常见错误及解决方案 + +### Paddle 不支持类型提升 +- 如果报错是因为 Paddle 不支持类型提升或标量输入(已知问题),可以禁用对应测试用例,其他情况不允许禁用单测: + ```python + # 将 def test_case_x(): 改为 def _test_case_x(): + # 并添加注释说明原因 + def _test_case_2(): # Paddle does not support scalar input + ... + ```