Skip to content

Commit 5e28969

Browse files
authored
Improve compatibility test coverage (#50)
1 parent 36e6fcc commit 5e28969

34 files changed

+7959
-1
lines changed

.claude/skills/compatibility-testing/SKILL.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,13 @@ TEST_F(SomeOpTest, InvalidInputHandling) {
188188
- 如果./test/result_cmp.sh的对比结果有差异,请记录下来,在最后总结告诉我,不需要修改测试代码
189189
190190
191+
### 注意事项
192+
193+
**覆盖率计算陷阱与规避:**
194+
- **不要堆砌无效对象实例化**:覆盖率统计基于方法调用正则,仅实例化对象而不调用方法无法增加有效覆盖率。测试应包含真实的方法调用逻辑。
195+
- **避免硬编码兼容宏忽略真实 API**:测试宏背后的类方法或内部函数时,需模拟真实数据调用其对外开放的公共接口(如 `.call()`、`.get()`、`.has_value()` 等),而非依赖编译期宏替换。
196+
- **禁止使用条件编译区分 API**:测试 Paddle 兼容层与 PyTorch 的行为一致性时,**禁止**使用 `#if USE_PADDLE_API` 或类似条件编译区分同一方法的写法。应直接调用目标框架的实际 API,确保测试运行时行为而非编译期分支。
197+
191198
### 仅运行单个测试
192199
193200
```bash

doc/mismatch_api_record.md

Lines changed: 295 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
##### 记录PaddleCPPAPITest仓库检测出来的接口不一致情况
22

3-
# Allocator 类与 torch 存在差异
3+
# Allocator
44

55
## 差异点列表
66

@@ -14,3 +14,297 @@
1414
---
1515

1616
涉及到的 PR:https://github.com/PFCCLab/PaddleCppAPITest/pull/42/changes#diff
17+
18+
---
19+
20+
# Device
21+
22+
> Paddle 头文件:`c10\core\Device.h`
23+
24+
## 差异点列表
25+
26+
1. **未指定 Index 时的默认行为**:PyTorch index = -1,has_index() = false;Paddle 强制默认为 0,has_index() = true
27+
2. **纯字符串解析行为**:PyTorch 保持无索引状态(如 `cpu``cuda`);Paddle 自动补全为 0 号设备(如 `cpu:0``gpu:0`
28+
3. **GPU/CUDA 字符串表示**:PyTorch 严格输出 `cuda``cuda:0`;Paddle 底层映射为 GPU,输出 `gpu:0``gpu:1`
29+
4. **底层类型枚举值(Enum ID)**:PyTorch CPU=0,CUDA=1;Paddle CPU=1,CUDA/GPU=2
30+
5. **默认 Tensor 所在设备**:PyTorch 处于无明确索引的 cpu 状态;Paddle 明确挂载在 cpu:0 设备上
31+
32+
---
33+
34+
提交的对齐 PR:https://github.com/PaddlePaddle/Paddle/pull/78066
35+
36+
---
37+
38+
# BFloat16
39+
40+
> Paddle 头文件:`c10\util\BFloat16.h`
41+
42+
## 差异点列表
43+
44+
1. **BFloat16 ScalarType 枚举值**:PyTorch 为 **11**,Paddle 为 **15**
45+
2. **ComplexFloat ScalarType 枚举值**:PyTorch 为 **8**,Paddle 为 **9**
46+
47+
---
48+
49+
# DefaultDtype
50+
51+
> Paddle 头文件:`c10\core\DefaultDtype.h`
52+
53+
## 差异点列表
54+
55+
1. **BFloat16 枚举值**:PyTorch 为 **11**,Paddle 为 **15**
56+
2. **ComplexFloat(复数类型)枚举值**:PyTorch 为 **8**,Paddle 为 **9**
57+
58+
---
59+
60+
# IValue
61+
62+
> Paddle 头文件:`ATen/core/ivalue.h`
63+
64+
## 差异点列表
65+
66+
1. **命名空间**:PyTorch 为 `c10::IValue`;Paddle 为 `torch::IValue`(c10 命名空间中不存在 IValue)
67+
2. **方法命名风格**:PyTorch 使用 camelCase(如 `isNone()``toBool()`);Paddle 使用 snake_case(如 `is_none()``to_bool()`
68+
3. **`tagKind()` 方法**:PyTorch 存在;Paddle 中**不存在**
69+
4. **字符串提取方法**:PyTorch 为 `toStringRef()`;Paddle 为 `to_string()`
70+
71+
---
72+
73+
# SparseTensor
74+
75+
> Paddle 头文件:`ATen/ops/sparse_coo_tensor.h``ATen/ops/sparse_csr_tensor.h`
76+
77+
## 差异点列表
78+
79+
1. **sparse_coo_tensor 无 size 推断行为**:PyTorch 能根据 indices 内容正确推断完整 size(如 `2 2 2`);Paddle 推断结果第一个维度为 0(如 `0 2 2`
80+
81+
---
82+
83+
# OptionalArrayRef
84+
85+
> Paddle 头文件:`c10\util\OptionalArrayRef.h`
86+
87+
## 差异点列表
88+
89+
1. **运行时内存地址值**:两框架输出的内存地址不同(属正常运行时差异,不影响功能)
90+
2. **内部对象标识符**:两框架内部唯一标识符数值不同(属正常实现差异,不影响功能)
91+
92+
> 注:OptionalArrayRef 核心功能(has_value、size、元素访问、reset、swap、emplace、slice 等)在两个框架中完全兼容,仅运行时地址和标识符存在差异。
93+
94+
---
95+
96+
# at::indexing(Slice / EllipsisIndexType)
97+
98+
> Paddle 头文件:`ATen/indexing.h`
99+
> PyTorch 头文件:`ATen/TensorIndexing.h`
100+
101+
## 差异点列表
102+
103+
1. **头文件路径不同**:PyTorch 为 `ATen/TensorIndexing.h`;Paddle compat 为 `ATen/indexing.h`
104+
2. **`Tensor::operator[](Slice)` 不支持**:PyTorch 的 `Tensor::operator[]` 接受 `at::indexing::Slice`;Paddle compat 的 `operator[]` 仅重载 `int64_t`,传入 `Slice` 会编译报错
105+
3. **多维 Slice 索引写法不同**
106+
- PyTorch:`t.index({Slice(0,2), Slice(1,3)})` —— 接受 `std::initializer_list<TensorIndex>`
107+
- Paddle:`t.index(std::vector<at::indexing::Slice>{Slice(0,2), Slice(1,3)})` —— 仅重载 `std::vector<Slice>`
108+
4. **`TensorIndex` 类不存在**:Paddle compat 的 `indexing.h` 未定义 `TensorIndex` 类,注释掉了 `index(ArrayRef<TensorIndex>)` 重载,仅保留 `index(const std::vector<Slice>&)`
109+
110+
111+
---
112+
113+
# ScalarType 扩展类型函数
114+
115+
> Paddle 头文件:`c10/core/ScalarType.h`
116+
117+
## 差异点列表
118+
119+
### 1. 量化类型 `elementSize` 未实现
120+
121+
`c10::elementSize()` 对量化整型不支持:
122+
123+
| ScalarType | PyTorch 返回值 | Paddle 状态 |
124+
|------------|--------------|------------|
125+
| `QInt8` | 1 | 未实现,编译报错 |
126+
| `QUInt8` | 1 | 未实现,编译报错 |
127+
| `QInt32` | 4 | 未实现,编译报错 |
128+
129+
### 2. Float8 扩展枚举值缺失
130+
131+
Paddle compat 的 `ScalarType` 枚举未定义以下两个值,`isFloat8Type` 实现中也将其注释掉:
132+
133+
- `ScalarType::Float8_e5m2fnuz`
134+
- `ScalarType::Float8_e4m3fnuz`
135+
136+
PyTorch 完整支持这两个 Float8 变体,Paddle compat 仅保留了 `Float8_e5m2``Float8_e4m3fn`
137+
138+
### 3. `ComplexHalf` 枚举值缺失
139+
140+
Paddle compat 的 `ScalarType` 枚举未包含 `ComplexHalf``isComplexType` 实现中对该分支也已注释掉。PyTorch 完整支持。
141+
142+
### 4. 以下 10 个函数/常量在 Paddle compat 中完全缺失
143+
144+
整块 `#ifndef USE_PADDLE_API` 保护了如下 10 个测试,Paddle 下全部跳过:
145+
146+
| 函数/常量 | 说明 |
147+
|-----------|------|
148+
| `c10::isQIntType()` | 判断量化整型 |
149+
| `c10::isBitsType()` | 判断位类型 |
150+
| `c10::isBarebonesUnsignedType()` | 判断裸无符号整型 |
151+
| `c10::toQIntType()` | 转换为量化整型 |
152+
| `c10::toUnderlying()` | 量化类型的底层类型 |
153+
| `c10::isUnderlying()` | 判断底层类型关系 |
154+
| `c10::toRealValueType()` | 复数类型转实数类型 |
155+
| `c10::toComplexType()` | 实数类型转复数类型 |
156+
| `c10::canCast()` | 类型间是否可转换 |
157+
| `c10::NumScalarTypes` | ScalarType 枚举总数常量 |
158+
159+
## 修复方向
160+
161+
在 Paddle compat 的 `c10/core/ScalarType.h` 中逐一补全上述枚举值和函数实现,完成后将对应测试移出 `#ifndef USE_PADDLE_API` 块。
162+
163+
---
164+
165+
# TensorAccessor / GenericPackedTensorAccessor
166+
167+
> Paddle 头文件:`ATen/core/TensorAccessor.h`
168+
169+
## 差异点列表
170+
171+
### `GenericPackedTensorAccessorBase` / `GenericPackedTensorAccessor` 系列类缺失
172+
173+
Paddle compat 的 `ATen/core/TensorAccessor.h`**未实现**以下类和类型别名:
174+
175+
- `at::GenericPackedTensorAccessorBase<T, N, PtrTraits, index_t>`
176+
- `at::GenericPackedTensorAccessor<T, N, PtrTraits, index_t>`
177+
- `at::PackedTensorAccessor32<T, N, PtrTraits>``index_t = int32_t` 别名)
178+
- `at::PackedTensorAccessor64<T, N, PtrTraits>``index_t = int64_t` 别名)
179+
180+
以及 `at::Tensor` 上的 `packed_accessor64<T,N>()` 方法(Paddle compat 仅有 `packed_accessor32`)。
181+
182+
libtorch 在同路径头文件中完整定义了上述类,供 CUDA kernel 使用。
183+
184+
## 修复方向
185+
186+
在 Paddle compat 的 `ATen/core/TensorAccessor.h` 中补充 `GenericPackedTensorAccessorBase``GenericPackedTensorAccessor` 完整实现及 `PackedTensorAccessor32/64` 类型别名;并在 `ATen/core/Tensor.h` 中补充 `packed_accessor64<T,N>()` 方法。
187+
188+
---
189+
190+
# Exception 宏(TORCH_CHECK_EQ / TORCH_CHECK_NE 失败语义差异)
191+
192+
> Paddle 头文件:`c10/util/Exception.h`
193+
194+
## 差异点列表
195+
196+
1. **`TORCH_CHECK_EQ` 失败行为**:PyTorch 调用 `abort()` 终止进程(测试用 `EXPECT_DEATH` 捕获);Paddle 抛出 C++ 异常(测试用 try-catch 捕获)。
197+
2. **`TORCH_CHECK_NE` 失败行为**:同上,两者失败行为不一致。
198+
199+
当前代码通过 `#if USE_PADDLE_API` 分叉两套检测逻辑以绕过差异,但这导致两个平台实际走不同测试路径,无法真正对比行为。
200+
201+
---
202+
203+
# CUDA Context(`at::cuda::getCurrentCUDAStream` 缺失)
204+
205+
> Paddle 头文件:`ATen/cuda/CUDAContext.h`(Paddle compat 中不存在)
206+
207+
## 差异点列表
208+
209+
1. **`at::cuda::getCurrentCUDAStream()` 不存在**:Paddle compat 未提供该函数,整个调用块被 `#ifndef USE_PADDLE_API` 保护,Paddle 下只输出固定字符串 `"stream_skipped_paddle"`,无法进行真实对比。
210+
211+
---
212+
213+
# CUDA 工具类(CUDAGuard / CUDAStream / PhiloxCudaState 全部缺失)
214+
215+
> Paddle 头文件:`c10/cuda/CUDAGuard.h``c10/cuda/CUDAStream.h``c10/cuda/PhiloxCudaState.h`(Paddle compat 中均不存在)
216+
> 测试文件:`test/CUDATest2.cpp`
217+
218+
## 差异点列表
219+
220+
以下类和相关头文件在 Paddle compat 中**完全缺失**,对应测试被 `#ifndef USE_PADDLE_API` 整块保护跳过:
221+
222+
| 缺失类/结构 | 头文件 |
223+
|-------------|--------|
224+
| `c10::cuda::CUDAGuard` | `c10/cuda/CUDAGuard.h` |
225+
| `c10::cuda::OptionalCUDAGuard` | `c10/cuda/CUDAGuard.h` |
226+
| `c10::cuda::CUDAStream` | `c10/cuda/CUDAStream.h` |
227+
| `c10::cuda::getCurrentCUDAStream()` | `c10/cuda/CUDAStream.h` |
228+
| `c10::cuda::PhiloxCudaState` | `c10/cuda/PhiloxCudaState.h` |
229+
230+
---
231+
232+
# TensorOptions(`requires_grad` 传递问题)
233+
234+
> Paddle 头文件:`c10/core/TensorOptions.h`
235+
236+
## 差异点列表
237+
238+
1. **`at::empty()` 不支持含 `requires_grad``TensorOptions`**:Paddle 在通过 `at::empty({...}, opts)` 创建 tensor 时,若 `opts` 含有 `requires_grad(true)` 会抛出异常。PyTorch 完整支持。当前测试已绕过:将含 `requires_grad``opts` 与用于创建 tensor 的 `opts_for_dtype` 分离,单独测试 `requires_grad()` 的读取,但实际上 Paddle 无法通过 `TensorOptions` 在 tensor 创建时传递梯度需求。
239+
2. **`device_index()` 对 CPU 设备的返回值不同**:Torch 对 CPU 设备返回 `-1`(无显式 index);Paddle 会将 CPU 规范化为 `cpu:0`,因此返回 `0`
240+
241+
---
242+
243+
## 详细记录
244+
245+
- 测试用例:DeviceIndex
246+
- 字段:`c10::TensorOptions().device(c10::Device(c10::kCPU)).device_index()`
247+
- 差异:
248+
- Paddle 输出:`0`
249+
- Torch 输出:`-1`
250+
- 原因:Torch 将 CPU 设备视为无显式 index;Paddle 会将 CPU 设备规范化为 `cpu:0`
251+
- 处理:已在测试文件中注释掉该字段输出,并添加 `DIFF` 标注说明。
252+
253+
---
254+
255+
# Tensor::resize_(Paddle 不支持)
256+
257+
> Paddle 头文件:`ATen/core/Tensor.h`
258+
259+
## 差异点列表
260+
261+
1. **`resize_()` 不支持**:Paddle 调用 `tensor.resize_({...})` 会抛出异常,PyTorch 完整支持原地调整 tensor 形状。当前测试用 try-catch 捕获异常并输出 `"1 "` 表示异常发生,无法对比实际 resize 结果。
262+
263+
---
264+
265+
# TensorFactoryTest
266+
267+
## 差异点列表
268+
269+
1. **ScalarType::Bool 枚举值不同**:Paddle 的 DataType::BOOL = 10,Torch 的 ScalarType::Bool = 11。
270+
271+
---
272+
273+
## 详细记录
274+
275+
- 测试用例:TensorFromBoolArrayRef
276+
- 字段:scalar_type(write_tensor_info_to_file 输出的 static_cast<int>(t.scalar_type()))
277+
- 差异:
278+
- Paddle 输出:10
279+
- Torch 输出:11
280+
- 原因:Paddle 与 Torch 框架的 ScalarType::Bool 枚举值不同(Paddle=10,Torch=11),属于设计差异。
281+
- 处理:已在测试文件中注释掉该字段输出,并添加 DIFF 标注说明。
282+
283+
---
284+
285+
# CUDADataTypeTest
286+
287+
## 差异点列表
288+
289+
1. **`ScalarTypeToCudaDataType(Bool)` 支持范围不同**:Paddle compat 不支持 `Bool``cudaDataType`,会抛出异常;Torch 侧接口支持范围更完整。当前测试已跳过 `Bool`
290+
2. **`empty_cuda` 结果依赖运行时/构建环境**:Torch CUDA 版通常可成功创建 CUDA Tensor;Paddle compat 在未编译 CUDA 或运行时不可用时会抛异常并进入不可用分支。该差异属于环境差异,不属于接口语义差异。
291+
292+
---
293+
294+
## 详细记录
295+
296+
- 测试用例:GetCudaDataType
297+
- 字段:`Bool` 类型的 `ScalarTypeToCudaDataType` 转换
298+
- 差异:
299+
- Paddle:抛出 `Cannot convert ScalarType Bool to cudaDataType`
300+
- Torch:可返回对应的 `cudaDataType`
301+
- 原因:Paddle compat 的 `ATen/cuda/CUDADataType.h` 未实现 `Bool` 分支。
302+
- 处理:已在测试文件中跳过 `Bool` 的输出,并添加 `DIFF` 注释说明。
303+
304+
- 测试用例:EmptyCUDA / EmptyCudaDifferentDtype
305+
- 字段:结果字符串(`cuda_empty` / `cuda_empty_int` / `cuda_not_available`
306+
- 差异:
307+
- Paddle 输出:`cuda_not_available`
308+
- Torch 输出:`cuda_empty``cuda_empty_int`
309+
- 原因:该结果依赖 Paddle 是否为 GPU 版以及当前 CUDA 运行时是否可用,属于运行时/构建环境差异,而非接口行为差异。
310+
- 处理:已在测试文件中保留调用、注释掉结果字符串输出,并添加 `DIFF` 注释说明。

src/file_manager.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <functional>
44
#include <mutex>
55
#include <shared_mutex>
6+
#include <sstream>
67
#include <string>
78

89
namespace paddle_api_test {
@@ -16,6 +17,12 @@ class FileManerger {
1617
void openAppend();
1718
void writeString(const std::string& str);
1819
FileManerger& operator<<(const std::string& str);
20+
template <typename T>
21+
FileManerger& operator<<(const T& value) {
22+
std::ostringstream oss;
23+
oss << value;
24+
return operator<<(oss.str());
25+
}
1926
void saveFile();
2027

2128
// 捕获标准输出到文件

0 commit comments

Comments
 (0)