بنقرة واحدة
cpp-sink
// 负责《Paddle API 对齐 PyTorch 项目》中 Step2:API 代码修改,实施 C++下沉的代码开发。通过将 Python API 下沉至 C++层,可以减少 Python 装饰器带来的性能开销,提升 API 调度效率。
// 负责《Paddle API 对齐 PyTorch 项目》中 Step2:API 代码修改,实施 C++下沉的代码开发。通过将 Python API 下沉至 C++层,可以减少 Python 装饰器带来的性能开销,提升 API 调度效率。
负责《Paddle API 对齐 PyTorch 项目》中 Step1:方案决策,分析 PyTorch API 与 Paddle API 之间的差异,制定合适的 API 改动方案
开展《Paddle API 对齐 PyTorch 项目》,负责项目整体统筹规划,调用多个 skill,完成输入的 API 对齐
负责《Paddle API 对齐 PyTorch 项目》中 Step4:API 文档修改,在 API 代码修改完成后,同步更新中文 API 文档,确保文档准确反映 API 的最新行为
负责《Paddle API 对齐 PyTorch 项目》中 Step5:代码提交,分别对 Paddle、PaConvert、Docs 三个仓库创建或更新 Pull Request
负责《Paddle API 对齐 PyTorch 项目》中 Step2:API 代码修改,实施 Python 装饰器的代码开发。通过 Python 装饰器,在 Python 层为 Paddle API 提供参数别名、参数顺序、参数类型和参数用法的兼容转换,实现 PyTorch 风格的 API 调用,并保持 Paddle API 的向后兼容性。
负责《Paddle API 对齐 PyTorch 项目》中 Step3:Pytorch 对齐验证,基于 PaConvert 工具验证 Paddle API 与 PyTorch API 是否用法完全对齐一致
| name | cpp-sink |
| description | 负责《Paddle API 对齐 PyTorch 项目》中 Step2:API 代码修改,实施 C++下沉的代码开发。通过将 Python API 下沉至 C++层,可以减少 Python 装饰器带来的性能开销,提升 API 调度效率。 |
| context | fork |
| background | false |
| verbose | true |
| disable-model-invocation | false |
根据 API 的复杂度,将下沉场景分为三类:
适用条件(需全部满足):
ops.yaml中参数类型或数量一致_C_ops前无任何前处理逻辑典型示例: paddle.log2
请严格按以下步骤依次执行,不要自行修改或跳过步骤:
python_api_info.yaml添加参数别名映射(按 op name 的字典序添加):
- op : log2
name : [paddle.log2, paddle.Tensor.log2]
args_alias :
use_default_mapping : True # 启用默认映射:x->input
说明:
name:指定对应的 Python API 名称(函数 API 和 Tensor 方法)use_default_mapping : True:自动配置如下字段映射:- op : op_name
name : [paddle.op_name, paddle.Tensor.op_name]
args_alias :
x: [input]
y: [other]
axis: [dim]
keepdims: [keepdim]
- op : op_name
name : [paddle.op_name, paddle.Tensor.op_name]
args_alias :
y : [exponent]
_paddle_docs.py注意要更新函数文档字符,在文档的 Args 部分为有别名的参数添加 Alias Support 说明,如下:
注:Alias 说明应放在该参数描述的末尾,句号前,格式为: Alias:
alias_name,多个 Alias 描述为: Alias:alias_name1oralias_name2
add_doc_and_signature(
"log2",
r"""
Calculates the log to the base 2 of the given input tensor, element-wise.
.. math::
Out = \log_2x
Args:
x (Tensor): Input tensor must be one of the following types: int32, int64, float16, bfloat16, float32, float64, complex64, complex128. Alias: ``input``.
name (str|None, optional): Name for the operation (optional, default is None). For more information, please refer to :ref:`api_guide_Name`.
out (Tensor, optional): The output Tensor. If set, the result will be stored in this Tensor. Default: None.
Keyword args:
out(Tensor, optional): The output tensor.
Returns:
Tensor: The log to the base 2 of the input Tensor computed element-wise.
Examples:
.. code-block:: pycon
>>> import paddle
>>> # example 1: x is a float
>>> x_i = paddle.to_tensor([[1.0], [2.0]])
>>> res = paddle.log2(x_i)
>>> res
Tensor(shape=[2, 1], dtype=float32, place=Place(cpu), stop_gradient=True,
[[0.],
[1.]])
>>> # example 2: x is float32
>>> x_i = paddle.full(shape=[1], fill_value=2, dtype='float32')
>>> paddle.to_tensor(x_i)
>>> res = paddle.log2(x_i)
>>> res
Tensor(shape=[1], dtype=float32, place=Place(cpu), stop_gradient=True,
[1.])
>>> # example 3: x is float64
>>> x_i = paddle.full(shape=[1], fill_value=2, dtype='float64')
>>> paddle.to_tensor(x_i)
>>> res = paddle.log2(x_i)
>>> res
Tensor(shape=[1], dtype=float64, place=Place(cpu), stop_gradient=True,
[1.])
""",
"""
def log2(
x: Tensor,
name: str | None = None,
*,
out: Tensor | None = None,
) -> Tensor
""",
)
如果支持了 out 参数,必须在 API 文档中描述 out 参数,out 为 keyword-only 参数(*后面)时注意增加Keyword Args:,并在此部分描述。out 为位置参数时直接在 Args 部分描述。如下:
# 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:
...
"""
注意事项:
_paddle_docs.py 现有文档格式保持一致找到 API 的 Python 实现位置(如 python/paddle/tensor/math.py,注意不要误检索到稀疏 API 位置):
# 在文件最上方合适位置导入(不要添加注释)
from paddle._C_ops import log2 # noqa: F401
# 以下内容全部删除(不要添加注释)
# def log2(x: Tensor, name: str | None = None) -> Tensor:
# ...
不要新建任何测试文件,直接在 test/legacy_test/test_api_compatibility[1-9]\.py(数字最大的) 中添加测试。严格按以下模板来编写:
测试模板:
class Test<APIName>API(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.<api_name>(x, ...)
# 2. Paddle keyword arguments
out2 = paddle.<api_name>(x=x, ...)
# 3. Pytorch Positional arguments (only if order different with paddle args)
out3 = paddle.<api_name>(x, ...)
# 4. PyTorch keyword arguments (alias)
out4 = paddle.<api_name>(input=x, dim=...)
# 5. Mixed arguments
out5 = paddle.<api_name>(x, axis=...)
# 6. out parameter test (only if supported)
out6 = paddle.empty_like(x)
out7 = paddle.<api_name>(x, ..., out=out6)
# 7. Tensor method - args (only if supported)
out8 = x.<api_name>(...)
# 8. Tensor method - kwargs (only if supported)
out9 = x.<api_name>(axis=...)
# Verify all outputs
for out in [out1, out2, out3, out4, out5, out6, out7, out8, out9]:
np.testing.assert_allclose(out.numpy(), ...)
paddle.enable_static()
def test_static_Compatibility(self):
paddle.enable_static()
main = paddle.static.Program()
startup = paddle.static.Program()
with paddle.static.program_guard(main, startup):
x = paddle.static.data(name="x", shape=self.shape, dtype=self.dtype)
# Create multiple outputs
out1 = paddle.<api_name>(x, ...)
out2 = paddle.<api_name>(x=x, ...)
out3 = paddle.<api_name>(input=x, dim=...)
exe = paddle.static.Executor()
fetches = exe.run(
main,
feed={"x": self.np_x},
fetch_list=[out1, out2, out3],
)
# Verify all outputs
for out in fetches:
np.testing.assert_allclose(out, ...)
测试规范: 动态图模式:
静态图模式:(inplace 无需测)
注意:
完整测试示例,请参考 ${ROOT_DIR}/Paddle/test/legacy_test/test_api_compatibility[1-9]\.py 中已有的测试类结构。
单测编写完成后,按以下命令验证执行(不可修改):
重新编译项目:
cd ${ROOT_DIR}/Paddle/build
cmake ..
make -j$(nproc)
运行单测文件:
python <所修改的单测文件名>
问题排查:根据报错信息调整代码或测试用例,确保所有测试用例通过。注意每次修改 Paddle 源码后,必须重新编译方可生效。
编译注意事项:
适用条件(全部满足):
ops.yaml中参数类型或数量一致_C_ops前有其他前处理逻辑典型示例: paddle.logsumexp
请严格按以下步骤依次执行,不要自行修改或跳过步骤:
python_api_info.yaml- op : logsumexp
name : [paddle.logsumexp, paddle.Tensor.logsumexp]
args_alias:
use_default_mapping : True # x->input, axis->dim
pre_process:
func : LogsumexpPreProcess(x, axis, reduce_all) # 前处理函数
关键点:
name:指定对应的 Python API 名称args_alias.use_default_mapping:启用默认参数映射pre_process.func:指定前处理函数名称及其参数列表将 Python 端在调用_C_ops前的其他前处理逻辑,修改为 C++的实现。尽可能参考 arg_pre_process.cc 中已有的预处理函数来实现,在风格和逻辑上保持尽可能一致。
在 paddle/fluid/pybind/arg_pre_process.h 声明:
namespace paddle {
namespace pybind {
// 动态图版本
void LogsumexpPreProcess(Tensor *x, std::vector<int> *axis, bool *reduce_all);
// 静态图版本
void LogsumexpPreProcess(pir::Value *x, std::vector<int> *axis, bool *reduce_all);
} // namespace pybind
} // namespace paddle
在 paddle/fluid/pybind/arg_pre_process.cc 实现:
#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<int> *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<int> *axis, bool *reduce_all) {
std::vector<int64_t> x_shape = pir::GetShapeFromValue(*x);
if (axis->empty() || axis->size() == x_shape.size()) {
*reduce_all = true;
} else {
*reduce_all = false;
}
}
} // namespace pybind
} // namespace paddle
注意事项:
_paddle_docs.py参考场景一的 Step 2 执行相同操作。
参考场景一的 Step 3 执行相同操作。
参考场景一的 Step 4 执行相同操作。
参考场景一的 Step 5 执行相同操作。
适用条件(全部满足):
ops.yaml中参数类型或数量不一致ops.yaml的参数差异典型示例: paddle.argmax/paddle.argmin
请严格按以下步骤依次执行,不要自行修改或跳过步骤:
python_api_info.yaml- op : argmax
name : [paddle.argmax, paddle.Tensor.argmax]
args_mapper :
func : ArgMaxMinMapper # 自定义参数映射函数
关键点:
name:指定对应的 Python API 名称args_mapper.func:指定自定义 Mapper 函数名称args_mapper时,不会生成默认参数解析代码将 Python API 与ops.yaml中参数类型或数量不一致的差异,通过 C++自定义 Mapper 来进行匹配与转换。尽可能参考 args_mapper.cc 中已有的 Mapper 来实现,在风格和逻辑上保持尽可能一致。
在 paddle/fluid/pybind/args_mapper.h 声明:
namespace paddle {
namespace pybind {
// 动态图版本
void ArgMaxMinMapper(PyObject* args,
PyObject* kwargs,
Tensor* x,
paddle::experimental::Scalar* axis,
bool* keepdims,
bool* flatten,
phi::DataType* dtype);
// 静态图版本
void ArgMaxMinMapper(PyObject* args,
PyObject* kwargs,
pir::Value* x,
pir::Value* axis,
bool* keepdims,
bool* flatten,
phi::DataType* dtype);
} // namespace pybind
} // namespace paddle
在 paddle/fluid/pybind/args_mapper.cc 实现:
#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<int>(PyTuple_Size(args)) : 0;
int remaining_kwargs = kwargs ? static_cast<int>(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<int>(PyTuple_Size(args)) : 0;
int remaining_kwargs = kwargs ? static_cast<int>(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<int64_t>{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<int64_t>{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
关键技术点:
参数解析工具函数:
GetTensorFromArgsOrKWArgs:解析 Tensor 参数,支持多个别名GetItemFromArgsOrKWArgs:获取通用 Python 对象CastPyArg2Scalar:转换为 Scalar 类型CastPyArg2Boolean:转换为 bool 类型CastPyArg2DataType:转换为 DataType 枚举CheckParamsCount:检查参数数量CheckRemainingParamsValidity:检查是否有未处理参数静态图特殊处理:
pir::Value代替TensorValue类型:使用paddle::dialect::full参数别名支持:
{"x", "input"}同时支持 Paddle 和 Torch 风格_paddle_docs.py除了完成场景一的文档迁移操作之外,场景三文档迁移还需要额外注意:
如果使用了自定义 Mapper,则 API 有可能支持了签名重载,需要分别描述两种签名,如下:
注:只需在文档正文中阐述两种签名(Paddle 在前,Pytorch 在后),文档其他位置如 Args/Returns 仍以 Paddle 风格签名为准
"""
This API has two signatures:
1. ``paddle.sum(x, axis=None, dtype=None, keepdim=False, name=None, *, out=None)`` (Paddle-style)
2. ``paddle.sum(input, dim=None, keepdim=False, dtype=None, *, out=None)`` (PyTorch-style)
Args:
...
Returns:
...
"""
参考场景一的 Step 2 执行文档迁移操作。
参考场景一的 Step 3 执行相同操作。
参考场景一的 Step 4 执行相同操作。
参考场景一的 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 |
// 获取 Tensor 参数(支持别名)
Tensor GetTensorFromArgsOrKWArgs(
const std::string& op_name,
const std::string& param_name,
PyObject* args, int arg_idx,
PyObject* kwargs, const std::vector<std::string>& aliases,
int nargs, int* remaining_kwargs, bool required);
// 获取通用 Python 对象
PyObject* GetItemFromArgsOrKWArgs(
PyObject* args, int arg_idx,
PyObject* kwargs, const std::vector<std::string>& 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<int> 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);
${ROOT_DIR} 变量表示根目录,需自行替换为实际路径_C_ops API 不同,属于特殊情况,Cpp 下沉方案无法实现,需要使用 Python 装饰器方案out参数,必须修改add_doc_and_signature中的字符串,增加 out 参数generated_tensor_methods_patch.py,该文件是自动生成的,修改没有意义,如无法对齐可考虑放弃 C++下沉方案而不是改动该文件 .. code-block:: pycon
>>> # type: ignore
>>> import paddle
>>> x = paddle.to_tensor([1.0, 2.0])
错误现象:
TypeError: (InvalidType) all(): argument (position 1) must be Value, but got Variable
解决方法:
rm -rf test/deprecated/test_xxx.py
CMakeLists.txt中涉及的单测配置错误现象:
TypeError: argmax() got an unexpected keyword argument 'invalid_param'
解决方法: