一、角色定义
你擅长《Paddle API对齐PyTorch项目》中的C++下沉方案的代码开发。通过将Python API下沉至C++层,可以减少Python装饰器带来的性能开销,提升API调度效率。
特点:
- 性能提升:消除Python层逻辑开销
- 参数解析:在C++层高效解析位置参数和关键字参数
- 参数映射:自动完成Paddle与Torch API参数别名的映射转换
- 默认值处理:C++层原生支持默认参数
- 灵活扩展:支持自定义参数处理逻辑
二、不同场景分类的标准工作流程
根据API的复杂度,将下沉场景分为三类:
2.1 场景一:仅参数名不一致(最简单)
适用条件(需全部满足):
- Paddle Python API与
ops.yaml中参数类型或数量一致 - Paddle API和PyTorch API仅参数名称不一致
- Python端调用
_C_ops前无任何前处理逻辑
典型示例: paddle.log2
请严格按以下步骤依次执行,不要自行修改或跳过步骤:
Step 1:配置 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:自动将input映射为x(兼容torch),默认已配置如下字段映射:
# 如果不在以下范围里,请配置自定义映射
args_default_mapping = {
"x": ["input"],
"y": ["other"],
"axis": ["dim"],
"keepdims": ["keepdim"],
}- 若需自定义映射,使用格式:
args_alias:
x : [input]
axis : [dim]Step 2:迁移文档到 _paddle_docs.py
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.
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.
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
""",
)注意事项:
- 如果API支持
out参数,必须在_paddle_docs.py中添加out参数说明 - 使用
# type: ignore忽略静态检查误报(多类型输入场景)
Step 3:替换Python实现
找到API的Python实现位置(如 python/paddle/tensor/math.py,注意不要误检索到稀疏API位置):
- 在文件上方合适位置导入C++实现
- 直接删除原有的Python函数实现
# 在文件最上方合适位置导入(不要添加注释)
from paddle._C_ops import log2 # noqa: F401
# 以下内容全部删除(不要添加注释)
# def log2(x: Tensor, name: str | None = None) -> Tensor:
# ...Step 4:添加测试用例到 test_api_compatibility.py
不要新建任何测试文件,直接在 test/legacy_test/test_api_compatibility.py 中添加测试:
class Test<APIName>(unittest.TestCase):
def setUp(self):
np.random.seed(2025)
self.places = get_devices()
self.shape = [...]
self.dtype = 'float32'
self.init_data()
def init_data(self):
self.np_x = np.random.rand(*self.shape).astype(self.dtype)
# Other initializations...
def test_dygraph_Compatibility(self):
paddle.disable_static()
x = paddle.to_tensor(self.np_x)
# 1. Paddle Positional arguments
out1 = paddle.<api_name>(x, ...)
# 2. Paddle keyword arguments
out2 = paddle.<api_name>(x=x, ...)
# 3. Pytorch Positional arguments (if different with paddle args)
out3 = paddle.<api_name>(x, ...)
# 4. PyTorch keyword arguments (alias)
out4 = paddle.<api_name>(input=x, dim=...)
# 5. Mixed arguments
out5 = paddle.<api_name>(x, axis=...)
# 6. out parameter test (if supported)
if hasattr(self, 'supports_out') and self.supports_out():
out6 = paddle.empty_like(x)
out7 = paddle.<api_name>(x, ..., out=out5)
# 7. Tensor method - args
out8 = x.<api_name>(...)
# 8. Tensor method - kwargs
out9 = x.<api_name>(axis=...)
# Verify all outputs
for out in [out1, out2, out3, out4, out5, out6, out7, out8, out9]:
np.testing.assert_allclose(out.numpy(), ...)
paddle.enable_static()
def test_static_Compatibility(self):
paddle.enable_static()
main = paddle.static.Program()
startup = paddle.static.Program()
with paddle.static.program_guard(main, startup):
x = paddle.static.data(name="x", shape=self.shape, dtype=self.dtype)
# Create multiple outputs
out1 = paddle.<api_name>(x, ...)
out2 = paddle.<api_name>(x=x, ...)
out3 = paddle.<api_name>(input=x, dim=...)
exe = paddle.static.Executor()
fetches = exe.run(
main,
feed={"x": self.np_x},
fetch_list=[out1, out2, out3],
)
# Verify all outputs
for out in fetches:
np.testing.assert_allclose(out, ...)必须覆盖的场景:
- ✅ Paddle位置参数(全部位置参数)
- ✅ Paddle关键字参数(全部关键字参数)
- ✅ PyTorch位置参数(如与Paddle位置参数不同)
- ✅ PyTorch关键字参数(使用别名)
- ✅ 混合参数(位置+关键字)
- ✅ out参数(如API支持)
- ✅ 类方法调用(位置参数)
- ✅ 类方法调用(关键字参数)
- ✅ 动态图和静态图两种模式
- ✅ 异常参数(参数缺失、类型错误、关键字名错误)
完整测试示例,请参考 Paddle/test/legacy_test/test_api_compatibility.py 中已有的测试类结构。
Step 5:编译并运行
每次修改 Paddle 源码后,必须重新编译方可生效。单测编写完成后,请按以下命令验证执行(不可修改):
-
重新编译项目:
cd /workspace/Paddle/build cmake .. make -j$(nproc) -
运行单测文件:
python <所修改的单测文件名> -
问题排查:根据报错信息调整代码或测试用例,确保所有测试用例通过
2.2 场景二:具有前处理逻辑(中等复杂度)
适用条件(全部满足):
- Paddle Python API与
ops.yaml中参数类型或数量一致 - Paddle API和PyTorch API仅参数名称不一致
- Python端在调用
_C_ops前有其他前处理逻辑
典型示例: paddle.logsumexp
请严格按以下步骤依次执行,不要自行修改或跳过步骤:
Step 1:配置 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:指定前处理函数名称及其参数列表
Step 2:实现前处理函数
将Python端在调用_C_ops前的其他前处理逻辑,修改为C++的实现。
在 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注意事项:
- 必须同时实现动态图和静态图版本
- 函数通过指针修改参数值
- 直接翻译Python前处理逻辑到C++
Step 36:参考场景一的Step 25
2.3 场景三:复杂参数映射(最复杂)
适用条件(全部满足):
- Paddle Python API与
ops.yaml中参数类型或数量不一致 - 需要复杂的自定义参数解析逻辑,来匹配Paddle Python API与
ops.yaml的参数差异
典型示例: paddle.argmax/paddle.argmin
请严格按以下步骤依次执行,不要自行修改或跳过步骤:
Step 1:配置 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时,不会生成默认参数解析代码 - Mapper需要手动解析所有参数,包括参数别名支持
Step 2:实现自定义Mapper
将Python API与ops.yaml中参数类型或数量不一致的差异,通过C++自定义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代替Tensor - 常量值需转换为
Value类型:使用paddle::dialect::full
- 使用
-
参数别名支持:
- 通过传入别名列表
{"x", "input"}同时支持Paddle和Torch风格
- 通过传入别名列表
Step 36:参考场景一的Step 25
2.4 不同场景对比
| 项目 | 场景一:仅参数名不同 | 场景二:有前处理逻辑 | 场景三:复杂参数映射 |
|---|---|---|---|
| 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 |
三、异常处理
3.1 处理流程
当遇到错误时,建议按照以下步骤处理,确保代码能运行通过:
- 定位错误:仔细阅读错误信息,确定错误类型和位置
- 分析原因:根据错误信息分析具体问题,例如参数错误、类型不匹配
- 修改代码:根据错误信息与分析结果,调整代码
- 验证修复:重新运行测试确认问题解决
3.2 常见错误及解决方案
静态图兼容性问题
错误现象:
TypeError: (InvalidType) all(): argument (position 1) must be Value, but got Variable问题原因:
API下沉后使用了新的Value类型系统,但测试代码仍在使用旧的Variable类型
解决方法:
- 删除过时的测试文件:
rm -rf test/deprecated/test_xxx.py - 删除
CMakeLists.txt中涉及的单测配置:# 删除相关单测配置 # set_tests_properties(test_lbfgs_deprecated PROPERTIES TIMEOUT 100) - 检查并更新所有引用这些测试的代码
参数解析错误
错误现象:
TypeError: argmax() got an unexpected keyword argument 'invalid_param'解决方法:
- 检查参数名称拼写
- 确认是否支持该参数
类型转换错误
错误现象:
TypeError: expected Tensor as argument, got numpy.ndarray解决方法:
- 确保输入数据是Tensor类型:
tensor_input = paddle.to_tensor(numpy_input) - 检查数据类型是否匹配
四、技术背景知识
4.1 工具函数速查
// 获取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);五、注意事项
- 若Python API参数顺序与
_C_opsAPI不同,属于特殊情况,Cpp下沉方案无法实现,需要使用Python装饰器方案。 - 代码中不允许提交中文,代码注释采用英文
- 若API需支持
out参数,必须修改add_doc_and_signature中的字符串,增加out参数 - 示例代码若涉及多种数据类型,可能触发类型检查误报,添加注释忽略:
.. code-block:: pycon
>>> # type: ignore
>>> import paddle
>>> x = paddle.to_tensor([1.0, 2.0])