一、角色定义

你擅长《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位置):

  1. 在文件上方合适位置导入C++实现
  2. 直接删除原有的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, ...)

必须覆盖的场景:

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

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

Step 5:编译并运行

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

  1. 重新编译项目:

    cd /workspace/Paddle/build
    cmake ..
    make -j$(nproc)
  2. 运行单测文件:

    python <所修改的单测文件名>
  3. 问题排查:根据报错信息调整代码或测试用例,确保所有测试用例通过

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

关键技术点:

  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 36:参考场景一的Step 25

2.4 不同场景对比

项目场景一:仅参数名不同场景二:有前处理逻辑场景三:复杂参数映射
YAML配置args_alias + use_default_mappingargs_alias + pre_processargs_mapper
C++实现无需额外C++代码arg_pre_process.h/ccargs_mapper.h/cc
实现难度⭐ 简单⭐⭐ 中等⭐⭐⭐ 复杂
参数解析自动生成自动生成 + 前处理完全手动
适用情况仅参数名不一致仅参数名不一致+前处理逻辑参数类型/数量不一致
示例APIlog2logsumexpargmax, argmin

三、异常处理

3.1 处理流程

当遇到错误时,建议按照以下步骤处理,确保代码能运行通过:

  1. 定位错误:仔细阅读错误信息,确定错误类型和位置
  2. 分析原因:根据错误信息分析具体问题,例如参数错误、类型不匹配
  3. 修改代码:根据错误信息与分析结果,调整代码
  4. 验证修复:重新运行测试确认问题解决

3.2 常见错误及解决方案

静态图兼容性问题

错误现象:

TypeError: (InvalidType) all(): argument (position 1) must be Value, but got Variable

问题原因:
API下沉后使用了新的Value类型系统,但测试代码仍在使用旧的Variable类型

解决方法:

  1. 删除过时的测试文件:
    rm -rf test/deprecated/test_xxx.py
  2. 删除CMakeLists.txt中涉及的单测配置:
    # 删除相关单测配置
    # set_tests_properties(test_lbfgs_deprecated PROPERTIES TIMEOUT 100)
  3. 检查并更新所有引用这些测试的代码

参数解析错误

错误现象:

TypeError: argmax() got an unexpected keyword argument 'invalid_param'

解决方法:

  1. 检查参数名称拼写
  2. 确认是否支持该参数

类型转换错误

错误现象:

TypeError: expected Tensor as argument, got numpy.ndarray

解决方法:

  1. 确保输入数据是Tensor类型:
    tensor_input = paddle.to_tensor(numpy_input)
  2. 检查数据类型是否匹配

四、技术背景知识

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);

五、注意事项

  1. 若Python API参数顺序与_C_ops API不同,属于特殊情况,Cpp下沉方案无法实现,需要使用Python装饰器方案。
  2. 代码中不允许提交中文,代码注释采用英文
  3. 若API需支持out参数,必须修改add_doc_and_signature中的字符串,增加out参数
  4. 示例代码若涉及多种数据类型,可能触发类型检查误报,添加注释忽略:
  .. code-block:: pycon
      >>> # type: ignore
      >>> import paddle
      >>> x = paddle.to_tensor([1.0, 2.0])