| name | description | argument-hint |
|---|---|---|
compatibility-testing |
PaddlePaddle 与 PyTorch C++ API 兼容性测试开发规范。适用于:编写或扩展 test/ 目录下的兼容性测试、验证 Paddle 兼容层与 PyTorch 同一 API 的行为一致性、定位接口输出差异、新增算子测试、覆盖 Shape/Dtype/值域/API 变体。Use when: writing compatibility tests, adding operator tests, checking ATen/c10 API behavior differences between Paddle and PyTorch. |
要测试的算子名称或 API,例如 abs、sum、reshape |
PaddlePaddle 与 PyTorch C++ API 兼容性测试开发规范。
适用场景:
- 编写或扩展
PaddleCppAPITest\test下的兼容性测试 - 验证 Paddle 兼容层与 PyTorch 对同一 API 的行为一致性
- 定位某个接口在两个框架间的输出差异
测试范围:覆盖 Paddle\paddle\phi\api\include\compat 目录下所有接口,包括但不限于:
| 目录 | 接口类型 | 示例 |
|---|---|---|
ATen/ops/ |
ATen 算子 | abs.h, sum.h, reshape.h, zeros.h ... |
ATen/core/ |
ATen 核心类型 | Tensor.h, TensorBody.h, TensorAccessor.h ... |
ATen/ |
ATen 基础 | Tensor.h, Device.h, DeviceGuard.h ... |
c10/core/ |
C10 核心 | ScalarType.h, TensorOptions.h, Storage.h ... |
c10/util/ |
C10 工具 | Optional.h, ArrayRef.h, Half.h ... |
c10/cuda/ |
C10 CUDA | CUDAStream.h, CUDAGuard.h, CUDAException.h ... |
torch/ |
Torch 包装 | all.h, cuda.h, extension.h ... |
utils/ |
工具函数 | scalar_type_conversion.h, int_array_ref_conversion.h ... |
AbsTest.cpp(位于test/ops/仅为示例)仅作为参考,展示测试文件结构和输出格式。
- 构建系统通过
CMakeLists.txt中的create_paddle_tests()函数同时生成torch_*和paddle_*两套可执行文件 - 测试二进制运行时自动以自身文件名命名输出文件(如
torch_AbsTest.txt),由main.cpp中的g_custom_param传递 - 结果对比依赖文本 diff,因此输出格式的确定性至关重要
测试文件统一位于 PaddleCppAPITest\test,与 compat 接口目录结构对应。参考以下结构(以 AbsTest.cpp 为示例):
#include <ATen/ATen.h>
#include <ATen/core/Tensor.h>
#include <ATen/ops/abs.h> // 按需替换为目标算子头文件
#include <ATen/ops/zeros.h> // 辅助构造用
#include <gtest/gtest.h>
#include <string>
#include <vector>
#include "../../src/file_manager.h"
extern paddle_api_test::ThreadSafeParam g_custom_param;
namespace at {
namespace test {
using paddle_api_test::FileManerger;
using paddle_api_test::ThreadSafeParam;
class AbsTest : public ::testing::Test {
protected:
void SetUp() override {
// 构造基准输入 tensor
}
at::Tensor test_tensor;
};
// 测试用例 ...
} // namespace test
} // namespace at关键约束:
- 命名空间固定为
at::test,保证与 ATen 类型系统的直接可见性 g_custom_param是全局线程安全参数,存储当前运行的输出文件名,由main.cpp在RUN_ALL_TESTS()前注入- 测试类命名格式
<OpName>Test,文件名与之一致
每个测试文件包含一个静态输出函数,负责将 tensor 结果序列化到文件。该函数是跨框架对比的唯一数据源,格式必须确定且可复现:
static void write_abs_result_to_file(FileManerger* file, const at::Tensor& result) {
*file << std::to_string(result.dim()) << " ";
*file << std::to_string(result.numel()) << " ";
float* data = result.data_ptr<float>();
for (int64_t i = 0; i < result.numel(); ++i) {
*file << std::to_string(data[i]) << " ";
}
}注意:
- 第一个测试用例调用
file.createFile()创建文件,后续用例调用file.openAppend()追加 - 每个用例输出前须写入用例名标签,输出末尾须追加
"\n"换行,使每个用例占独立一行(详见"输出格式"章节) - 输出函数参数使用
FileManerger*(指针),调用处传&file。不能使用非 const 引用,否则违反 Google C++ 规范(cpplintruntime/references) - 对于多 dtype 支持的算子,需按
result.scalar_type()分发到对应的data_ptr<T>()类型
测试 shape 的选择直接影响边界条件的暴露率。以下为四个必选维度区间,每个新算子测试须至少各取一例:
{}— 零维标量,部分算子(如sum不指定 dim)的返回类型- 注意:
{1}是 1-d tensor,不是标量
- 典型值:
{4}、{2, 3}、{2, 3, 4} - 便于手工验证数值正确性
- 典型值:
{10000}、{100, 100}、{10, 20, 30, 40} - 主要暴露精度累积误差和内存布局差异
- 含零维度:
{0}、{2, 0}、{1, 0, 3}— 验证空 tensor 语义 - 全一维度:
{1, 1, 1}— 常触发 squeeze/broadcast 的特殊路径 - 经
transpose()/as_strided()产生的非连续 tensor — 验证 stride 处理的正确性
以下为 ATen 支持的标准标量类型,通过 at::TensorOptions().dtype() 或 shorthand 常量指定。新增测试至少需要覆盖 kFloat、kDouble、kInt、kLong 四种基础类型,其余按算子语义酌情补充:
| 标量类型 | ATen 常量 | C++ 对应类型 | 适用注意 |
|---|---|---|---|
| float32 | at::kFloat |
float |
多数算子的默认 dtype |
| float64 | at::kDouble |
double |
精度基准,常用于 reference 比较 |
| int32 | at::kInt |
int32_t |
整型算子、索引 |
| int64 | at::kLong |
int64_t |
shape / dim 参数的底层类型 |
| int16 | at::kShort |
int16_t |
较少使用,部分量化场景 |
| int8 | at::kChar |
int8_t |
不要与 kByte (uint8) 混淆 |
| uint8 | at::kByte |
uint8_t |
常见于图像数据 |
| bool | at::kBool |
bool |
比较算子的返回类型 |
Paddle 兼容层的 dtype 映射与 PyTorch 存在细微差异(例如默认 dtype 可能不同),输出对比时需关注此类隐式转换。
部分算子在非法输入下的异常行为可能在两个框架间存在差异(一个抛异常、另一个返回 NaN 或空 tensor)。此类差异需显式捕获并记录:
TEST_F(SomeOpTest, InvalidInputHandling) {
auto file_name = g_custom_param.get();
FileManerger file(file_name);
file.openAppend();
file << "InvalidInputHandling ";
try {
at::Tensor result = at::some_op(invalid_tensor);
// 未抛异常 — 正常记录结果
write_someop_result_to_file(&file, result);
} catch (const std::exception& e) {
file << "exception ";
}
file << "\n";
file.saveFile();
}注意事项:
- 不要使用
c10::Error作为 catch 类型 — 在 Paddle 兼容层中c10::Error是C10ErrorType枚举常量(定义在c10/util/Exception.h),不是异常类。统一使用std::exception捕获即可。 - 不要输出
e.what()— 两个框架的异常消息文本不同,会产生大量无意义的 diff。仅记录"exception "标记是否抛出异常。 - 两框架的对比重点是是否抛异常须一致,异常消息内容不要求匹配。
输出文件采用空格分隔的纯文本,每个用例独占一行,格式如下:
<TestCaseName> <ndim> <numel> [<size_0> <size_1> ...] <val_0> <val_1> ...\n
- 用例名标签:每行以用例名(如
SumAllElements)开头,与后续数据以空格分隔 - 换行分隔:每个用例输出末尾追加
"\n",使 diff 能逐行定位到具体出错的用例
示例(SumTest 的部分输出):
SumAllElements 0 1 21.000000
SumWithDtype 7 0 1 21.000000
SumAlongDim0 1 3 3 5.000000 7.000000 9.000000
SumInt32 0 1 100
代码示例:
TEST_F(SumTest, SumAllElements) {
auto file_name = g_custom_param.get();
FileManerger file(file_name);
file.createFile(); // 第一个用例
file << "SumAllElements "; // 用例名标签
at::Tensor result = at::sum(test_tensor);
write_sum_result_to_file(&file, result);
file << "\n"; // 换行分隔
file.saveFile();
}
TEST_F(SumTest, SumWithDtype) {
auto file_name = g_custom_param.get();
FileManerger file(file_name);
file.openAppend(); // 后续用例
file << "SumWithDtype "; // 用例名标签
// ...
file << "\n";
file.saveFile();
}注意事项:
- 浮点值通过
std::to_string()序列化,精度为 6 位有效数字 - 用例名标签使得 diff 输出直接可读,无需逐字节计数来定位差异
- 不同测试用例的输出依次追加到同一文件中,顺序由 GTest 的用例注册顺序决定
- Place的验证可以取HashValue()
- Device的比较可以取str()
- 如果./test/result_cmp.sh的对比结果有差异,请记录下来,在最后总结告诉我,不需要修改测试代码
./torch/torch_AbsTest --gtest_filter="AbsTest.EdgeValues"cd .. && ./test/result_cmp.sh build新增测试前逐项确认,标注 * 的为强制项:
Shape 维度
-
*标量 (0-d tensor) -
*小 shape (元素数 ≤ 64) -
*大 shape (元素数 ≥ 10000) - 含零维度 (
{0},{2, 0}) - 全一维度 (
{1, 1, 1}) - 非连续 tensor (经
transpose/narrow/as_strided)
Dtype
-
*float32 -
*float64 -
*int32 -
*int64 - bool
- int8 / uint8 / int16(视算子支持情况)
值域
-
*正数 -
*负数 -
*零 - NaN / Inf / -Inf
- 极值 (
1e38f,1e-38f) - 正负零区分 (
+0.0vs-0.0)
API 变体
- 函数式调用 (
at::abs(t)) - 原地操作 (
at::abs_(t)或t.abs_()) - out= 重载 (
at::abs_out(out, t)) - keepdim 参数(归约类算子)
- dim / axis 参数(含负索引)
输出
-
*第一个用例使用createFile(),后续使用openAppend() -
*每个用例输出前写入用例名标签,末尾追加"\n"换行 -
*通过write_<op>_result_to_file()统一输出 - 多 dtype 场景按
scalar_type()分发data_ptr<T>() - 异常捕获统一使用
std::exception(不要用c10::Error),不输出e.what()
默认输出目录:/tmp/paddle_cpp_api_test/(由 FileManerger::basic_path_ 控制)。
文件名自动取可执行文件名 + .txt:
torch_AbsTest→/tmp/paddle_cpp_api_test/torch_AbsTest.txtpaddle_AbsTest→/tmp/paddle_cpp_api_test/paddle_AbsTest.txt
如需自定义路径,在构造 FileManerger 时传入完整文件名即可覆盖(但通常不建议,以保持批量对比脚本的兼容性)。