From 1c81735ec06cefc6f0d05e252718a7c7f7aaaa8c Mon Sep 17 00:00:00 2001 From: zhouwei25 Date: Tue, 26 May 2026 14:33:38 +0000 Subject: [PATCH] [Coding Agent] Update API Compatibility Claude Code Skills --- .../skills/add-compatibility-test/SKILL.md | 253 ++++++++ .../.claude/skills/add-new-api/SKILL.md | 237 +++++++ .../references/api_docs_guidelines.md | 291 +++++++++ .../add-new-api/references/new_cpp_op.md | 542 ++++++++++++++++ .../add-new-api/references/new_python_api.md | 175 +++++ .../skills/api-change-decider/SKILL.md | 613 ++++++++---------- .../.claude/skills/api-compatibility/SKILL.md | 378 ++++++----- .../.claude/skills/api-docs-updater/SKILL.md | 99 +-- .../.claude/skills/cpp-sink/SKILL.md | 583 ++--------------- .../.claude/skills/create-pr/SKILL.md | 318 +++++---- .../.claude/skills/modify-origin-api/SKILL.md | 347 ++++++++++ .../.claude/skills/python-decorator/SKILL.md | 248 +------ .../pytorch-alignment-validator/SKILL.md | 158 ++--- .../api_compatibility/batch_api_compat.sh | 38 ++ 14 files changed, 2730 insertions(+), 1550 deletions(-) create mode 100644 docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-compatibility-test/SKILL.md create mode 100644 docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-new-api/SKILL.md create mode 100644 docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-new-api/references/api_docs_guidelines.md create mode 100644 docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-new-api/references/new_cpp_op.md create mode 100644 docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-new-api/references/new_python_api.md create mode 100644 docs/dev_guides/coding_agent/api_compatibility/.claude/skills/modify-origin-api/SKILL.md create mode 100755 docs/dev_guides/coding_agent/api_compatibility/batch_api_compat.sh diff --git a/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-compatibility-test/SKILL.md b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-compatibility-test/SKILL.md new file mode 100644 index 00000000000..c7b206fdaf1 --- /dev/null +++ b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-compatibility-test/SKILL.md @@ -0,0 +1,253 @@ +--- +name: add-compatibility-test +description: 负责《Paddle API 对齐 PyTorch 项目》中 Step3:兼容性测试,为已修改的 Paddle API 添加兼容性单测并执行验证,确保 API 的 Paddle 用法与 PyTorch 用法均能正常工作。 +disable-model-invocation: false +--- + +# 一、标准工作流程 + +## Step 1:编写测试用例(仅首次执行) + +在 `${ROOT_DIR}/Paddle/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 API 无需测) +7. ✅ 类方法 PyTorch 位置参数(如果有类方法) +8. ✅ 类方法 PyTorch 关键字参数(如果有类方法) + +**静态图模式必测项**(inplace API 无需测): +1. ✅ Paddle 位置参数(全部位置参数) +2. ✅ Paddle 关键字参数(全部关键字参数) +3. ✅ PyTorch 位置参数(如果 PyTorch 与 Paddle 参数顺序不同) +4. ✅ PyTorch 关键字参数(使用参数别名) +5. ✅ 类方法 PyTorch 位置参数(如果有类方法) +6. ✅ 类方法 PyTorch 关键字参数(如果有类方法) + +### 测试注意事项 + +1. **可选测试项判断**:根据 API 实际支持的用法判断是否需要添加对应测试项 +2. **顺序要求**:添加测试项需遵循上述顺序,不要打乱 +3. **输出编号连贯**:输出结果序号需要保持连贯,每个输出结果均需检验 +4. **避免重复**:对于内容相同的测试项,不要重复添加 +5. **inplace API 特殊处理**:inplace API 只需测试动态图,无需测试静态图 + +### 特殊参数测试 + +**pin_memory 参数测试**: +```python +if paddle.device.is_compiled_with_cuda() or paddle.device.is_compiled_with_xpu(): + x = paddle.xxx([2], device="gpu", pin_memory=True) + self.assertTrue("pinned" in str(x.place)) +``` + +**device 参数测试**: +```python +# 测试不同设备 +if paddle.device.is_compiled_with_cuda(): + out_cpu = paddle.(x, device="cpu") + out_gpu = paddle.(x, device="gpu:0") +``` + +## Step 2:编译并运行单测(每次改动均需执行) + +单测编写完成后,按以下命令验证执行: + +```bash +cd ${ROOT_DIR}/Paddle/build +cmake .. && make -j$(nproc) +python test_xxx.py +``` + +根据报错信息修改测试用例或回退,确保所有测试用例通过。每次修改后均需要重新执行本步骤。 + +**编译注意事项**: +- 无需重装,直接生效(勿执行 setup/install 等安装操作) +- 勿删除 build 目录(否则增量编译失效,编译时间极长) + +# 二、技术背景知识 + +### 测试框架说明 + +- **unittest**:Python 标准库测试框架,本步骤采用该框架 +- **动态图测试**:通过 `paddle.disable_static()` 和 `paddle.enable_static()` 切换模式 +- **静态图测试**:通过 `paddle.static.program_guard` 创建程序上下文 + +### 常用断言方法 + +```python +# 数值比对 +np.testing.assert_allclose(actual, expected, rtol=1e-5, atol=1e-8) +np.testing.assert_array_equal(actual, expected) + +# 布尔断言 +self.assertTrue(condition) +self.assertFalse(condition) +self.assertEqual(a, b) +self.assertIsInstance(obj, cls) +``` + +### 测试数据生成 + +```python +# 随机数据 +np.random.seed(2025) +self.np_x = np.random.rand(2, 3).astype(np.float32) + +# 特定形状 +self.shape = [2, 3, 4] +self.dtype = np.float32 +``` + +# 三、注意事项 + +1. 严格按标准工作流程执行,杜绝自行臆断和跳过步骤 +2. `${ROOT_DIR}` 变量表示根目录,需自行替换为实际路径,若未通过 ROOT_DIR 指定,则均为相对于 ROOT_DIR 的路径 +3. 不要新建测试文件,直接在已有的 `test_api_compatibility[1-9]\\.py` 中添加 +4. 测试类命名遵循 `TestAPI` 格式,如 `TestArgmaxAPI` +5. 确保测试覆盖所有新增的参数别名和参数用法 + +# 四、异常回退原则 + +当本步骤多次尝试仍无法通过时,需要根据错误信息诊断问题根源: + +1. **若判断为测试用例编写有误**(如断言逻辑错误、测试数据错误): + - 直接在本步骤修正测试用例 + - 无需回退到其他步骤 + +2. **若判断为代码实现有误**(如参数处理逻辑错误、类型转换失败等): + - 回退到总步骤 Step2(代码修改)调整实现方式 + - 回退后再进入本步骤(Step3),则只需执行:编译并运行,其他步骤无需执行 + +3. **若判断为方案选择错误**(如当前方案不适用、底层不支持等): + - 回退到总步骤 Step1(方案决策)重新决策 + - 回退后再进入本步骤(Step3),则只需执行:编译并运行,其他步骤无需执行 + +# 五、常见问题处理 + +### Q1:测试报错 "unexpected keyword argument" + +**错误现象**: +``` +TypeError: xxx() got an unexpected keyword argument 'out' +``` + +**解决方法**: +检查 API 签名是否正确添加了 out 参数,或参数名是否拼写正确。 + +### Q2:pin_memory 测试报错 "Pinning memory is not supported" + +**错误现象**: +``` +RuntimeError: Pinning memory is not supported for Place(cpu) +``` + +**解决方法**: +`pin_memory=True` 仅在 GPU/XPU 设备上有意义。确保测试逻辑中正确添加了环境判断: +```python +if paddle.device.is_compiled_with_cuda() or paddle.device.is_compiled_with_xpu(): + x = paddle.xxx([2], device="gpu", pin_memory=True) + self.assertTrue("pinned" in str(x.place)) +``` + +### Q3:静态图测试失败 "object has no attribute" + +**错误现象**: +``` +AttributeError: 'OpResult' object has no attribute 'pin_memory' +``` + +**解决方法**: +某些动态图专用的方法需要在 `in_dynamic_mode()` 保护下执行: +```python +if in_dynamic_mode(): + tensor = tensor.pin_memory() +``` + +### Q4:数值比对失败 + +**错误现象**: +``` +AssertionError: Not equal to tolerance rtol=1e-5, atol=1e-8 +``` + +**解决方法**: +1. 检查输入数据是否正确生成 +2. 检查预期输出值是否正确 +3. 适当调整容差参数 `rtol` 和 `atol` diff --git a/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-new-api/SKILL.md b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-new-api/SKILL.md new file mode 100644 index 00000000000..463a7cf23fa --- /dev/null +++ b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-new-api/SKILL.md @@ -0,0 +1,237 @@ +--- +name: add-new-api +description: 负责《Paddle API 对齐 PyTorch 项目》中 Step2:API 代码修改,实施『新增 API』方案。通过新增 Paddle API(新增 API 别名、新增 Python 层 API、新增 C++算子),覆盖 Pytorch API 调用路径,实现与 PyTorch API 行为对齐。 +context: fork +disable-model-invocation: false +--- + +# 一、适用场景 + +根据 API 的性质,将新增场景分为三种模式: + +**场景一:新增 API 别名** +- 适用:目标 API 已有对应 Paddle 实现,仅名称/路径不同 +- 是否需要编译:不需要 +- 修改文件数:1~5 个 .py +- 实现难度:简单 +- 典型示例:ne、cat、paddle.optim、paddle.distributions + +**场景二:新增 Python 层 API** +- 适用:需要新 API 逻辑,可纯 Python 实现(不需要开发新的 C++ 算子,可用其他 Python API 组合实现) +- 是否需要编译:不需要 +- 修改文件数:3~6 个 .py +- 实现难度:中等 +- 典型示例:argwhere、from_numpy + +**场景三:新增 C++ 算子** +- 适用:需要新的底层计算逻辑,涉及 C++/CUDA(需要开发新的 C++ 算子,用 C++ 开发算子实现代码,再封装 Python API) +- 是否需要编译:必须重新编译 +- 修改文件数:10~18 个(.py/.h/.cc/.cu/.yaml) +- 实现难度:复杂 +- 典型示例:aminmax、addcmul + +# 二、标准工作流程 + +根据实际需求,选择场景一、场景二或场景三执行。所有场景完成后,统一执行新增 API 英文文档和编译运行测试。 + +## 场景一:新增 API 别名 + +新增的 API 别名应统一在 `__init__.py` 中定义,便于统一管理,避免分散在各个实现文件中,与现有别名管理保持一致(位于 Paddle/python/paddle/__init__.py、Paddle/python/paddle/tensor/__init__.py、Paddle/python/paddle/nn/functional/__init__.py、Paddle/python/paddle/nn/__init__.py 等文件中)。 + +### 类型一:函数/类别名 + +适用条件:目标 PyTorch API 与某个已有 Paddle API 功能完全一致,仅名称不同。 + +典型示例:paddle.ne(对齐 torch.ne)、paddle.cat(对齐 torch.cat)、paddle.nn.SiLU(对齐 torch.nn.SiLU) + +请严格按以下步骤依次执行: + +#### Step 1:在模块文件中添加别名赋值 + +找到原始 API 的实现位置(如 python/paddle/tensor/math.py),在文件末尾添加别名赋值: + +```python +# python/paddle/tensor/math.py 末尾 +ne = not_equal +lt = less_than +less = less_than +le = less_equal +greater = gt +ge = greater_equal +``` + +若属于 paddle.nn 命名空间(如 SiLU = Silu),则在对应的 nn/__init__.py 中添加: + +```python +# python/paddle/nn/__init__.py +SiLU = Silu +``` + +#### Step 2:在 __init__.py 中导出别名 + +在顶层 python/paddle/__init__.py 的对应 from paddle.tensor import (...) 块内,按字母序插入新别名: + +```python +# python/paddle/__init__.py +from paddle.tensor import ( + ... + ge, + greater, + le, + less, + lt, + ne, + ... +) +``` + +#### Step 3(如需):添加 Tensor 方法别名 + +若需要以 paddle.Tensor.xxx 形式调用(对齐 torch.Tensor.xxx),在 python/paddle/tensor/__init__.py 中同步处理: + +```python +# python/paddle/tensor/__init__.py +# 1. 在 tensor_method_func 列表中按字母序添加方法名 +tensor_method_func = [ + ... + 'ne', + 'lt', + 'less', + ... +] + +# 2. 在文件末尾与模块导出一起声明别名(作为 Tensor 方法注入来源) +ne = not_equal +lt = less_than +``` + +Paddle 通过 patch 机制将 paddle.tensor 模块中的函数动态 setattr 到 paddle.Tensor,因此只需在此处声明即可自动注入为 Tensor 方法,无需修改 class Tensor 定义。 + +### 类型二:类方法/属性别名 + +适用条件:需要对齐类的方法或属性,根据已有实现与目标形式的性质差异,分为三种情况处理。 + +#### 情况一:方法 → 方法 或 属性 → 属性(性质相同) + +当已有实现与目标形式性质相同时(都是方法或都是属性),直接使用别名赋值即可。 + +典型示例: +```python +# 方法 → 方法 +get_submodule = get_sublayer +set_submodule = set_sublayer + +# 属性 → 属性 +grad = _grad +``` + +操作步骤:在目标类中直接添加别名赋值。 + +#### 情况二:方法 → 属性 + +当已有实现是方法(需调用),目标形式是属性(直接访问)时,需使用 `@property` 装饰器包装原有方法。 + +典型示例:PyLayerContext.saved_tensors(对齐 torch.saved_tensors,原有 saved_tensor() 需加括号调用) + +```python +# python/paddle/autograd/py_layer.py + +class PyLayerContext: + def saved_tensor(self): + # original method implementation + ... + + @property + def saved_tensors(self): + return self.saved_tensor() # delegate to existing method +``` + +#### 情况三:属性 → 方法 + +当已有实现是属性(直接访问),目标形式是方法(需调用)时,需定义新方法返回属性值。 + +典型示例:假设 Paddle 有属性 `x.shape`,需对齐 PyTorch 方法 `x.get_shape()`: + +```python +class Tensor: + @property + def shape(self): + # original property implementation + ... + + def get_shape(self): + """New method that delegates to the existing property.""" + return self.shape +``` + +若目标类是 `paddle.Tensor`,由于 Python 层无对应 class 定义,还需额外通过 patch 机制动态注入上述别名。 + +**新增 Tensor 方法的详细说明请参考**:主 SKILL 文档的「3.5 类方法 API 实现原理」章节。 + +### 类型三:命名空间别名 + +适用条件:PyTorch 拥有某个 Paddle 没有的命名空间,需创建新包将已有 API 重新组织暴露。 + +典型示例:paddle.optim(对齐 torch.optim)、paddle.utils.data(对齐 torch.utils.data)、paddle.distributions(对齐 torch.distributions) + +(详细步骤待补充) + +## 场景二:新增 Python 层 API + +适用条件:PyTorch 有某个 API,Paddle 没有,但可以基于已有 Paddle API 组合实现,无需新增 C++ 算子。 + +典型示例:paddle.argwhere(= paddle.nonzero(input, as_tuple=False))、paddle.from_numpy、paddle.asarray + +详细开发流程请参考:开发 API Python 端(references/new_python_api.md) + +## 场景三:新增 C++ 算子 + +适用条件:PyTorch 有某个计算 OP,Paddle 完全没有对应实现,需要从底层开始完整新增算子。 + +典型示例:paddle.aminmax(对齐 torch.aminmax)、paddle.addcmul(对齐 torch.addcmul) + +详细开发流程请参考:开发 C++ 算子(references/new_cpp_op.md) + +# 三、背景知识 + +代码文件存放位置应尽可能与已有 API 的目录保持一致。下方介绍了 `python/paddle/tensor/` 目录的组织结构,供开发者参考。 + +大部分常用的数组运算 API(在 NumPy 中有功能相似的 numpy.xxx API)都放在 python/paddle/tensor 目录下: +- array.py:TensorArray 相关操作 +- attribute.py:Tensor 元数据操作:is_complex, is_integer, shape, rank 等 +- creation.py:Tensor 创建:to_tensor, ones, full_like 等 +- einsum.py:einsum 运算 +- linalg.py:线性代数:matmul, norm, det +- logic.py:逻辑运算:logical_and, allclose, greater_than +- manipulation.py:数组元素操作:concat, stack, transpose 等 +- math.py:算术运算:加减乘除、三角函数、sum、cumsum 等 +- random.py:随机数:randn, uniform +- search.py:搜索排序:argsort, argmin +- stat.py:统计:mean, var, std +- to_string.py:Tensor 打印功能 + +python/paddle/nn/functional 目录中包含用于神经网络的函数,如 batch_norm、conv2d,这些在 NumPy 中通常没有直接对应的函数。 + + +# 四、注意事项 + +1. 严格按标准工作流程执行,杜绝自行臆断和跳过步骤 +2. 代码中不允许提交中文,代码注释与 docstring 均使用英文 +3. 别名添加需注意顺序:在 __init__.py 中按字母序插入,保持代码风格一致 +4. Inplace 别名注意:mul_ 等 inplace 方法需确保原方法支持对应调用形式;inplace API 无需测试静态图 +5. 新命名空间不要遗漏注册:创建新包后,务必在 setup.py.in 和 setup.py 中同步注册 + + +# 五、常见问题处理 + +## Q1:Tensor 方法别名未生效(AttributeError) + +错误现象: +```python +x = paddle.to_tensor([1, 2, 3]) +x.ne(y) # AttributeError: 'Tensor' object has no attribute 'ne' +``` + +解决方法: +1. 检查 python/paddle/tensor/__init__.py 中 tensor_method_func 列表是否包含 'ne' +2. 检查 python/paddle/tensor/math.py 末尾是否有 ne = not_equal diff --git a/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-new-api/references/api_docs_guidelines.md b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-new-api/references/api_docs_guidelines.md new file mode 100644 index 00000000000..e8eebd7c15e --- /dev/null +++ b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-new-api/references/api_docs_guidelines.md @@ -0,0 +1,291 @@ +# API 文档书写规范 + +路径说明:本文档中的路径描述如无特别说明,均相对于 ${ROOT_DIR}/Paddle 目录。 + +## 一、概述 + +本文档介绍飞桨框架 API 文档的书写规范,包括文档的基本要求、各模块的写作说明、典型案例以及文档测试等内容。 + +**基本要求:** + +1. **至关重要**:API 文档对该 API 的描述,一定要与 API 的行为保持一致。中英文文档的内容要严格一致 +2. **API 文档的字段**:API 名称、API 功能描述、API 参数、API 返回、API 代码示例、API 属性(class)、API 方法(methods)等。API 抛出异常的情况,不需要在文档中体现 +3. **API 功能描述**:请注意,看文档的用户没有和开发同学一样的知识背景。因此,请提示用户在什么场景下使用该 API。请使用深度学习领域通用的词汇和说法 +4. **API 参数**:写清楚对输入参数的要求,写清楚在不同情况下的行为区别(如默认值时的行为)。同类性质的参数(如:输入 Tensor `x`,每个 API 中的 `name` 参数) +5. **API 代码示例**:中英文文档当中的代码示例完全一致(注释可不用翻译),中文文档建议使用 COPY-FROM 的方式与英文文档做同步。代码示例尽量不用随机输入,并给出输出值。构造输入数据时,尽量使用 paddle 提供的 API,如 `paddle.zeros`、`paddle.ones`、`paddle.full`、`paddle.arange`、`paddle.rand`、`paddle.randn`、`paddle.randint`、`paddle.normal`、`paddle.uniform`,尽量不要引入第三方库(如 NumPy) +6. **其他**:对于 `Variable`、`DenseTensor`、`Tensor` 等描述,统一使用 `Tensor` +7. 对于 `Linear`、`Conv2D`、`L1Loss` 这些 class 形式的 API,需要写清楚被调用时输入输出的形状(如 `forward` 方法的参数)。位置放在 `Parameters` / `参数` block 后面,具体为: + +中文时: + 形状: + - **input** (Tensor):形状为(批大小,通道数,高度,宽度),即,NCHW 格式的 4-D Tensor。 + - **output** (Tensor):形状为(批大小,卷积核个数,输出图像的高度,输出图像的高度)的 4-D Tensor。 + +英文时: + Shape: + - input: 4-D tensor with shape: (batch, num_channels, height, width), i.e.: NCHW. + - output: 4-D tensor with shape: (batch, num_filters, new_height, new_width). + +## 二、典型案例 + +- paddle.concat:python/paddle/tensor/manipulation.py +- paddle.split:python/paddle/tensor/manipulation.py +- paddle.squeeze:python/paddle/tensor/manipulation.py +- paddle.full_like:python/paddle/tensor/creation.py +- paddle.ones:python/paddle/tensor/creation.py +- paddle.ones_like:python/paddle/tensor/creation.py + +## 三、英文模板 + + def add(x, y, name=None): + """ + + Add two tensors element-wise. The equation is: + + .. math:: + out = x + y + + Note: + ``paddle.add`` supports broadcasting. If you want know more about broadcasting, please refer to :ref:`user_guide_broadcasting`. + + Args: + x (Tensor): The input tensor, it's data type should be float32, float64, int32, int64. + y (Tensor): The input tensor, it's data type should be float32, float64, int32, int64. + name (str, optional): For details, please refer to :ref:`api_guide_Name`. Generally, no setting is required. Default: None. + + Returns: + N-D Tensor. A location into which the result is stored. It's dimension equals with :attr:`x`. + + Examples: + .. code-block:: pycon + + >>> import paddle + + >>> x = paddle.to_tensor([2, 3, 4], 'float64') + >>> y = paddle.to_tensor([1, 5, 2], 'float64') + >>> z = paddle.add(x, y) + >>> print(z) + Tensor(shape=[3], dtype=float64, place=Place(cpu), stop_gradient=True, + [3., 8., 6.]) + + """ + +## 四、中文模板 + + .. _cn_api_paddle_add: + + add + ------------------------------- + + .. py:function:: paddle.add(x, y, name=None) + + 输入 :attr:`x` 与输入 :attr:`y` 逐元素相加,并将各个位置的输出元素保存到返回结果中。计算公式为: + + .. math:: + out = x + y + + .. note:: + ``paddle.add`` 遵守广播机制,如您想了解更多,请参见 :ref:`cn_user_guide_broadcasting`。 + + 参数 + ::::::::: + - **x** (Tensor) - 输入的 Tensor,数据类型为 float32、float64、int32 或 int64。 + - **y** (Tensor) - 输入的 Tensor,数据类型为 float32、float64、int32 或 int64。 + - **name** (str,可选) - 具体用法请参见 :ref:`api_guide_Name`,一般无需设置,默认值为 None。 + + 返回 + ::::::::: + ``Tensor``,维度和数据类型都与 :attr:`x` 相同,存储运算后的结果。 + + 代码示例 + :::::::::: + + COPY-FROM: paddle.add + +## 五、API 文档各模块写作说明 + +### 5.1 API 标签 + +标签 `api_label` 一般用于文档间的引用。英文 API 文档的标签是自动生成的,而中文 API 文档的标签则需要在文档第一行手动编写。 + +如 paddle.add: + .. _cn_api_paddle_add: + +其中 `api_label` 是 `cn_api_paddle_add`,但在中文文档中,需要在标签 `cn_api_paddle_add` 的前面加上 `.. _` 、后面加上 `:` (固定格式) + +**api_label 设定规范**: +1. 英文 api_label:`api_` + <完整的 API 名称,把 `.` 替换成 `_` >,如 `paddle.add` 对应 `api_paddle_add` +2. 中文 api_label:`cn_` + 英文 api_label,如 `paddle.add` 对应 `cn_api_paddle_add` + +### 5.2 API 名称 + +API 名称直接写 API 的名字即可,不需要写全完整路径。如 paddle.add: + add + --------- + +### 5.3 API 声明 + +API 的声明部分,要给出 API 的声明信息。 + +function:如 paddle.add + .. py:function:: paddle.add(x, y, name=None) + +class:如 paddle.nn.Conv2D + .. py:class:: paddle.nn.Conv2D(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, padding_mode='zeros', weight_attr=None, bias_attr=None, data_format='NCHW') + +注意:此处的参数名称需要与后文 API 参数板块中的严格保持一致。 + +### 5.4 API 功能描述 + +API 功能描述部分只需要尽可能简单的描述出 API 的功能作用即可,要让用户能快速看懂。可以拆解为 3 个部分:功能作用 + 计算公式 + 注解部分。 + +- 功能作用:描述该 API 的功能作用。由于用户没有对应的背景,所以需要补充必要的细节,比如是不是逐元素的 +- 计算公式:给出该 API 的计算公式,由于公式中每个变量都对应 API 的参数,所以不需要做额外的说明 +- 注解部分:如果 API 有需要特殊说明的部分,可以在注解部分给出 + +**注意事项**: +1. 写作 API 文档时,请使用深度学习领域通用的词汇和说法 +2. 文档中的前后说明要一致,比如维度的说明,统一使用 4-D Tensor 的格式,不确定的写"多维" +3. 功能描述中涉及到的专有数据结构如 `Tensor`、`DenseTensor` 和 `Variable`,中英文都统一使用 `Tensor`,无需翻译 +4. 如果涉及到一些通用的知识,如广播机制,可以用注解的方式写出来 + +中文: +``` +.. note:: + ``paddle.add`` 遵守广播机制,如您想了解更多,请参见 :ref:`cn_user_guide_broadcasting`。 +``` + +英文: +``` +Note: + ``paddle.add`` supports broadcasting. If you want know more about broadcasting, please refer to :ref:`user_guide_broadcasting`. +``` + +### 5.5 API 参数(重要) + +**注意**: +- 一些通用的参数说明,直接复制 docs/templates/common_docs.py(位于 docs 仓库)中的描述即可 +- 若当前 API 无参数,则不需要填写该板块 + +API 参数部分,要解释清楚每个参数的意义和使用场景。**需要注意以下两点**: + +1. 对于有默认值的参数,至少要讲清楚在默认值下的逻辑,而不仅仅是介绍这个参数是什么以及默认值是什么。 + +如 stop_gradient 的对比: +```python +# 错误写法 +stop_gradient (bool,可选) - 提示是否应该停止计算梯度,默认值为 True。 + +# 正确写法:需添加默认值为 True 的行为,即表示停止计算梯度 +stop_gradient (bool,可选) - 提示是否应该停止计算梯度,默认值为 True,表示停止计算梯度。 +``` + +或如 return_numpy:需要分别描述 True 和 False 两种情况: +```python +# 错误写法 +return_numpy (bool) – 该变量表示是否将 fetched tensor 转换为 NumPy 数据。默认值为 True。 + +# 正确写法 +return_numpy (bool) – 该参数表示是否将返回的计算结果转化为 NumPy 数据;如果为 False,则每个变量返回的类型为 Tensor,否则返回变量的类型为 numpy.ndarray。默认为:True。 +``` + +2. 在讲清楚每个 API 参数是什么的同时,还需要描述清楚每个参数的具体作用是什么。 + +### 5.6 API 返回 + +先描述 API 返回值的类型,然后描述 API 的返回值及其含义。如 paddle.add: + 返回 + ::::::::: + ``Tensor``,维度和数据类型都与 :attr:`x` 相同,存储运算后的结果。 + +### 5.7 API 抛出异常 + +API 抛出异常部分,由于历史原因写在文档中,建议在源码的 warning 中做提示,不在文档中展开。 + +### 5.8 API 代码示例(重要) + +代码示例是 API 文档的核心部分之一,应该对 API 使用的各种场景尽可能在一个示例中给出,并给出对应的结果。 + +**书写规范** + +书写示例代码如同在 Python 的标准交互界面 REPL 中编程一样: +- `>>> ` 表示单行语句 +- `... ` 表示多行或复合语句 + +```python +>>> import paddle +>>> x = paddle.to_tensor([[1, 2], [3, 4]]) +>>> y = paddle.to_tensor([[5, 6], [7, 8]]) +>>> res = paddle.multiply(x, y) +``` + +为保证示例代码正确性,CI 环境会对其进行检查。更多规范请参考 Python 文档示例代码书写规范(code_example_writing_specification_cn.md)。 + +**注意事项** +- 中英文示例代码保持完全一致,中文文档建议使用 COPY-FROM 同步 +- 原则上所有 API 都需提供示例代码,class member methods、abstract API、callback 等特殊情况可通过白名单审核 +- 仅 GPU 环境的 API,在 CPU 上运行时给出含 "Not compiled with CUDA" 的错误提示即可 + +英文 API 代码示例格式: +```python +def api(): + """ + Examples: + .. code-block:: pycon + + >>> import paddle + # ... 示例代码 ... + """ +``` + +中文文档格式: +``` +代码示例 +:::::::::: + +COPY-FROM: paddle.add +``` + +### 5.9 API 属性 + +API 的属性用来描述 API 所包含的属性。如果 API 有属性,每个属性需要分为以下部分描述: +- 名称:属性名称直接写属性的名字即可,不需要将全路径写全 +- 注意:列举出使用该属性时应注意的一些问题,如果没有可以不填 +- 描述:与 API 功能描述部分要求一致 +- 返回:与 API 返回部分要求一致 +- 代码示例:与 API 代码示例部分要求一致 + +### 5.10 API 方法 + +API 的方法用来描述 API 所包含的方法,一些类的 API 会有这个内容,没有方法的 API 可以不写此模块。如果有,每个方法需要分为六个部分描述: +- 名称:方法名称直接写方法的名字即可,不需要将全路径写全 +- 声明:与 API 声明的要求一致 +- 参数:与 API 参数的要求一致 +- 描述:与 API 功能描述的要求一致 +- 返回:与 API 返回的要求一致 +- 代码示例:与 API 代码示例部分要求一致 + +### 5.11 注解 + +注解部分描述用户使用该 API 时需要额外注意的事项。 + +例 1:使用注意事项(paddle.sqrt) +- 中文:`.. note:: 请确保输入中的数值是非负数。` +- 英文:`Note: Input value must be greater than or equal to zero.` + +### 5.12 警告 + +警告部分用于不推荐用户使用的 API 或计划废弃的 API。 + +例 1:计划废弃的 API(paddle.fluid.clip.set_gradient_clip) +- 中文:`.. warning:: 此 API 对位置使用的要求较高...不推荐使用。推荐在 optimizer 初始化时设置梯度裁剪。` +- 英文:`Warning: This API must be used after building network...It is recommended to set grad_clip when initializing the optimizer...` + +## 六、注意事项 + +- 中文文档、英文文档齐全,内容一一对应 +- 文档清晰可读,易于用户使用 +- 给出易于理解的 API 介绍,包括文字描述和公式描述 +- 参数命名通俗易懂无歧义,明确给出传参类型,对参数含义以及使用方法进行详细说明,对返回值进行详细说明 +- 示例代码需要做到复制粘贴即可运行,并且需要明确给出预期运行结果(如果可以) +- 阅读无障碍:无错别字、上下文连贯、内容清晰易懂、链接可正常跳转、图片公式显示正常 diff --git a/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-new-api/references/new_cpp_op.md b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-new-api/references/new_cpp_op.md new file mode 100644 index 00000000000..78bb4bd3d31 --- /dev/null +++ b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-new-api/references/new_cpp_op.md @@ -0,0 +1,542 @@ +# 新增 C++ 算子 + +本文档介绍如何在飞桨框架中开发 C++ 算子,主要包括算子描述定义、Kernel 实现、Python API 封装、单元测试编写等内容。 + +## 一、获取并分析 PyTorch API 信息 + +若尚未获得 PyTorch API 的相关信息,则自行获取,获取方式请参考`api-compatibility/SKILL.md` 中的「3.6 API 信息获取方式」章节。 + + +然后分析 PyTorch API 的功能和行为,在 Paddle 中新增对应的 C++ 算子和 Python API,使其与 PyTorch API 保持一致。具体包括:API 名称、调用路径、参数名及参数功能等。 + +## 二、开发流程概述 + +新增一个 C++ 算子需要以下步骤: +1. 新增算子描述及定义:描述前反向算子的输入、输出、属性,实现 InferMeta 函数 +2. 新增算子 Kernel:实现算子在各种设备上的计算逻辑 +3. 封装 Python API:封装 Python 端调用算子的接口 +4. 添加单元测试:验证新增算子的正确性 + +各步骤对应的文件位置(假设算子名为 xxx): +- 算子描述及定义:前向算子定义在 Paddle/paddle/phi/ops/yaml/ops.yaml,反向算子定义在 Paddle/paddle/phi/ops/yaml/backward.yaml +- 算子 InferMeta:Paddle/paddle/phi/infermeta/ 目录下的相应文件 +- 算子 Kernel:Paddle/paddle/phi/kernels/ 目录下的 xxx_kernel.h、xxx_kernel.cc、xxx_grad_kernel.h、xxx_grad_kernel.cc 等文件 +- Python API:Paddle/python/paddle/ 目录下的相应子目录中的 .py 文件 +- 单元测试:Paddle/test/legacy_test/ 目录下的 test_xxx_op.py + +用户使用飞桨开发神经网络模型时使用的 Python 接口(如 paddle.add(),paddle.relu()等)称为飞桨的 Python API,每个运算类的 Python API 在框架内部都会对应到一个或多个 C++ 端算子,每个算子在不同硬件设备上(CPU,GPU 等)实现的运算逻辑代码称为 Kernel。算子 InferMeta 函数是在算子 kernel 执行前将输出结果的维度、数据类型等信息进行处理,每个算子只需要实现一个 InferMeta 函数。 + +**Python API、算子 Yaml 配置、算子 InferMeta 函数和算子 Kernel 之间的关系:** + +Python API 执行时会进入到 C++ 端由框架进行调度并执行相应的算子逻辑,算子的执行主要包括两个过程: +1. 执行算子 InferMeta 函数完成输出结果的维度、数据类型等静态信息的推导 +2. 根据输入变量的设备信息选择对应的硬件设备来执行算子 Kernel,完成输出结果的数值计算 + +Python API 到算子 InferMeta 函数和 Kernel 调用之间的框架调度部分的逻辑代码主要通过算子 Yaml 配置中的信息自动生成。 + +接下来以 paddle.trace 为例,介绍如何新增算子。trace 算子用于计算输入 Tensor 在指定平面上的对角线元素之和,并输出相应的计算结果。 + +trace 示例代码路径: +- 算子描述及定义:Paddle/paddle/phi/ops/yaml/ops.yaml、Paddle/paddle/phi/ops/yaml/backward.yaml +- 算子 InferMeta:Paddle/paddle/phi/infermeta/unary.cc +- 算子 Kernel:Paddle/paddle/phi/kernels/trace_kernel.h、Paddle/paddle/phi/kernels/cpu/trace_kernel.cc、Paddle/paddle/phi/kernels/gpu/trace_kernel.cu、Paddle/paddle/phi/kernels/trace_grad_kernel.h、Paddle/paddle/phi/kernels/cpu/trace_grad_kernel.cc、Paddle/paddle/phi/kernels/gpu/trace_grad_kernel.cu +- Python API:Paddle/python/paddle/tensor/math.py +- 单元测试:Paddle/test/legacy_test/test_trace_op.py + +## 三、新增算子描述及定义 + +算子描述及定义主要是定义算子的基本属性,包括算子的输入、输出以及各项非计算逻辑的配置,这些都是设备无关的。 + +### 3.1 算子 Yaml 文件配置 + +在 Paddle/paddle/phi/ops/yaml/ops.yaml 和 Paddle/paddle/phi/ops/yaml/backward.yaml 文件中对算子进行描述及定义,在框架编译时会根据 YAML 文件中的配置自动生成 C++ 端的相关代码接口以及内部实现。 + +Paddle/paddle/phi/ops/yaml/ops.yaml 中 trace 相关配置: + +```yaml +- op : trace + args : (Tensor x, int offset = 0, int axis1 = 0, int axis2 = 1) + output : Tensor(out) + infer_meta : + func : TraceInferMeta + kernel : + func : trace + backward : trace_grad +``` + +Paddle/paddle/phi/ops/yaml/backward.yaml 中 trace 相关配置: + +```yaml +- backward_op : trace_grad + forward : trace (Tensor x, int offset, int axis1, int axis2) -> Tensor(out) + args : (Tensor x, Tensor out_grad, int offset, int axis1, int axis2) + output : Tensor(x_grad) + infer_meta : + func : UnchangedInferMeta + param : [x] + kernel : + func : trace_grad + data_type : x + no_need_buffer : x +``` + +ops.yaml 和 backward.yaml 分别对算子的前向和反向进行配置。 + +#### 前向算子基本配置项 + +- **op**:算子名称,与该算子 Python API 函数名相同(命名方式为:全小写+下划线),示例中为 trace +- **args**:算子输入参数,与该算子 Python API 函数的输入参数对应。当前支持的输入数据类型包括:Tensor, Tensor[], float, double, bool, int, int64_t, int[], int64_t[], str, Place, DataType, DataLayout, IntArray, Scalar。Tensor 类型的参数称为 Input(输入),非 Tensor 类型的参数称为 Attribute(属性)。注:Tensor[]表示 Tensor 数组;IntArray 为 int 类型数组,主要用于表示 shape,index 和 axes 等类型数据;Scalar 表示标量,可以支持不同的普通数据类型 +- **output**:算子输出类型(目前支持 Tensor 和 Tensor[]类型),多个输出间用逗号","分隔开。可以使用"()"选择性标记输入的名字,如未标记默认为'out'。注:当返回类型为 Tensor[]时,需要在 Tensor[]后的'{}'内通过表达式指定返回数组的 size,如:Tensor[](out){input.size()} +- **infer_meta**:InferMeta 函数负责根据输入变量推断返回 Tensor 的维度与类型 +- **infer_meta:func**:调用的 InferMeta 函数,示例中为 TraceInferMeta +- **infer_meta:param**:InferMeta 函数的输入参数,可以对 args 中的参数进行选择传入,未配置则默认传入 args 中的所有参数 +- **kernel**:算子的计算 Kernel 配置 +- **kernel:func**:算子对应 kernel 函数的注册名 +- **kernel:param**:kernel 函数的输入参数,配置规则与 infer_meta:param 相同 +- **kernel:data_type**:根据指定参数推导调用 kernel 的 data_type(对应 kernel 函数的模板参数'T'),默认不配置会根据输入 Tensor 自动推导。如果 kernel 的 data_type 类型由某个输入参数决定,需要将该参数的变量名填入该项 +- **kernel:backend**:根据指定参数来选择调用 kernel 的 Backend(Kernel 执行的具体设备),默认不配置会根据输入 Tensor 自动推导 +- **backward**:算子对应的反向算子名称,如果没有反向则不需要配置 + +#### 前向算子特殊配置项 + +- **optional**:指定输入 Tensor 为可选输入,用法可参考 dropout 中的 seed_tensor(位于 Paddle/paddle/phi/ops/yaml/ops.yaml) +- **inplace**:算子对指定的输入做原位处理并作为输出结果返回,使用格式:(x -> out)。特殊规则:如果 api 中算子名称有'_'后缀则只生成支持 inplace 功能的接口,如果算子名称没有'_'后缀,则会同时生成支持 inplace 操作的接口和不支持 inplace 的普通接口 +- **view**:与 inplace 机制类似,区别在于 view 模式返回的结果只是与输入共享内存,使用格式:(x -> out) +- **intermediate**:标记前向计算中输出的用于反向计算的中间变量,不会出现在 Python API 的返回结果中,新增算子时不建议使用 +- **invoke**:复用已有的算子接口或实现自定义的 C++ API,配置时以函数调用的形式配置即可,使用 invoke 时不需要配置 infer_meta 和 kernel +- **data_transform**:控制算子输入参数的自动转换行为,包括类型(dtype)、设备(backend)和布局(layout) +- **data_transform:skip_transform**:跳过指定参数的所有数据转换 +- **data_transform:support_trans_dtype**:开启指定参数的自动类型转换 + +#### 反向算子配置项 + +- **backward_op**:反向算子名称,一般命名方式为:前向算子名称+'_grad' +- **forward**:对应前向算子的名称、参数、返回值,需要与 ops.yaml 中前向算子配置一致 +- **args**:反向算子输入参数。约束:所有参数需要在 forward 配置项的参数中找到对应;反向输入参数需要按顺序排列:前向输入 Tensor、前向输出 Tensor、前向输出 Tensor 的反向梯度、前向非 Tensor 类型属性变量 +- **output**:反向算子输出,顺序需要与前向输入 Tensor 一致 +- **infer_meta** / **kernel** / **data_transform**:配置规则与前向算子相同 +- **no_need_buffer**:标记的 Tensor 变量在前向运行完成后,持有的内存或显存会被释放。注意:由于 Tensor 内存被释放后会影响 dtype 接口的使用,所以需要在 kernel 的 data_type 配置项中指定其他的 Tensor 来推导 kernel 的 data_type + +### 3.2 实现 InferMeta 函数 + +InferMeta 函数是根据输入参数,推断算子输出 Tensor 基本信息的函数,推断的信息包括输出 Tensor 的 shape、data type,同时它也承担了检查输入数据维度、类型等是否合法的功能。 + +InferMeta 与 kernel 共同组成了一个算子的运算过程。InferMeta 在 kernel 前执行,用于维度、数据类型等信息的计算处理,kernel 中不再需要专门推导这些信息。 + +trace 算子的 InferMeta 函数实现在 Paddle/paddle/phi/infermeta/unary.cc 中,主要逻辑包括:参数校验(维度>=2、axis 范围检查、axis1!=axis2)、计算输出维度。示例: + +```cpp +void TraceInferMeta(const MetaTensor& x, int offset, int axis1, int axis2, MetaTensor* out) { + auto x_dims = x.dims(); + PADDLE_ENFORCE_GE(x_dims.size(), 2, + errors::InvalidArgument("Input(X)'s rank is %d. Must be >= 2.", x_dims.size())); + // ... 更多参数校验 ... + auto sizes = common::vectorize(x_dims); + if (x_dims.size() == 2) { + sizes.clear(); + sizes.push_back(1); + } else { + // 移除 axis1 和 axis2 对应的维度 + int axis1_ = axis1 < 0 ? axis1 + x_dims.size() : axis1; + int axis2_ = axis2 < 0 ? axis2 + x_dims.size() : axis2; + sizes.erase(sizes.begin() + std::max(axis1_, axis2_)); + sizes.erase(sizes.begin() + std::min(axis1_, axis2_)); + } + out->set_dims(common::make_ddim(sizes)); + out->set_dtype(x.dtype()); +} +``` + +其中,MetaTensor 是对底层异构 Tensor 的抽象封装,仅支持对底层 Tensor 的维度、数据类型、布局等属性进行读取和设置,具体方法请参考 Paddle/paddle/phi/core/meta_tensor.h。 + +**InferMeta 的实现位置**(paddle/phi/infermeta/ 目录下,以 Tensor 输入个数为判定标准): +- nullary.h:没有输入 Tensor 参数的函数 +- unary.h:仅有一个输入 Tensor 参数的函数 +- binary.h:有两个输入 Tensor 参数的函数 +- ternary.h:有三个输入 Tensor 参数的函数 +- multiary.h:有三个以上输入 Tensor 或者输入为 vector 的函数 +- backward.h:反向算子的 InferMeta 函数一律在此文件中 + +**InferMeta 的编译时与运行时** + +在静态图模型中,InferMeta 操作在编译时和运行时都会被调用。在 compile time 时,由于真实的维度未知,框架内部用 -1 来表示;在 run time 时,用实际的维度表示。因此维度的值在 compile time 和 run time 时可能不一致,如果存在维度的判断和运算操作,InferMeta 就需要区分 compile time 和 run time。 + +对于此类 InferMeta 函数,需要在 InferMeta 函数声明的参数列表末尾增加 MetaConfig 参数,例如: + +```cpp +void ConcatInferMeta(const std::vector& x, + const Scalar& axis_scalar, + MetaTensor* out, + MetaConfig config = MetaConfig()); +``` + +然后在函数体中,使用 config.is_runtime 判断处于编译时还是运行时。 + +以下两种情况需要区分 compile time 和 run time: +- 检查:compile time 时不判断维度等于 -1 的情况,但在 runtime 时检查 +- 运算:-1 和其他数做任何运算都要等于 -1 + +参考代码: +- 判断实现参考 Paddle/paddle/phi/infermeta/multiary.cc 中的 SigmoidCrossEntropyWithLogitsInferMeta 函数 +- 运算实现参考 Paddle/paddle/phi/infermeta/multiary.cc 中的 ConcatInferMeta 函数 + +## 四、新增算子 Kernel + +### 4.1 Kernels 目录结构 + +新增算子 Kernel 在 Paddle/paddle/phi/kernels/ 目录中完成,基本目录结构: +- 根目录:放置设备无关的 kernel 声明和实现 +- cpu:仅放置 cpu 后端的 kernel 实现 +- gpu:仅放置 gpu 后端的 kernel 实现 +- xpu:仅放置百度 kunlun 后端的 kernel 实现 +- funcs:放置一些支持多设备的、在多个 kernel 中使用的公共 functor 和 functions + +新增算子仅需要关注 kernels 根目录及 kernel 所支持设备的子目录: +- kernels 根目录:放置设备无关的 kernel.h 和 kernel.cc。如果 kernel 除了一些简单的设备无关的 C++ 逻辑,关键计算逻辑均是复用已有的 kernel 函数实现的,那么它的声明和实现均直接放置到 kernels 目录下即可 +- kernels 下一级子目录:放置特定后端的 kernel 实现代码 + +典型 kernel 新增时文件放置位置(假设算子名为 xxx): +- 新增与设备无关的 kernel:新增文件包括 Paddle/paddle/phi/kernels/xxx_kernel.h、Paddle/paddle/phi/kernels/xxx_kernel.cc。反向 kernel 使用 grad_kernel 后缀 +- 新增与设备相关、且 CPU & GPU 分别实现的 kernel:CPU 实现位于 Paddle/paddle/phi/kernels/cpu/ 目录下;GPU 实现位于 Paddle/paddle/phi/kernels/gpu/ 下。新增文件包括:Paddle/paddle/phi/kernels/xxx_kernel.h、Paddle/paddle/phi/kernels/cpu/xxx_kernel.cc、Paddle/paddle/phi/kernels/gpu/xxx_kernel.cu + +### 4.2 Kernel 写法 + +#### 4.2.1 声明 Kernel 函数 + +以 trace 算子为例,首先在 Paddle/paddle/phi/kernels/ 目录下新建 Paddle/paddle/phi/kernels/trace_kernel.h 文件,用于放置前向 kernel 函数声明。 + +注意: +- Kernel 函数声明的参数列表原则上与 Python API 参数列表一致 +- 所有的 kernel 声明,统一放在 namespace phi 中 + +```cpp +namespace phi { +template +void TraceKernel(const Context& ctx, + const DenseTensor& x, + int offset, + int axis1, + int axis2, + DenseTensor* out); +} +``` + +模板为固定写法: +- 第一个模板参数为数据类型 T,第二个模板参数为设备上下文 Context +- 函数命名:kernel 的命名统一加 Kernel 后缀,驼峰式命名 +- 参数顺序:Context,InputTensor..., Attribute..., OutTensor* +- 第 1 个函数参数,类型为 const Context& 的 dev_ctx +- 第 2 个函数参数,输入 Tensor,类型一般为 const DenseTensor& +- 第 3-5 个函数参数,均为 attribute,多个 attribute 可以参考 Python 端 API 定义的顺序 +- 第 6 个函数参数,输出 Tensor,类型一般为 DenseTensor* + +特殊情况说明: +- 特殊模板参数:对于某些 kernel(如 reshape,copy),这些 kernel 不关注数据类型 T,可以省去第一个模板参数 +- 特殊输入类型:对于某些特殊 kernel(如 concat 和 split kernel)的部分输入或输出是数组类型的 DenseTensor,此时输入类型为 const std::vector&;输出类型为 std::vector + +#### 4.2.2 实现 Kernel 函数 + +**复用已有 Kernel 实现设备无关 Kernel 函数** + +以 linear 算子 (out = x * w + b) 为例介绍复用已有 kernel 实现设备无关 Kernel 函数的方法: + +```cpp +#include "paddle/phi/kernels/elementwise_add_kernel.h" +#include "paddle/phi/kernels/elementwise_multiply_kernel.h" + +template +void LinearKernel(const Context& dev_ctx, + const DenseTensor& x, + const DenseTensor& w, + const DenseTensor& b, + DenseTensor* out) { + dev_ctx.template Alloc(out); // 为 out 分配内存 + MultiplyKernel(dev_ctx, x, w, out); // 复用 MultiplyKernel + AddKernel(dev_ctx, out, b, out); // 复用 AddKernel +} +``` + +复用 kernel 的流程: +1. 在源文件中 include 要复用 kernel 的头文件 +2. 直接调用相应的 kernel 函数进行复用 + +注意:设备无关 kernel 实现时计算逻辑部分只能复用现有 kernel 或设备无关的 functor,不能使用设备相关的语法或者函数接口 + +**实现设备相关 Kernel 函数** + +trace 算子的 CPU kernel 实现位于 Paddle/paddle/phi/kernels/cpu/trace_kernel.cc;GPU kernel 实现位于 Paddle/paddle/phi/kernels/gpu/trace_kernel.cu。 + +TraceKernel 的 CPU 实现示例: + +```cpp +template +void TraceKernel(const Context& dev_ctx, + const DenseTensor& x, + int offset, + int axis1, + int axis2, + DenseTensor* out) { + auto* out_data = dev_ctx.template Alloc(out); + + const DenseTensor diag = + funcs::Diagonal(dev_ctx, &x, offset, axis1, axis2); + if (diag.numel() > 0) { + auto x = phi::EigenMatrix::Reshape(diag, diag.dims().size() - 1); + auto output = phi::EigenVector::Flatten(*out); + auto reduce_dim = Eigen::array({1}); + output.device(*dev_ctx.eigen_device()) = x.sum(reduce_dim); + out->Resize(out->dims()); + } else { + std::fill(out_data, out_data + out->numel(), static_cast(0)); + } +} +``` + +说明:对于 kernel 内部临时使用的 DenseTensor 目前推荐使用 Empty、EmptyLike、Full 和 FullLike 接口进行创建。 + +**实现反向 Kernel 函数** + +反向 kernel 的实现与前向是类似的。相关文件:paddle/phi/kernels/trace_grad_kernel.h、paddle/phi/kernels/cpu/trace_grad_kernel.cc、paddle/phi/kernels/gpu/trace_grad_kernel.cu。 + +**公共函数管理** + +如果有一些函数会被多个 kernel 调用,可以创建非 kernel 的文件管理代码: +- 仅有当前 kernel 使用的辅助函数,和 kernel 实现放到同一个设备文件夹中 +- 有同设备多个 kernel 使用的辅助函数,在 kernel 所在的设备目录创建 .h 放置代码 +- 有跨设备多个 kernel 使用的辅助函数,在 kernels/funcs 目录下创建 .h/cc/cu 管理代码 + +#### 4.2.3 注册 Kernel 函数 + +在对应的 kernel 实现代码中添加注册 kernel 函数: + +```cpp +PD_REGISTER_KERNEL(trace, + CPU, + ALL_LAYOUT, + phi::TraceKernel, + float, + double, + int, + int64_t, + phi::dtype::float16, + phi::dtype::complex, + phi::dtype::complex) {} +``` + +字段说明: +- trace:kernel 名称,和算子的名称一致 +- CPU:backend 名称,一般主要就是 CPU 和 GPU +- ALL_LAYOUT:kernel 支持的 Tensor 布局,一般为 ALL_LAYOUT +- phi::TraceKernel:kernel 的函数名称,记得带上 namespace phi +- 剩余的均为 kernel 支持的数据类型 + +注意: +- 如果忘记添加注册相关的头文件,会给出一个 error: expected constructor, destructor, or type conversion before '(' token 的错误 +- phi 下的注册宏后边是带函数体 { },不是直接加分号 +- 注册 kernel 的宏声明需要在 global namespace + +## 五、封装 Python API + +飞桨框架会对新增的算子 kernel 自动绑定 Python,开发者需要在 Python 端定义相应的 API。 + +详见:新增 Python 层 API(references/new_python_api.md) + +## 六、添加单元测试 + +单测文件存放路径和命名方式:在 test/legacy_test/ 目录下,把对 Python API 的单元测试和 C++ 算子的单元测试写在同一个文件中,以 test_xxx_op.py 的形式命名(假设算子名为 xxx)。 + +### 6.1 C++ 算子单元测试 + +算子单元测试继承自 test/legacy_test/op_test.py 中的 OpTest 类。测试要点: +1. 在 setUp 函数定义输入、输出、属性参数,并生成随机输入数据 +2. 在 Python 脚本中实现与前向算子相同的计算逻辑,与算子输出对比 +3. 反向计算已自动集成进测试框架 + +注意:单测中的测试用例需要尽可能地覆盖 kernel 中的所有分支。 + +```python +class TestTraceOp(OpTest): + def setUp(self): + self.op_type = "trace" + self.python_api = paddle.trace + self.init_dtype() + self.case = np.random.randn(20, 6).astype(self.dtype) + self.inputs = {'Input': self.case} + self.attrs = {'offset': 0, 'axis1': 0, 'axis2': 1} + self.outputs = {'Out': np.trace(self.case)} + + def init_dtype(self): + self.dtype = np.float64 + + def test_check_output(self): + self.check_output(check_pir=True) + + def test_check_grad(self): + self.check_grad(['Input'], 'Out', check_pir=True) +``` + +关键点: +- self.op_type:算子名称,与 YAML 配置中的算子名一致 +- self.python_api:对应的 Python API 函数 +- self.inputs:输入数据,字典格式,key 与 YAML 配置中的输入名对应 +- self.attrs:属性参数 +- self.outputs:期望输出,用于与算子实际输出对比 +- check_pir=True:开启 PIR 模式单测 + +### 6.2 Python API 单元测试 + +详见:新增 Python 层 API - 添加单元测试(references/new_python_api.md#五添加单元测试) + +### 6.3 编译并运行单测(每次修改均需执行) + +单测编写完成后,按以下命令验证执行: + +```bash +cd ${ROOT_DIR}/Paddle/build +cmake .. && make -j$(nproc) +python test_xxx_op.py +``` + +根据报错信息修改代码,确保所有测试用例通过。每次修改后均需要重新执行本步骤。 + +**编译注意事项**: +- 无需重装,直接生效(勿执行 setup/install 等安装操作) +- 勿删除 build 目录(否则增量编译失效,编译时间极长) + +## 七、新增 API 英文文档 + +详见:新增 Python 层 API - 新增 API 英文文档(references/new_python_api.md#六新增-api-英文文档) + +## 八、开发算子注意事项 + +### 8.1 报错检查 + +实现算子时检查数据的合法性需要使用 PADDLE_ENFORCE 以及 PADDLE_ENFORCE_EQ 等宏定义: + +``` +PADDLE_ENFORCE(表达式, 错误提示信息) +PADDLE_ENFORCE_EQ(比较对象 A, 比较对象 B, 错误提示信息) +``` + +如果表达式为真,或者比较对象 A=B,则检查通过,否则会终止程序运行。 + +总体原则:任何使用了 PADDLE_ENFORCE 与 PADDLE_ENFORCE_XX 检查的地方,必须有详略得当的备注解释,错误提示信息不能为空。 + +报错提示信息书写建议: +1. 哪里错了?为什么错了?例如:ValueError: Mismatched label shape +2. 期望的输入是什么样的?实际的输入是怎样的?例如:Expected labels dimension=1. Received 4. +3. 能否给出修改意见? + +更详细的报错检查规范请参考:《Paddle 报错信息文案书写规范》(../style_guide_and_references/error_message_writing_specification_cn.md) + +### 8.2 算子兼容性问题 + +对算子的修改需要考虑兼容性问题,要保证算子修改之后,之前的模型都能够正常加载及运行。兼容性要求如下: +- 算子当前的所有输入输出参数不能被修改或删除 +- 可以新增参数,但新增的 Tensor 类型变量需要设置为 optional +- 新增的非 Tensor 变量需要设置默认值 + +### 8.3 显存优化 + +**为可原位计算的算子注册 inplace** + +有些算子的计算逻辑中,输出可以复用输入的显存空间。对于这类算子,可以注册 inplace,从而让框架在运行时自动地进行显存优化。 + +注册方式为在算子的 YAML 配置中添加 inplace 配置项,格式如:(x -> out)。示例: + +```yaml +- op : reshape + args : (Tensor x, IntArray shape) + output : Tensor(out) + ... + inplace : (x -> out) +``` + +**减少反向算子中的无关变量** + +通常反向算子会依赖于前向算子的某些输入、输出 Tensor。若反向算子只需要使用前向算子中输入和输出变量的 Shape 和 LoD 信息,但不依赖于变量中 Tensor 的内存 Buffer 数据,则可以通过 no_need_buffer 对该变量进行配置。示例: + +```yaml +- backward_op : trace_grad + forward : trace (Tensor x, int offset, int axis1, int axis2) -> Tensor(out) + args : (Tensor x, Tensor out_grad, int offset, int axis1, int axis2) + output : Tensor(x_grad) + ... + no_need_buffer : x +``` + +### 8.4 性能优化 + +- 第三方库选择:优先使用 cudnn、mkldnn、mklml、eigen 等高性能库,但需做 benchmark 验证 +- CUDA Kernel 优化:减少 CUDA Kernel 调用次数,将多个小 Kernel 合并;减少 CPU 与 GPU 之间的拷贝和同步操作 +- 更多优化方法参考:算子性能优化方法介绍(../op_optimization/op_optimization_method_introduction_cn.html) + +### 8.5 稀疏梯度参数更新方法 + +目前稀疏梯度在做更新的时候会先对梯度做 merge,即对相同参数的梯度做累加,然后做参数以及附加参数(如 velocity)的更新。 + +### 8.6 混合设备调用 + +由于 GPU 是异步执行的,当 CPU 调用返回之后,GPU 端可能还没有真正的执行,所以如果在算子中创建了 GPU 运行时需要用到的临时变量,当 GPU 开始运行的时候,该临时变量可能在 CPU 端已经被释放,这样可能会导致 GPU 计算出错。 + +关于 GPU 中的一些同步和异步操作:Kernel launches、Memory copies within a single device's memory、Memory copies from host to device of a memory block of 64 KB or less、Memory copies performed by functions that are suffixed with Async、Memory set function calls 都是异步的。 + +关于 cudaMemCpy 和 cudaMemCpyAsync 注意事项: +- 如果数据传输是从 GPU 端到非页锁定的 CPU 端,数据传输将是同步,即使调用的是异步拷贝操作 +- 如果数据传输是从 CPU 端到 CPU 端,数据传输将是同步的,即使调用的是异步拷贝操作 + +### 8.7 算子数值稳定性问题 + +有些算子存在数值稳定性问题,主要原因是在多次运行时,对浮点型数据施加操作的顺序可能不同。GPU 是通过多线程并行计算的方式来加速计算的,所以很容易出现对浮点数施加操作的顺序不固定现象。 + +目前发现 cudnn 中的卷积操作、cudnn 中的 MaxPooling、CUDA 中 CudaAtomicXX、ParallelExecutor 的 Reduce 模式下参数梯度的聚合等操作运行结果是非确定的。 + +Paddle 中添加了一些 FLAGS,比如使用 FLAGS_cudnn_deterministic 来强制 cudnn 使用确定性算法、FLAGS_cpu_deterministic 强制 CPU 端的计算使用确定性方法。 + +### 8.8 算子的数学公式 + +如果算子有数学公式,一定要在代码中将数学公式写明,并在 Python API 的 Doc 中显示,因为用户在对比不同框架的计算结果时可能需要了解 Paddle 对算子是怎么实现的。 + +### 8.9 LoD 在算子内部的传导规范 + +根据算子是否依赖 LoD,分为两类: +- LoD-transparent:计算不依赖 LoD,如 conv2d_op、batch_norm_op +- LoD-Based:计算依赖 LoD,如 lstm_op、gru_op、sequence_ops + +前向传导:对于"不变"和"改变"两种情况,需在 InferMeta 中调用 ShareLoD() 进行传导。 + +反向传导:输入 Var 对应的梯度 GradVar 的 LoD 应与 Var 自身相同,直接共享即可。 + +### 8.10 PyTorch 对齐关键配置文件 + +新增 OP 时,除标准流程外,PyTorch 对齐还需特别关注: +- op_compat.yaml:C++ 层参数名兼容性配置,支持 dim↔axis、input↔x 等参数名互换 +- python_api_info.yaml:Python API 名称注册,注册 paddle.xxx 和 paddle.Tensor.xxx 两种访问路径 + +### 8.11 多输出 OP 注意事项 + +YAML 配置、InferMeta 和 Kernel 函数签名中,多输出 OP 需为每个输出分别命名并传递指针。 + +## 九、更多信息 + +### 9.1 Paddle 基于 Yaml 配置自动生成算子代码的逻辑解读 + +Paddle 支持动态图和静态图两种模式,在 YAML 配置文件中完成算子基本属性的定义后,需要进行解析并分别生成动态图和静态图所对应的算子代码逻辑。 + +架构说明:当开发者添加一个新的 C++ 算子时,只需要完成 Kernel、算子定义 Yaml 配置文件和 Python API 三个部分的代码开发,其余部分都会通过自动代码生成来完成。 + +开发者需要开发的部分: +- Python API(python 层) +- YAML 配置文件(ops.yaml) +- Kernel 实现(CPU/GPU) + +自动代码生成部分: +- 动态图:Python-C 接口 -> Autograd API -> C++ API +- 静态图:OpMaker 注册 -> REGISTER_OPERATOR 等注册组件 + +动态图中自动生成的代码包括从 Python API 到计算 Kernel 间的各层调用接口实现: +- C++ API:一套与 Python API 参数对齐的 C++ 接口。前向算子生成的 C++ API 代码位于 `paddle/phi/api/include/` 目录下的 `api.h` 和 `api.cc`;反向算子生成的 C++ API 代码位于 `paddle/phi/api/backward/` 目录下的 `backward_api.h` 和 `backward_api.cc` +- 动态图前向函数与反向节点(Autograd API):在 C++ API 的基础上进行了封装,生成的相关代码位于 `paddle/fluid/eager/api/generated/eager_generated/forwards/` 目录下的 `dygraph_functions.h` 和 `dygraph_functions.cc` +- Python-C 函数:将支持自动微分功能的 C++ 的函数接口暴露到 Python 层,生成的 Python-C 接口代码位于 `paddle/fluid/pybind/` 目录下的 `eager_op_function.h` 和 `eager_op_function.cc` + +静态图的执行流程与动态图不同,Python API 主要负责组网,算子的调度和 kernel 计算由静态图执行器来完成,自动生成的代码是将配置文件中的算子信息注册到框架内供执行器调度,主要包括 Paddle/paddle/fluid/framework/op_proto_maker.h 中的 OpMaker 和 REGISTER_OPERATOR 等静态图算子注册组件。 diff --git a/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-new-api/references/new_python_api.md b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-new-api/references/new_python_api.md new file mode 100644 index 00000000000..d5852824892 --- /dev/null +++ b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/add-new-api/references/new_python_api.md @@ -0,0 +1,175 @@ +# 新增 Python 层 API + +本文档介绍如何在飞桨框架中开发 Python API,主要包括 API 代码开发、单元测试编写等内容。 + +## 一、获取并分析 PyTorch API 信息 + +若尚未获得 PyTorch API 的相关信息,则自行获取,获取方式请参考`api-compatibility/SKILL.md` 中的「3.6 API 信息获取方式」章节。 + +然后分析 PyTorch API 的功能和行为,在 Paddle 中新增对应的 API,使其与 PyTorch API 保持一致。具体包括:API 名称、调用路径、参数名及参数功能等。 + +## 二、开发 Python API 代码 + +Python API 的代码开发主要有两种方式:组合其他 Python API 实现和调用 C++ 算子接口实现。 + +### 2.1 方式一:组合其他 Python API + +这种方式适用于可以通过组合已有 Python API 来实现的新 API。以 zeros 函数为例,它通过组合 fill_constant 实现: + +```python +def zeros( + shape: ShapeLike, + dtype: DTypeLike | None = None, + name: str | None = None, +) -> paddle.Tensor: + if dtype is None: + dtype = paddle.get_default_dtype() + return fill_constant(value=0.0, shape=shape, dtype=dtype, name=name) +``` + +完整代码请参考 Paddle/python/paddle/tensor/creation.py。 + +### 2.2 方式二:调用 C++ 算子接口 + +如果 API 的实现需要调用 C++ 算子,则需要调用 C++ 算子对应的 Python C 函数。以 paddle.trace API 为例: + +```python +def trace( + x: Tensor, + offset: int = 0, + axis1: int = 0, + axis2: int = 1, + name: str | None = None, +) -> Tensor: + # 动静统一分支,直接调用算子对应的 Python C 函数 + if in_dynamic_or_pir_mode(): + return _C_ops.trace(x, offset, axis1, axis2) +``` + +完整代码请参考 Paddle/python/paddle/tensor/math.py。 + +关键要点: +- _C_ops:位于 Paddle/python/paddle/_C_ops.py,是从 Paddle 编译得到的二进制文件中导入的 Python C 函数,与 C++ 算子对应 +- trace:Python API 对应的 C++算子名 +- 参数顺序:(x, offset, axis1, axis2) 需按照 YAML 配置文件(paddle/phi/ops/yaml/ops.yaml)中定义的顺序传入 + +注意:目前飞桨动态图与 PIR 模式已统一,使用 in_dynamic_or_pir_mode() 进行判断即可,新增 API 时无需添加老静态图分支代码。 + +## 三、将 API 绑定为 Tensor 方法 + +Paddle 中的许多计算函数,既能够作为独立函数使用,也能作为 Tensor 的方法使用: + +```python +x = paddle.randn([2, 3]) +paddle.abs(x) # 与 x.abs() 等价 +paddle.sin(paddle.abs(x)) # 与 x.abs().sin() 等价 +paddle.sum(x, axis=0) # 与 x.sum(axis=0) 等价 +``` + +当作为 Tensor 方法调用时,相当于自动把该 Tensor 作为独立函数的第一个参数传入。 + +**新增 Tensor 方法的详细说明请参考**:主 SKILL 文档的「3.5 类方法 API 实现原理」章节。 + +## 四、设置 API 公开名称与别名 + +API 开发完成后需关注: +- 公开 API 需加入对应目录 __init__.py 的 __all__ 列表 +- 常用 API 可在更高层级建立别名,如 paddle.tensor 下的 API 可在 paddle 根目录建立别名 + +以 paddle.trace API 为例,其 trace 函数定义在 Paddle/python/paddle/tensor/math.py 中,又在 Paddle/python/paddle/tensor/__init__.py 中被 import,并且也在 python/paddle/__init__.py 中被 import。 + +```python +# Paddle/python/paddle/tensor/math.py +def trace(...): + ... + +# Paddle/python/paddle/tensor/__init__.py +from .math import trace + +# Paddle/python/paddle/__init__.py +from .tensor.math import trace +``` + +以 paddle.trace 为例,设置 paddle.trace 为正式名称: +- 仅在 Paddle/python/paddle/__init__.py 的 __all__ 中加入 'trace' +- 不在 Paddle/python/paddle/tensor/__init__.py 和 Paddle/python/paddle/tensor/math.py 的 __all__ 中加入 + +说明:当出现类似把一个元素放入一个集中管理的列表的操作时,可以考虑按照字母表顺序插入列表中的合适位置。如果有多人同时新增 API 时,这样的方式比直接加在末尾更不容易出现冲突。 + +## 五、添加单元测试 + +### 5.1 Python API 单元测试 + +单测文件存放路径和命名方式:在 Paddle/test/legacy_test/ 目录下,以 test_xxx.py 的形式命名(假设 Python API 名为 xxx)。 + +Python API 的单元测试继承 unittest.TestCase,用 NumPy/SciPy 对应功能作为参考基准进行测试。参考示例:Paddle/test/legacy_test/test_activation_op.py。 + +开发步骤: +1. 用 NumPy/SciPy 实现用于对比结果的计算函数(NumPy/SciPy 有现成函数时可跳过这一步) +2. 在 setUp 函数中定义输入等相关属性参数 +3. 实现动态图以及 PIR 分支单元测试代码 + +示例代码: + +```python +# 使用 numpy 实现对比函数(NumPy 无现成函数时需自行实现) +def ref_hardtanh(x, min=-1.0, max=1.0): + out = np.minimum(np.maximum(x, min), max) + return out + +class TestHardtanhAPI(unittest.TestCase): + def setUp(self): + np.random.seed(1024) + self.x_np = np.random.uniform(-3, 3, [10, 12]).astype('float32') + self.place = paddle.CUDAPlace(0) if paddle.is_compiled_with_cuda() else paddle.CPUPlace() + + # 静态图单测 + def test_static_api(self): + with static_guard(): + with paddle.static.program_guard(paddle.static.Program()): + x = paddle.static.data('X', [10, 12], dtype="float32") + out1 = F.hardtanh(x) + exe = paddle.static.Executor(self.place) + res = exe.run(feed={'X': self.x_np}, fetch_list=[out1]) + np.testing.assert_allclose(ref_hardtanh(self.x_np), res[0], rtol=1e-05) + + # 动态图单测 + def test_dygraph_api(self): + with dynamic_guard(): + x = paddle.to_tensor(self.x_np) + out = F.hardtanh(x) + np.testing.assert_allclose(ref_hardtanh(self.x_np), out.numpy(), rtol=1e-05) + + # 错误处理测试(可选) + def test_errors(self): + with static_guard(): + with paddle.static.program_guard(paddle.static.Program()): + # 测试非法输入类型 + self.assertRaises(TypeError, F.hardtanh, 1) +``` + +开发要点: +- 必须添加动态图和静态图测试用例,确保对应情况工作正常 +- 通常无需测试反向计算(C++ 算子单测已覆盖反向算子功能) +- 数值对比用 numpy.testing.assert_allclose(actual, desired) 或 numpy.allclose(actual, desired) +- 每个 case 开头需用 static_guard() 或 dynamic_guard() 显式切换运行模式 + +### 5.2 编译并运行单测(每次修改均需执行) + +单测编写完成后,按以下命令验证执行: + +```bash +cd ${ROOT_DIR}/Paddle/build +cmake .. && make -j$(nproc) +python test_xxx.py +``` + +根据报错信息修改代码,确保所有测试用例通过。每次修改后均需要重新执行本步骤。 + +**编译注意事项**: +- 无需重装,直接生效(勿执行 setup/install 等安装操作) +- 勿删除 build 目录(否则增量编译失效,编译时间极长) + +## 六、新增 API 英文文档 + +完成 API 代码开发后,需要新增 API 英文文档。详细规范请参考:API 文档书写规范(references/api_docs_guidelines.md) 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 index ab7a6ac339f..236d9aa44ef 100644 --- 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 @@ -1,191 +1,165 @@ --- name: api-change-decider description: 负责《Paddle API 对齐 PyTorch 项目》中 Step1:方案决策,分析 PyTorch API 与 Paddle API 之间的差异,制定合适的 API 改动方案 +context: fork +agent: Explore disable-model-invocation: false --- -# 一、输入输出规范 - -## 1.1 输入 -需要对齐的 PyTorch API 列表(如 `torch.atan`、`torch.asinh`) - -## 1.2 输出 -以表格形式展示,列说明如下: - -| Pytorch API | 方案类型 | Paddle API | 差异分类 | 决策依据 | -|-|-|-|-|-| -| 需对齐的 PyTorch API | 从方案 1~6 中选择 | 需改动的 Paddle API 完整路径 | 差异分类 | 总结差异分析过程和选择理由 | - -以下为示例表格: - -| Pytorch API | 方案类型 | Paddle API | 差异分类 | 决策依据 | -|-|-|-|-|-| -| torch.atan | 方案 2 | paddle.atan | torch 参数更多 | 仅参数名不同(input→x)+仅多 out 参数,Python 实现仅有一次`_C_ops.atan(x)`调用,满足 C++下沉条件,性能最优 | -| torch.select_scatter | 方案 1 | paddle.select_scatter | 仅参数名不一致 | input→x, src→values, dim→axis 参数名不同。底层调用 `_C_ops.set_value_with_tensor`(非 `_C_ops.select_scatter`),不满足方案 2 条件 1,降级为方案 1(Python 装饰器) | -| torch.Tensor.select_scatter | 方案 1 | paddle.Tensor.select_scatter | 仅参数名不一致 | 与 paddle.select_scatter 共享实现(patch 机制),随主 API 一并修改 | -| torch.logspace | 方案 3 + 方案 1 | paddle.logspace | 参数名不一致 + torch 参数更多 | 全文有两类差异:①end→stop/steps→num 参数名不同;②torch 多 out/device/requires_grad 参数。方案 3 末尾新增 out=None/device=None/requires_grad=False(后向兼容);方案 1 装饰器添加 end/stop、steps/num 别名 | - - -# 二、候选方案 +# 一、候选方案 ## 方案 1:Python 装饰器 **适用场景**: -- 支持多种参数重载情况:参数名不同、参数顺序不同、参数个数不同、参数类型不同 -- 重载条件:能通过输入参数特征(名称、类型、个数)区分两套签名 +参数顺序不同、参数个数不同、参数类型不同、参数名不同 -**工作原理**: -根据输入参数的名称、类型、个数判断是 PyTorch 签名还是 Paddle 签名,针对两套签名分别适配功能,既保留原有 Paddle 功能也实现了 PyTorch 对齐 - -**核心限制**: -- **重载条件**:能通过输入参数特征(名称、类型、个数)区分两套签名 -- **无法重载则不适用**:如果无法通过输入参数特征区分两套签名,则不适用 +**适用条件**(必须满足): +1. API 路径一致 +2. 重载条件:两套签名必须存在参数特征差异(个数、类型、名称至少一项不同),且根据调用时传入的参数能够唯一判定应该使用哪个签名: +- 参数个数不同:如 func(a) vs func(a, b),根据传入的参数个数不同,可以唯一确定调用的是哪个函数。(默认参数可不传入,不计入个数) +- 参数类型不同:如 func(x: Tensor, y: Tensor) vs func(x: Tensor, y: int),根据传入的第二个位置参数的类型不同,可以唯一确定调用的是哪个函数。 +- 参数名称不同:如 func(a, b) vs func(a, c),根据调用时使用的关键字参数名不同(如 `func(a=1, b=2)` vs `func(a=1, c=2)`),可以唯一确定调用的是哪个函数。 **优点**:灵活性强,兼容性好 **缺点**:性能低于 C++ 下沉实现 ## 方案 2:C++ 下沉 **适用场景**: -- 仅涉及参数名不同或仅多 out 参数的情况 +仅涉及参数名不同或仅多 out 参数的情况 **适用条件**(必须全部满足): -1. API 名称和 `_C_ops.xxx` 的 OP 名称一致 - - ✅ 正确示例:`paddle.atan` 调用 `_C_ops.atan` - - ❌ 错误示例:`paddle.select_scatter` 调用 `_C_ops.set_value_with_tensor`(名称不一致) -2. API 差异**仅为参数名不同或仅多 out 参数**(不涉及参数顺序或个数差异;"仅多 out 参数"的精确含义见 2.1 关键概念) -3. Python 实现中**仅有一次** `_C_ops.xxx` 调用 -4. Python 实现中不涉及其他 Paddle API 调用 - - ❌ **禁止**:调用任何其他 paddle API(如 `paddle.where`、`paddle.abs`、`paddle.flatten` 等) - - ❌ **禁止**:调用 Tensor 方法(如 `x.flatten()`、`x.reshape()`、`x.unsqueeze()` 等,这些等同于调用 paddle API) - - ❌ **禁止**:使用 `paddle.full_like`、`paddle.zeros_like`、`paddle.cast` 等 - - ✅ **允许**:Python 内置函数(如 `len()`、`isinstance()`、`list()`、`range()` 等) - - ✅ **允许**:简单的属性访问(如 `x.shape`、`x.dtype`、`x.ndim` 等) - - ✅ **允许**:简单的算术运算(如 `index + 1`、`axis < 0` 等) -5. `_C_ops.xxx` 前面**无复杂前处理逻辑**,前处理逻辑(如存在)容易改写为 C++ - - ❌ **复杂前处理**(不满足条件): - - 涉及多个 paddle API 调用的逻辑(如 `fill_constant`、`paddle.where`、`paddle.cast` 等) - - 复杂的 shape 处理逻辑(如 `reshape`、`transpose`、`flatten` 等) - - 条件分支中的 paddle API 调用 - - 循环中的 paddle API 调用 - - ✅ **简单前处理**(满足条件): - - 仅做参数校验(如 `isinstance()`、类型检查、范围检查) - - 仅做参数类型转换(如 `convert_np_dtype_to_dtype_`) - - 仅做简单参数处理(如负索引转正索引、默认值设置) - - 仅做属性获取(如 `x.shape`、`x.dtype`) - -**注意**: -- 读取 Python 实现逻辑时,忽略静态图部分(LayerHelper 分支代码不再维护) +1. API 路径一致 +2. API 名称和 `_C_ops.xxx` 的 OP 名称一致 + - ✅ 正确示例:`paddle.atan` 调用 `_C_ops.atan` + - ❌ 错误示例:`paddle.select_scatter` 调用 `_C_ops.set_value_with_tensor`(名称不一致) +3. API 差异**仅为参数名不同或仅多 out 参数**(不涉及参数顺序或个数差异) + - torch 比 paddle 恰好只多一个参数 `out`,其余参数完全一致 + - ✅ 符合:仅多 `out` 一个参数 + - ❌ 不符合:多两个及以上参数、多的参数不是 `out`、还存在参数名不同等其他差异 +4. Python 实现中**仅有一次** `_C_ops.xxx` 调用 +5. Python 实现中不涉及其他 Paddle API 调用 + - ❌ **禁止**:调用任何其他 paddle API(如 `paddle.where`、`paddle.abs`、`paddle.flatten` 等) + - ❌ **禁止**:调用 Tensor 方法(如 `x.flatten()`、`x.reshape()`、`x.unsqueeze()` 等,这些等同于调用 paddle API) + - ❌ **禁止**:使用 `paddle.full_like`、`paddle.zeros_like`、`paddle.cast` 等 + - ✅ **允许**:Python 内置函数(如 `len()`、`isinstance()`、`list()`、`range()` 等) + - ✅ **允许**:简单的属性访问(如 `x.shape`、`x.dtype`、`x.ndim` 等) + - ✅ **允许**:简单的算术运算(如 `index + 1`、`axis < 0` 等) +6. `_C_ops.xxx` 前面**无复杂前处理逻辑**,前处理逻辑(如存在)容易改写为 C++ + - ❌ **复杂前处理**(不满足条件): + - 涉及多个 paddle API 调用的逻辑(如 `fill_constant`、`paddle.where`、`paddle.cast` 等) + - 复杂的 shape 处理逻辑(如 `reshape`、`transpose`、`flatten` 等) + - 条件分支中的 paddle API 调用 + - 循环中的 paddle API 调用 + - ✅ **简单前处理**(满足条件): + - 仅做参数校验(如 `isinstance()`、类型检查、范围检查) + - 仅做参数类型转换(如 `convert_np_dtype_to_dtype_`) + - 仅做简单参数处理(如负索引转正索引、默认值设置) + - 仅做属性获取(如 `x.shape`、`x.dtype`) **优点**:性能最优 **缺点**:限制条件很多,需严格满足 -## 方案 3:修改 API +## 方案 3:修改原有 API **适用场景**: -- API 相对引用路径一致,但存在可通过修改原有实现来对齐的差异 -- 包括以下情况: - 1. **新增参数**:向现有 API 添加新参数(如添加 out 参数) - 2. **参数默认值调整**:修改参数默认值以对齐 PyTorch - 3. **扩展参数功能**:对已有参数扩展新功能同时保留原有功能 - 4. **参数用法调整**:修改参数的处理逻辑以支持不同用法 - -**适用条件**(必须全部满足)**: -1. API 相对引用路径一致 -2. 修改必须保持后向兼容性,例如: - - 在 API 参数末尾添加有默认值的参数 - - 对已有参数保留原有功能时,扩展新功能 - -**不适用条件**(❌ 禁止)**: +同方案 1(参数顺序、个数、类型、名称差异),但通过直接修改原 API 实现而非装饰器 + +**适用条件**(必须全部满足): +1. API 路径一致 +2. 修改必须能保持后向兼容性 + +**不适用条件**(❌ 禁止): - 改变已有参数顺序 - 改变已有参数名称 - 修改返回值类型 - 删除现有参数 -- 修改现有参数的默认值 +- 修改现有参数的默认行为 + +**修改模式**: +- 新增参数:向现有 API 添加新参数 +- 扩展参数类型/功能:对已有参数扩展新功能同时保留原有功能 ## 方案 4:新增 API **适用场景**: -- 无法通过修改现有 API 实现对齐,需要新增 API 来实现功能 -- 具体包括以下情况: - 1. **API 相对引用路径不一致**:API 调用方式、模块路径与 PyTorch 不同 - 2. **仅 API 调用方式不一致**:API 相对引用路径不一致,但参数完全相同 - 3. **组合替代实现**:PyTorch API 需多个 Paddle API 组合实现 - 4. **API 别名**:为 PyTorch 别名 API 新增对应的 Paddle 别名 API - 5. **功能缺失**:Paddle 暂无等效实现,需新增完整 API +- API 路径不一致 +- 仅 API 调用方式不一致 +- 组合替代实现 +- API 别名 +- 功能缺失 + +**修改模式**: +1. 新增 API 别名:PyTorch API 与 Paddle API 功能完全一致,仅 API 路径/名称不同 +2. 新增 Python 层 API:需在 Python 层实现新 API +3. 新增 OP:需在 C++ 层新增底层算子 ## 方案 5:新增 compat 类型 API -**适用场景**: -- 无法通过其他方案实现对齐的退而求其次方案 -- 具体包括以下情况: - 1. **无法原地修改**:修改现有 API 会引入严重的后向兼容性问题 - 2. **无法新增 API**:PyTorch 对应的 API 路径在 Paddle 中已被占用 - 3. **返回值不一致**:修改返回值类型会破坏现有调用 - 4. **无法重载**:通过装饰器无法根据输入参数特征区分两套签名 +**适用条件**(必须全部满足): +1. API 路径一致 +2. 不满足方案 1/2/3 的适用条件 -**注意**: -- 新增对标 PyTorch 的兼容 API(位于 `paddle.compat.*` 路径下),不影响原 Paddle API -- 新增 API 需与 PyTorch 完全一致(路径、参数、返回值、行为) +**典型场景**: +- API 路径一致,但修改原有 API 会破坏后向兼容性(如修改返回值类型) +- API 路径一致,但不满足装饰器的重载条件 +**不适用条件**(❌ 禁止): +- API 路径不一致 -# 三、标准工作流程 +**实现要点**: +- 新增兼容 API 位于 `paddle.compat.*` 路径下,不影响原 Paddle API -## Step 1: 差异分析 -### 1.1 获取差异信息 +# 二、标准工作流程 -> **⚠️ 短路逻辑(必须严格遵守)**: -> - 按**优先级 1 → 2 → 3 的顺序**依次尝试 -> - **一旦从某个来源获取到完整差异信息,立即停止,不再查找其他来源** -> - 这是"三选一"而非"三者都要",严禁同时查找多个来源 +## Step 1: 获取差异信息 -#### 优先级 1:查阅 API 差异文档 ⭐ -- 查阅 PyTorch API 对应的 API 差异文档(`torch.{api_name}.md`,位于 `${ROOT_DIR}/docs/docs/guides/model_convert/convert_from_pytorch/api_difference/` 目录下) -- 如果文档存在且信息完整(包含对应 Paddle API、转写示例),则以此为准 ✅ **→ 停止查找,进入下一步** -- 如果文档不存在或信息不完整 → 继续优先级 2 +按优先级依次查找差异信息,一旦获取完整信息立即停止,这是"三选一"而非"三者都要"。 -> **⚠️ 注意:差异文档标题仅反映主要分类,须阅读全文** -> - 差异文档的标题头(如 `[ torch 参数更多 ]`、`[ 仅参数名不一致 ]`)只选取了**最显著的一类差异**作为标题 -> - 一个 API 往往同时涉及多种差异,例如既有参数名不同,又有 torch 多出 out 参数 -> - 必须通读差异文档的**完整参数映射表**,提取所有差异点,不能仅以文件标题作为差异分类的唯一依据 +### 优先级 1:查阅 API 差异文档 +查阅 `${ROOT_DIR}/docs/docs/guides/model_convert/convert_from_pytorch/api_difference/torch.{api_name}.md`。如果文档存在且完整(包含对应 Paddle API、转写示例),则以此为准并停止查找;否则继续优先级 2。 -#### 优先级 2:查询转写配置 ⭐⭐ -- 查询 PyTorch API 对应的转写配置(位于 `${ROOT_DIR}/PaConvert/paconvert/api_mapping.json` 或 `${ROOT_DIR}/PaConvert/paconvert/attribute_mapping.json`) -- 根据 Matcher 类型和字段内容分析差异分类和对应的 Paddle API -- 如果能从中获取到差异信息 ✅ **→ 停止查找,进入下一步** -- 如果转写配置不存在或无法获取信息 → 继续优先级 3 -- **转写配置字段说明**: +**注意**: +- 文档可能有细微错误,当发现问题可自行分析,或结合源码进一步确认 +- 一个 API 可能涉及多种差异,差异文档标题仅反映主要分类,必须通读完整参数映射表提取所有差异,不能仅看标题 +- 忽略参数:torch 侧 `generator`、`memory_format`、`layout`;paddle 侧 `name`。其他参数不可忽略 + +### 优先级 2:查询转写配置 +查询 `${ROOT_DIR}/PaConvert/paconvert/api_mapping.json` 或 `attribute_mapping.json`,根据 Matcher 类型分析差异分类。如果能直接从配置中提取完整差异信息,则以此为准并停止查找;否则继续优先级 3。 + +| 字段 | 对应差异分类 | +|------|------------| +| `ChangePrefixMatcher` | API 完全一致 | +| `ChangeAPIMatcher` / `NumelMatcher` / `TensorFunc2PaddleFunc` / `Func2Attribute` / `Attribute2Func` | 仅 API 调用方式不一致(若同时包含 `unsupport_args`、`kwargs_change` 字段,则存在多重差异) | +| `GenericMatcher` | 参数名不一致 / paddle 参数更多 / 参数默认值不一致 / torch 参数更多 | +| `paddle_api` | API 映射关系 | +| `kwargs_change` | 参数名不一致 | +| `unsupport_args` | torch 参数更多(部分不支持) | +| `paddle_default_kwargs` | 参数默认值不一致 / paddle 参数更多 | +| 其他自定义 Matcher | 参数用法/类型不一致 / 组合替代实现 | -| 字段 | 说明 | 对应差异分类 | -|------|------|------------| -| `Matcher: "ChangePrefixMatcher"` | API 完全一致,仅框架前缀不同(torch→paddle),无需任何参数转换 | API 完全一致 | -| `Matcher: "ChangeAPIMatcher"` | API 调用方式不同,但功能一致,无需参数转换 | 仅 API 调用方式不一致 | -| `Matcher: "NumelMatcher"` | 特定 API(如 torch.numel)的专用转换器 | 仅 API 调用方式不一致 | -| `Matcher: "TensorFunc2PaddleFunc"` | Tensor 类方法转为 Paddle 函数,如 `x.func()` → `paddle.func(x)` | 仅 API 调用方式不一致(无其他差异时) | -| `Matcher: "Func2Attribute"` | 函数调用转为属性访问,如 `x.func()` → `x.attr` | 仅 API 调用方式不一致(无其他差异时) | -| `Matcher: "Attribute2Func"` | 属性访问转为函数调用,如 `x.attr` → `x.func()` | 仅 API 调用方式不一致(无其他差异时) | -| `Matcher: "GenericMatcher"` | 通用转换器,适用于可直接映射的 API | 仅参数名不一致 / 仅 paddle 参数更多 / 仅参数默认值不一致 / torch 参数更多| -| `paddle_api` | 对应的 Paddle API 完整路径,如 `"paddle.transpose"` | 表示 API 映射关系| -| `kwargs_change` | 参数名映射关系,如 `{"input": "x", "dims": "perm"}` | 仅参数名不一致 | -| `unsupport_args` | 不支持的参数列表,如 `["stride"]` | torch 参数更多(部分不支持) | -| `paddle_default_kwargs` | Paddle 需要设置的默认参数,如 `{"axis": -1}` | 参数默认值不一致 / paddle 参数更多 | -| 其他自定义 `Matcher` | 除上述 Matcher 外的其他自定义 Matcher 名称 | 参数用法/类型不一致 / 组合替代实现等复杂情况 | +**注意**: +- 更多 Matcher 可参考 `api_matcher.py` 分析。 -- **注意**: -- 当 `TensorFunc2PaddleFunc`、`Func2Attribute`、`Attribute2Func` 同时包含 `unsupport_args`、`kwargs_change` 或 `paddle_default_kwargs` 字段时,说明除了调用方式差异外,还存在其他差异,不能归类为"仅 API 调用方式不一致" -- 更多的分类映射关系则需要自行分析,可结合 `${ROOT_DIR}/PaConvert/paconvert/api_matcher.py` 中对应 Matcher 的实现逻辑进行差异分类 +### 优先级 3:自行获取 API 信息 +当优先级 1 和优先级 2 均无法获取完整差异信息时,通过多种方式自行获取 PyTorch API 和 Paddle API 的信息,进行对比分析。 -#### 优先级 3:网络搜索 API 文档 ⭐⭐⭐ -- 从网络中分别搜索: - 1. PyTorch 官方 API 文档(https://pytorch.org/docs/stable/) - 2. Paddle 官方 API 文档(https://www.paddlepaddle.org.cn/documentation/docs/zh/api/) -- 对比两者的 API 文档,手动分析差异,获取 API 的签名、参数、返回值等信息 +获取方式请参考`api-compatibility/SKILL.md` 中的「3.6 API 信息获取方式」章节。 +## Step 2:提取差异信息 -### 1.2 确定差异分类 -差异分类共 13 类,具体如下: +根据获取到的 PyTorch API 与 Paddle API 信息,提取以下内容: + +| 项目 | 内容 | +|------|------| +| API 映射 | PyTorch API vs 对应的 Paddle API(可能为空)| +| 差异分类 | 可包含多种差异分类 | +| 参数映射 | 参数对应关系和差异说明 | +| 转写示例 | 代码转换示例 | + +候选差异分类为: | 序号 | 差异分类 | 说明 | -|:---:|:---|:---| +|:---:|:---|---| | 1 | API 完全一致 | 无差异,无需改动 | -| 2 | 仅 API 调用方式不一致 | API 相对引用路径不一致,但参数完全相同 | +| 2 | 仅 API 调用方式不一致 | API 路径不一致,但参数完全相同 | | 3 | 仅参数名不一致 | 参数功能相同但参数名称不同 | | 4 | paddle 参数更多 | Paddle 提供更多可选参数 | | 5 | 参数默认值不一致 | 参数默认值不同 | @@ -198,219 +172,186 @@ disable-model-invocation: false | 12 | API 别名 | PyTorch API 是其他 API 的别名 | | 13 | 功能缺失 | Paddle 暂无等效实现 | -### 1.3 提取差异信息 +## Step 3: 方案决策 -总结相关差异信息,提供给 Step2 进行方案决策: +### 3.1 判断 API 路径一致性 -| 项目 | 内容 | -|------|------| -| 差异分类 | 具体差异分类 | -| API 映射 | PyTorch API vs 对应的 Paddle API(可能为空)| -| 参数映射 | 参数对应关系和差异说明 | -| 转写示例 | 代码转换示例 | +**定义**:API 路径 = 去掉框架前缀后的调用路径(包括名称)。 + +**判断方法**:执行 `python -c "import paddle; paddle.{path}"` 验证是否存在。 + +| PyTorch API | API 路径 | Paddle 对应 | 路径一致性 | +|-------------|---------|-------------|:---:| +| `torch.trunc` | `trunc` | `paddle.trunc` | ✅ 一致 | +| `torch.histc` | `histc` | `paddle.histogram` | ❌ 不一致 | +| `torch.Tensor.addmv_` | `Tensor.addmv_` | 无 | ❌ 不一致 | +| `torch.autograd.enable_grad` | `autograd.enable_grad` | `paddle.autograd.enable_grad` | ✅ 一致 | -注意以下参数直接忽略,不视作差异信息: -1. 忽略第 1 列的 generator、memory_format、layout 参数 -2. 忽略第 2 列的 name 参数 +**示例**: + +```bash +python -c "import paddle; paddle.trunc" # 存在 → 路径一致 +python -c "import paddle; paddle.addmv" # AttributeError → 路径不一致 +python -c "import paddle; paddle.Tensor.addmv_" # AttributeError → 路径不一致 +``` + +**输出**:记录路径一致性结果(一致/不一致),用于选择决策表。 + + +### 3.2 查表决策 + +根据 3.1 的路径一致性结果选择决策表:**路径不一致** → 决策表 A;**路径一致** → 决策表 B。 + +**方案切换说明**:`→` 表示「不满足当前条件则切换到下一个备选方案」。 +- 方案 2 → 方案 1:不满足 C++ 下沉条件 +- 方案 3 → 方案 1:修改会破坏后向兼容 +- 方案 1 → 方案 5:不满足装饰器重载条件 + +**决策表 A:路径不一致**(必须包含方案 4) + +| 差异分类 | 决策链(→ 备选) | +|----------|------------------| +| 分类 2, 10, 12, 13 | 方案 4(新增 Python 层 API 或 C++ 算子) | +| 分类 3 | 方案 2 + 方案 4(新增 API 别名)→ 方案 1 + 方案 4(新增 API 别名) | +| 分类 4, 5 | 不影响对齐 → 无需修改;否则 → 方案 3 + 方案 4(新增 API 别名)→ 方案 4(新增 Python 层 API) | +| 分类 6 | 仅多 out 且满足方案 2 条件 → 方案 2 + 方案 4(新增 API 别名);否则 → 方案 3 + 方案 4(新增 API 别名)→ 方案 1 + 方案 4(新增 API 别名)→ 方案 4(新增 Python 层 API) | +| 分类 7, 8 | 方案 3 + 方案 4(新增 API 别名)→ 方案 1 + 方案 4(新增 API 别名)→ 方案 4(新增 Python 层 API) | +| 分类 9 | 方案 4(新增 Python 层 API) | +| 分类 11 | 无需修改 | + +**决策表 B:路径一致** + +| 差异分类 | 决策链(→ 备选) | +|----------|------------------| +| 分类 1, 11 | 无需修改 | +| 分类 3 | 方案 2 → 方案 1 | +| 分类 4, 5 | 不影响对齐 → 无需修改;否则 → 方案 3 → 方案 5 | +| 分类 6 | 仅多 out 且满足方案 2 条件 → 方案 2;否则 → 方案 3 → 方案 1 → 方案 5 | +| 分类 7, 8 | 方案 3 → 方案 1 → 方案 5 | +| 分类 9 | 方案 5 | + + +### 3.3 多差异分类组合决策 + +当 API 存在多个差异分类时,**逐个差异分类独立查表,再合并方案**。 + +**步骤 1**:遍历每个差异分类,根据决策表独立查表得到对应方案。 + +**步骤 2**:对多个方案执行合并。 -> 只能忽略上述明确列出的参数,**其他参数不可忽略**。例如 **device、out、requires_grad、dtype** 等不可忽略,必须处理。 +| 规则 | 判断条件 | 操作 | +|------|----------|------| +| 去重 | 多个差异分类对应相同方案 | 只保留一个 | +| 互斥 | 方案 2 与方案 1 同时出现 | 仅保留方案 2 | +| 组合 | 不同方案且不互斥 | 按编号升序,用 `+` 连接 | -## Step 2: 方案决策 +**步骤 3**:输出最终组合方案。 -### 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` +| API | 差异分类 | 查表结果 | 合并结果 | +|-----|----------|----------|----------| +| torch.atan | 分类 3 + 分类 6 | 分类 3 → 方案 2;分类 6 → 方案 2 | 方案 2(去重) | +| torch.logspace | 分类 3 + 分类 6 | 分类 3 → 方案 1;分类 6 → 方案 3 | 方案 3 + 方案 1 | +| torch.histc | 分类 2 + 分类 6 + 分类 9 | 分类 2 → 方案 4;分类 6 → 方案 4;分类 9 → 方案 4 | 方案 4(去重) | -**仅多 out 参数**:torch 比 paddle **恰好只多一个参数**,且该参数名为 `out`,其余所有参数完全一致 -- ✅ 符合:torch 签名比 paddle 签名仅多 `out` 一个参数,其余参数完全一致 -- ❌ 不符合:torch 比 paddle 多两个及以上参数(即使其中包含 `out`,如同时多了 `out` 和 `requires_grad`) -- ❌ 不符合:torch 多的参数不是 `out`(如仅多 `dtype`) -- ❌ 不符合:除参数个数差异外,还存在参数名不同等其他差异(此时差异分类应合并为"torch 参数更多 + 参数名不一致",不满足"仅多 out 参数") -### 2.2 关键原则 +# 三、输出要求 -> **⚠️ 严格遵循**:决策时必须严格遵守以下原则,不得主观臆断: -> -> 1. **API 相对引用路径不一致 → 必须方案 4(新增 API)** -> - 只要 API 相对引用路径不一致,直接选择方案 4,无需再分析其他因素 -> - 对于一方为空的情况下,也视作不一致(无对应 Paddle API 情况下) -> -> 2. **严格按流程图和规则判断** -> - 必须按照决策流程图的路径执行 -> - 严格按照各方案的适用条件判断 -> - 不得因"可以"、"应该"等主观判断偏离规则定义 +**重要**:本 SKILL 以 agent 模式执行,上下文不共享。完成决策后,必须按以下格式输出完整信息,供后续步骤使用。 -### 2.3 决策流程图 +## 输出格式 ``` -开始 - │ - ├───→ API 相对引用路径是否一致? ──────┐ - │ │ - │是 │否 - ↓ ↓ -具体有哪些差异? 方案 4(新增 API)→结束 - │ - ├──→ 1. API 完全一致 → 方案 6(无需改动)→结束 - │ - ├──→ 2. 仅 API 调用方式不一致 → 方案 4(新增 API)→结束 - │ - ├──→ 3. 仅参数名不一致 → 方案 2(C++下沉)→ 不适用则方案 1→结束 - │ - ├──→ 4. paddle 参数更多 → 是否影响对齐?─┬→否→方案 6(无需改动)→结束 - │ └→是→方案 3(修改 API)→导致后向不兼容则方案 5→结束 - │ - ├──→ 5. 参数默认值不一致 → 是否影响对齐?─┬→否→方案 6(无需改动)→结束 - │ └→是→方案 3(修改 API)→导致后向不兼容则方案 5→结束 - │ - ├──→ 6. torch 参数更多 → 仅多 out 参数(见 2.1 定义)?─┬→是→方案 2(C++下沉)→不适用则方案 1→结束 - │ └→否→方案 3(修改 API)→导致后向不兼容则方案 1→无法重载则方案 5→结束 - │ - ├──→ 7. 输入参数用法不一致 → 方案 3(修改 API)→导致后向不兼容则方案 1→无法重载则方案 5→结束 - │ - ├──→ 8. 输入参数类型不一致 → 方案 3(修改 API)→导致后向不兼容则方案 1→无法重载则方案 5→结束 - │ - ├──→ 9. 返回参数类型不一致 → 方案 5(新增 compat 类型 API)→结束 - │ - ├──→ 10. 组合替代实现 → 方案 4(新增 API)→结束 - │ - ├──→ 11. 可删除 → 方案 6(无需改动)→结束 - │ - ├──→ 12. API 别名 → 方案 4(新增 API)→结束 - │ - └──→ 13. 功能缺失 → 方案 4(新增 API)→结束 +### [序号] PyTorch API 名称 + +**基本信息** +- PyTorch API:`完整签名` +- Paddle API:`完整签名`(如无对应则填"无") +- 实现位置:`文件路径:行号`(如未获取则填"未获取") + +**差异分析** +- API 路径一致性:一致 / 不一致 +- 差异分类:`分类名称`(多个分类用" + "连接) +- 具体差异: + - 参数名映射:`torch 参数 → paddle 参数`(如有) + - 额外参数:torch 多出 `xxx, yyy`(如有) + - 参数类型:`参数名: torch 类型 vs paddle 类型`(如有) + +**方案决策** +- 选择方案:`方案 N(方案名称)` 或 `方案 N1 + 方案 N2` +- 决策依据:`简述选择理由` +- 组合说明:`分别说明各方案解决的差异点`(仅组合方案需要) ``` -### 2.4 详细决策规则 - -**前置判断**:分类 3~10 判定规则均基于**API 相对引用路径一致**的前提。只要 API 相对引用路径不一致,直接选择**方案 4(新增 API)**,无需进行如下判断。 - -#### 1. API 完全一致 -- **决策**:方案 6(无需改动) - -#### 2. 仅 API 调用方式不一致 -- **决策**:方案 4(新增 API) -- **说明**:API 相对引用路径不一致,但参数完全相同,需要新增 API 来实现路径对齐 - -#### 3. 仅参数名不一致 -- **优先级 1**:方案 2(C++下沉) - - **前置检查**:确认满足方案 2 的全部适用条件 - - **任一不满足** → **优先级 2** -- **优先级 2**:方案 1(Python 装饰器) - - 当方案 2 不满足时,使用方案 1 - -#### 4. paddle 参数更多 -- **判断**:额外参数是否影响对齐 - - **否**(例如默认参数,Paddle 保持默认即可)→ 方案 6(无需改动) - - **是** → 方案 3(修改 API) - - **检查兼容性**:修改是否会引入后向不兼容问题 - - **会导致后向不兼容** → 方案 5(新增 compat 类型 API) - -#### 5. 参数默认值不一致 -- **判断**:不一致的默认值是否影响对齐 - - **否**(例如不影响结果的参数)→ 方案 6(无需改动) - - **是** → 方案 3(修改 API) - - **检查兼容性**:修改是否会引入后向不兼容问题 - - **会导致后向不兼容** → 方案 5(新增 compat 类型 API) - -#### 6. torch 参数更多 -- **判断**:是否仅多 out 参数(详见 2.1 定义,必须满足"恰好只多一个参数且该参数名为 out") - - **是**: - - **优先级 1**:方案 2(C++下沉) - - **前置检查**:确认满足方案 2 的全部 5 个适用条件 - - **任一不满足** → **优先级 2** - - **优先级 2**:方案 1(Python 装饰器) - - **否**: - - **优先级 1**:方案 3(修改 API) - - **检查兼容性**:修改是否会引入后向不兼容问题 - - **会导致后向不兼容** → **优先级 2** - - **优先级 2**:方案 1(Python 装饰器) - - **检查能否区分**:能否根据输入参数特征区分两套签名 - - **无法重载** → **优先级 3** - - **优先级 3**:方案 5(新增 compat 类型 API) - -#### 7. 输入参数用法不一致 -- **优先级 1**:方案 3(修改 API) - - **检查兼容性**:修改是否会引入后向不兼容问题 - - **会导致后向不兼容** → **优先级 2** -- **优先级 2**:方案 1(Python 装饰器) - - **检查能否区分**:能否根据输入参数特征区分两套签名 - - **无法重载** → **优先级 3** -- **优先级 3**:方案 5(新增 compat 类型 API) - -#### 8. 输入参数类型不一致 -- **优先级 1**:方案 3(修改 API) - - **检查兼容性**:修改是否会引入后向不兼容问题 - - **会导致后向不兼容** → **优先级 2** -- **优先级 2**:方案 1(Python 装饰器) - - **检查能否区分**:能否根据输入参数特征区分两套签名 - - **无法重载** → **优先级 3** -- **优先级 3**:方案 5(新增 compat 类型 API) - -#### 9. 返回参数类型不一致 -- **唯一决策**:方案 5(新增 compat 类型 API) -- **原因**: - - ❌ 方案 3:修改返回值必然存在兼容性问题 - - ❌ 方案 1:只能根据输入参数特征来实现重载,返回值不一致不满足重载条件 - - ✅ 方案 5:在 compat 路径下新增,不影响原 API - -#### 10. 组合替代实现 -- **决策**:方案 4(新增 API) -- **说明**:PyTorch API 需要多个 Paddle API 组合实现,通过新增 API 实现组合计算逻辑 - -#### 11. 可删除 -- **决策**:方案 6(无需改动) -- **说明**:PyTorch API 在 Paddle 中可直接删除,无需开发 - -#### 12. API 别名 -- **决策**:方案 4(新增 API) -- **说明**:为 PyTorch 别名 API 新增对应的 Paddle 别名 API - -#### 13. 功能缺失 -- **决策**:方案 4(新增 API) -- **说明**:Paddle 暂无等效实现,需要新增 API 来实现该功能 - -### 2.5 方案组合说明 - -> **重要提醒**:一个 API 可能涉及多种差异分类,需要综合分析所有差异点,可以组合多种方案来消除所有差异点。 - -**示例 1**:`torch.logspace` 差异文档标题为 `[ torch 参数更多 ]`,但全文实际涉及两类差异: -- 参数名不一致:`end→stop`、`steps→num` -- torch 参数更多:额外 `out`、`device`、`requires_grad` 参数 -- **组合方案**:方案 3 + 方案 1 - - 方案 3:在 `paddle.logspace` 末尾新增 `out=None`、`device=None`、`requires_grad=False` 参数(后向兼容),处理"torch 参数更多"差异 - - 方案 1:Python 装饰器添加参数别名 `end/stop`、`steps/num`,处理"参数名不一致"差异 - -**示例 2**:`torch.slice_scatter` 差异文档标题为 `[ 输入参数用法不一致 ]`,但全文实际涉及两类差异: -- 参数名不一致:`input→x`、`src→value`、`dim→axes`、`start→starts`、`end→ends`、`step→strides` -- 输入参数类型不一致:torch 各参数为 `int`,Paddle 对应参数为 `list of int` -- **组合方案**:方案 3 + 方案 1 - - 方案 3:扩展 `paddle.slice_scatter` 的 `axes`/`starts`/`ends`/`strides` 参数,使其同时支持 `int` 和 `list of int`(后向兼容),处理"输入参数类型不一致"差异 - - 方案 1:Python 装饰器添加参数别名 `input/x`、`src/value`、`dim/axes`、`start/starts`、`end/ends`、`step/strides`,处理"参数名不一致"差异 - - -# 四、常见问题处理 - -### Q1:如何判断方案 3 是否会导致后向不兼容? - -此问题仅适用于**流程图中标注"导致后向不兼容则方案 1/5"的分支**(如差异分类 6/7/8)。 - -**判断方法**:严格按照方案 3 的"不适用条件"判断: -- ❌ 改变已有参数顺序 → 后向不兼容 -- ❌ 改变已有参数名称(旧名不可用)→ 后向不兼容 -- ❌ 修改返回值类型 → 后向不兼容 -- ❌ 删除现有参数 → 后向不兼容 -- ❌ 修改现有参数的默认行为 → 后向不兼容 +## 示例 -**示例**: -- `torch.tril_indices` 多 `device` 参数 → 在末尾添加 `device=None` → 不违反上述条件 → **后向兼容** → **方案 3** -- `torch.slice_scatter` 扩展参数类型(int → int|list)→ 不违反上述条件 → **后向兼容** → **方案 3** +**示例 1:单方案** + +``` +### [1] torch.atan + +**基本信息** +- PyTorch API:`torch.atan(input, *, out=None)` +- Paddle API:`paddle.atan(x, name=None, *, out=None)` +- 实现位置:`python/paddle/tensor/math.py:45` + +**差异分析** +- API 路径一致性:一致 +- 差异分类:参数名不一致 + torch 参数更多 +- 具体差异: + - 参数名映射:`input → x` + - 额外参数:torch 多出 `out` + +**方案决策** +- 选择方案:方案 2(C++ 下沉) +- 决策依据:API 路径一致;仅参数名不同 + 多 out 参数,且满足 C++ 下沉全部条件。 +``` + +**示例 2:组合方案** + +``` +### [2] torch.logspace + +**基本信息** +- PyTorch API:`torch.logspace(start, end, steps, base=10.0, *, out=None, dtype=None, layout=None, device=None, requires_grad=False)` +- Paddle API:`paddle.logspace(start, stop, num, base=10.0, dtype=None, name=None)` +- 实现位置:`python/paddle/tensor/creation.py:557-749` + +**差异分析** +- API 路径一致性:一致 +- 差异分类:参数名不一致 + torch 参数更多 +- 具体差异: + - 参数名映射:`end → stop, steps → num` + - 额外参数:torch 多出 `out, device, requires_grad` + +**方案决策** +- 选择方案:方案 3 + 方案 1 +- 决策依据:API 路径一致;新增 out/device/requires_grad 参数可保持后向兼容;参数名差异需通过装饰器适配。 +- 组合说明:方案 3 处理 torch 参数更多;方案 1 处理参数名不一致。 +``` + +# 四、注意事项 + +1. **严格按标准工作流程执行**,杜绝自行臆断和跳过步骤 +2. 决策前务必检查 Paddle 代码实际状态,差异文档可能滞后,代码反映真实情况(有可能已经完成了 Paddle 代码修改但未更正映射文档) +3. **读取或修改 Python 实现时,忽略静态图部分**(LayerHelper 分支代码不再维护) + +# 五、常见问题处理 + +### Q1:为什么 torch.histc 不能选择方案 5? + +**答**:方案 5(新增 compat 类型 API)要求 API 路径一致。torch.histc 对应的 Paddle API 是 `paddle.histogram`,API 路径不一致(`histc` vs `histogram`),所以必须选择方案 4。 + +### Q2:API 路径不一致时,方案 1/2/3 还能用吗? + +**答**:可以组合使用,但执行顺序很重要: + +1. **先执行方案 1/2/3**:修改原 Paddle API(如新增参数、适配参数名等) +2. **再执行方案 4**:新增 API 别名指向修改后的原 API -# 五、注意事项 +**示例**:`torch.histc` vs `paddle.histogram` +- **步骤 1**:方案 3 修改 `paddle.histogram`,新增 `out` 参数支持 +- **步骤 2**:方案 4 新增 `paddle.histc` 作为 `paddle.histogram` 的别名 -1. 严格按标准工作流程执行,杜绝自行臆断和跳过步骤 -2. 所有路径使用 `${ROOT_DIR}` 变量表示根目录,需自行替换为实际路径 -3. 决策前检查 Paddle 代码的实际状态,差异文档可能滞后,代码反映真实情况(有可能已经完成了 Paddle 代码修改但未更正映射文档) +**输出方案**:方案 3 + 方案 4(新增 API 别名) 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 index a266c88d16e..e8414dab220 100644 --- 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 @@ -2,35 +2,27 @@ name: api-compatibility description: 开展《Paddle API 对齐 PyTorch 项目》,负责项目整体统筹规划,调用多个 skill,完成输入的 API 对齐 allowed-tools: Read Write Edit Bash Glob Grep Agent Skill WebFetch WebSearch -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 列表,示例: + +用户提供待对齐的 Pytorch API 列表,格式如下: ```markdown torch.argmax torch.log2 torch.logsumexp ``` +本轮用户输入的 Pytorch API 为$ARGUMENTS,需对其完成对齐工作。 + ## 2.2 输出 用户输入列表的对齐情况,格式示例: @@ -44,24 +36,20 @@ torch.logsumexp |torch.logsumexp|未对齐|方案 1|<简介失败原因>例如:方案 1 暂不支持修改| ``` -# 三、技术背景知识 +# 三、背景知识 ### 3.1 工作目录说明 **ROOT_DIR 变量定义**: -- `${ROOT_DIR}` 是项目的根工作目录变量,例如 `/workspace`、`/home/user/projects` 等 -- 本项目涉及的三个仓库位于 `${ROOT_DIR}` 下: - - `${ROOT_DIR}/Paddle`:Paddle 框架源码仓库 - - `${ROOT_DIR}/PaConvert`:PyTorch 转换工具仓库 - - `${ROOT_DIR}/docs`:Paddle 文档仓库 -- 在实际执行时,需自行分析环境并将 `${ROOT_DIR}` 替换为实际路径 +- `${ROOT_DIR}` 表示项目的根工作目录(如 `/workspace`),通常为包含 `Paddle`、`PaConvert`、`docs` 子目录的父目录 +- **路径规则**:含 `${ROOT_DIR}` 为绝对路径,不含则为相对于根目录的相对路径,需自行展开 | 工作目录 | 完整路径 | 内容说明 | 对应步骤 | |---------|---------|---------|---------------| -| Paddle | `${ROOT_DIR}/Paddle` | 包含所有 Paddle API 的实现 | Step2:代码修改 | -| PaConvert | `${ROOT_DIR}/PaConvert` | 包含所有 Pytorch 单元测试 | Step3:对齐验证 | -| docs | `${ROOT_DIR}/docs` | 包含所有 Paddle API 中文文档 | Step4:文档更新 | -| Paddle、PaConvert、docs | - | 代码修改 Diff | Step5:代码提交 | +| Paddle | `${ROOT_DIR}/Paddle` | Paddle 框架源码仓库,包含所有 Paddle API 的实现 | Step2:代码修改 + Step3:兼容测试 | +| PaConvert | `${ROOT_DIR}/PaConvert` | PyTorch 转换工具仓库,包含所有 Pytorch 单元测试 | Step4:对齐验证 | +| docs | `${ROOT_DIR}/docs` | Paddle 文档仓库,包含所有 Paddle API 中文文档 | Step5:文档更新 | + ### 3.2 相关文件位置 @@ -113,44 +101,107 @@ void AtanKernel(const Context& dev_ctx, const DenseTensor& x, DenseTensor* out) | Paddle | 飞桨深度学习框架,导入模块为`paddle` | - | | API | 应用程序接口 | 既可以是一个 Python 函数,也可以是一个 Python 类 | | API 完整路径 | API 完整路径 | 如`torch.nn.functional.dropout`、`paddle.nn.functional.dropout`| -| API 相对引用路径 | API 完整路径在去掉框架导入模块(torch/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 的行为完全对齐一致 | 对齐包括 API 路径、输入参数、返回值、计算逻辑等| | API 中文文档 | 中文描述了该 API 的功能与行为 | 位于 `${ROOT_DIR}/docs/docs/api/paddle/` 目录,命名类似 tan_cn.rst | | API 差异文档 | 中文描述了 Pytorch API 与 Paddle API 两者的行为差异 | 位于 `${ROOT_DIR}/docs/docs/guides/model_convert/convert_from_pytorch/api_difference/` 下一级子目录,命名类似 torch.tan.md | -| compat 类型 API | 兼容性 API | 为保持后向兼容而添加的 API,能实现除 API 相对引用路径之外的完全对齐,实现之后差异分类将成为『仅 API 调用方式不一致』| +| compat 类型 API | 兼容性 API | 为保持后向兼容而添加的 API,能实现除 API 路径之外的完全对齐| ### 3.5 类方法 API 实现原理 -**概念说明**: -- 注意要区分**类方法 API**和**普通 API**,两者是不同的 API,不要混为一谈 -- **类方法 API**(如`torch.Tensor.abs`):`torch.Tensor`类方法 -- **普通 API**(如`torch.abs`):普通方法 +**概念**:类方法 API(如 `torch.Tensor.abs`)和普通 API(如 `torch.abs`)是不同 API,但实现一致,合并处理即可。Paddle 通过 patch 机制将方法动态添加到 Tensor 类上。 + +**实现方式选择**: + +| 场景 | 方式 | 操作 | +|------|------|------| +| 数学运算类方法,直接转发到普通函数 | 方式一:配置 tensor_method_func | 见下方步骤 | +| 自定义实现逻辑、property、魔术方法 | 方式二:修改 patch 文件 | 见下方步骤 | -Paddle 的 Tensor 类方法通过**patch 机制**实现,即将普通方法动态添加到`paddle.Tensor`(即`core.eager.Tensor`)类上成为类方法,因此两者虽然是不同 API,但实现一致,合并处理即可。 +**方式一:配置 tensor_method_func** -**实现机制**(参考`${ROOT_DIR}/Paddle/python/paddle/base/dygraph/math_op_patch.py`): +文件:`Paddle/python/paddle/tensor/__init__.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) +# 1. import 所需函数 +from .math import trace + +# 2. 加入 tensor_method_func 列表(按字母表顺序插入) +tensor_method_func = [ + ... + 'trace', + ... +] ``` -**查找类方法实现时的注意事项**: -- ✅ **正确做法**:直接搜索对应的普通方法实现,如搜索`def abs(`或`def atan(` -- ❌ **错误做法**:不要搜索`class Tensor`或在 Tensor 类定义中查找方法 -- 原因:Tensor 类方法是通过`setattr`动态添加的,不在类定义的源码中直接体现 +完成。各 patch 文件会自动遍历此列表完成绑定,无需额外修改。 + +**方式二:修改 patch 文件** + +需修改的文件: + +| 文件 | 语言 | 适用 | +|------|------|------| +| `python/paddle/base/dygraph/math_op_patch.py` | Python | 动态图 | +| `python/paddle/pir/math_op_patch.py` | Python | PIR 静态图 | +| `python/paddle/base/layers/math_op_patch.py` | Python | 老静态图 | +| `paddle/fluid/pybind/eager_math_op_patch.cc` | C++ | 动态图(性能更优) | + +选择规则: +- 动静统一 API:三处 Python patch 文件都改 +- 动态图专用 API:仅改动态图 patch 文件 +- 追求性能:可用 C++ patch 文件替代 Python + +修改方式: +- Python:在 `eager_methods` 列表中添加元组 `('方法名', 方法实现)` +- C++:参考 `eager_math_op_patch.cc` 中已有方法实现 + +**查找注意事项**: +- ✅ 在 patch 文件中搜索,或搜索对应的普通方法 `def abs(` +- ❌ 不要搜索 `class Tensor`(方法通过 setattr 动态添加,不在类定义中) -### 3.6 Inplace API 实现原理 +### 3.6 API 信息获取方式 + +在开展 API 对齐工作过程中,需要获取 PyTorch API 和 Paddle API 的相关信息。 + +**参考资源**: + +| 资源类型 | 资源位置 | 说明 | +|---------|---------|------| +| PyTorch 官方文档 | https://pytorch.org/docs/stable/ | 了解 API 参数功能定义 | +| PyTorch 源码 | https://github.com/pytorch/pytorch | 参考 PyTorch 底层逻辑(需注意 PyTorch 与 Paddle 架构设计存在差异) | +| Paddle 官方文档 | https://www.paddlepaddle.org.cn/documentation/docs/zh/api/ | 了解 Paddle API 参数功能定义 | +| Paddle 源码 | 见本地仓库 `${ROOT_DIR}/Paddle` | 参考 Paddle 底层逻辑 | + +**获取方式**: +1. **查阅官方文档**:优先查阅 PyTorch 和 Paddle 的官方文档,了解 API 的参数定义、功能说明、使用示例等 +2. **查阅源码实现**:当文档信息不够详细时,可查阅源码实现 +3. **获取 API 签名**:通过 Python 的 `inspect` 模块或 `help()` 函数获取 API 签名 +4. **获取文档字符串**:通过 `__doc__` 属性获取 API 的文档字符串 +5. **实际测试验证**:编写测试代码,实际运行 API 验证其行为和参数用法 + +**示例**: +```python +import paddle +import torch + +# 获取 API 签名 +import inspect +print(inspect.signature(paddle.abs)) +print(inspect.signature(torch.abs)) + +# 获取文档字符串 +print(paddle.abs.__doc__) +print(torch.abs.__doc__) + +# 实际测试验证 +x = paddle.to_tensor([1.0, -2.0, 3.0]) +print(paddle.abs(x)) +``` + +### 3.7 Inplace API 实现原理 **概念说明**: - 注意要区分**inplace API**和**非 inplace API**,两者是不同的 API,不要混为一谈 @@ -178,7 +229,7 @@ Paddle 支持自动生成 inplace API,无需在`ops.yaml`中单独配置。当 ``` 2. **Python API 配置**(`python_api_info.yaml`,第 6-9 行): - - ⚠️ **仅在 C++下沉(方案 2)时需要配置** +- ⚠️ **仅在 C++下沉(方案 2)时需要配置** ```yaml - op : abs_ name : [paddle.abs_, paddle.Tensor.abs_] @@ -188,138 +239,68 @@ Paddle 支持自动生成 inplace API,无需在`ops.yaml`中单独配置。当 # 四、整体工作流程 -## 执行逻辑 +## 流程重要约束 1. 接收用户输入的待对齐 API 列表(如 `torch.argmax`, `torch.log2`, `torch.logsumexp`) 2. **批量处理模式**:依次执行,每个 Step 结束后才进入下一个 Step - - Step1:对**所有 API**进行方案决策,记录每个 API 的方案类型 + - Step1:对**所有 API**进行方案决策 - Step2:对**所有 API**进行代码修改 - - Step3:对**所有 API**进行对齐验证 - - Step4:对**所有 API**更新文档 - - Step5:对**所有 API**进行代码提交 -3. **流程推进的豁免与放弃条件**: - - **豁免条件**: - * 每一个执行步骤均需调用相应的 skill 来执行 - * 当前仅支持方案 1、方案 2,若为其他方案,由于无对应 skill,无需处理,直接跳过该 API - - **放弃条件**(合理分配精力,最大化成功率): - * **放弃判断标准**: - - 当某个 API 在 Step2 或 Step3 经过多次尝试(建议 3 次)仍无法通过验证 - * **放弃执行要求**: - - ⚠️ 必须完整回退该 API 在 Step2 和 Step3 中的所有代码修改 - - ⚠️ 确保项目处于干净状态,不得保留任何"修改了但没改对"的中间状态 - - 在最终的对齐结果统计表中标记该 API 为"未对齐",并简要说明放弃原因 - * **整体策略原则**: - - 目标是最大化 API 列表的整体对齐成功率,而非执着于单个 API - - 优先处理更可能成功的 API,避免在困难 API 上消耗过多时间 - - 放弃是为了提高整体效率的理性决策,不是逃避问题 -4. 所有 API 都完成 5 个步骤(除被豁免或放弃外)后,任务结束 + - Step3:对**所有 API**进行兼容测试 + - Step4:对**所有 API**进行对齐验证 + - Step5:对**所有 API**进行文档更新 +3. **流程正向推进原则** + - 正常情况下必须遵循 Step1 → Step2 → Step3 → Step4 → Step5 的顺序 + - 每个步骤完成后,才能进入下一步骤,禁止跳过任何步骤 +4. **异常回退原则** + - 当 Step3 或 Step4 无法通过时 + - 需要根据错误信息诊断问题根源: + * 若判断为方案选择错误 → 回退到 Step1 重新决策 + * 若判断为代码实现有误 → 回退到 Step2 调整实现方式 + - 回退后需从该步骤重新按流程向前推进,例如回退到 Step2,则重新执行 Step2 → Step3 → Step4 → Step5 +5. **允许放弃部分 API**(合理分配精力,最大化成功率): + - 当某个 API 异常回退 3 次以上仍无法通过,则放弃该 API,在最终对齐结果统计表中标记该 API 为"未对齐",并简要说明放弃原因 + - ⚠️ 必须完整回退该 API 的所有修改,确保项目处于干净状态,不得保留任何 API"修改了但没改对"的中间状态 +6. 所有 API 都完成 5 个步骤(除被放弃外)后,任务结束 ## 流程概览 - ``` -输入 API 列表 → Step1:所有 API 方案决策 → Step2:所有 API 代码修改 → Step3:所有 API 对齐验证 → Step4:所有 API 文档更新 → Step5:代码提交 → 全部完成(流程全自动推进,不用询问) +输入 API 列表 → Step1:所有 API 方案决策 → Step2:所有 API 代码修改 → Step3:所有 API 兼容测试 → Step4:所有 API 对齐验证 → Step5:所有 API 文档更新 → 全部完成(流程全自动推进,不用询问) ``` 具体如下: ### Step 1:方案决策(调用 `/api-change-decider` skill) - Step 1.1: 分析差异文档 - Step 1.2: 方案决策 + Step 1.1: 获取差异信息 + Step 1.2: 提取差异信息 + Step 1.3: 方案决策 ### Step 2:代码修改 +根据 Step1 的方案决策结果,**按方案分组**,依次调用对应 skill,同一方案的所有 API 在一个 skill 调用中批量处理。 + +**各方案步骤**: #### 方案 1:Python 装饰器(调用 `/python-decorator` skill) - Step 1.1: 差异分析与选择装饰器 - Step 1.2: 应用或开发装饰器 - Step 1.3: 添加 out 参数支持 - Step 1.4: 更新函数文档字符串 - Step 1.5: 添加测试用例 - Step 1.6: 编译并运行 + Step 2.1: 差异分析与选择装饰器 + Step 2.2: 应用或开发装饰器 + Step 2.3: 更新函数文档 #### 方案 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: 编译并运行 -#### 方案 3~5:无对应 skill,无需处理,直接跳过 - -### Step 3:对齐验证(调用 `/pytorch-alignment-validator` skill) - Step 3.1: 标记已对齐的 API - Step 3.2: 增加测试用例 - Step 3.3: 运行单元测试 - -### Step 4:文档更新(调用 `/api-docs-updater` skill) +#### 方案 3:修改 API(调用 `/modify-origin-api` skill) + Step 2.1: 修改 API 签名 + Step 2.2: 修改函数实现逻辑 + Step 2.3: 更新函数文档 +#### 方案 4:新增 API(调用 `/add-new-api` skill) +#### 方案 5:新增 compat 类型 API(调用 `/add-new-compat-api` skill) -### Step 5:代码提交(调用 `/create-pr` skill) +### Step 3:兼容测试(调用 `/add-compatibility-test` skill) + Step 3.1: 编写测试用例(仅首次执行) + Step 3.2: 编译并运行(每次改动均需执行) -## 详细步骤 - -### Step 1: 方案决策 ⚙️ - -**调用**:`/api-change-decider` skill - -**输入**:需要对齐的 PyTorch API 列表(如 `torch.atan`、`torch.asinh`) - -**输出**: -- 方案类型(无需改动/方案 1~5) -- 对应 Paddle API -- 差异分类 -- 决策依据 - -### Step 2: 代码修改 💻 - -**根据方案类型调用对应 skill**: -- 方案 1 → `/python-decorator` -- 方案 2 → `/cpp-sink` -- 方案 3/4/5 → 无对应 skill,无需处理,直接跳过 -- 方案 6 → 无需处理,直接跳过 - -**输出**:代码修改是否完成 - -**异常处理**:如多次尝试仍失败,需回退到 Step1 重新决策 - -### Step 3: 对齐验证 ✅ **(金标准)** - -**调用**:`/pytorch-alignment-validator` skill - -**输入**:PyTorch API 列表 - -**输出**:验证是否通过 - -**异常处理**:如多次尝试仍失败,需回退到 Step2 调整实现或 Step1 重新决策 - -### Step 4: 文档更新 📝 - -**调用**:`/api-docs-updater` skill - -**输入**:已修改的 API 信息 - -**输出**:文档是否更新完成 - -### Step 5: 代码提交 📤 - -**调用**:`/create-pr` skill - -**输入**:已修改的代码 - -**输出**:PR 是否创建成功 - -## 重要约束 ⚠️ - -1. **流程正向推进原则** - - 正常情况下必须遵循 Step1 → Step2 → Step3 → Step4 → Step5 的顺序 - - 每个步骤完成并验证通过后,才能进入下一步骤 - - 禁止跳过任何步骤(特别是 Step3 对齐验证步骤) - -2. **异常回溯调整原则** - - 当 Step2(代码修改)或 Step3(对齐验证)多次尝试仍无法通过时 - - 主智能体需要根据错误信息诊断问题根源: - * 若判断为方案选择错误 → 返回 Step1 重新决策 - * 若判断为代码实现有误 → 在 Step2 调整实现方式 - - 回溯后需从该步骤重新按流程向前推进,例如回溯到 Step2,则重新 Step2 → Step3 → Step4 → Step5 - -3. **完整性保障** - - 每个 API 的 5 个步骤都必须完整执行(除被豁免或放弃外) - - 即使需要回溯调整,最终也要确保走完全流程 - - 所有步骤的产出物(代码、测试、文档、PR)必须齐全 +### Step 4:对齐验证(调用 `/pytorch-alignment-validator` skill) + Step 4.1: 标记已完成的 API(仅首次执行) + Step 4.2: 增加测试用例(仅首次执行) + Step 4.3: 运行单元测试(每次改动均需执行) +### Step 5:文档更新(调用 `/api-docs-updater` skill) ## 工作示例 @@ -327,49 +308,58 @@ Paddle 支持自动生成 inplace API,无需在`ops.yaml`中单独配置。当 ``` 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 +2. Step2: 代码修改 → 修改 Paddle 目录文件,将 paddle.argmax 下沉到 C++ +3. Step3: 兼容测试 → 在 Paddle 目录添加兼容性单测,编译并运行验证 +4. Step4: 对齐验证 → 修改 PaConvert 目录文件,编写 Pytorch 单元测试,对比测试,验证对齐 +5. Step5: 文档更新 → 修改 docs 目录文件,更新 paddle.argmax 文档 ``` +# 五、编程风格指南 -# 五、代码修改规范 +- 最小化注释;保持简洁;代码应当自解释、自文档化。 +- 注释应有实际价值,例如提醒读者一些非显而易见、无法从局部推断的全局背景。 +- 不要为只使用一次的简短逻辑(1-2 行)创建辅助函数,除非能显著提升代码可读性。 +- 优先使用清晰的抽象,状态管理应当显式。例如,在 Python 类中管理状态时,应有明确的类定义并列出所有成员,不要在对象上动态 setattr 字段后再动态 getattr。 +- 与现有代码风格和架构模式保持一致。 +- 假定读者熟悉 Paddle。他们不一定是所读代码的专家,但应具备该领域的一定经验。 +- 新增代码注释仅使用 ASCII 字符,不引入 Unicode 字符(如弯引号、破折号、箭头、非 ASCII 字母)。对未改动注释中已有的 Unicode 保持原样,仅对新增或改写的注释执行此规则。 +- 如有不确定,选择更简单、更简洁的实现。 -## 5.1 代码注释规范 -- ✅ 改动位置如果方便注释,可以注释`# Edit by AI Agent`,但只能注释 1 次 +# 六、各 Skill 说明 -## 5.2 代码质量规范 -- ✅ 保持代码风格与原项目一致 -- ✅ 不破坏现有功能 -- ✅ 确保向后兼容性 +## 6.1 总控 Skill:api-compatibility(本文件) -## 5.3 测试规范 -- ✅ 修改后必须通过原有测试 -- ✅ 必须添加新的单元测试 -- ✅ 必须通过 PyTorch 对齐验证 +**功能定位**: +- 本文件(`api-compatibility`)是项目的**总控 skill**,负责整体统筹规划 +- 作为用户入口,接收 API 列表输入,协调各子 skill 完成对齐工作 +- 遵循「流程正向推进」原则,按 Step1 → Step2 → Step3 → Step4 → Step5 顺序执行 -## 5.4 文档规范 +**核心职责**: +1. 解析用户输入的 API 列表 +2. 依次调用各步骤对应的子 skill +3. 汇总各步骤执行结果,输出最终对齐统计表 -- ✅ 同步更新中文文档 -- ✅ 文档格式符合 Paddle 规范 -- ✅ 准确描述 API 功能和参数 +**Skill 调用规范**: +- 本项目专用 skill 为下表所列的子 skill +- 优先使用本项目专用 skill,确保流程一致性和可控性,除非其无法完成任务,否则**尽量不调用其他 skill** -# 六、各 Skill 说明 - -| Skill | 对应步骤 | 功能说明 | -|-------|--------|---------| -| `/api-change-decider` | Step1:方案决策 | 分析 PyTorch 与 Paddle API 差异,制定改动方案 | -| `/python-decorator` | Step2:Python 装饰器方案 | 实施 Python 层面的兼容性转换,参数别名、参数顺序、参数类型和参数用法的兼容转换 | -| `/cpp-sink` | Step2:C++下沉方案 | 实施 C++ 层面的代码下沉,减少 Python 装饰器的性能开销,提升 API 调度效率 | -| `/pytorch-alignment-validator` | Step3:对齐验证 | 基于 PaConvert 工具验证 Paddle API 与 PyTorch API 是否用法完全对齐一致(金标准) | -| `/api-docs-updater` | Step4:文档更新 | 同步更新 API 中文文档,确保文档准确反映代码的最新行为 | -| `/create-pr` | Step5:代码提交 | 在 Paddle、PaConvert、Docs 三个仓库分别创建或更新 Pull Request | +## 6.2 项目专用 Skill 列表 +| Skill | 对应步骤 | +|-------|--------| +| `/api-change-decider` | Step1:方案决策 | +| `/python-decorator` | Step2:方案 1 Python 装饰器 | +| `/cpp-sink` | Step2:方案 2 C++下沉 | +| `/modify-origin-api` | Step2:方案 3 修改原有 API | +| `/add-new-api` | Step2:方案 4 新增 API | +| `/add-new-compat-api` | Step2:方案 5 新增 compat API | +| `/add-compatibility-test` | Step3:兼容测试 | +| `/pytorch-alignment-validator` | Step4:对齐验证 | +| `/api-docs-updater` | Step5:文档更新 | # 七、自进化机制 -**本项目涉及的所有 skill 均具备自进化能力**,通过持续学习和优化来提升工作质量。 +**本项目涉及的所有 skill 均具备自进化能力**,通过持续学习和优化来提升工作质量。修改的 SKILL 目录为:`${ROOT_DIR}/docs/docs/dev_guides/coding_agent/api_compatibility/.claude/skills` **如何自进化**: - 每次交互结束后,自动复盘分析工作过程中的问题、错误和成功经验 @@ -381,14 +371,14 @@ Paddle 支持自动生成 inplace API,无需在`ops.yaml`中单独配置。当 2. **常见问题处理**章节:补充新发现的问题-解决方案、特殊情况处理方案 3. 修正或补充 SKILL 中新发现的错误或遗漏内容 -**跨会话学习**: +**修改注意**: - 所有优化和改进都写入各 skill 的 SKILL.md 文档,确保知识持久化 -- 后续会话可以复用之前积累的经验和教训 +- 禁止在 SKILL 中添加任何网络代理的内容,以免安全信息泄露 +- 禁止在代码中编写任何网络代理的内容,以免安全信息泄露 # 八、注意事项 1. 严格按标准工作流程执行,杜绝自行臆断和跳过步骤 -2. 所有路径使用 `${ROOT_DIR}` 变量表示根目录,需自行替换为实际路径 # 九、常见问题处理 @@ -404,5 +394,3 @@ Paddle 支持自动生成 inplace API,无需在`ops.yaml`中单独配置。当 **处理办法**: - 查看具体错误信息,返回相应 Step 重新处理 - 使用调试技巧逐步排查问题 - ---- 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 index 5c1a658f3d2..97f74b0e167 100644 --- 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 @@ -1,7 +1,6 @@ --- name: api-docs-updater -description: 负责《Paddle API 对齐 PyTorch 项目》中 Step4:API 文档修改,在 API 代码修改完成后,同步更新中文 API 文档,确保文档准确反映 API 的最新行为 -allowed-tools: Read Grep Glob Write Edit +description: 负责《Paddle API 对齐 PyTorch 项目》中 Step5:文档更新,在 API 代码修改完成后,同步更新中文 API 文档,确保文档准确反映 API 的最新行为 disable-model-invocation: false --- @@ -22,13 +21,27 @@ Step 1. **查找 API 英文文档** - 在两个位置查找: Step 2. **对比英文和中文文档** - 识别不一致之处 -Step 3. **根据代码修改方案选择对应模式** - 见第三章 +Step 3. **更新中文文档与英文一致** -Step 4. **按照格式规范更新中文文档** - 见第四章 +# 三、注意事项 -# 三、常见修改模式 +1. 严格按标准工作流程执行,杜绝自行臆断和跳过步骤 +2. **Tensor 类方法**(如 `paddle.Tensor.abs`) + - 没有独立文档,无需处理 + - 勿与普通方法(如 `paddle.abs`)混淆 +3. **Inplace 方法**(如 `paddle.abs_`) + - 仅更新代码签名,不需修改文档 + - 参数别名支持与原方法一致 +4. **文档内容保持** + - 保留原有的文档风格和格式 + - 不要大面积删除文档原内容 + - 示例代码采用 COPY-FROM: 格式,不要修改 +5. **英文文档与中文文档必须对应** + - 别名格式完全相同 + - Overload 说明内容对应 + - out 参数描述对齐 -根据代码修改方案的不同,文档需要采用不同的修改模式。本章覆盖所有常见场景,并提供完整的实战示例。 +# 四、常见修改模式 ## 模式 1:参数别名(通用装饰器) @@ -79,7 +92,6 @@ Args: Tensor:计算结果 Tensor。 ``` ---- ## 模式 2:Overload 重载(专用装饰器) @@ -104,13 +116,12 @@ Args: ```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 参数 +.. note:: + 此 API 有两种调用方式: + 1. ``paddle.broadcast_tensors(input, name=None)`` (Paddle 风格): + 接收一个 Tensor 序列作为参数 + 2. ``paddle.broadcast_tensors(*tensors)`` (PyTorch 风格): + 接收可变个 Tensor 参数 参数 ::::::::: @@ -122,8 +133,6 @@ Args: list[Tensor]:广播后的 Tensor 列表。 ``` ---- - ## 模式 3:out 参数支持 **适用场景**: @@ -176,19 +185,9 @@ keyword-only 形式: - **name** (str,可选) - 操作名称。 ``` ---- - -## 模式 4:返回值为元组的 out 参数 +**元组形式的 out 参数**: -**适用场景**: -- API 返回多个值(如 `frexp` 返回 mantissa 和 exponent) -- out 参数也是元组类型 - -**修改内容**: -1. 函数签名:添加 `, *, out=None` -2. 关键字参数:out 类型标注为 `tuple[Tensor, Tensor]` - -**完整实战示例 - paddle.frexp** +当 API 返回多个值(如 `frexp` 返回 mantissa 和 exponent)时,out 参数也是元组类型。 ```rst .. py:function:: paddle.frexp(x, name=None, *, out=None) @@ -210,9 +209,8 @@ mantissa(Tensor):分解后的尾数,形状和原输入一致。 exponent(Tensor):分解后的指数,形状和原输入一致。 ``` ---- -## 模式 5:Inplace API 的别名说明 +## 模式 4:Inplace API 的别名说明 **适用场景**: - Inplace API(函数名以 `_` 结尾,如 `paddle.abs_`、`paddle.floor_divide_`) @@ -238,44 +236,3 @@ Inplace 版本的 :ref:`cn_api_paddle_floor_divide` API,对输入 `x` 采用 I 别名支持:参数名 ``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. 严格按标准工作流程执行,杜绝自行臆断和跳过步骤 -2. 所有路径使用 `${ROOT_DIR}` 变量表示根目录,需自行替换为实际路径 -3. **Tensor 类方法**(如 `paddle.Tensor.abs`) - - 没有独立文档,无需处理 - - 勿与普通方法(如 `paddle.abs`)混淆 -4. **Inplace 方法**(如 `paddle.abs_`) - - 仅更新代码签名,不需修改文档 - - 参数别名支持与原方法一致 -5. **文档内容保持** - - 保留原有的文档风格和格式 - - 不要大面积删除文档原内容 - - 示例代码采用 COPY-FROM: 格式,不要修改 -6. **英文文档与中文文档必须对应** - - 别名格式完全相同 - - 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 index 1a337bd29a0..71c3607e3d9 100644 --- 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 @@ -1,9 +1,6 @@ --- name: cpp-sink -description: 负责《Paddle API 对齐 PyTorch 项目》中 Step2:API 代码修改,实施 C++下沉的代码开发。通过将 Python API 下沉至 C++层,可以减少 Python 装饰器带来的性能开销,提升 API 调度效率。 -context: fork -background: false -verbose: true +description: 负责《Paddle API 对齐 PyTorch 项目》中 Step2:API 代码修改,实施『C++下沉』方案。通过将 Python API 下沉至 C++层,可以减少 Python 装饰器带来的性能开销,提升 API 调度效率。 disable-model-invocation: false --- @@ -20,8 +17,6 @@ disable-model-invocation: false **典型示例:** `paddle.log2` -请严格按以下步骤依次执行,不要自行修改或跳过步骤: - ### Step 1:配置 `python_api_info.yaml` 添加参数别名映射(按 op name 的字典序添加): @@ -35,80 +30,45 @@ disable-model-invocation: false **说明:** - `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(列表) +- `use_default_mapping : True`:自动配置常用字段映射(x→input, y→other, axis→dim, keepdims→keepdim) +- 不在默认映射范围时,可配置自定义映射:`y: [exponent]`(Paddle 参数名为 key,PyTorch 参数名为 value) ### Step 2:迁移文档到 `_paddle_docs.py` -注意要更新函数文档字符,在文档的 Args 部分为有别名的参数添加 Alias Support 说明,如下: -> 注:Alias 说明应放在该参数描述的末尾,**句号前**,格式为: Alias: ``alias_name`` ,多个 Alias 描述为: Alias: ``alias_name1`` or ``alias_name2`` +使用 `add_doc_and_signature` 函数迁移文档,关键要点: +1. **Alias 说明格式**:在 Args 部分为有别名的参数添加 `Alias: ``alias_name``` +2. **out 参数处理**:keyword-only 参数放在 `Keyword Args:` 部分,位置参数放在 `Args:` 部分,如下: +```python +# out 为 keyword-only 参数 +""" +Args: + ... +Keyword Args: + out (Tensor|optional): The output tensor. Default: None. +""" + +# out 为位置参数 +""" +Args: + out (Tensor, optional): The output Tensor. Default: None. +""" +``` + +**示例**: ```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. - + x (Tensor): Input tensor. Alias: ``input``. + ... + Keyword Args: + out (Tensor, optional): The output tensor. Default: None. 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( @@ -121,32 +81,7 @@ def log2( ) ``` -如果支持了 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` 现有文档格式保持一致 @@ -167,126 +102,6 @@ from paddle._C_ops import log2 # noqa: F401 # ... ``` -### 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. 比对测试项,对于内容相同的测试项,不要重复添加。 - -完整测试示例,请参考 `${ROOT_DIR}/Paddle/test/legacy_test/test_api_compatibility[1-9]\.py` 中已有的测试类结构。 - -### Step 5:编译并运行 - -单测编写完成后,按以下命令验证执行(不可修改): - -1. **重新编译项目**: - ```bash - cd ${ROOT_DIR}/Paddle/build - cmake .. - make -j$(nproc) - ``` - -2. **运行单测文件**: - ```bash - python <所修改的单测文件名> - ``` - -3. **问题排查**:根据报错信息调整代码或测试用例,确保所有测试用例通过。注意每次修改 Paddle 源码后,必须重新编译方可生效。 - -编译注意事项: -- 无需重装,直接生效(勿执行 setup/install 等安装操作) -- 勿删除 build 目录(否则增量编译失效,编译时间极长) - ## 场景二: 具有前处理逻辑(中等复杂度) **适用条件(全部满足):** @@ -296,8 +111,6 @@ class TestAPI(unittest.TestCase): **典型示例:** `paddle.logsumexp` -请严格按以下步骤依次执行,不要自行修改或跳过步骤: - ### Step 1:配置 `python_api_info.yaml` ```yaml @@ -317,68 +130,19 @@ class TestAPI(unittest.TestCase): ### 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); +将 Python 端在调用`_C_ops`前的**其他前处理逻辑**,修改为 C++的实现。在 `paddle/fluid/pybind/arg_pre_process.h` 声明,在 `arg_pre_process.cc` 实现。 -} // namespace pybind -} // namespace paddle -``` - -在 `paddle/fluid/pybind/arg_pre_process.cc` 实现: +**关键要点:** +- 必须同时实现**动态图版本**(参数类型为 `Tensor*`)和**静态图版本**(参数类型为 `pir::Value*`) +- 函数通过指针修改参数值 +- 参考 `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 +void LogsumexpPreProcess(Tensor *x, std::vector *axis, bool *reduce_all); // 动态图 +void LogsumexpPreProcess(pir::Value *x, std::vector *axis, bool *reduce_all); // 静态图 ``` -**注意事项:** -- 必须同时实现动态图和静态图版本 -- 函数通过指针修改参数值 -- 直接翻译 Python 前处理逻辑到 C++ - ### Step 3:迁移文档到 `_paddle_docs.py` 参考场景一的 Step 2 执行相同操作。 @@ -387,14 +151,6 @@ void LogsumexpPreProcess(pir::Value *x, std::vector *axis, bool *reduce_all 参考场景一的 Step 3 执行相同操作。 -### Step 5:添加测试用例 - -参考场景一的 Step 4 执行相同操作。 - -### Step 6:编译并运行 - -参考场景一的 Step 5 执行相同操作。 - ## 场景三: 复杂参数映射(最复杂) **适用条件(全部满足):** @@ -403,8 +159,6 @@ void LogsumexpPreProcess(pir::Value *x, std::vector *axis, bool *reduce_all **典型示例:** `paddle.argmax`/`paddle.argmin` -请严格按以下步骤依次执行,不要自行修改或跳过步骤: - ### Step 1:配置 `python_api_info.yaml` ```yaml @@ -422,190 +176,31 @@ void LogsumexpPreProcess(pir::Value *x, std::vector *axis, bool *reduce_all ### Step 2:实现自定义 Mapper -将 Python API 与`ops.yaml`中参数**类型或数量不一致**的差异,通过 C++自定义 Mapper 来进行匹配与转换。尽可能参考 `args_mapper.cc` 中已有的 Mapper 来实现,在风格和逻辑上保持尽可能一致。 +将 Python API 与`ops.yaml`中参数**类型或数量不一致**的差异,通过 C++自定义 Mapper 来进行匹配与转换。在 `paddle/fluid/pybind/args_mapper.h` 声明,在 `args_mapper.cc` 实现。 -在 `paddle/fluid/pybind/args_mapper.h` 声明: +**关键要点:** +- 必须同时实现**动态图版本**和**静态图版本** +- 使用 `args_mapper` 时不会生成默认参数解析代码,需手动解析所有参数 +- 参考 `args_mapper.cc` 中已有实现,保持风格一致 -```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 -``` +**核心工具函数:** +| 函数 | 用途 | +|------|------| +| `GetTensorFromArgsOrKWArgs` | 解析 Tensor 参数,支持别名列表 | +| `GetItemFromArgsOrKWArgs` | 获取通用 Python 对象 | +| `CastPyArg2Scalar/Boolean/DataType` | 类型转换 | +| `CheckParamsCount` / `CheckRemainingParamsValidity` | 参数校验 | -在 `paddle/fluid/pybind/args_mapper.cc` 实现: +**静态图特殊处理:** 使用 `pir::Value` 代替 `Tensor`,常量需通过 `paddle::dialect::full` 转换为 Value 类型。 +**声明示例**: ```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 +void ArgMaxMinMapper(PyObject* args, PyObject* kwargs, + Tensor* x, Scalar* axis, bool* keepdims, bool* flatten, DataType* dtype); // 动态图 +void ArgMaxMinMapper(PyObject* args, PyObject* kwargs, + pir::Value* x, pir::Value* axis, ...); // 静态图 ``` -**关键技术点:** - -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` 除了完成场景一的文档迁移操作之外,场景三文档迁移还需要额外注意: @@ -628,69 +223,19 @@ Returns: """ ``` -参考场景一的 Step 2 执行文档迁移操作。 - ### Step 4:替换 Python 实现 参考场景一的 Step 3 执行相同操作。 -### Step 5:添加测试用例 - -参考场景一的 Step 4 执行相同操作。 - -### Step 6:编译并运行 - -参考场景一的 Step 5 执行相同操作。 - -## 不同场景对比 -|------|---------------------|---------------------|---------------------| -| **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 工具函数速查 - -```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. 严格按标准工作流程执行,杜绝自行臆断和跳过步骤 -2. 所有路径使用 `${ROOT_DIR}` 变量表示根目录,需自行替换为实际路径 -3. 若 Python API 参数顺序与`_C_ops` API 不同,属于特殊情况,Cpp 下沉方案无法实现,需要使用 Python 装饰器方案 -4. 代码中不允许提交中文,代码注释采用英文 -5. 若 API 需支持`out`参数,必须修改`add_doc_and_signature`中的字符串,增加 out 参数 -6. 不要修改`generated_tensor_methods_patch.py`,该文件是自动生成的,修改没有意义,如无法对齐可考虑放弃 C++下沉方案而不是改动该文件 -7. 示例代码若涉及多种数据类型,可能触发类型检查误报,添加注释忽略: +2. 若 Python API 参数顺序与`_C_ops` API 不同,属于特殊情况,Cpp 下沉方案无法实现,需要使用 Python 装饰器方案 +3. 代码中不允许提交中文,代码注释采用英文 +4. 若 API 需支持`out`参数,必须修改`add_doc_and_signature`中的字符串,增加 out 参数 +5. 不要修改`generated_tensor_methods_patch.py`,该文件是自动生成的,修改没有意义,如无法对齐可考虑放弃 C++下沉方案而不是改动该文件 +6. 示例代码若涉及多种数据类型,可能触发类型检查误报,添加注释忽略: ```python .. code-block:: pycon >>> # type: ignore @@ -698,7 +243,7 @@ void CheckRemainingParamsValidity(PyObject* args, PyObject* kwargs, int remainin >>> x = paddle.to_tensor([1.0, 2.0]) ``` -# 四、常见问题处理 +# 三、常见问题处理 ## Q1:静态图报错"must be Value, but got Variable" @@ -727,5 +272,3 @@ TypeError: argmax() got an unexpected keyword argument 'invalid_param' **解决方法**: 1. 检查参数名称拼写 2. 确认是否支持该参数 - ---- 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 index 83a336b3ed2..df6c440208c 100644 --- 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 @@ -1,132 +1,230 @@ --- name: create-pr -description: 负责《Paddle API 对齐 PyTorch 项目》中 Step5:代码提交,分别对 Paddle、PaConvert、Docs 三个仓库创建或更新 Pull Request +description: 负责《Paddle API 对齐 PyTorch 项目》中代码提交,分别对 Paddle、PaConvert、Docs 三个仓库创建或更新 Pull Request allowed-tools: Bash(git *) disable-model-invocation: false --- -# 一、标准工作流程 +# 一、代码库说明 -该 skill 负责将前序步骤的成果提交到三个代码库: +**ROOT_DIR 变量定义**: +- `${ROOT_DIR}` 是项目的根工作目录变量,例如 `/workspace`、`/home/user/projects` 等 +- 在实际执行时,需自行分析环境并将 `${ROOT_DIR}` 替换为实际路径 -| 仓库 | 说明 | Base 分支 | -|------|------|---------| -| **PaddlePaddle/Paddle** | API 代码实现 | develop | -| **PaddlePaddle/PaConvert** | PyTorch 兼容性测试 | master | -| **PaddlePaddle/docs** | 中文 API 文档 | develop | +该 skill 负责将三个代码库中的改动提交到远程仓库,并创建或更新 Pull Request: -## Step 1:检查三个仓库的改动状态 +| 本地目录 | 远程仓库 | 内容说明 | Base 分支 | +|---------|---------|---------|---------| +| `${ROOT_DIR}/Paddle` | **https://github.com/PaddlePaddle/Paddle** | Paddle API 代码实现 | develop | +| `${ROOT_DIR}/docs` | **https://github.com/PaddlePaddle/docs** | 中文 API 文档 | develop | +| `${ROOT_DIR}/PaConvert` | **https://github.com/PaddlePaddle/PaConvert** | PyTorch 兼容性测试 | master | -三个仓库的本地目录名称分别为 Paddle、PaConvert、docs,自行找到对应本地路径,检查三个仓库是否有未提交的改动,**只对有代码改动的仓库进行后续提交**(排除未跟踪文件): +# 二、标准工作流程 -```bash -cd /path/to/Paddle && git status --untracked-files=no -cd /path/to/PaConvert && git status --untracked-files=no -cd /path/to/docs && git status --untracked-files=no -``` - -判断标准: -- 若仓库中有 `M`、`A`、`D` 等标记的**已跟踪文件**,则该仓库**需要提交** -- 若仓库中只有 `??` 标记的**未跟踪文件**,则该仓库**无需提交** -- 若仓库无任何改动,则该仓库**无需提交** +## Step 1:确认 PyTorch API 名单 -## Step 2:获取 PyTorch API 名单 +**此步骤必须在所有后续操作之前完成**,因为 API 名单将用于 commit message 和 PR title/body。 -根据各仓库的改动状态,自行从多个渠道获取 PyTorch API 名单,**最后需要取并集**: +从以下各个渠道获取 API 名单并取并集: ### 渠道 1:从上下文获取 -```bash -# 从 api-change-decider、python-decorator、cpp-sink 等前序步骤的上下文中自动提取 API 名单 -``` +从 api-change-decider、python-decorator、cpp-sink 等前序步骤的上下文中自动提取 API 名单。 ### 渠道 2:从用户输入获取 -```bash -# 用户直接提供 PyTorch API 名单,例如:torch.relu、torch.sigmoid、torch.tanh 等 -``` +用户直接提供 PyTorch API 名单,例如:`torch.relu`、`torch.sigmoid`、`torch.tanh` 等。 -### 渠道 3:从 Paddle 仓库分析获取 +### 渠道 3:从仓库改动分析获取 -若 Paddle 仓库有改动,分析以下位置: +若渠道 1 和 2 均无法获取,则分析仓库改动: ```bash -cd /path/to/Paddle -# 1. 分析任意 Python 文件的改动 -git diff origin/develop -- '*.py' | grep -E "^\+.*def|^\+.*class" +# 从 Paddle 仓库分析 +git -C ${ROOT_DIR}/Paddle diff origin/develop -- '*.py' | grep -E "^\+.*def|^\+.*class" +git -C ${ROOT_DIR}/Paddle diff origin/develop -- python_api_info.yaml -# 2. 分析 python_api_info.yaml 的改动 -git diff origin/develop -- python_api_info.yaml +# 从 PaConvert 仓库分析 +git -C ${ROOT_DIR}/PaConvert diff origin/master -- api_mapping.json | grep -E "ChangePrefixMatcher|torch\." ``` -### 渠道 4:从 PaConvert 仓库分析获取 +### API 名单合并 -若 PaConvert 仓库有改动,从 `api_mapping.json` 提取标记为 `ChangePrefixMatcher` 的 API: +将各渠道获取的 API 名单取并集,确保不重复,生成最终的统一 API 名单。 -```bash -cd /path/to/PaConvert -# 分析 api_mapping.json 中标记为 ChangePrefixMatcher 的 API -git diff origin/master -- api_mapping.json | grep -E "ChangePrefixMatcher|torch\." -``` +**格式要求**: +- commit message 中:`api_name_1/api_name_2/api_name_3/...` +- PR body 中:每行一个,格式为 `torch.api_name` -**API 名单合并** +## Step 2:检查三个仓库的改动状态 + +三个仓库的本地目录名称分别为 Paddle、PaConvert、docs,**分别检查**三个仓库是否有未提交的改动: ```bash -# 将从各渠道获取的 API 名单取并集,确保不重复,生成最终的统一 API 名单 -# 例如:torch.relu, torch.sigmoid, torch.tanh, ... +git -C ${ROOT_DIR}/Paddle status --untracked-files=no +git -C ${ROOT_DIR}/PaConvert status --untracked-files=no +git -C ${ROOT_DIR}/docs status --untracked-files=no ``` +**重要约束**: +- 必须分别执行三个命令,确保每个仓库的状态都被正确检查 +- **所有已跟踪文件的修改都必须纳入检查,不得以任何方式忽略或排除** +- 只对有代码改动的仓库执行后续操作 + ## Step 3:添加改动并提交 -仅对**有代码改动的仓库**执行以下操作(顺序:Paddle → Docs → PaConvert): +仅对**有代码改动的仓库**执行以下操作。 + +**执行顺序**:Paddle → Docs → PaConvert(必须严格按此顺序执行) ```bash -# Paddle 仓库 -cd /path/to/Paddle -git add -u -git commit -m "[API Compatibility] api_name_1/api_name_2/api_name_3/... Edit By AI Agent" +# ===== Paddle 仓库 ===== +git -C ${ROOT_DIR}/Paddle add -u +git -C ${ROOT_DIR}/Paddle commit -m "$(cat <<'EOF' +[API Compatibility] api_name_1/api_name_2/api_name_3/... Edit By AI Agent + +Co-Authored-By: Claude Opus 4.6 +EOF +)" # 等待 pre-commit hook 完成 -# 如果 pre-commit 失败,修复问题后重新 git add 和 commit +# 如果 pre-commit 失败,修复问题后重新 add 和 commit -# docs 仓库 -cd /path/to/docs -git add -u -git commit -m "[API Compatibility] api_name_1/api_name_2/api_name_3/... Edit By AI Agent" +# ===== docs 仓库 ===== +git -C ${ROOT_DIR}/docs add -u +git -C ${ROOT_DIR}/docs commit -m "$(cat <<'EOF' +[API Compatibility] api_name_1/api_name_2/api_name_3/... Edit By AI Agent + +Co-Authored-By: Claude Opus 4.6 +EOF +)" # 等待 pre-commit hook 完成 -# 如果 pre-commit 失败,修复问题后重新 git add 和 commit +# 如果 pre-commit 失败,修复问题后重新 add 和 commit -# PaConvert 仓库 -cd /path/to/PaConvert -git add -u -git commit -m "[API Compatibility] api_name_1/api_name_2/api_name_3/... Edit By AI Agent" +# ===== PaConvert 仓库 ===== +git -C ${ROOT_DIR}/PaConvert add -u +git -C ${ROOT_DIR}/PaConvert commit -m "$(cat <<'EOF' +[API Compatibility] api_name_1/api_name_2/api_name_3/... Edit By AI Agent + +Co-Authored-By: Claude Opus 4.6 +EOF +)" # 等待 pre-commit hook 完成 -# 如果 pre-commit 失败,修复问题后重新 git add 和 commit +# 如果 pre-commit 失败,修复问题后重新 add 和 commit ``` +**注意**:将 `api_name_1/api_name_2/...` 替换为 Step 1 确认的实际 API 名单。 + ## Step 4:推送代码到 upstream claude 分支 -仅对**有代码改动的仓库**执行推送操作: +仅对**有代码改动的仓库**执行推送操作。 + +**执行顺序**:Paddle → Docs → PaConvert + +```bash +# Paddle 仓库 +git -C ${ROOT_DIR}/Paddle push upstream develop:claude -f + +# docs 仓库 +git -C ${ROOT_DIR}/docs push upstream develop:claude -f + +# PaConvert 仓库 +git -C ${ROOT_DIR}/PaConvert push upstream master:claude -f +``` + +## Step 5:检查 claude 分支 PR 状态 + +推送完成后,检查各仓库的 claude 分支是否已存在打开的 PR: ```bash # Paddle 仓库 -cd /path/to/Paddle -git push upstream HEAD:claude -f +gh pr list --repo PaddlePaddle/Paddle --head claude --state open --json number,title,url # docs 仓库 -cd /path/to/docs -git push upstream HEAD:claude -f +gh pr list --repo PaddlePaddle/docs --head claude --state open --json number,title,url # PaConvert 仓库 -cd /path/to/PaConvert -git push upstream HEAD:claude -f +gh pr list --repo PaddlePaddle/PaConvert --head claude --state open --json number,title,url +``` + +**记录结果**: +- 若返回结果包含 PR 信息(`number` 不为空):记录 PR 号,该仓库需要**更新 PR** +- 若返回结果为空数组 `[]`:该仓库需要**创建新 PR** + +## Step 6:创建或更新 PR + +根据 Step 5 的检查结果,对有代码改动的仓库执行相应操作。 + +**PR 创建的依赖关系**: +- Docs PR 的 body 引用 Paddle PR 号 +- PaConvert PR 的 body 引用 Docs PR 号和 Paddle PR 号 + +**因此执行顺序必须是**:Paddle PR → Docs PR → PaConvert PR + +### 6.1 更新已有 PR + +若仓库已有 PR,执行更新操作: + +```bash +# Paddle PR 更新 +gh pr edit {paddle_pr_number} --repo PaddlePaddle/Paddle \ + --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 更新(依赖 Paddle PR 号) +gh pr edit {docs_pr_number} --repo PaddlePaddle/docs \ + --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 更新(依赖 Docs 和 Paddle PR 号) +gh pr edit {paconvert_pr_number} --repo PaddlePaddle/PaConvert \ + --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 +)" ``` -## Step 5:创建 PR +### 6.2 创建新 PR -根据自动获取的 PyTorch API 名单生成 PR,**仅对有代码改动的仓库**执行以下命令创建 PR(顺序:Paddle → Docs → PaConvert): +若仓库没有 PR,执行创建操作: ```bash -# Paddle PR +# ===== 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' @@ -138,35 +236,32 @@ Improvements ### Description **API Compatibility Edit By AI Agent:** -\`\`\` -torch.api_name_1 -torch.api_name_2 -... -\`\`\` + torch.api_name_1 + torch.api_name_2 + ... ### 是否引起精度变化 否 EOF )" +# 记录返回的 PR 号作为 paddle_pr_number -# Docs PR +# ===== Docs PR 创建(依赖 Paddle 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 -... -\`\`\` + torch.api_name_1 + torch.api_name_2 + ... - https://github.com/PaddlePaddle/Paddle/pull/{paddle_pr_number} EOF )" +# 记录返回的 PR 号作为 docs_pr_number -# PaConvert PR +# ===== PaConvert PR 创建(依赖 Docs 和 Paddle 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' @@ -175,37 +270,38 @@ gh pr create --repo PaddlePaddle/PaConvert --base master --head zhwesky2010:clau ### PR APIs **API Compatibility Edit By AI Agent:** -\`\`\` -torch.api_name_1 -torch.api_name_2 -... -\`\`\` + torch.api_name_1 + torch.api_name_2 + ... - https://github.com/PaddlePaddle/Paddle/pull/{paddle_pr_number} EOF )" +# 记录返回的 PR 号作为 paconvert_pr_number ``` -其中: -- 将 `api_name_1/api_name_2/...` 替换为实际的 API 名单 -- 将 `{paddle_pr_number}` 和 `{docs_pr_number}` 替换为实际创建的 PR 号 - -# 二、注意事项 - -- 所有路径使用 `${ROOT_DIR}` 变量表示根目录 -- **PyTorch API 名单获取策略**: - - 先检查三个仓库的改动状态(Step 1) - - 根据各仓库的改动情况,灵活选择获取渠道 - - 从多个渠道获取的 API 名单需要**取并集**,确保完整性和准确性 - - Paddle 仓库改动分析:任意 Python 文件修改、python_api_info.yaml 修改 - - PaConvert 仓库改动分析:api_mapping.json 中标记为 `ChangePrefixMatcher` 的 API -- **仅对有代码改动的仓库进行提交和 PR 创建**,如果某个仓库无代码改动,则跳过该仓库的提交、推送和 PR 创建步骤 -- 三个 PR 的 API 名单必须完全一致 -- 如果 pre-commit hook 失败,修复问题后重新提交 -- 所有改动必须推送到 upstream 的 claude 分支 -- 确保 PR 创建成功,如果失败需要继续修正直到成功 -- 复盘记忆中的历史易错点,避免重复犯错 -- 严格按标准工作流程执行,杜绝自行臆断和跳过步骤 - -# 三、常见问题处理 +### 6.3 混合场景处理 + +若部分仓库已有 PR、部分仓库需要新建,按以下逻辑处理: + +| 场景 | 处理方式 | +|------|---------| +| Paddle 有 PR,Docs 无 PR | 更新 Paddle PR → 创建 Docs PR(引用 Paddle PR 号) → 处理 PaConvert | +| Paddle 无 PR,Docs 有 PR | 创建 Paddle PR → 更新 Docs PR(更新引用) → 处理 PaConvert | +| 其他组合 | 遵循「Paddle → Docs → PaConvert」顺序,按实际情况更新或创建 | + +# 三、注意事项 + +1. **API 名单一致性**:三个仓库的 PR,其 API 名单必须完全一致 +2. **执行顺序**:所有操作必须严格按「Paddle → Docs → PaConvert」顺序执行 +3. **已跟踪文件**:所有已跟踪文件的修改都必须处理,不得以任何理由忽略或排除 +4. **pre-commit hook**:如果失败,修复问题后重新 add 和 commit +5. **分支推送**:所有改动必须推送到 upstream 的 claude 分支 +6. **PR 操作**:确保 PR 创建或更新成功,失败需修正后重试 +7. **历史经验**:复盘记忆中的历史易错点,避免重复犯错 +8. **安全约束**:禁止在 SKILL 中添加任何网络代理设置的内容,发现代码 Diff 中包含网络代理设置需立即删除并拒绝提交 + +# 四、常见问题处理 + +(待补充) diff --git a/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/modify-origin-api/SKILL.md b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/modify-origin-api/SKILL.md new file mode 100644 index 00000000000..98254de457f --- /dev/null +++ b/docs/dev_guides/coding_agent/api_compatibility/.claude/skills/modify-origin-api/SKILL.md @@ -0,0 +1,347 @@ +--- +name: modify-origin-api +description: 负责《Paddle API 对齐 PyTorch 项目》中 Step2:API 代码修改,实施『修改原有 API』方案。通过修改原有 Paddle API 的实现(新增参数、扩展参数类型/功能),使 Paddle API 与 PyTorch API 行为对齐,同时保持后向兼容性。 +disable-model-invocation: false +--- + +# 一、适用场景 + +根据差异分析结果,本方案适用于以下四类场景: + +| 场景 | 适用情况 | 典型示例 | +|------|---------|---------| +| 新增参数 | torch 比 paddle 多出某些参数(不含参数名/顺序差异) | `paddle.randn` 新增 `out/device/requires_grad/pin_memory` | +| 扩展参数类型/功能 | 参数支持的类型范围不同(torch 支持 paddle 不支持的类型) | `paddle.eye` 的 `num_rows` 从 `int` 扩展为 `int \| paddle.Tensor` | + +# 二、标准工作流程 + +## Step 1:修改 API 代码 + +若尚未获得 PyTorch API 的相关信息,则自行获取,获取方式请参考`api-compatibility/SKILL.md` 中的「3.6 API 信息获取方式」章节。 + +然后分析 PyTorch API 的功能和行为,以及与 Paddle API 的差异。在 `${ROOT_DIR}/Paddle/python/paddle/` 目录下定位到对应的 API 实现位置,修改 Paddle API 的签名和实现代码。 + +**修改原则**: +1. **后向兼容**:修改不得破坏现有调用,禁止以下操作: + - 改变已有参数顺序 + - 改变已有参数名称 + - 修改返回值类型 + - 删除现有参数 + - 修改现有参数的默认行为 +2. **与 PyTorch 一致**:参数名、类型、顺序与 PyTorch 相同。若与原则 1 冲突,则放宽为:Paddle 签名需包含 PyTorch 签名的全部用法(例如:PyTorch 的某参数仅支持关键字传参,而 Paddle 可同时支持位置传参和关键字传参) + +若不满足以上两个原则,则无法适用本方案,需考虑其他方案。 + +### 修改 API 签名 + +修改**现有 API 签名**: + +```python +# 修改前 +def randn( + shape: ShapeLike, + dtype: DTypeLike | None = None, + name: str | None = None, +) -> Tensor: + +# 修改后:新增 out/device/requires_grad 为 keyword-only 参数 +def randn( + shape: ShapeLike, + dtype: DTypeLike | None = None, + name: str | None = None, + *, + out: paddle.Tensor | None = None, + device: PlaceLike | None = None, + requires_grad: bool = False, +) -> Tensor: +``` + +### 修改函数实现逻辑 + +以下给出了几个常用参数的实现方式: + +#### 新增 `out` 参数 + +**前置检查**:若 PyTorch API 有 `out` 参数,必须先确认其性质: +- **Keyword-only**(`*` 后):`torch.api(input, *, out=None)` → Paddle 实现中 `out` 也应为 keyword-only(`*, out=None`) +- **位置参数**:`torch.api(input, out=None)` → Paddle 实现中 `out` 可为位置参数 + +**方式一:直接指定 out(推荐)** + +适用条件: +1. 情况 1:API 最后一个逻辑是调用`_C_ops`;情况 2:API 调用了其他 API,调用的最后一个其他 API 也支持 out +2. out 参数只有一个 Tensor + +```python +# 情况 1:API 最后一个逻辑是调用`_C_ops` +def less_than(x, y, name=None, *, out=None) -> Tensor: + if in_dynamic_or_pir_mode(): + return _C_ops.less_than(x, y, out=out) + else: + ... + +# 情况 2:API 调用的最后一个其他 API 也支持 out +def fft(x, n=None, axis=-1, norm="backward", name=None, *, out=None) -> Tensor: + 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): + # 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 参数 + +#### 新增 `device` 参数 + +替换原有的 `_current_expected_place()` 调用,根据 `device` 是否为 None 来决定使用哪个设备: + +```python +# 修改前 +place = _current_expected_place() + +# 修改后 +place = ( + _current_expected_place() + if device is None + else _get_paddle_place(device) +) +``` + +需确保 `_get_paddle_place` 已在文件中导入: +```python +from ..framework import ( + _get_paddle_place, + ... +) +``` + +#### 新增 `requires_grad` 参数 + +在返回 tensor 前设置 `stop_gradient`: + +```python +tensor = _C_ops.xxx(...) +if requires_grad is True: + tensor.stop_gradient = False +return tensor +``` + +#### 新增 `pin_memory` 参数 + +`pin_memory` 参数已封装为 `_to_pinned_place` 函数,使用方式如下: + +需确保 `_to_pinned_place` 已在文件中导入: +```python +from paddle.framework import ( + _to_pinned_place, + ... +) +``` + +实现逻辑: + +```python +# 第一处:处理 device 后,在调用 _C_ops 创建 tensor 前添加 +if pin_memory and in_dynamic_mode() and device is not None: + device = _to_pinned_place(device) + +# ... 调用 _C_ops 创建 tensor ... + +# 第二处:返回前 +if requires_grad is True: + tensor.stop_gradient = False +if pin_memory and in_dynamic_mode(): + tensor = tensor.pin_memory() +return tensor +``` + +#### 扩展参数类型 + +若现有实现已能通过 `_C_ops` 接受新类型(通常情况下 C++ 底层支持 `Scalar` 类型可同时接受 int 和 Tensor),则无需改动实现逻辑。 + +若底层不支持,添加类型转换分支: + +```python +# 若底层需要 int,先将 Tensor 转换 +if isinstance(num_rows, paddle.Tensor): + num_rows = int(num_rows) +if isinstance(num_columns, paddle.Tensor): + num_columns = int(num_columns) +``` + +## Step 2:更新函数文档 + +在 docstring 的 Args 部分(或 Keyword Args 部分,取决于参数是否为 keyword-only)添加新参数说明。 + +**文档更新模板**: + +```python +""" +... +Args: + shape (ShapeLike): ... + dtype (DTypeLike|None, optional): ... + name (str|None, optional): ... + +Keyword Args: + out (Tensor|None, optional): ... + device (PlaceLike|None, optional): ... + requires_grad (bool, optional): ... + +Returns: + ... +""" +``` + +**文档规范**: +- keyword-only 参数须放在 `Keyword Args:` 下描述 +- Tensor 类方法(如 paddle.Tensor.abs)没有文档,无需处理 +- Inplace 方法(如 paddle.abs_ 等下划线 API),只需更新 API 签名,不需要修改文档 + +# 三、背景知识 + +## 3.1 常用工具函数 + +```python +# 获取当前期望的 place +from paddle.framework import _current_expected_place +place = _current_expected_place() + +# 将 device 字符串/对象转为 Place +from paddle.framework import _get_paddle_place +place = _get_paddle_place("cpu") # -> core.CPUPlace() +place = _get_paddle_place("gpu:0") # -> core.CUDAPlace(0) +place = _get_paddle_place("cuda:0") # -> core.CUDAPlace(0)(扩展后支持) + +# 将 device 转为 pinned place(用于 pin_memory 参数) +from paddle.framework import _to_pinned_place +if pin_memory and in_dynamic_mode() and device is not None: + device = _to_pinned_place(device) + +# 判断运行模式 +from paddle.framework import in_dynamic_mode, in_dynamic_or_pir_mode +if in_dynamic_mode(): ... +if in_dynamic_or_pir_mode(): ... +``` + +## 3.2 `pin_memory` 参数机制 + +`pin_memory=True` 将 Tensor 分配在锁页内存(pinned memory),提升 CPU->GPU 数据传输速度。 + +Paddle 已封装 `_to_pinned_place` 函数(参见 PR #78823),简化实现: + +```python +from paddle.framework import _to_pinned_place + +# 使用方式 +if pin_memory and in_dynamic_mode() and device is not None: + device = _to_pinned_place(device) + +# 创建 tensor 后 +if pin_memory and in_dynamic_mode(): + tensor = tensor.pin_memory() +``` + +## 3.3 `PlaceLike` 类型导入 + +若函数签名中需要使用 `PlaceLike` 类型,需在文件顶部的 `TYPE_CHECKING` 块中添加导入: + +```python +if TYPE_CHECKING: + from paddle import Tensor + from paddle._typing import DTypeLike, PlaceLike, ShapeLike # 添加 PlaceLike +``` + +# 四、注意事项 + +1. 严格按标准工作流程执行,杜绝自行臆断和跳过步骤 +2. **后向兼容性是红线**:任何修改都不得破坏现有调用,新参数必须有合理默认值 +3. 若新参数需要透传(如 `randn -> standard_normal -> gaussian`),必须在整个调用链上都添加该参数 +4. `math_op_patch.py` 中的 Tensor 方法必须与对应的普通函数保持参数同步,且需同时修改 dygraph 版和 pir 版两个文件 + +# 五、常见问题处理 + +## Q1:新增参数后,`_C_ops` 调用报错 "unexpected keyword argument 'out'" + +**错误现象**: +``` +TypeError: xxx() got an unexpected keyword argument 'out' +``` + +**解决方法**: +该 op 对应的 `_C_ops` 接口不支持 `out` 参数。需确认底层 op 是否有 `out` 支持,若没有则不能使用 C++ 侧 out 注入方式,改为手动复制结果: +```python +tensor = _C_ops.xxx(...) +if out is not None: + paddle.assign(tensor, output=out) + return out +return tensor +``` + +## Q2:`pin_memory` 测试时报 "Pinning memory is not supported" + +**错误现象**: +``` +RuntimeError: Pinning memory is not supported for Place(cpu) +``` + +**解决方法**: +`pin_memory=True` 仅在 GPU/XPU 设备上有意义。确保测试逻辑中正确添加了环境判断: +```python +if paddle.device.is_compiled_with_cuda() or paddle.device.is_compiled_with_xpu(): + x = paddle.xxx([2], device="gpu", pin_memory=True) + self.assertTrue("pinned" in str(x.place)) +``` + +## Q3:修改 `math_op_patch.py` 后静态图测试失败 + +**错误现象**: +``` +AttributeError: 'OpResult' object has no attribute 'pin_memory' +``` + +**解决方法**: +`pin_memory` 调用(`tensor.pin_memory()`)需在 `in_dynamic_mode()` 保护下执行,PIR 静态图中 `tensor` 是 `Value` 对象,不支持该方法。确认实现中有正确的模式判断: +```python +if pin_memory and in_dynamic_mode(): + tensor = tensor.pin_memory() +``` + +## Q4:扩展参数类型(如 `int | Tensor`)后运行报错 + +**错误现象**: +``` +TypeError: eye(): argument 'num_rows' must be int, not Tensor +``` + +**解决方法**: +底层 `_C_ops` 接口尚不支持该类型。在 Python 层添加显式转换: +```python +if isinstance(num_rows, paddle.Tensor): + num_rows = int(num_rows) +``` 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 index f86804a1054..949681acb05 100644 --- 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 @@ -1,9 +1,6 @@ --- name: python-decorator -description: 负责《Paddle API 对齐 PyTorch 项目》中 Step2:API 代码修改,实施 Python 装饰器的代码开发。通过 Python 装饰器,在 Python 层为 Paddle API 提供参数别名、参数顺序、参数类型和参数用法的兼容转换,实现 PyTorch 风格的 API 调用,并保持 Paddle API 的向后兼容性。 -context: fork -background: false -verbose: true +description: 负责《Paddle API 对齐 PyTorch 项目》中 Step2:API 代码修改,实施『Python 装饰器』方案。通过 Python 装饰器,在 Python 层为 Paddle API 实现参数名称、参数顺序、参数类型和参数用法的重载,实现 PyTorch 风格的 API 调用,并保持 Paddle API 的向后兼容性。 disable-model-invocation: false --- @@ -36,12 +33,14 @@ Paddle 现有装饰器统一位于 `${ROOT_DIR}/Paddle/python/paddle/utils/decor # 二、标准工作流程 -**整体流程**:Step 1 差异分析与选择装饰器 → Step 2 应用或开发装饰器 → Step 3 添加 out 参数支持 → Step 4 更新函数文档 → Step 5 添加测试用例 → Step 6 编译并运行 +**整体流程**:Step 1 差异分析与选择装饰器 → Step 2 应用或开发装饰器 → Step 3 更新函数文档 ## Step 1: 差异分析与选择装饰器 根据 PyTorch API 与 Paddle API 的**差异分析**来区分不同场景,选择合适的装饰器方案。 +--- + ### 场景决策表 | 差异类型 | 参数顺序 | 参数个数 | 参数用法 | 推荐方案 | @@ -272,84 +271,9 @@ def gather( ) -> 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 参数。 +> **注意**:`out` 参数由方案 3(修改原有 API)负责,本方案不处理 out 参数的新增。 -## Step 4: 更新函数文档字符串 +## Step 3: 更新函数文档字符串 如果使用的是通用别名装饰器,则在文档的 Args 部分为有别名的参数添加 Alias Support 说明,如下: > 注:Alias 说明应放在该参数描述的末尾,格式为: Alias: ``alias_name`` ,多个 Alias 描述为: Alias: ``alias_name1`` or ``alias_name2`` @@ -396,13 +320,12 @@ 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. + Note: + 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: ... @@ -429,147 +352,13 @@ def func(x, name=None, *, out=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. 比对测试项,对于内容相同的测试项,不要重复添加。 - -完整测试示例,请参考 `${ROOT_DIR}/Paddle/test/legacy_test/test_api_compatibility[1-9]\.py` 中已有的测试类结构。 - -## Step 6: 编译与运行 - -单测编写完成后,按以下命令验证执行(不可修改): - -1. **重新编译项目**: - ```bash - cd ${ROOT_DIR}/Paddle/build - cmake .. - make -j$(nproc) - ``` - -2. **运行单测文件**: - ```bash - python <所修改的单测文件名> - ``` - -3. **问题排查**:根据报错信息调整代码或测试用例,确保所有测试用例通过。注意每次修改 Paddle 源码后,必须重新编译方可生效。 - -编译注意事项: -- 无需重装,直接生效(勿执行 setup/install 等安装操作) -- 勿删除 build 目录(否则增量编译失效,编译时间极长) - -# 三、技术背景知识 +# 三、背景知识 ## 3.1 Paddle API 分层结构 @@ -635,12 +424,11 @@ if len(args) >= 2 and isinstance(args[1], int): # 四、注意事项 1. 严格按标准工作流程执行,杜绝自行臆断和跳过步骤 -2. 所有路径使用 `${ROOT_DIR}` 变量表示根目录,需自行替换为实际路径 -3. 不要修改 sparse 目录下的 API -4. 确保不破坏现有功能,保持向后兼容性 -5. 开发专用装饰器时参考现有实现 -6. 代码中不允许提交中文,代码注释采用英文 -7. 复盘记忆中的历史易错点,避免重复犯错 +2. 不要修改 sparse 目录下的 API +3. 确保不破坏现有功能,保持向后兼容性 +4. 开发专用装饰器时参考现有实现 +5. 代码中不允许提交中文,代码注释采用英文 +6. 复盘记忆中的历史易错点,避免重复犯错 # 五、常见问题处理 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 index 293adea0836..2aa1df67987 100644 --- 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 @@ -1,6 +1,6 @@ --- name: pytorch-alignment-validator -description: 负责《Paddle API 对齐 PyTorch 项目》中 Step3:Pytorch 对齐验证,基于 PaConvert 工具验证 Paddle API 与 PyTorch API 是否用法完全对齐一致 +description: 负责《Paddle API 对齐 PyTorch 项目》中 Step4:对齐验证,基于 PaConvert 工具验证 Paddle API 与 PyTorch API 是否用法完全对齐一致 disable-model-invocation: false --- @@ -8,13 +8,15 @@ disable-model-invocation: false 请严格按以下 Step 依次执行,不要自行修改或跳过 Step: -## Step 1: 标记已对齐的 API +## Step 1: 标记已完成的 API(仅首次执行) 1. 定位文件:`${ROOT_DIR}/PaConvert/paconvert/api_mapping.json` -2. 将已对齐的 PyTorch API 的 Matcher 设置为`ChangePrefixMatcher`,其他字段全部删除掉 +2. 将已完成的 PyTorch API 的 Matcher 设置为`ChangePrefixMatcher`,其他字段全部删除掉 -> 注意 torch.abs、torch.abs_、torch.Tensor.abs、torch.Tensor.abs_是四个不同的 API +**注意**: +- torch.abs、torch.abs_、torch.Tensor.abs、torch.Tensor.abs_是四个不同的 API,需分别标记为 `ChangePrefixMatcher`。 +- ⚠️ **`ChangePrefixMatcher` 是任务最终验收金标准,不可妥协**:只要 API 已完成代码层面的对齐,就必须标记为 `ChangePrefixMatcher`,**绝对禁止**为了让测试通过而将 Matcher 改为其他类型(如 GenericMatcher、SliceScatterMatcher 等)。 -## Step 2: 增加测试用例 +## Step 2: 增加测试用例(仅首次执行) **目的:** 判断是否满足如下测试规范,如不满足,则需增加测试用例使之符合规范 **修改位置:** @@ -119,122 +121,104 @@ def test_case_7(): obj.run(pytorch_code, ["result"]) ``` -**特殊场景处理:** +## Step 3: 运行单元测试(每次改动均需执行) -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 ${ROOT_DIR}/PaConvert/ - python -m pytest tests/test_.py - ``` - -2. 根据报错信息,修改代码或测试用例(禁止通过修改 api_mapping.json 来使单测通过),确保所有测试用例通过。注意每次修改 Paddle 源码后,必须重新编译方可生效: ```bash -cd /workspace/Paddle/build -cmake .. -make -j$(nproc) +cd ${ROOT_DIR}/PaConvert/ +python -m pytest tests/test_.py ``` -编译注意事项: -- 无需重装,直接生效(勿执行 setup/install 等安装操作) -- 勿删除 build 目录(否则增量编译失效,编译时间极长) - - +根据报错信息修改测试用例或回退,确保所有测试用例通过。每次修改后均需要重新执行本步骤。 # 二、注意事项 1. 严格按标准工作流程执行,杜绝自行臆断和跳过步骤 -2. 所有路径使用 `${ROOT_DIR}` 变量表示根目录,需自行替换为实际路径 -3. 确保测试用例覆盖所有参数组合和边界情况 -4. 验证通过是对齐的金标准,不可跳过 -5. 复盘记忆中的历史易错点,避免重复犯错 -6. **PaConvert 说明**:Pytorch->Paddle 代码转换工具,可以搭建起 Pytorch-Paddle API 之间的桥梁。 +2. 确保测试用例覆盖所有参数组合和边界情况 +3. 验证通过是对齐的金标准,不可跳过 +4. 复盘记忆中的历史易错点,避免重复犯错 +5. **PaConvert 说明**:Pytorch->Paddle 代码转换工具,可以搭建起 Pytorch-Paddle API 之间的桥梁。 -# 三、常见问题处理 +# 三、异常回退原则 -## Q1:PyTorch 代码执行失败 +当本步骤(对齐验证)多次尝试仍无法通过时,需要根据错误信息诊断问题根源: -**错误标识**:Failed to execute pytorch code +1. **若判断为测试用例编写有误**(如 PyTorch 代码语法错误、参数使用错误): + - 直接在本步骤修正测试用例 + - 无需回退到其他步骤 -**根本原因**:PyTorch 单元测试代码存在问题,无法正常执行 +2. **若判断为代码实现有误**(如参数处理逻辑错误、类型转换失败等): + - 回退到总步骤 Step2(代码修改)调整实现方式 + - 回退后再进入本步骤(Step4),则只需执行:运行单元测试,其他步骤无需执行 -**处理策略**:修改 PyTorch 单元测试代码,确保能正确执行 Pytorch 代码 +3. **若判断为方案选择错误**(如当前方案不适用、底层不支持等): + - 回退到总步骤 Step1(方案决策)重新决策 + - 回退后再进入本步骤(Step4),则只需执行:运行单元测试,其他步骤无需执行 -**验证标准**:测试代码应该能够在标准的 PyTorch 环境中正常运行 +# 四、常见问题处理 ---- +### Q1:报错(Failed to execute pytorch code) -## Q2:Paddle 代码执行失败 +**根本原因**:PyTorch 单元测试代码自身问题 -**错误标识**:Failed to execute paddle code +**处理策略**:修改 PyTorch 单元测试代码,确保是正确可执行的 Pytorch 代码 -**根本原因**:Pytorch 单测正确,但修改后的 Paddle API 实现存在问题,导致代码无法执行 +### Q2:报错(Failed to execute paddle code) -**处理策略**:需要返回到前序 Step 修改,因此结束本 Step,将报错信息返回给主控智能体分析 +**根本原因**:Paddle API 修改不正确,导致代码无法执行 -**关联任务**:Paddle API 需要进一步修改以兼容 PyTorch 接口 +**处理策略**:回退到 Step1 或 Step2 ---- +### Q3:报错(Unable to align results) -## Q3:计算结果不一致 +**根本原因**:Paddle API 修改不正确,计算结果与 PyTorch API 存在差异 -**错误标识**:Unable to align results +**处理策略**:回退到 Step1 或 Step2 -**根本原因**:Pytorch 单测正确,但 Paddle API 与 PyTorch API 计算结果存在差异 +### Q4:如何跳过测试用例 -**处理策略**:需要返回到前序 Step 修改,因此结束本 Step,将报错信息返回给主控智能体分析 +**场景**:Paddle 暂不支持某些功能(如类型提升、标量输入、特定硬件特性等),需要跳过对应测试用例 -**验证要求**:Paddle API 需要进一步修改以兼容 PyTorch 接口,确保数值精度、数据类型、形状等完全一致 +**解决方法**: -**禁止通过配置 api_mapping.json 为非 ChangePrefixMatcher 来使单测通过**,本步骤的通过标准为:Matcher 配置为 ChangePrefixMatcher + 单测运行通过。 +使用 pytest 的 `skip` 标记: +```python ---- +@pytest.mark.skip(reason="Not supported") +def test_unsupported_case(): + ... + +@pytest.mark.skipif(condition=not paddle.device.is_compiled_with_cuda(), reason="Test requires CUDA") +def test_case_with_cuda(): + ... +``` -## Q4:如何禁用不支持的测试用例 +**注意**:仅允许跳过 类型提升、标量输入、特定硬件特性 导致的失败用例,其他情况不允许跳过测试用例。 -**错误现象**: -Paddle 因为暂不支持某些功能(如类型提升、标量输入等)而报错 + +### Q5:自定义比较逻辑 + +**场景**:默认的比较逻辑不适用,需要自定义比较方式(通常不需要) **解决方法**: ```python -# 将 def test_case_x(): 改为 def _test_case_x(): -# 并添加注释说明原因 -def _test_case_2(): # Paddle does not support scalar input - ... +class CustomAPIBase(APIBase): + def compare(self, name, pytorch_result, paddle_result, ...): + # 自定义比较逻辑 + pytorch_str = str(pytorch_result).removeprefix("torch.") + assert pytorch_str == paddle_result ``` -**注意**:仅允许禁用因已知 Paddle 限制导致的失败用例,其他情况不允许禁用 +### Q6:参数顺序不同导致 ChangePrefixMatcher 下位置传参测试失败 ---- +**错误现象**:标记为 `ChangePrefixMatcher` 后,torch 的位置参数调用(如 `func(a, b, c)`)因参数位置与 paddle 不一致而报错或结果不对齐。 + +**根本原因**:Step2 中只添加了参数名别名(通用装饰器),但没有处理参数顺序差异,导致位置传参时 torch 的第 N 个参数映射到了 paddle 错误的参数。 + +**处理策略**:返回 Step2,将通用别名装饰器升级为**专用装饰器**,在装饰器内通过位置参数类型/特征检测区分 torch 顺序和 paddle 顺序,将 torch 位置参数统一转换为 paddle 关键字参数后再调用。 + +**典型案例**:`torch.nansum(input, dim, keepdim, *, dtype)` vs `paddle.nansum(x, axis, dtype, keepdim)` +- 第 3 个位置参数在 torch 是 `keepdim`(bool),在 paddle 是 `dtype`(str/dtype) +- 通用别名装饰器无法解决位置顺序冲突 +- 专用装饰器方案:检测第 3 个位置参数类型(`bool` → keepdim,`str/type` → dtype)来区分两套签名顺序 diff --git a/docs/dev_guides/coding_agent/api_compatibility/batch_api_compat.sh b/docs/dev_guides/coding_agent/api_compatibility/batch_api_compat.sh new file mode 100755 index 00000000000..bff1332b978 --- /dev/null +++ b/docs/dev_guides/coding_agent/api_compatibility/batch_api_compat.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Usage: ./batch_api_compat.sh api1 api2 api3 ... +# Or pipe from file: ./batch_api_compat.sh $(cat api_list.txt) + +set -e + +APIS=("$@") +BATCH_SIZE=8 +TOTAL=${#APIS[@]} + +if [ "$TOTAL" -eq 0 ]; then + echo "Usage: $0 ... " + echo " $0 \$(cat api_list.txt)" + exit 1 +fi + +echo "Total APIs: $TOTAL, batch size: $BATCH_SIZE" +echo "================================" + +for (( i=0; i>> Batch $batch_num / $total_batches: $batch_str" + ducc -p "/api-compatibility $batch_str" +done + +echo "" +echo "================================" +echo ">>> All batches done. Creating PR..." +ducc -p "/create-pr" + +echo "" +echo "Done."