Skip to content

Commit ef4c009

Browse files
committed
add skill
1 parent a82099e commit ef4c009

File tree

2 files changed

+291
-5
lines changed

2 files changed

+291
-5
lines changed
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
---
2+
name: compatibility-testing
3+
description: '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.'
4+
argument-hint: '要测试的算子名称或 API,例如 abs、sum、reshape'
5+
---
6+
7+
# compatibility-testing
8+
9+
PaddlePaddle 与 PyTorch C++ API 兼容性测试开发规范。
10+
11+
## 触发条件
12+
13+
适用场景:
14+
- 编写或扩展 `PaddleCppAPITest\test` 下的兼容性测试
15+
- 验证 Paddle 兼容层与 PyTorch 对同一 API 的行为一致性
16+
- 定位某个接口在两个框架间的输出差异
17+
18+
## 测试目标
19+
20+
**测试范围**:覆盖 `Paddle\paddle\phi\api\include\compat` 目录下**所有**接口,包括但不限于:
21+
22+
| 目录 | 接口类型 | 示例 |
23+
|------|---------|------|
24+
| `ATen/ops/` | ATen 算子 | `abs.h`, `sum.h`, `reshape.h`, `zeros.h` ... |
25+
| `ATen/core/` | ATen 核心类型 | `Tensor.h`, `TensorBody.h`, `TensorAccessor.h` ... |
26+
| `ATen/` | ATen 基础 | `Tensor.h`, `Device.h`, `DeviceGuard.h` ... |
27+
| `c10/core/` | C10 核心 | `ScalarType.h`, `TensorOptions.h`, `Storage.h` ... |
28+
| `c10/util/` | C10 工具 | `Optional.h`, `ArrayRef.h`, `Half.h` ... |
29+
| `c10/cuda/` | C10 CUDA | `CUDAStream.h`, `CUDAGuard.h`, `CUDAException.h` ... |
30+
| `torch/` | Torch 包装 | `all.h`, `cuda.h`, `extension.h` ... |
31+
| `utils/` | 工具函数 | `scalar_type_conversion.h`, `int_array_ref_conversion.h` ... |
32+
33+
> `AbsTest.cpp`(位于 `test/ops/` 仅为示例)仅作为**参考**,展示测试文件结构和输出格式。
34+
35+
## 项目约定
36+
37+
- 构建系统通过 `CMakeLists.txt` 中的 `create_paddle_tests()` 函数同时生成 `torch_*``paddle_*` 两套可执行文件
38+
- 测试二进制运行时自动以自身文件名命名输出文件(如 `torch_AbsTest.txt`),由 `main.cpp` 中的 `g_custom_param` 传递
39+
- 结果对比依赖文本 diff,因此输出格式的确定性至关重要
40+
41+
## 测试文件结构
42+
43+
### 文件头与命名空间
44+
45+
测试文件统一位于 `PaddleCppAPITest\test`,与 compat 接口目录结构对应。参考以下结构(以 `AbsTest.cpp` 为示例):
46+
47+
```cpp
48+
#include <ATen/ATen.h>
49+
#include <ATen/core/Tensor.h>
50+
#include <ATen/ops/abs.h> // 按需替换为目标算子头文件
51+
#include <ATen/ops/zeros.h> // 辅助构造用
52+
#include <gtest/gtest.h>
53+
54+
#include <string>
55+
#include <vector>
56+
57+
#include "../../src/file_manager.h"
58+
59+
extern paddle_api_test::ThreadSafeParam g_custom_param;
60+
61+
namespace at {
62+
namespace test {
63+
64+
using paddle_api_test::FileManerger;
65+
using paddle_api_test::ThreadSafeParam;
66+
67+
class AbsTest : public ::testing::Test {
68+
protected:
69+
void SetUp() override {
70+
// 构造基准输入 tensor
71+
}
72+
at::Tensor test_tensor;
73+
};
74+
75+
// 测试用例 ...
76+
77+
} // namespace test
78+
} // namespace at
79+
```
80+
81+
**关键约束**:
82+
- 命名空间固定为 `at::test`,保证与 ATen 类型系统的直接可见性
83+
- `g_custom_param` 是全局线程安全参数,存储当前运行的输出文件名,由 `main.cpp` 在 `RUN_ALL_TESTS()` 前注入
84+
- 测试类命名格式 `<OpName>Test`,文件名与之一致
85+
86+
### 结果输出函数
87+
88+
每个测试文件包含一个静态输出函数,负责将 tensor 结果序列化到文件。该函数是跨框架对比的唯一数据源,格式必须确定且可复现:
89+
90+
```cpp
91+
static void write_abs_result_to_file(FileManerger* file, const at::Tensor& result) {
92+
*file << std::to_string(result.dim()) << " ";
93+
*file << std::to_string(result.numel()) << " ";
94+
float* data = result.data_ptr<float>();
95+
for (int64_t i = 0; i < result.numel(); ++i) {
96+
*file << std::to_string(data[i]) << " ";
97+
}
98+
}
99+
```
100+
101+
注意:
102+
- 第一个测试用例调用 `file.createFile()` 创建文件,后续用例调用 `file.openAppend()` 追加
103+
- 每个用例输出前须写入**用例名标签**,输出末尾须追加 `"\n"` 换行,使每个用例占独立一行(详见"输出格式"章节)
104+
- 输出函数参数使用 `FileManerger*`(指针),调用处传 `&file`。不能使用非 const 引用,否则违反 Google C++ 规范(cpplint `runtime/references`
105+
- 对于多 dtype 支持的算子,需按 `result.scalar_type()` 分发到对应的 `data_ptr<T>()` 类型
106+
107+
## Shape 覆盖要求
108+
109+
测试 shape 的选择直接影响边界条件的暴露率。以下为四个必选维度区间,每个新算子测试须至少各取一例:
110+
111+
### 标量 (0-d tensor)
112+
- `{}` — 零维标量,部分算子(如 `sum` 不指定 dim)的返回类型
113+
- 注意:`{1}` 是 1-d tensor,**不是**标量
114+
115+
### 小 shape(元素数 ≤ 64)
116+
- 典型值:`{4}``{2, 3}``{2, 3, 4}`
117+
- 便于手工验证数值正确性
118+
119+
### 大 shape(元素数 ≥ 10000)
120+
- 典型值:`{10000}``{100, 100}``{10, 20, 30, 40}`
121+
- 主要暴露精度累积误差和内存布局差异
122+
123+
### 边界 shape
124+
- 含零维度:`{0}``{2, 0}``{1, 0, 3}` — 验证空 tensor 语义
125+
- 全一维度:`{1, 1, 1}` — 常触发 squeeze/broadcast 的特殊路径
126+
-`transpose()` / `as_strided()` 产生的非连续 tensor — 验证 stride 处理的正确性
127+
128+
## Dtype 覆盖要求
129+
130+
以下为 ATen 支持的标准标量类型,通过 `at::TensorOptions().dtype()` 或 shorthand 常量指定。新增测试至少需要覆盖 `kFloat``kDouble``kInt``kLong` 四种基础类型,其余按算子语义酌情补充:
131+
132+
| 标量类型 | ATen 常量 | C++ 对应类型 | 适用注意 |
133+
|---------|-----------|-------------|---------|
134+
| float32 | `at::kFloat` | `float` | 多数算子的默认 dtype |
135+
| float64 | `at::kDouble` | `double` | 精度基准,常用于 reference 比较 |
136+
| int32 | `at::kInt` | `int32_t` | 整型算子、索引 |
137+
| int64 | `at::kLong` | `int64_t` | shape / dim 参数的底层类型 |
138+
| int16 | `at::kShort` | `int16_t` | 较少使用,部分量化场景 |
139+
| int8 | `at::kChar` | `int8_t` | 不要与 `kByte` (uint8) 混淆 |
140+
| uint8 | `at::kByte` | `uint8_t` | 常见于图像数据 |
141+
| bool | `at::kBool` | `bool` | 比较算子的返回类型 |
142+
143+
> Paddle 兼容层的 dtype 映射与 PyTorch 存在细微差异(例如默认 dtype 可能不同),输出对比时需关注此类隐式转换。
144+
145+
## 异常行为测试
146+
147+
部分算子在非法输入下的异常行为可能在两个框架间存在差异(一个抛异常、另一个返回 NaN 或空 tensor)。此类差异需显式捕获并记录:
148+
149+
```cpp
150+
TEST_F(SomeOpTest, InvalidInputHandling) {
151+
auto file_name = g_custom_param.get();
152+
FileManerger file(file_name);
153+
file.openAppend();
154+
file << "InvalidInputHandling ";
155+
try {
156+
at::Tensor result = at::some_op(invalid_tensor);
157+
// 未抛异常 — 正常记录结果
158+
write_someop_result_to_file(&file, result);
159+
} catch (const std::exception& e) {
160+
file << "exception ";
161+
}
162+
file << "\n";
163+
file.saveFile();
164+
}
165+
```
166+
167+
**注意事项**:
168+
- **不要使用 `c10::Error` 作为 catch 类型** — 在 Paddle 兼容层中 `c10::Error` 是 `C10ErrorType` 枚举常量(定义在 `c10/util/Exception.h`),不是异常类。统一使用 `std::exception` 捕获即可。
169+
- **不要输出 `e.what()`** — 两个框架的异常消息文本不同,会产生大量无意义的 diff。仅记录 `"exception "` 标记是否抛出异常。
170+
- 两框架的对比重点是**是否抛异常**须一致,异常消息内容不要求匹配。
171+
172+
## 输出格式
173+
174+
输出文件采用空格分隔的纯文本,**每个用例独占一行**,格式如下:
175+
176+
```
177+
<TestCaseName> <ndim> <numel> [<size_0> <size_1> ...] <val_0> <val_1> ...\n
178+
```
179+
180+
- **用例名标签**:每行以用例名(如 `SumAllElements`)开头,与后续数据以空格分隔
181+
- **换行分隔**:每个用例输出末尾追加 `"\n"`,使 diff 能逐行定位到具体出错的用例
182+
183+
示例(SumTest 的部分输出):
184+
```
185+
SumAllElements 0 1 21.000000
186+
SumWithDtype 7 0 1 21.000000
187+
SumAlongDim0 1 3 3 5.000000 7.000000 9.000000
188+
SumInt32 0 1 100
189+
```
190+
191+
代码示例:
192+
```cpp
193+
TEST_F(SumTest, SumAllElements) {
194+
auto file_name = g_custom_param.get();
195+
FileManerger file(file_name);
196+
file.createFile(); // 第一个用例
197+
file << "SumAllElements "; // 用例名标签
198+
at::Tensor result = at::sum(test_tensor);
199+
write_sum_result_to_file(&file, result);
200+
file << "\n"; // 换行分隔
201+
file.saveFile();
202+
}
203+
204+
TEST_F(SumTest, SumWithDtype) {
205+
auto file_name = g_custom_param.get();
206+
FileManerger file(file_name);
207+
file.openAppend(); // 后续用例
208+
file << "SumWithDtype "; // 用例名标签
209+
// ...
210+
file << "\n";
211+
file.saveFile();
212+
}
213+
```
214+
215+
注意事项:
216+
- 浮点值通过 `std::to_string()` 序列化,精度为 6 位有效数字
217+
- 用例名标签使得 diff 输出直接可读,无需逐字节计数来定位差异
218+
- 不同测试用例的输出依次追加到同一文件中,顺序由 GTest 的用例注册顺序决定
219+
- Place的验证可以取HashValue()
220+
- Device的比较可以取str()
221+
- 如果./test/result_cmp.sh的对比结果有差异,请记录下来,在最后总结告诉我,不需要修改测试代码
222+
223+
224+
### 仅运行单个测试
225+
226+
```bash
227+
./torch/torch_AbsTest --gtest_filter="AbsTest.EdgeValues"
228+
```
229+
230+
#### 运行对比脚本
231+
232+
```bash
233+
cd .. && ./test/result_cmp.sh build
234+
```
235+
236+
## 新算子测试检查清单
237+
238+
新增测试前逐项确认,标注 `*` 的为强制项:
239+
240+
**Shape 维度**
241+
- [ ] `*` 标量 (0-d tensor)
242+
- [ ] `*` 小 shape (元素数 ≤ 64)
243+
- [ ] `*` 大 shape (元素数 ≥ 10000)
244+
- [ ] 含零维度 (`{0}`, `{2, 0}`)
245+
- [ ] 全一维度 (`{1, 1, 1}`)
246+
- [ ] 非连续 tensor (经 `transpose` / `narrow` / `as_strided`)
247+
248+
**Dtype**
249+
- [ ] `*` float32
250+
- [ ] `*` float64
251+
- [ ] `*` int32
252+
- [ ] `*` int64
253+
- [ ] bool
254+
- [ ] int8 / uint8 / int16(视算子支持情况)
255+
256+
**值域**
257+
- [ ] `*` 正数
258+
- [ ] `*` 负数
259+
- [ ] `*`
260+
- [ ] NaN / Inf / -Inf
261+
- [ ] 极值 (`1e38f`, `1e-38f`)
262+
- [ ] 正负零区分 (`+0.0` vs `-0.0`)
263+
264+
**API 变体**
265+
- [ ] 函数式调用 (`at::abs(t)`)
266+
- [ ] 原地操作 (`at::abs_(t)``t.abs_()`)
267+
- [ ] out= 重载 (`at::abs_out(out, t)`)
268+
- [ ] keepdim 参数(归约类算子)
269+
- [ ] dim / axis 参数(含负索引)
270+
271+
**输出**
272+
- [ ] `*` 第一个用例使用 `createFile()`,后续使用 `openAppend()`
273+
- [ ] `*` 每个用例输出前写入用例名标签,末尾追加 `"\n"` 换行
274+
- [ ] `*` 通过 `write_<op>_result_to_file()` 统一输出
275+
- [ ] 多 dtype 场景按 `scalar_type()` 分发 `data_ptr<T>()`
276+
- [ ] 异常捕获统一使用 `std::exception`(不要用 `c10::Error`),不输出 `e.what()`
277+
278+
## 输出文件路径
279+
280+
默认输出目录:`/tmp/paddle_cpp_api_test/`(由 `FileManerger::basic_path_` 控制)。
281+
282+
文件名自动取可执行文件名 + `.txt`
283+
- `torch_AbsTest``/tmp/paddle_cpp_api_test/torch_AbsTest.txt`
284+
- `paddle_AbsTest``/tmp/paddle_cpp_api_test/paddle_AbsTest.txt`
285+
286+
如需自定义路径,在构造 `FileManerger` 时传入完整文件名即可覆盖(但通常不建议,以保持批量对比脚本的兼容性)。

cmake/external.cmake

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ function(ExternalProject repourl tag destination)
3535
GIT_REPOSITORY ${repourl}
3636
GIT_TAG ${tag}
3737
CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
38-
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
39-
${cmake_cli_args}
40-
-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
41-
-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}
38+
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} ${cmake_cli_args}
39+
-DCMAKE_CXX_STANDARD=17
4240
PREFIX "${destination}"
43-
INSTALL_DIR "${destination}")
41+
INSTALL_DIR "${destination}"
42+
INSTALL_COMMAND "${CMAKE_COMMAND}" --install "<BINARY_DIR>" --prefix
43+
"${destination}")
4444
endfunction()

0 commit comments

Comments
 (0)