Skip to content

Commit f7f9b9e

Browse files
authored
Merge branch 'master' into 223
2 parents 6c0a618 + f831018 commit f7f9b9e

17 files changed

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

0 commit comments

Comments
 (0)