From 5953ea2c560f97e131d6a065781e66bec0e73598 Mon Sep 17 00:00:00 2001 From: youge325 Date: Thu, 28 May 2026 18:43:26 +0800 Subject: [PATCH 1/9] [Cpp API Compatibility] Add broadcast_to cross-framework test and update mapping doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add test/ATen/ops/BroadcastToTest.cpp with 11 test cases - Shape coverage: small, large, boundary empty, boundary rank less - Dtype coverage: kFloat, kDouble, kInt, kLong - Exception coverage: invalid non-singleton, high rank to low rank - Function form: at::broadcast_to(t, size) - Update doc/cpp_api_mapping_cn.md: broadcast_to from 功能缺失 to API完全一致 Co-Authored-By: Claude Opus 4.7 (1M context) --- test/ATen/ops/BroadcastToTest.cpp | 227 ++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 test/ATen/ops/BroadcastToTest.cpp diff --git a/test/ATen/ops/BroadcastToTest.cpp b/test/ATen/ops/BroadcastToTest.cpp new file mode 100644 index 0000000..4351970 --- /dev/null +++ b/test/ATen/ops/BroadcastToTest.cpp @@ -0,0 +1,227 @@ +#include +#include +#include + +#include +#include + +#include "src/file_manager.h" + +extern paddle_api_test::ThreadSafeParam g_custom_param; + +namespace at { +namespace test { + +using paddle_api_test::FileManerger; + +class BroadcastToTest : public ::testing::Test { + protected: + void SetUp() override {} +}; + +static void write_broadcast_to_result_to_file(FileManerger* file, + const at::Tensor& result) { + *file << std::to_string(result.dim()) << " "; + *file << std::to_string(result.numel()) << " "; + for (int64_t i = 0; i < result.dim(); ++i) { + *file << std::to_string(result.sizes()[i]) << " "; + } + if (result.numel() == 0) { + *file << "empty "; + return; + } + at::Tensor cont = result.contiguous(); + float* data = cont.data_ptr(); + *file << std::to_string(data[0]) << " "; + *file << std::to_string(data[cont.numel() - 1]) << " "; + *file << std::to_string(cont.sum().item()) << " "; +} + +// ======================== Shape coverage ======================== + +// Small shape test +TEST_F(BroadcastToTest, BroadcastToSmall) { + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.createFile(); + file << "BroadcastToSmall "; + at::Tensor small = at::ones({1, 3}, at::kFloat); + at::Tensor result = small.broadcast_to({2, 3}); + write_broadcast_to_result_to_file(&file, result); + file << "\n"; + file.saveFile(); +} + +// Large shape test +TEST_F(BroadcastToTest, BroadcastToLarge) { + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "BroadcastToLarge "; + at::Tensor large = at::ones({1, 1, 128}, at::kFloat); + at::Tensor result = large.broadcast_to({64, 32, 128}); + write_broadcast_to_result_to_file(&file, result); + file << "\n"; + file.saveFile(); +} + +// Boundary: empty tensor +TEST_F(BroadcastToTest, BroadcastToBoundaryEmpty) { + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "BroadcastToBoundaryEmpty "; + at::Tensor t = at::ones({1, 0}, at::kFloat); + at::Tensor result = t.broadcast_to({2, 0}); + file << std::to_string(result.dim()) << " "; + file << std::to_string(result.numel()) << " "; + file << "empty "; + file << "\n"; + file.saveFile(); +} + +// Boundary: rank less (input rank < target rank) +TEST_F(BroadcastToTest, BroadcastToBoundaryRankLess) { + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "BroadcastToBoundaryRankLess "; + at::Tensor t = at::ones({1}, at::kFloat); + at::Tensor result = t.broadcast_to({2, 3}); + write_broadcast_to_result_to_file(&file, result); + file << "\n"; + file.saveFile(); +} + +// ======================== Dtype coverage ======================== + +TEST_F(BroadcastToTest, BroadcastToDtypeFloat) { + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "BroadcastToDtypeFloat "; + at::Tensor t = at::ones({1, 2}, at::kFloat); + at::Tensor result = t.broadcast_to({3, 2}); + file << std::to_string(static_cast(result.scalar_type())) << " "; + write_broadcast_to_result_to_file(&file, result); + file << "\n"; + file.saveFile(); +} + +TEST_F(BroadcastToTest, BroadcastToDtypeDouble) { + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "BroadcastToDtypeDouble "; + at::Tensor t = at::ones({1, 2}, at::kDouble); + at::Tensor result = t.broadcast_to({3, 2}); + file << std::to_string(static_cast(result.scalar_type())) << " "; + file << std::to_string(result.dim()) << " "; + file << std::to_string(result.numel()) << " "; + for (int64_t i = 0; i < result.dim(); ++i) { + file << std::to_string(result.sizes()[i]) << " "; + } + at::Tensor cont = result.contiguous(); + double* data = cont.data_ptr(); + file << std::to_string(data[0]) << " "; + file << std::to_string(data[cont.numel() - 1]) << " "; + file << "\n"; + file.saveFile(); +} + +TEST_F(BroadcastToTest, BroadcastToDtypeInt) { + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "BroadcastToDtypeInt "; + at::Tensor t = at::ones({1, 2}, at::kInt); + at::Tensor result = t.broadcast_to({3, 2}); + file << std::to_string(static_cast(result.scalar_type())) << " "; + file << std::to_string(result.dim()) << " "; + file << std::to_string(result.numel()) << " "; + for (int64_t i = 0; i < result.dim(); ++i) { + file << std::to_string(result.sizes()[i]) << " "; + } + at::Tensor cont = result.contiguous(); + int* data = cont.data_ptr(); + file << std::to_string(data[0]) << " "; + file << std::to_string(data[cont.numel() - 1]) << " "; + file << "\n"; + file.saveFile(); +} + +TEST_F(BroadcastToTest, BroadcastToDtypeLong) { + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "BroadcastToDtypeLong "; + at::Tensor t = at::ones({1, 2}, at::kLong); + at::Tensor result = t.broadcast_to({3, 2}); + file << std::to_string(static_cast(result.scalar_type())) << " "; + file << std::to_string(result.dim()) << " "; + file << std::to_string(result.numel()) << " "; + for (int64_t i = 0; i < result.dim(); ++i) { + file << std::to_string(result.sizes()[i]) << " "; + } + at::Tensor cont = result.contiguous(); + int64_t* data = cont.data_ptr(); + file << std::to_string(data[0]) << " "; + file << std::to_string(data[cont.numel() - 1]) << " "; + file << "\n"; + file.saveFile(); +} + +// ======================== Exception coverage ======================== + +TEST_F(BroadcastToTest, BroadcastToInvalidNonSingleton) { + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "BroadcastToInvalidNonSingleton "; + + try { + at::Tensor t = at::ones({2, 3}, at::kFloat); + at::Tensor result = t.broadcast_to({2, 4}); + write_broadcast_to_result_to_file(&file, result); + } catch (const std::exception&) { + file << "exception "; + } + + file << "\n"; + file.saveFile(); +} + +TEST_F(BroadcastToTest, BroadcastToHighRankToLowRank) { + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "BroadcastToHighRankToLowRank "; + + try { + at::Tensor t = at::ones({2, 3, 4}, at::kFloat); + at::Tensor result = t.broadcast_to({3, 4}); + write_broadcast_to_result_to_file(&file, result); + } catch (const std::exception&) { + file << "exception "; + } + + file << "\n"; + file.saveFile(); +} + +// ======================== Function form ======================== + +TEST_F(BroadcastToTest, BroadcastToFunction) { + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "BroadcastToFunction "; + at::Tensor t = at::ones({1, 2}, at::kFloat); + at::Tensor result = at::broadcast_to(t, {3, 2}); + write_broadcast_to_result_to_file(&file, result); + file << "\n"; + file.saveFile(); +} + +} // namespace test +} // namespace at From 218c2128624223ba1695cc8ffcba861bba387e82 Mon Sep 17 00:00:00 2001 From: youge325 Date: Sun, 31 May 2026 17:32:09 +0800 Subject: [PATCH 2/9] [Cpp API Compatibility] Remove contiguous from BroadcastToTest, record strides diff - Replace .contiguous() calls with strides-aware element access in BroadcastToTest.cpp, so strides differences between Paddle and PyTorch are preserved and detected by result_cmp. - Add strides field to result output for layout comparison. - Record the known mismatch (Paddle expand uses non-zero strides for broadcast dims vs PyTorch uses zero strides) in mismatch_api_record.md. Co-Authored-By: Claude Opus 4.7 (1M context) --- doc/mismatch_api_record.md | 36 ++++++++++++ test/ATen/ops/BroadcastToTest.cpp | 93 ++++++++++++++++++++----------- 2 files changed, 97 insertions(+), 32 deletions(-) diff --git a/doc/mismatch_api_record.md b/doc/mismatch_api_record.md index 2e3b5eb..aafc64d 100644 --- a/doc/mismatch_api_record.md +++ b/doc/mismatch_api_record.md @@ -2,6 +2,42 @@ --- +## 2026-05-31 broadcast_to 对齐测试 strides 差异记录 + +### 输入链接 +- 链接类型:review comment +- 原始链接:https://github.com/PaddlePaddle/Paddle/pull/79173 +- 关联 PR:#79173 [Execute Infrastructure] Add at::broadcast_to compat interface + +### 问题与根因 + +| # | 问题接口 | 触发场景 | 根因说明 | +|---|---------|---------|---------| +| 1 | `at::broadcast_to` / `Tensor::broadcast_to` | 对齐测试移除 `.contiguous()` 后 result_cmp DIFFER | Paddle `expand` 对广播维度分配非零 strides(如 `{1,3}` expand 到 `{2,3}` 时 strides 为 `[3,1]`),而 PyTorch 对广播维度分配 stride 为 0(`[0,1]`)。两者在逻辑值上完全一致,但底层存储布局(strides)策略不同 | + +### 修复内容 + +**PaddleCppAPITest 改动文件:** +- `test/ATen/ops/BroadcastToTest.cpp` + - 移除所有 `.contiguous()` 调用,改用 strides-aware 元素访问(`compute_offset_from_flat_index`) + - 在结果输出中增加 strides 字段,使 result_cmp 能检测布局差异 + - 测试不掩盖 strides 差异,保留 DIFFER 并在此文档归档 + +### 验证结果 + +| 测试项 | Paddle 输出示例 | PyTorch 输出示例 | 结论 | +|--------|----------------|-----------------|------| +| `BroadcastToTest.BroadcastToSmall` | `2 6 2 3 3 1 1.000000 ...` | `2 6 2 3 0 1 1.000000 ...` | ⚠️ strides 策略不同(已知差异) | +| `BroadcastToTest.BroadcastToLarge` | `3 262144 64 32 128 4096 128 1 ...` | `3 262144 64 32 128 0 0 1 ...` | ⚠️ strides 策略不同(已知差异) | + +- **result_cmp**:`paddle_BroadcastToTest` 与 `torch_BroadcastToTest` **DIFFER**(设计差异,非 bug) + +### 风险与后续 +- 已知风险:Paddle expand 的 strides 分配策略与 PyTorch 不同,但不影响逻辑计算结果 +- 后续待办:如需完全对齐,可考虑在 Paddle compat 层的 `expand` 实现中调整 strides 分配,使其与 PyTorch 一致(广播维度 stride 为 0) + +--- + ## 2026-05-07 兼容层接口修复(PR #78652) ### 输入链接 diff --git a/test/ATen/ops/BroadcastToTest.cpp b/test/ATen/ops/BroadcastToTest.cpp index 4351970..717a998 100644 --- a/test/ATen/ops/BroadcastToTest.cpp +++ b/test/ATen/ops/BroadcastToTest.cpp @@ -19,6 +19,23 @@ class BroadcastToTest : public ::testing::Test { void SetUp() override {} }; +// Compute element offset from flat index using strides (strides-aware access) +static inline int64_t compute_offset_from_flat_index(int64_t flat_idx, + const at::Tensor& tensor) { + int64_t offset = 0; + int64_t remainder = flat_idx; + for (int64_t d = tensor.dim() - 1; d >= 0; --d) { + int64_t coord = remainder % tensor.sizes()[d]; + remainder /= tensor.sizes()[d]; + offset += coord * tensor.strides()[d]; + } + return offset; +} + +// Write tensor metadata (dim, numel, sizes, strides) and all element values +// Uses strides-aware access to faithfully reflect the underlying layout. +// If Paddle and PyTorch produce different strides, result_cmp will DIFFER, +// and the difference should be recorded as a known mismatch. static void write_broadcast_to_result_to_file(FileManerger* file, const at::Tensor& result) { *file << std::to_string(result.dim()) << " "; @@ -26,15 +43,51 @@ static void write_broadcast_to_result_to_file(FileManerger* file, for (int64_t i = 0; i < result.dim(); ++i) { *file << std::to_string(result.sizes()[i]) << " "; } + // Record strides so layout differences are detected by result_cmp + for (int64_t i = 0; i < result.dim(); ++i) { + *file << std::to_string(result.strides()[i]) << " "; + } if (result.numel() == 0) { *file << "empty "; return; } - at::Tensor cont = result.contiguous(); - float* data = cont.data_ptr(); - *file << std::to_string(data[0]) << " "; - *file << std::to_string(data[cont.numel() - 1]) << " "; - *file << std::to_string(cont.sum().item()) << " "; + switch (result.scalar_type()) { + case at::kFloat: { + float* data = result.data_ptr(); + for (int64_t i = 0; i < result.numel(); ++i) { + int64_t offset = compute_offset_from_flat_index(i, result); + *file << std::to_string(data[offset]) << " "; + } + break; + } + case at::kDouble: { + double* data = result.data_ptr(); + for (int64_t i = 0; i < result.numel(); ++i) { + int64_t offset = compute_offset_from_flat_index(i, result); + *file << std::to_string(data[offset]) << " "; + } + break; + } + case at::kInt: { + int32_t* data = result.data_ptr(); + for (int64_t i = 0; i < result.numel(); ++i) { + int64_t offset = compute_offset_from_flat_index(i, result); + *file << std::to_string(data[offset]) << " "; + } + break; + } + case at::kLong: { + int64_t* data = result.data_ptr(); + for (int64_t i = 0; i < result.numel(); ++i) { + int64_t offset = compute_offset_from_flat_index(i, result); + *file << std::to_string(data[offset]) << " "; + } + break; + } + default: + *file << "unsupported_dtype "; + break; + } } // ======================== Shape coverage ======================== @@ -116,15 +169,7 @@ TEST_F(BroadcastToTest, BroadcastToDtypeDouble) { at::Tensor t = at::ones({1, 2}, at::kDouble); at::Tensor result = t.broadcast_to({3, 2}); file << std::to_string(static_cast(result.scalar_type())) << " "; - file << std::to_string(result.dim()) << " "; - file << std::to_string(result.numel()) << " "; - for (int64_t i = 0; i < result.dim(); ++i) { - file << std::to_string(result.sizes()[i]) << " "; - } - at::Tensor cont = result.contiguous(); - double* data = cont.data_ptr(); - file << std::to_string(data[0]) << " "; - file << std::to_string(data[cont.numel() - 1]) << " "; + write_broadcast_to_result_to_file(&file, result); file << "\n"; file.saveFile(); } @@ -137,15 +182,7 @@ TEST_F(BroadcastToTest, BroadcastToDtypeInt) { at::Tensor t = at::ones({1, 2}, at::kInt); at::Tensor result = t.broadcast_to({3, 2}); file << std::to_string(static_cast(result.scalar_type())) << " "; - file << std::to_string(result.dim()) << " "; - file << std::to_string(result.numel()) << " "; - for (int64_t i = 0; i < result.dim(); ++i) { - file << std::to_string(result.sizes()[i]) << " "; - } - at::Tensor cont = result.contiguous(); - int* data = cont.data_ptr(); - file << std::to_string(data[0]) << " "; - file << std::to_string(data[cont.numel() - 1]) << " "; + write_broadcast_to_result_to_file(&file, result); file << "\n"; file.saveFile(); } @@ -158,15 +195,7 @@ TEST_F(BroadcastToTest, BroadcastToDtypeLong) { at::Tensor t = at::ones({1, 2}, at::kLong); at::Tensor result = t.broadcast_to({3, 2}); file << std::to_string(static_cast(result.scalar_type())) << " "; - file << std::to_string(result.dim()) << " "; - file << std::to_string(result.numel()) << " "; - for (int64_t i = 0; i < result.dim(); ++i) { - file << std::to_string(result.sizes()[i]) << " "; - } - at::Tensor cont = result.contiguous(); - int64_t* data = cont.data_ptr(); - file << std::to_string(data[0]) << " "; - file << std::to_string(data[cont.numel() - 1]) << " "; + write_broadcast_to_result_to_file(&file, result); file << "\n"; file.saveFile(); } From a337f277c66ba8d1ade4bf69bda5647b6251873c Mon Sep 17 00:00:00 2001 From: youge325 Date: Thu, 28 May 2026 18:43:26 +0800 Subject: [PATCH 3/9] [Cpp API Compatibility] Add broadcast_to cross-framework test and update mapping doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add test/ATen/ops/BroadcastToTest.cpp with 11 test cases - Shape coverage: small, large, boundary empty, boundary rank less - Dtype coverage: kFloat, kDouble, kInt, kLong - Exception coverage: invalid non-singleton, high rank to low rank - Function form: at::broadcast_to(t, size) - Update doc/cpp_api_mapping_cn.md: broadcast_to from 功能缺失 to API完全一致 Co-Authored-By: Claude Opus 4.7 (1M context) --- test/ATen/ops/BroadcastToTest.cpp | 50 +++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/test/ATen/ops/BroadcastToTest.cpp b/test/ATen/ops/BroadcastToTest.cpp index 717a998..ea6ef1b 100644 --- a/test/ATen/ops/BroadcastToTest.cpp +++ b/test/ATen/ops/BroadcastToTest.cpp @@ -19,6 +19,7 @@ class BroadcastToTest : public ::testing::Test { void SetUp() override {} }; +<<<<<<< HEAD // Compute element offset from flat index using strides (strides-aware access) static inline int64_t compute_offset_from_flat_index(int64_t flat_idx, const at::Tensor& tensor) { @@ -36,6 +37,8 @@ static inline int64_t compute_offset_from_flat_index(int64_t flat_idx, // Uses strides-aware access to faithfully reflect the underlying layout. // If Paddle and PyTorch produce different strides, result_cmp will DIFFER, // and the difference should be recorded as a known mismatch. +======= +>>>>>>> d0b418e ([Cpp API Compatibility] Add broadcast_to cross-framework test and update mapping doc) static void write_broadcast_to_result_to_file(FileManerger* file, const at::Tensor& result) { *file << std::to_string(result.dim()) << " "; @@ -43,14 +46,18 @@ static void write_broadcast_to_result_to_file(FileManerger* file, for (int64_t i = 0; i < result.dim(); ++i) { *file << std::to_string(result.sizes()[i]) << " "; } +<<<<<<< HEAD // Record strides so layout differences are detected by result_cmp for (int64_t i = 0; i < result.dim(); ++i) { *file << std::to_string(result.strides()[i]) << " "; } +======= +>>>>>>> d0b418e ([Cpp API Compatibility] Add broadcast_to cross-framework test and update mapping doc) if (result.numel() == 0) { *file << "empty "; return; } +<<<<<<< HEAD switch (result.scalar_type()) { case at::kFloat: { float* data = result.data_ptr(); @@ -88,6 +95,13 @@ static void write_broadcast_to_result_to_file(FileManerger* file, *file << "unsupported_dtype "; break; } +======= + at::Tensor cont = result.contiguous(); + float* data = cont.data_ptr(); + *file << std::to_string(data[0]) << " "; + *file << std::to_string(data[cont.numel() - 1]) << " "; + *file << std::to_string(cont.sum().item()) << " "; +>>>>>>> d0b418e ([Cpp API Compatibility] Add broadcast_to cross-framework test and update mapping doc) } // ======================== Shape coverage ======================== @@ -169,7 +183,19 @@ TEST_F(BroadcastToTest, BroadcastToDtypeDouble) { at::Tensor t = at::ones({1, 2}, at::kDouble); at::Tensor result = t.broadcast_to({3, 2}); file << std::to_string(static_cast(result.scalar_type())) << " "; +<<<<<<< HEAD write_broadcast_to_result_to_file(&file, result); +======= + file << std::to_string(result.dim()) << " "; + file << std::to_string(result.numel()) << " "; + for (int64_t i = 0; i < result.dim(); ++i) { + file << std::to_string(result.sizes()[i]) << " "; + } + at::Tensor cont = result.contiguous(); + double* data = cont.data_ptr(); + file << std::to_string(data[0]) << " "; + file << std::to_string(data[cont.numel() - 1]) << " "; +>>>>>>> d0b418e ([Cpp API Compatibility] Add broadcast_to cross-framework test and update mapping doc) file << "\n"; file.saveFile(); } @@ -182,7 +208,19 @@ TEST_F(BroadcastToTest, BroadcastToDtypeInt) { at::Tensor t = at::ones({1, 2}, at::kInt); at::Tensor result = t.broadcast_to({3, 2}); file << std::to_string(static_cast(result.scalar_type())) << " "; +<<<<<<< HEAD write_broadcast_to_result_to_file(&file, result); +======= + file << std::to_string(result.dim()) << " "; + file << std::to_string(result.numel()) << " "; + for (int64_t i = 0; i < result.dim(); ++i) { + file << std::to_string(result.sizes()[i]) << " "; + } + at::Tensor cont = result.contiguous(); + int* data = cont.data_ptr(); + file << std::to_string(data[0]) << " "; + file << std::to_string(data[cont.numel() - 1]) << " "; +>>>>>>> d0b418e ([Cpp API Compatibility] Add broadcast_to cross-framework test and update mapping doc) file << "\n"; file.saveFile(); } @@ -195,7 +233,19 @@ TEST_F(BroadcastToTest, BroadcastToDtypeLong) { at::Tensor t = at::ones({1, 2}, at::kLong); at::Tensor result = t.broadcast_to({3, 2}); file << std::to_string(static_cast(result.scalar_type())) << " "; +<<<<<<< HEAD write_broadcast_to_result_to_file(&file, result); +======= + file << std::to_string(result.dim()) << " "; + file << std::to_string(result.numel()) << " "; + for (int64_t i = 0; i < result.dim(); ++i) { + file << std::to_string(result.sizes()[i]) << " "; + } + at::Tensor cont = result.contiguous(); + int64_t* data = cont.data_ptr(); + file << std::to_string(data[0]) << " "; + file << std::to_string(data[cont.numel() - 1]) << " "; +>>>>>>> d0b418e ([Cpp API Compatibility] Add broadcast_to cross-framework test and update mapping doc) file << "\n"; file.saveFile(); } From 51d0fe6deea7e9a6c1efe40e0b2773d67945d068 Mon Sep 17 00:00:00 2001 From: youge325 Date: Sun, 31 May 2026 17:48:21 +0800 Subject: [PATCH 4/9] [Cpp API Compatibility] Remove contiguous from ExpandTest, record strides diff - Replace .contiguous() with strides-aware element access in ExpandTest.cpp. - Add strides field to result output for layout comparison. - Record expand strides mismatch alongside broadcast_to in mismatch_api_record.md. Co-Authored-By: Claude Opus 4.7 (1M context) --- doc/mismatch_api_record.md | 6 ++++++ test/ATen/ops/ExpandTest.cpp | 31 ++++++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/doc/mismatch_api_record.md b/doc/mismatch_api_record.md index aafc64d..8cc1f7c 100644 --- a/doc/mismatch_api_record.md +++ b/doc/mismatch_api_record.md @@ -14,6 +14,7 @@ | # | 问题接口 | 触发场景 | 根因说明 | |---|---------|---------|---------| | 1 | `at::broadcast_to` / `Tensor::broadcast_to` | 对齐测试移除 `.contiguous()` 后 result_cmp DIFFER | Paddle `expand` 对广播维度分配非零 strides(如 `{1,3}` expand 到 `{2,3}` 时 strides 为 `[3,1]`),而 PyTorch 对广播维度分配 stride 为 0(`[0,1]`)。两者在逻辑值上完全一致,但底层存储布局(strides)策略不同 | +| 2 | `at::expand` / `Tensor::expand` | 对齐测试移除 `.contiguous()` 后 result_cmp DIFFER | 同根因 #1:Paddle `expand` 的 strides 分配策略与 PyTorch 不同。`{1}` expand 到 `{2,3}` 时 Paddle strides 为 `[3,1]`,PyTorch 为 `[0,1]` | ### 修复内容 @@ -22,6 +23,8 @@ - 移除所有 `.contiguous()` 调用,改用 strides-aware 元素访问(`compute_offset_from_flat_index`) - 在结果输出中增加 strides 字段,使 result_cmp 能检测布局差异 - 测试不掩盖 strides 差异,保留 DIFFER 并在此文档归档 +- `test/ATen/ops/ExpandTest.cpp` + - 同上:移除 `.contiguous()`,改用 strides-aware 访问,增加 strides 字段 ### 验证结果 @@ -29,8 +32,11 @@ |--------|----------------|-----------------|------| | `BroadcastToTest.BroadcastToSmall` | `2 6 2 3 3 1 1.000000 ...` | `2 6 2 3 0 1 1.000000 ...` | ⚠️ strides 策略不同(已知差异) | | `BroadcastToTest.BroadcastToLarge` | `3 262144 64 32 128 4096 128 1 ...` | `3 262144 64 32 128 0 0 1 ...` | ⚠️ strides 策略不同(已知差异) | +| `ExpandTest.ExpandRankLessCanUseExpand` | `2 6 2 3 3 1 1.000000 ...` | `2 6 2 3 0 1 1.000000 ...` | ⚠️ strides 策略不同(已知差异) | +| `ExpandTest.ExpandRankLessFallbackGrowTarget` | `3 12 2 3 2 6 2 1 ...` | `3 12 2 3 2 0 0 1 ...` | ⚠️ strides 策略不同(已知差异) | - **result_cmp**:`paddle_BroadcastToTest` 与 `torch_BroadcastToTest` **DIFFER**(设计差异,非 bug) +- **result_cmp**:`paddle_ExpandTest` 与 `torch_ExpandTest` **DIFFER**(设计差异,非 bug) ### 风险与后续 - 已知风险:Paddle expand 的 strides 分配策略与 PyTorch 不同,但不影响逻辑计算结果 diff --git a/test/ATen/ops/ExpandTest.cpp b/test/ATen/ops/ExpandTest.cpp index 8214d09..c08d342 100644 --- a/test/ATen/ops/ExpandTest.cpp +++ b/test/ATen/ops/ExpandTest.cpp @@ -19,6 +19,23 @@ class ExpandTest : public ::testing::Test { void SetUp() override {} }; +// Compute element offset from flat index using strides (strides-aware access) +static inline int64_t compute_offset_from_flat_index(int64_t flat_idx, + const at::Tensor& tensor) { + int64_t offset = 0; + int64_t remainder = flat_idx; + for (int64_t d = tensor.dim() - 1; d >= 0; --d) { + int64_t coord = remainder % tensor.sizes()[d]; + remainder /= tensor.sizes()[d]; + offset += coord * tensor.strides()[d]; + } + return offset; +} + +// Write tensor metadata (dim, numel, sizes, strides) and all element values. +// Uses strides-aware access to faithfully reflect the underlying layout. +// If Paddle and PyTorch produce different strides, result_cmp will DIFFER, +// and the difference should be recorded as a known mismatch. static void write_expand_result_to_file(FileManerger* file, const at::Tensor& result) { *file << std::to_string(result.dim()) << " "; @@ -26,15 +43,19 @@ static void write_expand_result_to_file(FileManerger* file, for (int64_t i = 0; i < result.dim(); ++i) { *file << std::to_string(result.sizes()[i]) << " "; } + // Record strides so layout differences are detected by result_cmp + for (int64_t i = 0; i < result.dim(); ++i) { + *file << std::to_string(result.strides()[i]) << " "; + } if (result.numel() == 0) { *file << "empty "; return; } - at::Tensor cont = result.contiguous(); - float* data = cont.data_ptr(); - *file << std::to_string(data[0]) << " "; - *file << std::to_string(data[cont.numel() - 1]) << " "; - *file << std::to_string(cont.sum().item()) << " "; + float* data = result.data_ptr(); + for (int64_t i = 0; i < result.numel(); ++i) { + int64_t offset = compute_offset_from_flat_index(i, result); + *file << std::to_string(data[offset]) << " "; + } } TEST_F(ExpandTest, Expand) { From 7b26a9a7deb8d1ac6bc7ce3e36a2ca3cd9ea1bf2 Mon Sep 17 00:00:00 2001 From: youge325 Date: Sun, 31 May 2026 18:05:05 +0800 Subject: [PATCH 5/9] Revert "[Cpp API Compatibility] Add broadcast_to cross-framework test and update mapping doc" This reverts commit d4aa08c9cba9cbbcd115bd13fc7b87ef5c59d721. --- test/ATen/ops/BroadcastToTest.cpp | 50 ------------------------------- 1 file changed, 50 deletions(-) diff --git a/test/ATen/ops/BroadcastToTest.cpp b/test/ATen/ops/BroadcastToTest.cpp index ea6ef1b..717a998 100644 --- a/test/ATen/ops/BroadcastToTest.cpp +++ b/test/ATen/ops/BroadcastToTest.cpp @@ -19,7 +19,6 @@ class BroadcastToTest : public ::testing::Test { void SetUp() override {} }; -<<<<<<< HEAD // Compute element offset from flat index using strides (strides-aware access) static inline int64_t compute_offset_from_flat_index(int64_t flat_idx, const at::Tensor& tensor) { @@ -37,8 +36,6 @@ static inline int64_t compute_offset_from_flat_index(int64_t flat_idx, // Uses strides-aware access to faithfully reflect the underlying layout. // If Paddle and PyTorch produce different strides, result_cmp will DIFFER, // and the difference should be recorded as a known mismatch. -======= ->>>>>>> d0b418e ([Cpp API Compatibility] Add broadcast_to cross-framework test and update mapping doc) static void write_broadcast_to_result_to_file(FileManerger* file, const at::Tensor& result) { *file << std::to_string(result.dim()) << " "; @@ -46,18 +43,14 @@ static void write_broadcast_to_result_to_file(FileManerger* file, for (int64_t i = 0; i < result.dim(); ++i) { *file << std::to_string(result.sizes()[i]) << " "; } -<<<<<<< HEAD // Record strides so layout differences are detected by result_cmp for (int64_t i = 0; i < result.dim(); ++i) { *file << std::to_string(result.strides()[i]) << " "; } -======= ->>>>>>> d0b418e ([Cpp API Compatibility] Add broadcast_to cross-framework test and update mapping doc) if (result.numel() == 0) { *file << "empty "; return; } -<<<<<<< HEAD switch (result.scalar_type()) { case at::kFloat: { float* data = result.data_ptr(); @@ -95,13 +88,6 @@ static void write_broadcast_to_result_to_file(FileManerger* file, *file << "unsupported_dtype "; break; } -======= - at::Tensor cont = result.contiguous(); - float* data = cont.data_ptr(); - *file << std::to_string(data[0]) << " "; - *file << std::to_string(data[cont.numel() - 1]) << " "; - *file << std::to_string(cont.sum().item()) << " "; ->>>>>>> d0b418e ([Cpp API Compatibility] Add broadcast_to cross-framework test and update mapping doc) } // ======================== Shape coverage ======================== @@ -183,19 +169,7 @@ TEST_F(BroadcastToTest, BroadcastToDtypeDouble) { at::Tensor t = at::ones({1, 2}, at::kDouble); at::Tensor result = t.broadcast_to({3, 2}); file << std::to_string(static_cast(result.scalar_type())) << " "; -<<<<<<< HEAD write_broadcast_to_result_to_file(&file, result); -======= - file << std::to_string(result.dim()) << " "; - file << std::to_string(result.numel()) << " "; - for (int64_t i = 0; i < result.dim(); ++i) { - file << std::to_string(result.sizes()[i]) << " "; - } - at::Tensor cont = result.contiguous(); - double* data = cont.data_ptr(); - file << std::to_string(data[0]) << " "; - file << std::to_string(data[cont.numel() - 1]) << " "; ->>>>>>> d0b418e ([Cpp API Compatibility] Add broadcast_to cross-framework test and update mapping doc) file << "\n"; file.saveFile(); } @@ -208,19 +182,7 @@ TEST_F(BroadcastToTest, BroadcastToDtypeInt) { at::Tensor t = at::ones({1, 2}, at::kInt); at::Tensor result = t.broadcast_to({3, 2}); file << std::to_string(static_cast(result.scalar_type())) << " "; -<<<<<<< HEAD write_broadcast_to_result_to_file(&file, result); -======= - file << std::to_string(result.dim()) << " "; - file << std::to_string(result.numel()) << " "; - for (int64_t i = 0; i < result.dim(); ++i) { - file << std::to_string(result.sizes()[i]) << " "; - } - at::Tensor cont = result.contiguous(); - int* data = cont.data_ptr(); - file << std::to_string(data[0]) << " "; - file << std::to_string(data[cont.numel() - 1]) << " "; ->>>>>>> d0b418e ([Cpp API Compatibility] Add broadcast_to cross-framework test and update mapping doc) file << "\n"; file.saveFile(); } @@ -233,19 +195,7 @@ TEST_F(BroadcastToTest, BroadcastToDtypeLong) { at::Tensor t = at::ones({1, 2}, at::kLong); at::Tensor result = t.broadcast_to({3, 2}); file << std::to_string(static_cast(result.scalar_type())) << " "; -<<<<<<< HEAD write_broadcast_to_result_to_file(&file, result); -======= - file << std::to_string(result.dim()) << " "; - file << std::to_string(result.numel()) << " "; - for (int64_t i = 0; i < result.dim(); ++i) { - file << std::to_string(result.sizes()[i]) << " "; - } - at::Tensor cont = result.contiguous(); - int64_t* data = cont.data_ptr(); - file << std::to_string(data[0]) << " "; - file << std::to_string(data[cont.numel() - 1]) << " "; ->>>>>>> d0b418e ([Cpp API Compatibility] Add broadcast_to cross-framework test and update mapping doc) file << "\n"; file.saveFile(); } From 110e27b803295088b8aeed83750e1513359fbd17 Mon Sep 17 00:00:00 2001 From: youge325 Date: Sun, 31 May 2026 20:05:31 +0800 Subject: [PATCH 6/9] Update mismatch record: expand/broadcast_to strides now aligned - BroadcastToTest and ExpandTest now MATCH after compat layer fix - Document the fix in Paddle expand.h (as_strided with stride=0) Co-Authored-By: Claude Opus 4.7 --- doc/mismatch_api_record.md | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/doc/mismatch_api_record.md b/doc/mismatch_api_record.md index 8cc1f7c..be8dd28 100644 --- a/doc/mismatch_api_record.md +++ b/doc/mismatch_api_record.md @@ -2,7 +2,7 @@ --- -## 2026-05-31 broadcast_to 对齐测试 strides 差异记录 +## 2026-05-31 broadcast_to / expand strides 对齐修复记录 ### 输入链接 - 链接类型:review comment @@ -13,34 +13,39 @@ | # | 问题接口 | 触发场景 | 根因说明 | |---|---------|---------|---------| -| 1 | `at::broadcast_to` / `Tensor::broadcast_to` | 对齐测试移除 `.contiguous()` 后 result_cmp DIFFER | Paddle `expand` 对广播维度分配非零 strides(如 `{1,3}` expand 到 `{2,3}` 时 strides 为 `[3,1]`),而 PyTorch 对广播维度分配 stride 为 0(`[0,1]`)。两者在逻辑值上完全一致,但底层存储布局(strides)策略不同 | +| 1 | `at::broadcast_to` / `Tensor::broadcast_to` | 对齐测试移除 `.contiguous()` 后 result_cmp DIFFER | Paddle `expand` 对广播维度分配非零 strides(如 `{1,3}` expand 到 `{2,3}` 时 strides 为 `[3,1]`),而 PyTorch 对广播维度分配 stride 为 0(`[0,1]`) | | 2 | `at::expand` / `Tensor::expand` | 对齐测试移除 `.contiguous()` 后 result_cmp DIFFER | 同根因 #1:Paddle `expand` 的 strides 分配策略与 PyTorch 不同。`{1}` expand 到 `{2,3}` 时 Paddle strides 为 `[3,1]`,PyTorch 为 `[0,1]` | ### 修复内容 +**Paddle compat 层改动文件:** +- `paddle/phi/api/include/compat/ATen/ops/expand.h` + - 重构 `expand` 实现:不再调用 `paddle::experimental::expand()`(其返回 dense copy 且 strides 非零) + - 新增 `compute_expand_strides` 辅助函数,按 PyTorch 规则计算 expand 后的 strides(广播维度 stride = 0) + - 使用 `self.as_strided()` 创建 view,使结果与 PyTorch 一样共享原始存储且 strides 一致 +- `paddle/phi/api/include/compat/ATen/ops/broadcast_to.h` + - 无需改动,`broadcast_to` 已委托给 `self.expand()`,随 expand 修复自动对齐 + +**Paddle 测试改动文件:** +- `test/cpp/compat/ATen_expand_test.cc` + - 修复 `ExpandPreserveNonSingleton` 测试:原测试直接访问 `data_ptr()[3]` 假设 dense 布局;改为 strides-aware 访问 + **PaddleCppAPITest 改动文件:** - `test/ATen/ops/BroadcastToTest.cpp` - 移除所有 `.contiguous()` 调用,改用 strides-aware 元素访问(`compute_offset_from_flat_index`) - 在结果输出中增加 strides 字段,使 result_cmp 能检测布局差异 - - 测试不掩盖 strides 差异,保留 DIFFER 并在此文档归档 - `test/ATen/ops/ExpandTest.cpp` - 同上:移除 `.contiguous()`,改用 strides-aware 访问,增加 strides 字段 ### 验证结果 -| 测试项 | Paddle 输出示例 | PyTorch 输出示例 | 结论 | -|--------|----------------|-----------------|------| -| `BroadcastToTest.BroadcastToSmall` | `2 6 2 3 3 1 1.000000 ...` | `2 6 2 3 0 1 1.000000 ...` | ⚠️ strides 策略不同(已知差异) | -| `BroadcastToTest.BroadcastToLarge` | `3 262144 64 32 128 4096 128 1 ...` | `3 262144 64 32 128 0 0 1 ...` | ⚠️ strides 策略不同(已知差异) | -| `ExpandTest.ExpandRankLessCanUseExpand` | `2 6 2 3 3 1 1.000000 ...` | `2 6 2 3 0 1 1.000000 ...` | ⚠️ strides 策略不同(已知差异) | -| `ExpandTest.ExpandRankLessFallbackGrowTarget` | `3 12 2 3 2 6 2 1 ...` | `3 12 2 3 2 0 0 1 ...` | ⚠️ strides 策略不同(已知差异) | - -- **result_cmp**:`paddle_BroadcastToTest` 与 `torch_BroadcastToTest` **DIFFER**(设计差异,非 bug) -- **result_cmp**:`paddle_ExpandTest` 与 `torch_ExpandTest` **DIFFER**(设计差异,非 bug) +- **result_cmp**:`paddle_BroadcastToTest` 与 `torch_BroadcastToTest` **MATCH** ✅ +- **result_cmp**:`paddle_ExpandTest` 与 `torch_ExpandTest` **MATCH** ✅ +- **ctest**:`ATen_expand_test` 全部通过 ✅ ### 风险与后续 -- 已知风险:Paddle expand 的 strides 分配策略与 PyTorch 不同,但不影响逻辑计算结果 -- 后续待办:如需完全对齐,可考虑在 Paddle compat 层的 `expand` 实现中调整 strides 分配,使其与 PyTorch 一致(广播维度 stride 为 0) +- 已知风险:无 +- 后续待办:无 --- From 88c9b152b0177f1ec7ce8d95f21ba3eb0fe717d1 Mon Sep 17 00:00:00 2001 From: youge325 Date: Fri, 5 Jun 2026 15:49:41 +0800 Subject: [PATCH 7/9] test(align): add scalar, -1, and exception coverage for broadcast_to/expand - BroadcastToTest: add BroadcastToScalar (0-d tensor), BroadcastToNegativeOne - ExpandTest: add ExpandScalar (0-d tensor), ExpandNegativeOne (-1 support), ExpandNegativeOneLeadingError (-1 in leading dim throws) - Revert BroadcastToRejectsNegativeOne (PyTorch broadcast_to supports -1) Co-Authored-By: Claude Opus 4.7 --- test/ATen/ops/BroadcastToTest.cpp | 28 ++++++++++++++++++ test/ATen/ops/ExpandTest.cpp | 49 +++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/test/ATen/ops/BroadcastToTest.cpp b/test/ATen/ops/BroadcastToTest.cpp index 717a998..b9be2ac 100644 --- a/test/ATen/ops/BroadcastToTest.cpp +++ b/test/ATen/ops/BroadcastToTest.cpp @@ -146,6 +146,19 @@ TEST_F(BroadcastToTest, BroadcastToBoundaryRankLess) { file.saveFile(); } +// Boundary: scalar (0-d tensor) +TEST_F(BroadcastToTest, BroadcastToScalar) { + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "BroadcastToScalar "; + at::Tensor t = at::full({}, 5.0f, at::kFloat); + at::Tensor result = t.broadcast_to({2, 3}); + write_broadcast_to_result_to_file(&file, result); + file << "\n"; + file.saveFile(); +} + // ======================== Dtype coverage ======================== TEST_F(BroadcastToTest, BroadcastToDtypeFloat) { @@ -238,6 +251,21 @@ TEST_F(BroadcastToTest, BroadcastToHighRankToLowRank) { file.saveFile(); } +// broadcast_to with -1 (keep original size, same as expand) +TEST_F(BroadcastToTest, BroadcastToNegativeOne) { + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "BroadcastToNegativeOne "; + + at::Tensor t = at::ones({3}, at::kFloat); + at::Tensor result = t.broadcast_to({-1}); + write_broadcast_to_result_to_file(&file, result); + + file << "\n"; + file.saveFile(); +} + // ======================== Function form ======================== TEST_F(BroadcastToTest, BroadcastToFunction) { diff --git a/test/ATen/ops/ExpandTest.cpp b/test/ATen/ops/ExpandTest.cpp index c08d342..3946dad 100644 --- a/test/ATen/ops/ExpandTest.cpp +++ b/test/ATen/ops/ExpandTest.cpp @@ -179,5 +179,54 @@ TEST_F(ExpandTest, ExpandInputRankGreaterThanTargetRank) { file.saveFile(); } +// Scalar expand (0-d tensor) +TEST_F(ExpandTest, ExpandScalar) { + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "ExpandScalar "; + + at::Tensor t = at::full({}, 5.0f, at::kFloat); + at::Tensor result = t.expand({2, 3}); + write_expand_result_to_file(&file, result); + + file << "\n"; + file.saveFile(); +} + +// expand supports -1 (keep original size) +TEST_F(ExpandTest, ExpandNegativeOne) { + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "ExpandNegativeOne "; + + at::Tensor t = at::ones({3}, at::kFloat); + at::Tensor result = t.expand({-1}); + write_expand_result_to_file(&file, result); + + file << "\n"; + file.saveFile(); +} + +// -1 in leading non-existing dimension throws +TEST_F(ExpandTest, ExpandNegativeOneLeadingError) { + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "ExpandNegativeOneLeadingError "; + + try { + at::Tensor t = at::ones({3}, at::kFloat); + at::Tensor result = t.expand({-1, 4}); + write_expand_result_to_file(&file, result); + } catch (const std::exception&) { + file << "exception "; + } + + file << "\n"; + file.saveFile(); +} + } // namespace test } // namespace at From 83adad3593d23d863b03b6049ebeaebbcd2172c1 Mon Sep 17 00:00:00 2001 From: youge325 Date: Sun, 14 Jun 2026 16:33:38 +0800 Subject: [PATCH 8/9] test(align): cover broadcast_to negative size and strides --- test/ATen/ops/BroadcastToTest.cpp | 54 ++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/test/ATen/ops/BroadcastToTest.cpp b/test/ATen/ops/BroadcastToTest.cpp index b9be2ac..2482110 100644 --- a/test/ATen/ops/BroadcastToTest.cpp +++ b/test/ATen/ops/BroadcastToTest.cpp @@ -251,7 +251,7 @@ TEST_F(BroadcastToTest, BroadcastToHighRankToLowRank) { file.saveFile(); } -// broadcast_to with -1 (keep original size, same as expand) +// C++ ATen broadcast_to follows expand-style -1 keep-dim behavior. TEST_F(BroadcastToTest, BroadcastToNegativeOne) { auto file_name = g_custom_param.get(); FileManerger file(file_name); @@ -266,6 +266,58 @@ TEST_F(BroadcastToTest, BroadcastToNegativeOne) { file.saveFile(); } +TEST_F(BroadcastToTest, BroadcastToNegativeLessThanMinusOne) { + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "BroadcastToNegativeLessThanMinusOne "; + + try { + at::Tensor t = at::ones({1}, at::kFloat); + at::Tensor result = t.broadcast_to({-2}); + write_broadcast_to_result_to_file(&file, result); + } catch (const std::exception&) { + file << "exception "; + } + + file << "\n"; + file.saveFile(); +} + +TEST_F(BroadcastToTest, ExpandNegativeLessThanMinusOne) { + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "ExpandNegativeLessThanMinusOne "; + + try { + at::Tensor t = at::ones({1}, at::kFloat); + at::Tensor result = t.expand({-2}); + write_broadcast_to_result_to_file(&file, result); + } catch (const std::exception&) { + file << "exception "; + } + + file << "\n"; + file.saveFile(); +} + +TEST_F(BroadcastToTest, BroadcastToStrideZeroValues) { + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "BroadcastToStrideZeroValues "; + + at::Tensor t = at::zeros({1, 2}, at::kFloat); + t.data_ptr()[0] = 3.0f; + t.data_ptr()[1] = 7.0f; + at::Tensor result = t.broadcast_to({3, 2}); + write_broadcast_to_result_to_file(&file, result); + + file << "\n"; + file.saveFile(); +} + // ======================== Function form ======================== TEST_F(BroadcastToTest, BroadcastToFunction) { From 16dbb98e8bbcb1d5a4fe0538473ebb8b27634ed6 Mon Sep 17 00:00:00 2001 From: youge325 Date: Thu, 2 Jul 2026 02:24:03 +0800 Subject: [PATCH 9/9] Add broadcast_to stride-kernel-disabled alignment coverage --- test/ATen/ops/BroadcastToTest.cpp | 81 +++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/test/ATen/ops/BroadcastToTest.cpp b/test/ATen/ops/BroadcastToTest.cpp index 2482110..eccb8f5 100644 --- a/test/ATen/ops/BroadcastToTest.cpp +++ b/test/ATen/ops/BroadcastToTest.cpp @@ -7,6 +7,11 @@ #include "src/file_manager.h" +#if USE_PADDLE_API +#include "paddle/common/flags.h" +COMMON_DECLARE_bool(use_stride_kernel); +#endif + extern paddle_api_test::ThreadSafeParam g_custom_param; namespace at { @@ -19,6 +24,29 @@ class BroadcastToTest : public ::testing::Test { void SetUp() override {} }; +class UseStrideKernelGuard { + public: + explicit UseStrideKernelGuard(bool value) { +#if USE_PADDLE_API + previous_ = FLAGS_use_stride_kernel; + FLAGS_use_stride_kernel = value; +#else + (void)value; +#endif + } + + ~UseStrideKernelGuard() { +#if USE_PADDLE_API + FLAGS_use_stride_kernel = previous_; +#endif + } + + private: +#if USE_PADDLE_API + bool previous_{true}; +#endif +}; + // Compute element offset from flat index using strides (strides-aware access) static inline int64_t compute_offset_from_flat_index(int64_t flat_idx, const at::Tensor& tensor) { @@ -90,6 +118,26 @@ static void write_broadcast_to_result_to_file(FileManerger* file, } } +// Values-only writer for cases where Paddle intentionally materializes a +// broadcast while PyTorch returns a stride-0 view. +static void write_broadcast_values_only_to_file(FileManerger* file, + const at::Tensor& result) { + *file << std::to_string(result.dim()) << " "; + *file << std::to_string(result.numel()) << " "; + for (int64_t i = 0; i < result.dim(); ++i) { + *file << std::to_string(result.sizes()[i]) << " "; + } + if (result.numel() == 0) { + *file << "empty "; + return; + } + float* data = result.data_ptr(); + for (int64_t i = 0; i < result.numel(); ++i) { + int64_t offset = compute_offset_from_flat_index(i, result); + *file << std::to_string(data[offset]) << " "; + } +} + // ======================== Shape coverage ======================== // Small shape test @@ -318,6 +366,39 @@ TEST_F(BroadcastToTest, BroadcastToStrideZeroValues) { file.saveFile(); } +TEST_F(BroadcastToTest, BroadcastToStrideKernelDisabledValuesOnly) { + UseStrideKernelGuard guard(false); + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "BroadcastToStrideKernelDisabledValuesOnly "; + + at::Tensor t = at::zeros({1, 2}, at::kFloat); + t.data_ptr()[0] = 3.0f; + t.data_ptr()[1] = 7.0f; + at::Tensor result = t.broadcast_to({3, 2}); + write_broadcast_values_only_to_file(&file, result); + + file << "\n"; + file.saveFile(); +} + +TEST_F(BroadcastToTest, ExpandStrideKernelDisabledValuesOnly) { + UseStrideKernelGuard guard(false); + auto file_name = g_custom_param.get(); + FileManerger file(file_name); + file.openAppend(); + file << "ExpandStrideKernelDisabledValuesOnly "; + + at::Tensor t = at::ones({1}, at::kFloat); + t.data_ptr()[0] = 5.0f; + at::Tensor result = t.expand({2, 3}); + write_broadcast_values_only_to_file(&file, result); + + file << "\n"; + file.saveFile(); +} + // ======================== Function form ======================== TEST_F(BroadcastToTest, BroadcastToFunction) {