chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA)
|
||||
|
||||
#include <ATen/cuda/CUDABlas.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/backends/gpu/gpu_info.h"
|
||||
#include "paddle/phi/common/bfloat16.h"
|
||||
#include "paddle/phi/common/complex.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
|
||||
// Helper: allocate three same-sized device buffers, copy host data in,
|
||||
// invoke a kernel via |fn|, copy results back, synchronize, then free.
|
||||
// |fn| receives (d_a, d_b, d_c); it must not free them.
|
||||
template <typename T, typename Fn>
|
||||
static void runOnDevice(const std::vector<T>& h_a,
|
||||
const std::vector<T>& h_b,
|
||||
std::vector<T>* h_c,
|
||||
Fn fn) {
|
||||
size_t bytes = h_a.size() * sizeof(T);
|
||||
T *d_a = nullptr, *d_b = nullptr, *d_c = nullptr;
|
||||
|
||||
ASSERT_EQ(cudaMalloc(&d_a, bytes), cudaSuccess);
|
||||
ASSERT_EQ(cudaMalloc(&d_b, bytes), cudaSuccess);
|
||||
ASSERT_EQ(cudaMalloc(&d_c, bytes), cudaSuccess);
|
||||
|
||||
ASSERT_EQ(cudaMemcpy(d_a, h_a.data(), bytes, cudaMemcpyHostToDevice),
|
||||
cudaSuccess);
|
||||
ASSERT_EQ(cudaMemcpy(d_b, h_b.data(), bytes, cudaMemcpyHostToDevice),
|
||||
cudaSuccess);
|
||||
ASSERT_EQ(cudaMemcpy(d_c, h_c->data(), bytes, cudaMemcpyHostToDevice),
|
||||
cudaSuccess);
|
||||
|
||||
fn(d_a, d_b, d_c);
|
||||
|
||||
ASSERT_EQ(cudaMemcpy(h_c->data(), d_c, bytes, cudaMemcpyDeviceToHost),
|
||||
cudaSuccess);
|
||||
ASSERT_EQ(cudaDeviceSynchronize(), cudaSuccess);
|
||||
|
||||
cudaFree(d_a);
|
||||
cudaFree(d_b);
|
||||
cudaFree(d_c);
|
||||
}
|
||||
|
||||
// Runs 2x2 no-transpose gemm: C = alpha*A*B + beta*C and checks the result.
|
||||
//
|
||||
// Column-major layout:
|
||||
// A: col0={1,3}, col1={2,4} => logical A = [[1,2],[3,4]]
|
||||
// B: col0={5,7}, col1={6,8} => logical B = [[5,6],[7,8]]
|
||||
// A*B = [[19,22],[43,50]] stored col-major: col0={19,43}, col1={22,50}
|
||||
template <typename T, typename MathT = at::opmath_type<T>>
|
||||
class GemmTester {
|
||||
public:
|
||||
static constexpr int64_t N = 2;
|
||||
|
||||
static double toDouble(T val) { return static_cast<double>(val); }
|
||||
|
||||
void Run() {
|
||||
std::vector<T> h_a = {T(1), T(3), T(2), T(4)};
|
||||
std::vector<T> h_b = {T(5), T(7), T(6), T(8)};
|
||||
std::vector<T> h_c(N * N, T(0));
|
||||
|
||||
MathT alpha = static_cast<MathT>(1);
|
||||
MathT beta = static_cast<MathT>(0);
|
||||
|
||||
runOnDevice(h_a, h_b, &h_c, [&](T* d_a, T* d_b, T* d_c) {
|
||||
at::cuda::blas::gemm<T>(
|
||||
'N', 'N', N, N, N, alpha, d_a, N, d_b, N, beta, d_c, N);
|
||||
});
|
||||
|
||||
EXPECT_NEAR(toDouble(h_c[0]), 19.0, 1e-2); // C(0,0)
|
||||
EXPECT_NEAR(toDouble(h_c[1]), 43.0, 1e-2); // C(1,0)
|
||||
EXPECT_NEAR(toDouble(h_c[2]), 22.0, 1e-2); // C(0,1)
|
||||
EXPECT_NEAR(toDouble(h_c[3]), 50.0, 1e-2); // C(1,1)
|
||||
}
|
||||
|
||||
// transA='T': C = alpha * A^T * B + beta * C
|
||||
// A^T = [[1,3],[2,4]], A^T * B = [[26,30],[38,44]]
|
||||
void RunTransA() {
|
||||
std::vector<T> h_a = {T(1), T(3), T(2), T(4)};
|
||||
std::vector<T> h_b = {T(5), T(7), T(6), T(8)};
|
||||
std::vector<T> h_c(N * N, T(0));
|
||||
|
||||
MathT alpha = static_cast<MathT>(1);
|
||||
MathT beta = static_cast<MathT>(0);
|
||||
|
||||
runOnDevice(h_a, h_b, &h_c, [&](T* d_a, T* d_b, T* d_c) {
|
||||
at::cuda::blas::gemm<T>(
|
||||
'T', 'N', N, N, N, alpha, d_a, N, d_b, N, beta, d_c, N);
|
||||
});
|
||||
|
||||
EXPECT_NEAR(toDouble(h_c[0]), 26.0, 1e-2);
|
||||
EXPECT_NEAR(toDouble(h_c[1]), 38.0, 1e-2);
|
||||
EXPECT_NEAR(toDouble(h_c[2]), 30.0, 1e-2);
|
||||
EXPECT_NEAR(toDouble(h_c[3]), 44.0, 1e-2);
|
||||
}
|
||||
};
|
||||
|
||||
TEST(CUDABlasTest, GemmDouble) {
|
||||
GemmTester<double> t;
|
||||
t.Run();
|
||||
}
|
||||
|
||||
TEST(CUDABlasTest, GemmDoubleTransA) {
|
||||
GemmTester<double> t;
|
||||
t.RunTransA();
|
||||
}
|
||||
|
||||
TEST(CUDABlasTest, GemmFloat) {
|
||||
GemmTester<float> t;
|
||||
t.Run();
|
||||
}
|
||||
|
||||
TEST(CUDABlasTest, GemmFloatTransA) {
|
||||
GemmTester<float> t;
|
||||
t.RunTransA();
|
||||
}
|
||||
|
||||
TEST(CUDABlasTest, GemmFloatTransALowercase) {
|
||||
constexpr int64_t N = 2;
|
||||
|
||||
std::vector<float> h_a = {1.F, 3.F, 2.F, 4.F};
|
||||
std::vector<float> h_b = {5.F, 7.F, 6.F, 8.F};
|
||||
std::vector<float> h_c(N * N, 0.F);
|
||||
|
||||
float alpha = 1.F;
|
||||
float beta = 0.F;
|
||||
runOnDevice(h_a, h_b, &h_c, [&](float* d_a, float* d_b, float* d_c) {
|
||||
at::cuda::blas::gemm<float>(
|
||||
't', 'n', N, N, N, alpha, d_a, N, d_b, N, beta, d_c, N);
|
||||
});
|
||||
|
||||
EXPECT_NEAR(h_c[0], 26.0f, 1e-3f);
|
||||
EXPECT_NEAR(h_c[1], 38.0f, 1e-3f);
|
||||
EXPECT_NEAR(h_c[2], 30.0f, 1e-3f);
|
||||
EXPECT_NEAR(h_c[3], 44.0f, 1e-3f);
|
||||
}
|
||||
|
||||
TEST(CUDABlasTest, GemmComplexDouble) {
|
||||
GemmTester<c10::complex<double>> t;
|
||||
t.Run();
|
||||
}
|
||||
|
||||
TEST(CUDABlasTest, GemmComplexFloat) {
|
||||
GemmTester<c10::complex<float>> t;
|
||||
t.Run();
|
||||
}
|
||||
|
||||
TEST(CUDABlasTest, GemmHalf) {
|
||||
GemmTester<at::Half> t;
|
||||
t.Run();
|
||||
}
|
||||
|
||||
TEST(CUDABlasTest, GemmBFloat16) {
|
||||
GemmTester<at::BFloat16> t;
|
||||
t.Run();
|
||||
}
|
||||
|
||||
// to_cublas_op 'C'/'c' path: C = A^H * I = A^H (conjugate-transpose of A).
|
||||
//
|
||||
// A stored col-major: col0={1+i,2+2i}, col1={3+3i,4+4i}
|
||||
// A^H stored col-major: col0={1-i,3-3i}, col1={2-2i,4-4i}
|
||||
TEST(CUDABlasTest, GemmComplexFloatConjTrans) {
|
||||
constexpr int64_t N = 2;
|
||||
using T = c10::complex<float>;
|
||||
|
||||
std::vector<T> h_a = {T(1, 1), T(2, 2), T(3, 3), T(4, 4)};
|
||||
std::vector<T> h_b = {T(1, 0), T(0, 0), T(0, 0), T(1, 0)}; // identity
|
||||
std::vector<T> h_c(N * N, T(0, 0));
|
||||
|
||||
float alpha = 1.0f;
|
||||
float beta = 0.0f;
|
||||
|
||||
runOnDevice(h_a, h_b, &h_c, [&](T* d_a, T* d_b, T* d_c) {
|
||||
at::cuda::blas::gemm<T>(
|
||||
'C', 'N', N, N, N, alpha, d_a, N, d_b, N, beta, d_c, N);
|
||||
});
|
||||
|
||||
EXPECT_NEAR(h_c[0].real, 1.0f, 1e-3f);
|
||||
EXPECT_NEAR(h_c[0].imag, -1.0f, 1e-3f);
|
||||
EXPECT_NEAR(h_c[1].real, 3.0f, 1e-3f);
|
||||
EXPECT_NEAR(h_c[1].imag, -3.0f, 1e-3f);
|
||||
EXPECT_NEAR(h_c[2].real, 2.0f, 1e-3f);
|
||||
EXPECT_NEAR(h_c[2].imag, -2.0f, 1e-3f);
|
||||
EXPECT_NEAR(h_c[3].real, 4.0f, 1e-3f);
|
||||
EXPECT_NEAR(h_c[3].imag, -4.0f, 1e-3f);
|
||||
}
|
||||
|
||||
// Same as above but uses lowercase 'c'/'n' to exercise that switch-case branch.
|
||||
TEST(CUDABlasTest, GemmComplexDoubleConjTransLower) {
|
||||
constexpr int64_t N = 2;
|
||||
using T = c10::complex<double>;
|
||||
|
||||
std::vector<T> h_a = {T(1, 1), T(2, 2), T(3, 3), T(4, 4)};
|
||||
std::vector<T> h_b = {T(1, 0), T(0, 0), T(0, 0), T(1, 0)};
|
||||
std::vector<T> h_c(N * N, T(0, 0));
|
||||
|
||||
double alpha = 1.0;
|
||||
double beta = 0.0;
|
||||
|
||||
runOnDevice(h_a, h_b, &h_c, [&](T* d_a, T* d_b, T* d_c) {
|
||||
at::cuda::blas::gemm<T>(
|
||||
'c', 'n', N, N, N, alpha, d_a, N, d_b, N, beta, d_c, N);
|
||||
});
|
||||
|
||||
EXPECT_NEAR(h_c[0].real, 1.0, 1e-6);
|
||||
EXPECT_NEAR(h_c[0].imag, -1.0, 1e-6);
|
||||
EXPECT_NEAR(h_c[1].real, 3.0, 1e-6);
|
||||
EXPECT_NEAR(h_c[1].imag, -3.0, 1e-6);
|
||||
}
|
||||
|
||||
TEST(CUDABlasTest, GemmInvalidTransposeThrows) {
|
||||
constexpr int64_t N = 1;
|
||||
double alpha = 1.0;
|
||||
double beta = 0.0;
|
||||
EXPECT_THROW(at::cuda::blas::gemm<double>('X',
|
||||
'N',
|
||||
N,
|
||||
N,
|
||||
N,
|
||||
alpha,
|
||||
static_cast<const double*>(nullptr),
|
||||
N,
|
||||
static_cast<const double*>(nullptr),
|
||||
N,
|
||||
beta,
|
||||
static_cast<double*>(nullptr),
|
||||
N),
|
||||
std::exception);
|
||||
}
|
||||
|
||||
#endif // PADDLE_WITH_CUDA
|
||||
@@ -0,0 +1,468 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <c10/core/Allocator.h>
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <torch/cuda.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
#include "paddle/phi/backends/gpu/gpu_info.h"
|
||||
#endif
|
||||
|
||||
// Platform-specific definitions for memory operations
|
||||
#if defined(PADDLE_WITH_HIP)
|
||||
#include <hip/hip_runtime.h>
|
||||
#define MEMCPY_FN hipMemcpy
|
||||
#define MEMCPY_HOST_TO_DEVICE hipMemcpyHostToDevice
|
||||
#define MEMCPY_DEVICE_TO_HOST hipMemcpyDeviceToHost
|
||||
#define SUCCESS_CODE hipSuccess
|
||||
#define DEVICE_SYNCHRONIZE_FN hipDeviceSynchronize
|
||||
#elif defined(PADDLE_WITH_CUDA)
|
||||
#define MEMCPY_FN cudaMemcpy
|
||||
#define MEMCPY_HOST_TO_DEVICE cudaMemcpyHostToDevice
|
||||
#define MEMCPY_DEVICE_TO_HOST cudaMemcpyDeviceToHost
|
||||
#define SUCCESS_CODE cudaSuccess
|
||||
#define DEVICE_SYNCHRONIZE_FN cudaDeviceSynchronize
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CUDAFunctions.h — covers the 2 missing lines:
|
||||
// c10::cuda::device_synchronize() and c10::cuda::stream_synchronize()
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(CUDAFunctionsTest, DeviceSynchronize) {
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
// Exercises the PADDLE_ENFORCE_GPU_SUCCESS(cudaDeviceSynchronize()) branch
|
||||
ASSERT_NO_THROW(c10::cuda::device_synchronize());
|
||||
#else
|
||||
// In CPU-only builds, device_synchronize throws
|
||||
ASSERT_THROW(c10::cuda::device_synchronize(), std::exception);
|
||||
#endif
|
||||
}
|
||||
|
||||
// CPU-only: torch::cuda::synchronize must report "No CUDA GPUs are available"
|
||||
// rather than the older "Cannot visit device count" produced by device_count().
|
||||
// Matches PyTorch behavior where device_count() returns 0 in CPU-only builds
|
||||
// and the synchronize() pre-check is the single source of the GPU-missing
|
||||
// error message.
|
||||
TEST(CUDAFunctionsTest, SynchronizeReportsNoGpuMessageInCpuOnly) {
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
// Only relevant in CPU-only builds
|
||||
return;
|
||||
#else
|
||||
try {
|
||||
torch::cuda::synchronize();
|
||||
FAIL() << "expected exception";
|
||||
} catch (const std::exception& e) {
|
||||
const std::string msg = e.what();
|
||||
EXPECT_NE(msg.find("No CUDA GPUs are available"), std::string::npos) << msg;
|
||||
EXPECT_EQ(msg.find("Cannot visit device count"), std::string::npos) << msg;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
TEST(CUDAFunctionsTest, StreamSynchronize) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
// Exercises phi::backends::gpu::GpuStreamSync()
|
||||
auto stream = c10::cuda::getCurrentCUDAStream();
|
||||
ASSERT_NO_THROW(c10::cuda::stream_synchronize(stream));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
TEST(CUDAFunctionsTest, AtNamespaceAliases) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
// Exercises the using aliases in at::cuda namespace
|
||||
ASSERT_NO_THROW(at::cuda::device_synchronize());
|
||||
auto stream = c10::cuda::getCurrentCUDAStream();
|
||||
ASSERT_NO_THROW(at::cuda::stream_synchronize(stream));
|
||||
}
|
||||
|
||||
TEST(CUDAFunctionsTest, TorchSynchronizePreservesCurrentDevice) {
|
||||
if (!torch::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
if (torch::cuda::device_count() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr int current_device = 0;
|
||||
constexpr int other_device = 1;
|
||||
c10::cuda::CUDAGuard guard(static_cast<c10::DeviceIndex>(current_device));
|
||||
ASSERT_EQ(phi::backends::gpu::GetCurrentDeviceId(), current_device);
|
||||
|
||||
ASSERT_NO_THROW(torch::cuda::synchronize(other_device));
|
||||
EXPECT_EQ(phi::backends::gpu::GetCurrentDeviceId(), current_device);
|
||||
}
|
||||
|
||||
TEST(CUDAFunctionsTest, SynchronizeRejectsInvalidNegativeDevice) {
|
||||
if (!torch::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
ASSERT_THROW(torch::cuda::synchronize(-2), std::exception);
|
||||
}
|
||||
|
||||
TEST(CUDAFunctionsTest, CUDAGuardRestoresOriginalDeviceAfterMultipleSwitches) {
|
||||
if (!torch::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
if (torch::cuda::device_count() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr int original_device = 0;
|
||||
constexpr int intermediate_device = 1;
|
||||
phi::backends::gpu::SetDeviceId(original_device);
|
||||
ASSERT_EQ(phi::backends::gpu::GetCurrentDeviceId(), original_device);
|
||||
|
||||
{
|
||||
c10::cuda::CUDAGuard guard(
|
||||
static_cast<c10::DeviceIndex>(intermediate_device));
|
||||
ASSERT_EQ(phi::backends::gpu::GetCurrentDeviceId(), intermediate_device);
|
||||
guard.set_index(static_cast<c10::DeviceIndex>(original_device));
|
||||
ASSERT_EQ(phi::backends::gpu::GetCurrentDeviceId(), original_device);
|
||||
guard.set_index(static_cast<c10::DeviceIndex>(intermediate_device));
|
||||
ASSERT_EQ(phi::backends::gpu::GetCurrentDeviceId(), intermediate_device);
|
||||
}
|
||||
|
||||
EXPECT_EQ(phi::backends::gpu::GetCurrentDeviceId(), original_device);
|
||||
}
|
||||
|
||||
TEST(CUDAFunctionsTest,
|
||||
CUDAGuardRestoresOriginalDeviceAfterReturnToOriginalThenExit) {
|
||||
if (!torch::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
if (torch::cuda::device_count() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr int original_device = 0;
|
||||
constexpr int intermediate_device = 1;
|
||||
phi::backends::gpu::SetDeviceId(original_device);
|
||||
ASSERT_EQ(phi::backends::gpu::GetCurrentDeviceId(), original_device);
|
||||
|
||||
{
|
||||
c10::cuda::CUDAGuard guard(
|
||||
static_cast<c10::DeviceIndex>(intermediate_device));
|
||||
ASSERT_EQ(phi::backends::gpu::GetCurrentDeviceId(), intermediate_device);
|
||||
|
||||
guard.set_index(static_cast<c10::DeviceIndex>(original_device));
|
||||
ASSERT_EQ(phi::backends::gpu::GetCurrentDeviceId(), original_device);
|
||||
}
|
||||
|
||||
EXPECT_EQ(phi::backends::gpu::GetCurrentDeviceId(), original_device);
|
||||
}
|
||||
|
||||
TEST(CUDAFunctionsTest,
|
||||
OptionalCUDAGuardResetRestoresOriginalDeviceAfterReturnToOriginal) {
|
||||
if (!torch::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
if (torch::cuda::device_count() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr int original_device = 0;
|
||||
constexpr int intermediate_device = 1;
|
||||
phi::backends::gpu::SetDeviceId(original_device);
|
||||
ASSERT_EQ(phi::backends::gpu::GetCurrentDeviceId(), original_device);
|
||||
|
||||
c10::cuda::OptionalCUDAGuard guard;
|
||||
guard.set_index(static_cast<c10::DeviceIndex>(intermediate_device));
|
||||
ASSERT_EQ(phi::backends::gpu::GetCurrentDeviceId(), intermediate_device);
|
||||
|
||||
guard.set_index(static_cast<c10::DeviceIndex>(original_device));
|
||||
ASSERT_EQ(phi::backends::gpu::GetCurrentDeviceId(), original_device);
|
||||
|
||||
guard.reset();
|
||||
|
||||
EXPECT_EQ(phi::backends::gpu::GetCurrentDeviceId(), original_device);
|
||||
EXPECT_FALSE(guard.original_device().has_value());
|
||||
EXPECT_FALSE(guard.current_device().has_value());
|
||||
}
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CUDAContextLight.h — covers the 1 missing line: is_available()
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST(CUDAContextLightTest, IsAvailable) {
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
// With GPU compilation and at least one device, this must be true.
|
||||
int gpu_count = phi::backends::gpu::GetGPUDeviceCount();
|
||||
ASSERT_EQ(at::cuda::is_available(), gpu_count > 0);
|
||||
#else
|
||||
// In CPU-only builds, is_available() should return false
|
||||
ASSERT_FALSE(at::cuda::is_available());
|
||||
#endif
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CUDAContextLight.cpp — covers all 42 missing lines
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// getNumGPUs() delegages to c10::cuda::device_count()
|
||||
TEST(CUDAContextLightTest, GetNumGPUs) {
|
||||
int64_t n = at::cuda::getNumGPUs();
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
ASSERT_EQ(n, c10::cuda::device_count());
|
||||
ASSERT_GE(n, 0);
|
||||
#else
|
||||
// In CPU-only builds, device_count() returns 0
|
||||
ASSERT_EQ(n, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
// CPU-only: device_count() must return 0 instead of throwing, matching the
|
||||
// PyTorch contract that device_count() is a non-throwing query.
|
||||
TEST(CUDAContextLightTest, DeviceCountReturnsZeroInCpuOnly) {
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
// Only relevant in CPU-only builds
|
||||
return;
|
||||
#else
|
||||
ASSERT_NO_THROW({
|
||||
EXPECT_EQ(c10::cuda::device_count(), 0);
|
||||
EXPECT_EQ(torch::cuda::device_count(), 0);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
// CPU-only: is_available() must be false and not throw, matching PyTorch.
|
||||
TEST(CUDAContextLightTest, IsAvailableFalseAndNoThrowInCpuOnly) {
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
// Only relevant in CPU-only builds
|
||||
return;
|
||||
#else
|
||||
ASSERT_NO_THROW({
|
||||
EXPECT_FALSE(at::cuda::is_available());
|
||||
EXPECT_FALSE(torch::cuda::is_available());
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
|
||||
// The following tests require CUDA runtime and can only run in CUDA builds
|
||||
|
||||
// getCurrentDeviceProperties() / getDeviceProperties()
|
||||
TEST(CUDAContextLightTest, DeviceProperties) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
at::cuda::CUDAContextDeviceProp* prop =
|
||||
at::cuda::getCurrentDeviceProperties();
|
||||
ASSERT_NE(prop, nullptr);
|
||||
// Sanity-check a few well-known fields
|
||||
ASSERT_GT(prop->multiProcessorCount, 0);
|
||||
ASSERT_GT(prop->totalGlobalMem, 0UL);
|
||||
|
||||
// getDeviceProperties(explicit device id) must return the same struct
|
||||
int device_id = phi::backends::gpu::GetCurrentDeviceId();
|
||||
at::cuda::CUDAContextDeviceProp* prop2 =
|
||||
at::cuda::getDeviceProperties(device_id);
|
||||
ASSERT_EQ(prop, prop2);
|
||||
}
|
||||
|
||||
// warp_size()
|
||||
TEST(CUDAContextLightTest, WarpSize) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
int ws = at::cuda::warp_size();
|
||||
// All NVIDIA and AMD GPU architectures have warp size of 32 or 64
|
||||
ASSERT_TRUE(ws == 32 || ws == 64);
|
||||
}
|
||||
|
||||
// canDeviceAccessPeer() — a device cannot peer-access itself
|
||||
TEST(CUDAContextLightTest, CanDeviceAccessPeer) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
int device_id = phi::backends::gpu::GetCurrentDeviceId();
|
||||
// Self-to-self peer access is always false per CUDA spec
|
||||
bool self_peer = at::cuda::canDeviceAccessPeer(device_id, device_id);
|
||||
ASSERT_FALSE(self_peer);
|
||||
}
|
||||
|
||||
// Handle accessors — all must return non-null handles
|
||||
TEST(CUDAContextLightTest, GetCurrentCUDABlasHandle) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
at::cuda::CUDAContextBlasHandle h = at::cuda::getCurrentCUDABlasHandle();
|
||||
ASSERT_NE(h, nullptr);
|
||||
}
|
||||
|
||||
TEST(CUDAContextLightTest, GetCurrentCUDABlasLtHandle) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
at::cuda::CUDAContextBlasLtHandle h = at::cuda::getCurrentCUDABlasLtHandle();
|
||||
ASSERT_NE(h, nullptr);
|
||||
}
|
||||
|
||||
TEST(CUDAContextLightTest, GetCurrentCUDASparseHandle) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
at::cuda::CUDAContextSparseHandle h = at::cuda::getCurrentCUDASparseHandle();
|
||||
ASSERT_NE(h, nullptr);
|
||||
}
|
||||
|
||||
#if defined(CUDART_VERSION) || defined(USE_ROCM)
|
||||
TEST(CUDAContextLightTest, GetCurrentCUDASolverDnHandle) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
at::cuda::CUDAContextSolverHandle h =
|
||||
at::cuda::getCurrentCUDASolverDnHandle();
|
||||
ASSERT_NE(h, nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
// clearCublasWorkspaces() — must not crash (no-op in the compat layer)
|
||||
TEST(CUDAContextLightTest, ClearCublasWorkspaces) {
|
||||
ASSERT_NO_THROW(at::cuda::clearCublasWorkspaces());
|
||||
}
|
||||
|
||||
// cublas_handle_stream_to_workspace() — must return a stable reference
|
||||
TEST(CUDAContextLightTest, CublasHandleStreamToWorkspace) {
|
||||
at::cuda::WorkspaceMapWithMutex& wm =
|
||||
at::cuda::cublas_handle_stream_to_workspace();
|
||||
// The map should start empty
|
||||
ASSERT_TRUE(wm.map.empty());
|
||||
// Two calls must return the same singleton
|
||||
ASSERT_EQ(&wm, &at::cuda::cublas_handle_stream_to_workspace());
|
||||
}
|
||||
|
||||
// cublaslt_handle_stream_to_workspace() — same contract
|
||||
TEST(CUDAContextLightTest, CublasLtHandleStreamToWorkspace) {
|
||||
at::cuda::WorkspaceMapWithMutex& wm =
|
||||
at::cuda::cublaslt_handle_stream_to_workspace();
|
||||
ASSERT_TRUE(wm.map.empty());
|
||||
ASSERT_EQ(&wm, &at::cuda::cublaslt_handle_stream_to_workspace());
|
||||
}
|
||||
|
||||
// getChosenWorkspaceSize() — must be 32 MiB
|
||||
TEST(CUDAContextLightTest, GetChosenWorkspaceSize) {
|
||||
constexpr size_t kExpected = 32UL * 1024UL * 1024UL;
|
||||
ASSERT_EQ(at::cuda::getChosenWorkspaceSize(), kExpected);
|
||||
}
|
||||
|
||||
// getCUDABlasLtWorkspaceSize() / getCUDABlasLtWorkspace()
|
||||
TEST(CUDAContextLightTest, CUDABlasLtWorkspace) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
size_t sz = at::cuda::getCUDABlasLtWorkspaceSize();
|
||||
ASSERT_GT(sz, 0UL);
|
||||
|
||||
void* ptr = at::cuda::getCUDABlasLtWorkspace();
|
||||
ASSERT_NE(ptr, nullptr);
|
||||
}
|
||||
|
||||
TEST(CUDAContextLightTest, CUDADeviceAllocatorSingleton) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
c10::Allocator* a0 = at::cuda::getCUDADeviceAllocator();
|
||||
c10::Allocator* a1 = at::cuda::getCUDADeviceAllocator();
|
||||
ASSERT_NE(a0, nullptr);
|
||||
ASSERT_EQ(a0, a1);
|
||||
}
|
||||
|
||||
TEST(CUDAContextLightTest, CUDADeviceAllocatorCloneAndCopyData) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
c10::Allocator* alloc = at::cuda::getCUDADeviceAllocator();
|
||||
ASSERT_NE(alloc, nullptr);
|
||||
|
||||
constexpr size_t kBytes = 32;
|
||||
c10::DataPtr src = alloc->allocate(kBytes);
|
||||
ASSERT_NE(src.get(), nullptr);
|
||||
|
||||
uint8_t h_src[kBytes];
|
||||
uint8_t h_dst[kBytes];
|
||||
for (size_t i = 0; i < kBytes; ++i) {
|
||||
h_src[i] = static_cast<uint8_t>(i + 1);
|
||||
h_dst[i] = 0;
|
||||
}
|
||||
|
||||
ASSERT_EQ(MEMCPY_FN(src.get(), h_src, kBytes, MEMCPY_HOST_TO_DEVICE),
|
||||
SUCCESS_CODE);
|
||||
|
||||
c10::DataPtr cloned = alloc->clone(src.get(), kBytes);
|
||||
ASSERT_NE(cloned.get(), nullptr);
|
||||
|
||||
ASSERT_EQ(MEMCPY_FN(h_dst, cloned.get(), kBytes, MEMCPY_DEVICE_TO_HOST),
|
||||
SUCCESS_CODE);
|
||||
ASSERT_EQ(DEVICE_SYNCHRONIZE_FN(), SUCCESS_CODE);
|
||||
|
||||
for (size_t i = 0; i < kBytes; ++i) {
|
||||
ASSERT_EQ(h_dst[i], h_src[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CUDAContextLightTest, CUDADeviceAllocatorCloneZeroBytes) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
c10::Allocator* alloc = at::cuda::getCUDADeviceAllocator();
|
||||
ASSERT_NE(alloc, nullptr);
|
||||
|
||||
c10::DataPtr src = alloc->allocate(0);
|
||||
ASSERT_EQ(src.get(), nullptr);
|
||||
|
||||
c10::DataPtr cloned = alloc->clone(src.get(), 0);
|
||||
ASSERT_EQ(cloned.get(), nullptr);
|
||||
ASSERT_EQ(cloned.device().type(), c10::DeviceType::CUDA);
|
||||
}
|
||||
|
||||
TEST(CUDAContextLightTest, AllocatorZeroSizeAndNoopCopyBranches) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
c10::Allocator* alloc = at::cuda::getCUDADeviceAllocator();
|
||||
ASSERT_NE(alloc, nullptr);
|
||||
|
||||
c10::DataPtr zero = alloc->allocate(0);
|
||||
ASSERT_EQ(zero.device().type(), c10::DeviceType::CUDA);
|
||||
ASSERT_EQ(alloc->raw_deleter(), nullptr);
|
||||
|
||||
// n==0 branch should early-return without touching pointers.
|
||||
alloc->copy_data(nullptr, nullptr, 0);
|
||||
}
|
||||
|
||||
#if defined(USE_CUDSS)
|
||||
TEST(CUDAContextLightTest, CudssHandleIsUnimplemented) {
|
||||
ASSERT_THROW((void)at::cuda::getCurrentCudssHandle(), std::exception);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // PADDLE_WITH_CUDA || PADDLE_WITH_HIP
|
||||
@@ -0,0 +1,220 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/Layout.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// Test for TensorBase::accessor()
|
||||
TEST(TensorAccessorTest, AccessorBasic) {
|
||||
// Create a 2D tensor with known values
|
||||
at::Tensor tensor = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
|
||||
// Get accessor
|
||||
auto accessor = tensor.accessor<float, 2>();
|
||||
|
||||
// Verify accessor dimensions
|
||||
ASSERT_EQ(accessor.size(0), 3);
|
||||
ASSERT_EQ(accessor.size(1), 4);
|
||||
|
||||
// Verify accessor values
|
||||
float expected = 0.0f;
|
||||
for (int64_t i = 0; i < 3; ++i) {
|
||||
for (int64_t j = 0; j < 4; ++j) {
|
||||
ASSERT_EQ(accessor[i][j], expected);
|
||||
expected += 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TensorAccessorTest, AccessorWithConstType) {
|
||||
// Create a tensor
|
||||
at::Tensor tensor = at::ones({2, 3}, at::kFloat);
|
||||
|
||||
// Get const accessor
|
||||
auto accessor = tensor.accessor<const float, 2>();
|
||||
|
||||
// Verify values are all ones
|
||||
for (int64_t i = 0; i < 2; ++i) {
|
||||
for (int64_t j = 0; j < 3; ++j) {
|
||||
ASSERT_EQ(accessor[i][j], 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TensorAccessorTest, Accessor3D) {
|
||||
// Create a 3D tensor
|
||||
at::Tensor tensor = at::arange(24, at::kFloat).reshape({2, 3, 4});
|
||||
|
||||
// Get accessor
|
||||
auto accessor = tensor.accessor<float, 3>();
|
||||
|
||||
// Verify dimensions
|
||||
ASSERT_EQ(accessor.size(0), 2);
|
||||
ASSERT_EQ(accessor.size(1), 3);
|
||||
ASSERT_EQ(accessor.size(2), 4);
|
||||
|
||||
// Verify a few values
|
||||
ASSERT_EQ(accessor[0][0][0], 0.0f);
|
||||
ASSERT_EQ(accessor[0][0][3], 3.0f);
|
||||
ASSERT_EQ(accessor[1][2][3], 23.0f);
|
||||
}
|
||||
|
||||
TEST(TensorAccessorTest, AccessorModifyValues) {
|
||||
// Create a tensor
|
||||
at::Tensor tensor = at::zeros({2, 3}, at::kFloat);
|
||||
|
||||
// Get mutable accessor
|
||||
auto accessor = tensor.accessor<float, 2>();
|
||||
|
||||
// Modify values through accessor
|
||||
for (int64_t i = 0; i < 2; ++i) {
|
||||
for (int64_t j = 0; j < 3; ++j) {
|
||||
accessor[i][j] = static_cast<float>(i * 3 + j);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify modifications via data_ptr
|
||||
float* data = tensor.data_ptr<float>();
|
||||
for (int64_t i = 0; i < 6; ++i) {
|
||||
ASSERT_EQ(data[i], static_cast<float>(i));
|
||||
}
|
||||
}
|
||||
|
||||
// Test for TensorBase::packed_accessor64()
|
||||
TEST(TensorAccessorTest, PackedAccessor64Basic) {
|
||||
// Create a 2D tensor
|
||||
at::Tensor tensor = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
|
||||
// Get packed accessor with int64_t index type
|
||||
auto packed = tensor.packed_accessor64<float, 2>();
|
||||
|
||||
// Verify dimensions
|
||||
ASSERT_EQ(packed.size(0), 3);
|
||||
ASSERT_EQ(packed.size(1), 4);
|
||||
|
||||
// Verify strides
|
||||
ASSERT_EQ(packed.stride(0), 4);
|
||||
ASSERT_EQ(packed.stride(1), 1);
|
||||
|
||||
// Verify values
|
||||
float expected = 0.0f;
|
||||
for (int64_t i = 0; i < 3; ++i) {
|
||||
for (int64_t j = 0; j < 4; ++j) {
|
||||
ASSERT_EQ(packed[i][j], expected);
|
||||
expected += 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test for TensorBase::packed_accessor32()
|
||||
TEST(TensorAccessorTest, PackedAccessor32Basic) {
|
||||
// Create a small 2D tensor (within int32_t range)
|
||||
at::Tensor tensor = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
// Get packed accessor with int32_t index type
|
||||
auto packed = tensor.packed_accessor32<float, 2>();
|
||||
|
||||
// Verify dimensions
|
||||
ASSERT_EQ(packed.size(0), 2);
|
||||
ASSERT_EQ(packed.size(1), 3);
|
||||
|
||||
// Verify strides
|
||||
ASSERT_EQ(packed.stride(0), 3);
|
||||
ASSERT_EQ(packed.stride(1), 1);
|
||||
|
||||
// Verify values
|
||||
ASSERT_EQ(packed[0][0], 0.0f);
|
||||
ASSERT_EQ(packed[0][2], 2.0f);
|
||||
ASSERT_EQ(packed[1][0], 3.0f);
|
||||
ASSERT_EQ(packed[1][2], 5.0f);
|
||||
}
|
||||
|
||||
// Test for TensorBase::generic_packed_accessor()
|
||||
TEST(TensorAccessorTest, GenericPackedAccessor) {
|
||||
// Create a 3D tensor
|
||||
at::Tensor tensor = at::arange(24, at::kDouble).reshape({2, 3, 4});
|
||||
|
||||
// Get generic packed accessor with default template parameters
|
||||
auto packed = tensor.generic_packed_accessor<double, 3>();
|
||||
|
||||
// Verify dimensions
|
||||
ASSERT_EQ(packed.size(0), 2);
|
||||
ASSERT_EQ(packed.size(1), 3);
|
||||
ASSERT_EQ(packed.size(2), 4);
|
||||
|
||||
// Verify strides
|
||||
ASSERT_EQ(packed.stride(0), 12); // 3*4
|
||||
ASSERT_EQ(packed.stride(1), 4);
|
||||
ASSERT_EQ(packed.stride(2), 1);
|
||||
|
||||
// Verify corner values
|
||||
ASSERT_DOUBLE_EQ(packed[0][0][0], 0.0);
|
||||
ASSERT_DOUBLE_EQ(packed[1][2][3], 23.0);
|
||||
}
|
||||
|
||||
TEST(TensorAccessorTest, PackedAccessorWithIntType) {
|
||||
// Test with integer tensor
|
||||
at::Tensor tensor = at::arange(10, at::kInt).reshape({2, 5});
|
||||
|
||||
auto packed = tensor.packed_accessor64<int, 2>();
|
||||
|
||||
ASSERT_EQ(packed.size(0), 2);
|
||||
ASSERT_EQ(packed.size(1), 5);
|
||||
|
||||
int expected = 0;
|
||||
for (int64_t i = 0; i < 2; ++i) {
|
||||
for (int64_t j = 0; j < 5; ++j) {
|
||||
ASSERT_EQ(packed[i][j], expected);
|
||||
expected++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
TEST(TensorAccessorTest, PackedAccessorCUDA) {
|
||||
if (at::cuda::is_available()) {
|
||||
// Create CUDA tensor
|
||||
at::Tensor tensor =
|
||||
at::arange(12, at::TensorOptions().dtype(at::kFloat).device(at::kCUDA))
|
||||
.reshape({3, 4});
|
||||
|
||||
// Get packed accessor (typically used to pass to CUDA kernels)
|
||||
auto packed = tensor.packed_accessor64<float, 2>();
|
||||
|
||||
// Verify dimensions
|
||||
ASSERT_EQ(packed.size(0), 3);
|
||||
ASSERT_EQ(packed.size(1), 4);
|
||||
|
||||
// Verify strides
|
||||
ASSERT_EQ(packed.stride(0), 4);
|
||||
ASSERT_EQ(packed.stride(1), 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,181 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/Utils.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#include <c10/util/ArrayRef.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ============================================================
|
||||
// Tests for at::detail::tensor_cpu / tensor_backend / complex variants
|
||||
// and the at::tensor() factory macro-generated overloads (ATen/Utils.h)
|
||||
// ============================================================
|
||||
|
||||
// ---- tensor_cpu (via at::tensor public API) ----
|
||||
|
||||
TEST(ATenUtilsTest, TensorCPU_Float) {
|
||||
std::vector<float> data = {1.0f, 2.0f, 3.0f};
|
||||
at::Tensor t = at::tensor(c10::ArrayRef<float>(data),
|
||||
at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
ASSERT_EQ(t.scalar_type(), at::kFloat);
|
||||
ASSERT_EQ(t.numel(), 3);
|
||||
ASSERT_NEAR(t[0].item<float>(), 1.0f, 1e-6f);
|
||||
ASSERT_NEAR(t[2].item<float>(), 3.0f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(ATenUtilsTest, TensorCPU_Double) {
|
||||
std::vector<double> data = {1.1, 2.2, 3.3};
|
||||
at::Tensor t = at::tensor(c10::ArrayRef<double>(data),
|
||||
at::TensorOptions().dtype(at::kDouble));
|
||||
|
||||
ASSERT_EQ(t.scalar_type(), at::kDouble);
|
||||
ASSERT_NEAR(t[1].item<double>(), 2.2, 1e-10);
|
||||
}
|
||||
|
||||
TEST(ATenUtilsTest, TensorCPU_Int32) {
|
||||
std::vector<int32_t> data = {10, 20, 30};
|
||||
at::Tensor t = at::tensor(c10::ArrayRef<int32_t>(data),
|
||||
at::TensorOptions().dtype(at::kInt));
|
||||
|
||||
ASSERT_EQ(t.scalar_type(), at::kInt);
|
||||
ASSERT_EQ(t[0].item<int32_t>(), 10);
|
||||
ASSERT_EQ(t[2].item<int32_t>(), 30);
|
||||
}
|
||||
|
||||
TEST(ATenUtilsTest, TensorCPU_Int64) {
|
||||
std::vector<int64_t> data = {100LL, 200LL};
|
||||
at::Tensor t = at::tensor(c10::ArrayRef<int64_t>(data),
|
||||
at::TensorOptions().dtype(at::kLong));
|
||||
|
||||
ASSERT_EQ(t.scalar_type(), at::kLong);
|
||||
ASSERT_EQ(t[1].item<int64_t>(), 200LL);
|
||||
}
|
||||
|
||||
TEST(ATenUtilsTest, TensorCPU_Int8) {
|
||||
std::vector<int8_t> data = {-1, 0, 1};
|
||||
at::Tensor t = at::tensor(c10::ArrayRef<int8_t>(data),
|
||||
at::TensorOptions().dtype(at::kChar));
|
||||
|
||||
ASSERT_EQ(t.scalar_type(), at::kChar);
|
||||
ASSERT_EQ(t[0].item<int8_t>(), static_cast<int8_t>(-1));
|
||||
}
|
||||
|
||||
TEST(ATenUtilsTest, TensorCPU_Int16) {
|
||||
std::vector<int16_t> data = {256, 512};
|
||||
at::Tensor t = at::tensor(c10::ArrayRef<int16_t>(data),
|
||||
at::TensorOptions().dtype(at::kShort));
|
||||
|
||||
ASSERT_EQ(t.scalar_type(), at::kShort);
|
||||
ASSERT_EQ(t[0].item<int16_t>(), static_cast<int16_t>(256));
|
||||
}
|
||||
|
||||
TEST(ATenUtilsTest, TensorCPU_UInt8) {
|
||||
std::vector<uint8_t> data = {200, 255};
|
||||
at::Tensor t = at::tensor(c10::ArrayRef<uint8_t>(data),
|
||||
at::TensorOptions().dtype(at::kByte));
|
||||
|
||||
ASSERT_EQ(t.scalar_type(), at::kByte);
|
||||
ASSERT_EQ(t[1].item<uint8_t>(), static_cast<uint8_t>(255));
|
||||
}
|
||||
|
||||
TEST(ATenUtilsTest, TensorCPU_Bool) {
|
||||
// std::vector<bool> is a bitfield specialization without data(), so use a
|
||||
// plain C array to construct c10::ArrayRef<bool>.
|
||||
bool data[] = {true, false, true};
|
||||
at::Tensor t = at::tensor(c10::ArrayRef<bool>(data),
|
||||
at::TensorOptions().dtype(at::kBool));
|
||||
|
||||
ASSERT_EQ(t.scalar_type(), at::kBool);
|
||||
ASSERT_TRUE(t[0].item<bool>());
|
||||
ASSERT_FALSE(t[1].item<bool>());
|
||||
}
|
||||
|
||||
// ---- dtype promotion: values stored as native type then cast ----
|
||||
|
||||
TEST(ATenUtilsTest, TensorCPU_DtypePromotion_IntToFloat) {
|
||||
// Store int32 values, but request float32 output – should auto-cast.
|
||||
std::vector<int32_t> data = {1, 2, 3};
|
||||
at::Tensor t = at::tensor(c10::ArrayRef<int32_t>(data),
|
||||
at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
ASSERT_EQ(t.scalar_type(), at::kFloat);
|
||||
ASSERT_NEAR(t[0].item<float>(), 1.0f, 1e-6f);
|
||||
}
|
||||
|
||||
// ---- contiguity ----
|
||||
|
||||
TEST(ATenUtilsTest, TensorCPU_IsContiguous) {
|
||||
std::vector<float> data = {1.0f, 2.0f, 3.0f, 4.0f};
|
||||
at::Tensor t = at::tensor(c10::ArrayRef<float>(data),
|
||||
at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
ASSERT_TRUE(t.is_contiguous());
|
||||
}
|
||||
|
||||
// ---- tensor_backend (CPU -> same result since default is CPU in tests) ----
|
||||
|
||||
TEST(ATenUtilsTest, TensorBackend_CPUDevice_MatchesTensorCPU) {
|
||||
std::vector<float> data = {5.0f, 6.0f};
|
||||
at::TensorOptions opts =
|
||||
at::TensorOptions().dtype(at::kFloat).device(c10::Device(c10::kCPU));
|
||||
at::Tensor t = at::tensor(c10::ArrayRef<float>(data), opts);
|
||||
|
||||
ASSERT_EQ(t.scalar_type(), at::kFloat);
|
||||
ASSERT_EQ(t.device().type(), c10::DeviceType::CPU);
|
||||
ASSERT_NEAR(t[0].item<float>(), 5.0f, 1e-6f);
|
||||
}
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
TEST(ATenUtilsTest, TensorBackend_GPUDevice) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
std::vector<float> data = {7.0f, 8.0f};
|
||||
at::TensorOptions opts =
|
||||
at::TensorOptions().dtype(at::kFloat).device(c10::Device(c10::kCUDA, 0));
|
||||
at::Tensor t = at::tensor(c10::ArrayRef<float>(data), opts);
|
||||
|
||||
ASSERT_EQ(t.scalar_type(), at::kFloat);
|
||||
ASSERT_EQ(t.device().type(), c10::DeviceType::CUDA);
|
||||
}
|
||||
|
||||
TEST(ATenUtilsTest, TensorComplexBackend_GPUDevice) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
std::vector<c10::complex<float>> data = {{1.0f, 0.0f}};
|
||||
at::TensorOptions opts = at::TensorOptions()
|
||||
.dtype(at::kComplexFloat)
|
||||
.device(c10::Device(c10::kCUDA, 0));
|
||||
at::Tensor t = at::tensor(c10::ArrayRef<c10::complex<float>>(data), opts);
|
||||
|
||||
ASSERT_EQ(t.scalar_type(), at::kComplexFloat);
|
||||
ASSERT_EQ(t.device().type(), c10::DeviceType::CUDA);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,656 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#include <limits>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
TEST(TestAll, AllNoDim) {
|
||||
// Test all() without arguments - check all elements in tensor
|
||||
at::Tensor tensor = at::ones({3}, at::kBool);
|
||||
tensor[1] = false;
|
||||
at::Tensor result = tensor.all();
|
||||
|
||||
ASSERT_EQ(result.numel(), 1);
|
||||
ASSERT_EQ(result.item<bool>(), false);
|
||||
|
||||
// Test with all true values
|
||||
at::Tensor tensor_all_true = at::ones({3}, at::kBool);
|
||||
at::Tensor result_all_true = tensor_all_true.all();
|
||||
ASSERT_EQ(result_all_true.item<bool>(), true);
|
||||
}
|
||||
|
||||
TEST(TestAll, AllWithDim) {
|
||||
// Test all(dim) - check along specific dimension
|
||||
at::Tensor tensor = at::ones({2, 2}, at::kBool);
|
||||
tensor[1][0] = false;
|
||||
|
||||
// All along dimension 0
|
||||
at::Tensor result_dim0 = tensor.all(0);
|
||||
ASSERT_EQ(result_dim0.sizes(), c10::IntArrayRef({2}));
|
||||
ASSERT_EQ(result_dim0.data_ptr<bool>()[0], false); // column 0 has false
|
||||
ASSERT_EQ(result_dim0.data_ptr<bool>()[1], true); // column 1 has all true
|
||||
|
||||
// All along dimension 1
|
||||
at::Tensor result_dim1 = tensor.all(1);
|
||||
ASSERT_EQ(result_dim1.sizes(), c10::IntArrayRef({2}));
|
||||
ASSERT_EQ(result_dim1.data_ptr<bool>()[0], true); // row 0 has all true
|
||||
ASSERT_EQ(result_dim1.data_ptr<bool>()[1], false); // row 1 has false
|
||||
}
|
||||
|
||||
TEST(TestAll, AllWithDimKeepdim) {
|
||||
// Test all(dim, keepdim) - keep the dimension
|
||||
at::Tensor tensor = at::ones({2, 2}, at::kBool);
|
||||
|
||||
at::Tensor result = tensor.all(0, true);
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({1, 2}));
|
||||
}
|
||||
|
||||
TEST(TestAll, AllWithOptionalDim) {
|
||||
// Test all(OptionalIntArrayRef dim, keepdim)
|
||||
at::Tensor tensor = at::ones({2, 2}, at::kBool);
|
||||
|
||||
// With specific dimensions
|
||||
at::Tensor result = tensor.all(c10::IntArrayRef({0}), false);
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({2}));
|
||||
}
|
||||
|
||||
TEST(TestAll, AllNoDimAllFalse) {
|
||||
// Test all() on tensor with all false values
|
||||
at::Tensor tensor = at::zeros({4}, at::kBool);
|
||||
at::Tensor result = tensor.all();
|
||||
ASSERT_EQ(result.numel(), 1);
|
||||
ASSERT_EQ(result.item<bool>(), false);
|
||||
}
|
||||
|
||||
TEST(TestAll, AllNoDimSingleElement) {
|
||||
// Test all() on single-element tensor
|
||||
at::Tensor tensor_true = at::ones({1}, at::kBool);
|
||||
ASSERT_EQ(tensor_true.all().item<bool>(), true);
|
||||
|
||||
at::Tensor tensor_false = at::zeros({1}, at::kBool);
|
||||
ASSERT_EQ(tensor_false.all().item<bool>(), false);
|
||||
}
|
||||
|
||||
TEST(TestAll, AllWithNegativeDim) {
|
||||
// Test all(dim) with negative dimension index
|
||||
at::Tensor tensor = at::ones({2, 3}, at::kBool);
|
||||
tensor[0][1] = false;
|
||||
at::Tensor result = tensor.all(-1); // equivalent to dim=1
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({2}));
|
||||
ASSERT_EQ(result.data_ptr<bool>()[0], false); // row 0 has a false
|
||||
ASSERT_EQ(result.data_ptr<bool>()[1], true); // row 1 all true
|
||||
}
|
||||
|
||||
TEST(TestAll, AllWithDimKeepdimTrue) {
|
||||
// Test all(dim, keepdim=true) with different dims
|
||||
at::Tensor tensor = at::ones({2, 3}, at::kBool);
|
||||
tensor[1][0] = false;
|
||||
|
||||
at::Tensor result_dim0 = tensor.all(0, true);
|
||||
ASSERT_EQ(result_dim0.sizes(), c10::IntArrayRef({1, 3}));
|
||||
ASSERT_EQ(result_dim0.data_ptr<bool>()[0], false); // col 0 has false
|
||||
ASSERT_EQ(result_dim0.data_ptr<bool>()[1], true);
|
||||
ASSERT_EQ(result_dim0.data_ptr<bool>()[2], true);
|
||||
|
||||
at::Tensor result_dim1 = tensor.all(1, true);
|
||||
ASSERT_EQ(result_dim1.sizes(), c10::IntArrayRef({2, 1}));
|
||||
ASSERT_EQ(result_dim1.data_ptr<bool>()[0], true); // row 0 all true
|
||||
ASSERT_EQ(result_dim1.data_ptr<bool>()[1], false); // row 1 has false
|
||||
}
|
||||
|
||||
TEST(TestAll, AllWithOptionalDimNullopt) {
|
||||
// Test all(OptionalIntArrayRef) with nullopt - reduces all dimensions
|
||||
at::Tensor tensor = at::ones({2, 3}, at::kBool);
|
||||
at::OptionalIntArrayRef dim = std::nullopt;
|
||||
at::Tensor result = at::all(tensor, dim, false);
|
||||
ASSERT_EQ(result.numel(), 1);
|
||||
ASSERT_EQ(result.item<bool>(), true);
|
||||
}
|
||||
|
||||
TEST(TestAll, AllWithOptionalDimNulloptHasFalse) {
|
||||
// Test all(OptionalIntArrayRef nullopt) when tensor contains false
|
||||
at::Tensor tensor = at::ones({2, 3}, at::kBool);
|
||||
tensor[1][2] = false;
|
||||
at::OptionalIntArrayRef dim = std::nullopt;
|
||||
at::Tensor result = at::all(tensor, dim, false);
|
||||
ASSERT_EQ(result.numel(), 1);
|
||||
ASSERT_EQ(result.item<bool>(), false);
|
||||
}
|
||||
|
||||
TEST(TestAll, AllWithOptionalDimKeepdim) {
|
||||
// Test all(OptionalIntArrayRef, keepdim=true)
|
||||
at::Tensor tensor = at::ones({2, 3}, at::kBool);
|
||||
at::Tensor result = at::all(tensor, c10::IntArrayRef({0}), true);
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({1, 3}));
|
||||
}
|
||||
|
||||
TEST(TestAll, AllWithOptionalMultipleDims) {
|
||||
// Test all(OptionalIntArrayRef) with multiple dimensions
|
||||
at::Tensor tensor = at::ones({2, 3, 4}, at::kBool);
|
||||
at::Tensor result = at::all(tensor, c10::IntArrayRef({0, 2}), false);
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({3}));
|
||||
// All elements are true, so result should be all true
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_EQ(result.data_ptr<bool>()[i], true);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TestAll, MemberAllWithOptionalNullopt) {
|
||||
// Test member function Tensor::all(OptionalIntArrayRef, keepdim) with nullopt
|
||||
at::Tensor tensor = at::ones({3, 4}, at::kBool);
|
||||
at::OptionalIntArrayRef dim = std::nullopt;
|
||||
at::Tensor result = tensor.all(dim, false);
|
||||
ASSERT_EQ(result.numel(), 1);
|
||||
ASSERT_EQ(result.item<bool>(), true);
|
||||
}
|
||||
|
||||
TEST(TestAll, MemberAllWithOptionalNulloptKeepdim) {
|
||||
// Test member function Tensor::all(nullopt, keepdim=true)
|
||||
at::Tensor tensor = at::ones({2, 3}, at::kBool);
|
||||
at::OptionalIntArrayRef dim = std::nullopt;
|
||||
at::Tensor result = tensor.all(dim, true);
|
||||
ASSERT_EQ(result.numel(), 1);
|
||||
ASSERT_EQ(result.item<bool>(), true);
|
||||
}
|
||||
|
||||
TEST(TestAll, StandaloneFunction) {
|
||||
// Test at::all() standalone function
|
||||
at::Tensor tensor = at::ones({3}, at::kBool);
|
||||
tensor[2] = false;
|
||||
at::Tensor result = at::all(tensor);
|
||||
|
||||
ASSERT_EQ(result.item<bool>(), false);
|
||||
}
|
||||
|
||||
TEST(TestAll, StandaloneFunctionWithDim) {
|
||||
// Test at::all(tensor, dim, keepdim)
|
||||
at::Tensor tensor = at::ones({2, 3}, at::kBool);
|
||||
tensor[0][0] = false;
|
||||
|
||||
at::Tensor result = at::all(tensor, 0, false);
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({3}));
|
||||
ASSERT_EQ(result.data_ptr<bool>()[0], false);
|
||||
ASSERT_EQ(result.data_ptr<bool>()[1], true);
|
||||
ASSERT_EQ(result.data_ptr<bool>()[2], true);
|
||||
|
||||
at::Tensor result_kd = at::all(tensor, 0, true);
|
||||
ASSERT_EQ(result_kd.sizes(), c10::IntArrayRef({1, 3}));
|
||||
}
|
||||
|
||||
TEST(TestAll, AllWith3DTensor) {
|
||||
// Test all on a 3D tensor to exercise more paths
|
||||
at::Tensor tensor = at::ones({2, 2, 2}, at::kBool);
|
||||
tensor[0][0][0] = false;
|
||||
|
||||
at::Tensor result_all = tensor.all();
|
||||
ASSERT_EQ(result_all.item<bool>(), false);
|
||||
|
||||
at::Tensor result_dim0 = tensor.all(0, false);
|
||||
ASSERT_EQ(result_dim0.sizes(), c10::IntArrayRef({2, 2}));
|
||||
|
||||
at::Tensor result_dim2 = tensor.all(2, true);
|
||||
ASSERT_EQ(result_dim2.sizes(), c10::IntArrayRef({2, 2, 1}));
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseBasic) {
|
||||
// Test allclose - basic equal tensors
|
||||
at::Tensor tensor1 = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
at::Tensor tensor2 = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
bool result = tensor1.allclose(tensor2);
|
||||
ASSERT_EQ(result, true);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseNotEqual) {
|
||||
// Test allclose - tensors that are not close
|
||||
at::Tensor tensor1 = at::arange(1, 4, at::TensorOptions().dtype(at::kFloat));
|
||||
at::Tensor tensor2 = tensor1.clone();
|
||||
tensor2[2] = 4.0f;
|
||||
|
||||
bool result = tensor1.allclose(tensor2);
|
||||
ASSERT_EQ(result, false);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, StandaloneFunction) {
|
||||
// Test at::allclose() standalone function
|
||||
at::Tensor tensor1 = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
at::Tensor tensor2 = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
bool result = at::allclose(tensor1, tensor2);
|
||||
ASSERT_EQ(result, true);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseWithCustomRtol) {
|
||||
// Test allclose with custom relative tolerance
|
||||
at::Tensor tensor1 = at::ones({3}, at::kFloat);
|
||||
at::Tensor tensor2 = at::ones({3}, at::kFloat);
|
||||
tensor2[0] = 1.05f; // 5% difference
|
||||
|
||||
// With default rtol=1e-05, should fail
|
||||
bool result_default = at::allclose(tensor1, tensor2);
|
||||
ASSERT_EQ(result_default, false);
|
||||
|
||||
// With rtol=0.1 (10%), 5% difference should pass
|
||||
bool result_large_rtol = at::allclose(tensor1, tensor2, 0.1, 1e-08, false);
|
||||
ASSERT_EQ(result_large_rtol, true);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseWithCustomAtol) {
|
||||
// Test allclose with custom absolute tolerance
|
||||
at::Tensor tensor1 = at::zeros({3}, at::kFloat);
|
||||
at::Tensor tensor2 = at::zeros({3}, at::kFloat);
|
||||
tensor2[1] = 0.05f;
|
||||
|
||||
// With default atol=1e-08, should fail
|
||||
bool result_default = at::allclose(tensor1, tensor2);
|
||||
ASSERT_EQ(result_default, false);
|
||||
|
||||
// With atol=0.1, should pass
|
||||
bool result_large_atol = at::allclose(tensor1, tensor2, 1e-05, 0.1, false);
|
||||
ASSERT_EQ(result_large_atol, true);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseMemberWithAllParams) {
|
||||
// Test Tensor::allclose member function with all explicit parameters
|
||||
at::Tensor tensor1 = at::ones({2, 2}, at::kFloat);
|
||||
at::Tensor tensor2 = at::ones({2, 2}, at::kFloat);
|
||||
|
||||
bool result = tensor1.allclose(tensor2, 1e-05, 1e-08, false);
|
||||
ASSERT_EQ(result, true);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseMemberNotClose) {
|
||||
// Test Tensor::allclose member function returns false when not close
|
||||
at::Tensor tensor1 = at::ones({2, 3}, at::kFloat);
|
||||
at::Tensor tensor2 = at::ones({2, 3}, at::kFloat);
|
||||
tensor2[0][0] = 100.0f;
|
||||
|
||||
bool result = tensor1.allclose(tensor2, 1e-05, 1e-08, false);
|
||||
ASSERT_EQ(result, false);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseMemberWithCustomTolerance) {
|
||||
// Test Tensor::allclose member function with custom rtol and atol
|
||||
at::Tensor tensor1 = at::ones({4}, at::kFloat);
|
||||
at::Tensor tensor2 = at::ones({4}, at::kFloat);
|
||||
tensor2[3] = 1.001f; // small relative difference
|
||||
|
||||
// Default tolerance should fail
|
||||
ASSERT_EQ(tensor1.allclose(tensor2), false);
|
||||
|
||||
// Custom rtol=0.01 (1%) should pass
|
||||
ASSERT_EQ(tensor1.allclose(tensor2, 0.01, 1e-08, false), true);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseExactZeros) {
|
||||
// Test allclose with exact zero tensors
|
||||
at::Tensor tensor1 = at::zeros({5}, at::kFloat);
|
||||
at::Tensor tensor2 = at::zeros({5}, at::kFloat);
|
||||
|
||||
bool result = at::allclose(tensor1, tensor2);
|
||||
ASSERT_EQ(result, true);
|
||||
|
||||
bool result_member = tensor1.allclose(tensor2);
|
||||
ASSERT_EQ(result_member, true);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseHighDim) {
|
||||
// Test allclose with higher dimensional tensors
|
||||
at::Tensor tensor1 = at::arange(24, at::kFloat).reshape({2, 3, 4});
|
||||
at::Tensor tensor2 = at::arange(24, at::kFloat).reshape({2, 3, 4});
|
||||
|
||||
bool result = at::allclose(tensor1, tensor2);
|
||||
ASSERT_EQ(result, true);
|
||||
|
||||
bool result_member = tensor1.allclose(tensor2, 1e-05, 1e-08, false);
|
||||
ASSERT_EQ(result_member, true);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseEqualNanDefaultFalse) {
|
||||
// Test allclose default behavior: NaN != NaN when equal_nan not set
|
||||
// Use from_blob to avoid triggering fill_ operation which doesn't support NaN
|
||||
const float nan_val = std::numeric_limits<float>::quiet_NaN();
|
||||
float data1[3] = {0.0f, nan_val, 0.0f};
|
||||
at::Tensor tensor1 = at::from_blob(data1, {3}, at::kFloat);
|
||||
at::Tensor tensor2 = tensor1.clone();
|
||||
|
||||
// Default equal_nan=false: NaN is not equal to NaN, so result is false
|
||||
bool result_standalone = at::allclose(tensor1, tensor2);
|
||||
ASSERT_EQ(result_standalone, false);
|
||||
|
||||
bool result_member = tensor1.allclose(tensor2);
|
||||
ASSERT_EQ(result_member, false);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseEqualNanTrue) {
|
||||
// Test allclose with equal_nan=true: NaN == NaN should yield true
|
||||
// Use from_blob to avoid triggering fill_ operation which doesn't support NaN
|
||||
const float nan_val = std::numeric_limits<float>::quiet_NaN();
|
||||
float data1[3] = {0.0f, nan_val, 0.0f};
|
||||
at::Tensor tensor1 = at::from_blob(data1, {3}, at::kFloat);
|
||||
at::Tensor tensor2 = tensor1.clone();
|
||||
|
||||
// equal_nan=true: NaN is treated as equal to NaN
|
||||
bool result = at::allclose(tensor1, tensor2, 1e-05, 1e-08, true);
|
||||
ASSERT_EQ(result, true);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseEqualNanTrueAllNan) {
|
||||
// Test allclose with equal_nan=true on all-NaN tensors
|
||||
// Use from_blob to avoid triggering fill_ operation which doesn't support NaN
|
||||
const float nan_val = std::numeric_limits<float>::quiet_NaN();
|
||||
float data1[4] = {nan_val, nan_val, nan_val, nan_val};
|
||||
float data2[4] = {nan_val, nan_val, nan_val, nan_val};
|
||||
at::Tensor tensor1 = at::from_blob(data1, {4}, at::kFloat);
|
||||
at::Tensor tensor2 = at::from_blob(data2, {4}, at::kFloat);
|
||||
|
||||
bool result_equal_nan = at::allclose(tensor1, tensor2, 1e-05, 1e-08, true);
|
||||
ASSERT_EQ(result_equal_nan, true);
|
||||
|
||||
// Without equal_nan, all-NaN tensors should not be close
|
||||
bool result_no_equal_nan =
|
||||
at::allclose(tensor1, tensor2, 1e-05, 1e-08, false);
|
||||
ASSERT_EQ(result_no_equal_nan, false);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseMemberEqualNanTrue) {
|
||||
// Test Tensor::allclose member function with equal_nan=true
|
||||
// Use from_blob to avoid triggering fill_ operation which doesn't support NaN
|
||||
const float nan_val = std::numeric_limits<float>::quiet_NaN();
|
||||
float data1[4] = {nan_val, 0.0f, 0.0f, nan_val};
|
||||
at::Tensor tensor1 = at::from_blob(data1, {4}, at::kFloat);
|
||||
at::Tensor tensor2 = tensor1.clone();
|
||||
|
||||
bool result_true = tensor1.allclose(tensor2, 1e-05, 1e-08, true);
|
||||
ASSERT_EQ(result_true, true);
|
||||
|
||||
bool result_false = tensor1.allclose(tensor2, 1e-05, 1e-08, false);
|
||||
ASSERT_EQ(result_false, false);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseMixedNanAndValues) {
|
||||
// Test allclose where some elements match and one is NaN
|
||||
// Use from_blob to avoid triggering fill_ operation which doesn't support NaN
|
||||
const float nan_val = std::numeric_limits<float>::quiet_NaN();
|
||||
float data1[4] = {1.0f, 1.0f, nan_val, 1.0f};
|
||||
float data2[4] = {1.0f, 1.0f, nan_val, 1.0f};
|
||||
at::Tensor tensor1 = at::from_blob(data1, {4}, at::kFloat);
|
||||
at::Tensor tensor2 = at::from_blob(data2, {4}, at::kFloat);
|
||||
|
||||
// NaN-aware comparison: non-NaN elements are equal, NaN treated equal
|
||||
bool result_eq_nan = at::allclose(tensor1, tensor2, 1e-05, 1e-08, true);
|
||||
ASSERT_EQ(result_eq_nan, true);
|
||||
|
||||
// Without equal_nan: NaN elements fail the check
|
||||
bool result_no_eq_nan = at::allclose(tensor1, tensor2, 1e-05, 1e-08, false);
|
||||
ASSERT_EQ(result_no_eq_nan, false);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseDouble) {
|
||||
// Test allclose with double-precision (float64) tensors
|
||||
at::Tensor tensor1 = at::arange(6, at::kDouble).reshape({2, 3});
|
||||
at::Tensor tensor2 = at::arange(6, at::kDouble).reshape({2, 3});
|
||||
|
||||
bool result = at::allclose(tensor1, tensor2);
|
||||
ASSERT_EQ(result, true);
|
||||
|
||||
bool result_member = tensor1.allclose(tensor2, 1e-05, 1e-08, false);
|
||||
ASSERT_EQ(result_member, true);
|
||||
|
||||
// Introduce a small difference
|
||||
tensor2[1][2] = 5.001;
|
||||
bool result_diff = at::allclose(tensor1, tensor2);
|
||||
ASSERT_EQ(result_diff, false);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseDoubleEqualNan) {
|
||||
// Test allclose with double-precision tensors and NaN
|
||||
// Use from_blob to avoid triggering fill_ operation which doesn't support NaN
|
||||
const double nan_val = std::numeric_limits<double>::quiet_NaN();
|
||||
double data1[3] = {nan_val, 0.0, 0.0};
|
||||
at::Tensor tensor1 = at::from_blob(data1, {3}, at::kDouble);
|
||||
at::Tensor tensor2 = tensor1.clone();
|
||||
|
||||
bool result_false = at::allclose(tensor1, tensor2, 1e-05, 1e-08, false);
|
||||
ASSERT_EQ(result_false, false);
|
||||
|
||||
bool result_true = at::allclose(tensor1, tensor2, 1e-05, 1e-08, true);
|
||||
ASSERT_EQ(result_true, true);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseStandaloneWithExplicitParams) {
|
||||
// Test at::allclose() standalone with all explicit parameters
|
||||
at::Tensor tensor1 = at::ones({3}, at::kFloat);
|
||||
at::Tensor tensor2 = at::ones({3}, at::kFloat);
|
||||
|
||||
// All explicit parameters including equal_nan
|
||||
bool result_false_nan = at::allclose(tensor1, tensor2, 1e-05, 1e-08, false);
|
||||
ASSERT_EQ(result_false_nan, true);
|
||||
|
||||
bool result_true_nan = at::allclose(tensor1, tensor2, 1e-05, 1e-08, true);
|
||||
ASSERT_EQ(result_true_nan, true);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseInfinityValues) {
|
||||
// Test allclose with infinity values
|
||||
// Use from_blob to avoid triggering fill_ operation
|
||||
const float inf_val = std::numeric_limits<float>::infinity();
|
||||
float data1[3] = {inf_val, 1.0f, 1.0f};
|
||||
at::Tensor tensor1 = at::from_blob(data1, {3}, at::kFloat);
|
||||
at::Tensor tensor2 = tensor1.clone();
|
||||
|
||||
// Identical infinity values should be close
|
||||
bool result = at::allclose(tensor1, tensor2);
|
||||
ASSERT_EQ(result, true);
|
||||
|
||||
bool result_member = tensor1.allclose(tensor2, 1e-05, 1e-08, false);
|
||||
ASSERT_EQ(result_member, true);
|
||||
|
||||
// Note: PyTorch's allclose considers +inf and -inf as close because:
|
||||
// |inf - (-inf)| = inf <= (atol + rtol * |inf|) = inf
|
||||
// So this test case expectation was wrong - we just verify the behavior
|
||||
float data3[3] = {-inf_val, 1.0f, 1.0f};
|
||||
at::Tensor tensor3 = at::from_blob(data3, {3}, at::kFloat);
|
||||
bool result_diff_inf = at::allclose(tensor1, tensor3);
|
||||
// PyTorch returns true here because inf <= inf is true mathematically
|
||||
ASSERT_EQ(result_diff_inf, true);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseInt32) {
|
||||
// Test allclose with int32 tensors
|
||||
at::Tensor tensor1 = at::arange(6, at::kInt).reshape({2, 3});
|
||||
at::Tensor tensor2 = at::arange(6, at::kInt).reshape({2, 3});
|
||||
|
||||
bool result = at::allclose(tensor1, tensor2);
|
||||
ASSERT_EQ(result, true);
|
||||
|
||||
// Test with different values
|
||||
at::Tensor tensor3 = at::ones({3}, at::kInt);
|
||||
at::Tensor tensor4 = at::ones({3}, at::kInt);
|
||||
tensor4[0] = 2;
|
||||
bool result_diff = at::allclose(tensor3, tensor4);
|
||||
ASSERT_EQ(result_diff, false);
|
||||
|
||||
// Test with custom tolerance
|
||||
bool result_tol = at::allclose(tensor3, tensor4, 1.0, 0.0, false);
|
||||
ASSERT_EQ(result_tol, true);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseInt64) {
|
||||
// Test allclose with int64 (long) tensors
|
||||
at::Tensor tensor1 = at::arange(6, at::kLong).reshape({2, 3});
|
||||
at::Tensor tensor2 = at::arange(6, at::kLong).reshape({2, 3});
|
||||
|
||||
bool result = at::allclose(tensor1, tensor2);
|
||||
ASSERT_EQ(result, true);
|
||||
|
||||
// Test with small difference and custom tolerance
|
||||
at::Tensor tensor3 = at::ones({4}, at::kLong);
|
||||
at::Tensor tensor4 = at::ones({4}, at::kLong);
|
||||
tensor4[0] = 2;
|
||||
bool result_diff = at::allclose(tensor3, tensor4);
|
||||
ASSERT_EQ(result_diff, false);
|
||||
|
||||
// With large tolerance, should pass
|
||||
bool result_tol = at::allclose(tensor3, tensor4, 1.0, 0.0, false);
|
||||
ASSERT_EQ(result_tol, true);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseEmptyTensor) {
|
||||
// Test allclose with empty tensors
|
||||
at::Tensor tensor1 = at::empty({0}, at::kFloat);
|
||||
at::Tensor tensor2 = at::empty({0}, at::kFloat);
|
||||
|
||||
// Empty tensors should be close to each other
|
||||
bool result = at::allclose(tensor1, tensor2);
|
||||
ASSERT_EQ(result, true);
|
||||
|
||||
// Member function
|
||||
bool result_member = tensor1.allclose(tensor2);
|
||||
ASSERT_EQ(result_member, true);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseScalarTensor) {
|
||||
// Test allclose with scalar tensors (0-dimensional)
|
||||
at::Tensor scalar1 = at::tensor(1.0, at::kFloat);
|
||||
at::Tensor scalar2 = at::tensor(1.0, at::kFloat);
|
||||
|
||||
bool result = at::allclose(scalar1, scalar2);
|
||||
ASSERT_EQ(result, true);
|
||||
|
||||
// Different values
|
||||
at::Tensor scalar3 = at::tensor(1.0, at::kFloat);
|
||||
at::Tensor scalar4 = at::tensor(2.0, at::kFloat);
|
||||
bool result_diff = at::allclose(scalar3, scalar4);
|
||||
ASSERT_EQ(result_diff, false);
|
||||
|
||||
// Within tolerance
|
||||
bool result_tol = at::allclose(scalar3, scalar4, 1.0, 0.0, false);
|
||||
ASSERT_EQ(result_tol, true);
|
||||
}
|
||||
|
||||
TEST(TestAllclose, AllcloseWithDifferentRtolAtolOrder) {
|
||||
// Test allclose with parameters in different orders (edge cases)
|
||||
at::Tensor tensor1 = at::zeros({3}, at::kFloat);
|
||||
at::Tensor tensor2 = at::zeros({3}, at::kFloat);
|
||||
tensor2[0] = 0.0001f;
|
||||
|
||||
// Test with zero rtol, small atol
|
||||
bool result1 = at::allclose(tensor1, tensor2, 0.0, 0.0001, false);
|
||||
ASSERT_EQ(result1, true);
|
||||
|
||||
// Test with zero atol, small rtol
|
||||
bool result2 = at::allclose(tensor1, tensor2, 0.0001, 0.0, false);
|
||||
ASSERT_EQ(result2, false); // relative tolerance is relative to values (0.0)
|
||||
|
||||
// Both zero tolerance - exact match required
|
||||
at::Tensor tensor3 = at::ones({2}, at::kFloat);
|
||||
at::Tensor tensor4 = at::ones({2}, at::kFloat);
|
||||
bool result3 = at::allclose(tensor3, tensor4, 0.0, 0.0, false);
|
||||
ASSERT_EQ(result3, true);
|
||||
}
|
||||
|
||||
TEST(TestAbsolute, AbsoluteBasic) {
|
||||
// Test absolute() - alias for abs()
|
||||
at::Tensor tensor = at::tensor({-3.0f, 2.0f, -1.0f});
|
||||
at::Tensor result = tensor.absolute();
|
||||
|
||||
ASSERT_EQ(result.numel(), 3);
|
||||
ASSERT_NEAR(result.data_ptr<float>()[0], 3.0f, 1e-6f);
|
||||
ASSERT_NEAR(result.data_ptr<float>()[1], 2.0f, 1e-6f);
|
||||
ASSERT_NEAR(result.data_ptr<float>()[2], 1.0f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(TestAbsolute, AbsoluteNegativeOnly) {
|
||||
// Test absolute() on all-negative tensor
|
||||
at::Tensor tensor = at::tensor({-5.0f, -10.0f, -0.5f});
|
||||
at::Tensor result = tensor.absolute();
|
||||
|
||||
ASSERT_NEAR(result.data_ptr<float>()[0], 5.0f, 1e-6f);
|
||||
ASSERT_NEAR(result.data_ptr<float>()[1], 10.0f, 1e-6f);
|
||||
ASSERT_NEAR(result.data_ptr<float>()[2], 0.5f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(TestAbsolute, AbsoluteZero) {
|
||||
// Test absolute() on zero tensor
|
||||
at::Tensor tensor = at::zeros({3}, at::kFloat);
|
||||
at::Tensor result = tensor.absolute();
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_NEAR(result.data_ptr<float>()[i], 0.0f, 1e-6f);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TestAbsolute, AbsoluteInPlace) {
|
||||
// Test absolute_() - in-place alias for abs_()
|
||||
at::Tensor tensor = at::tensor({-3.0f, 2.0f, -1.0f});
|
||||
at::Tensor& ref = tensor.absolute_();
|
||||
|
||||
// Should modify tensor in place
|
||||
ASSERT_NEAR(tensor.data_ptr<float>()[0], 3.0f, 1e-6f);
|
||||
ASSERT_NEAR(tensor.data_ptr<float>()[1], 2.0f, 1e-6f);
|
||||
ASSERT_NEAR(tensor.data_ptr<float>()[2], 1.0f, 1e-6f);
|
||||
|
||||
// Return value should be the same tensor
|
||||
ASSERT_EQ(ref.data_ptr<float>(), tensor.data_ptr<float>());
|
||||
}
|
||||
|
||||
TEST(TestAbsolute, AbsoluteInPlaceNegative) {
|
||||
// Test absolute_() on all-negative tensor
|
||||
at::Tensor tensor = at::tensor({-4.0f, -8.0f, -0.25f});
|
||||
tensor.absolute_();
|
||||
|
||||
ASSERT_NEAR(tensor.data_ptr<float>()[0], 4.0f, 1e-6f);
|
||||
ASSERT_NEAR(tensor.data_ptr<float>()[1], 8.0f, 1e-6f);
|
||||
ASSERT_NEAR(tensor.data_ptr<float>()[2], 0.25f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(TestAbsolute, AbsoluteDouble) {
|
||||
// Test absolute() with double precision
|
||||
at::Tensor tensor = at::tensor({-1.5, 2.5, -3.5}, at::kDouble);
|
||||
at::Tensor result = tensor.absolute();
|
||||
|
||||
ASSERT_NEAR(result.data_ptr<double>()[0], 1.5, 1e-10);
|
||||
ASSERT_NEAR(result.data_ptr<double>()[1], 2.5, 1e-10);
|
||||
ASSERT_NEAR(result.data_ptr<double>()[2], 3.5, 1e-10);
|
||||
}
|
||||
|
||||
TEST(TestAbsolute, AbsoluteMatchesAbs) {
|
||||
// Test that absolute() returns same result as abs()
|
||||
at::Tensor tensor = at::tensor({-3.0f, 2.0f, -1.0f, 0.0f});
|
||||
at::Tensor result_absolute = tensor.absolute();
|
||||
at::Tensor result_abs = tensor.abs();
|
||||
|
||||
ASSERT_EQ(result_absolute.numel(), result_abs.numel());
|
||||
for (int i = 0; i < result_absolute.numel(); ++i) {
|
||||
ASSERT_NEAR(result_absolute.data_ptr<float>()[i],
|
||||
result_abs.data_ptr<float>()[i],
|
||||
1e-6f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ======================== any tests ========================
|
||||
|
||||
TEST(TensorAnyTest, AnyNoDim) {
|
||||
at::Tensor t = at::zeros({3, 3}, at::kFloat);
|
||||
t.data_ptr<float>()[4] = 1.0f; // Set one element to non-zero
|
||||
|
||||
at::Tensor result = t.any();
|
||||
ASSERT_EQ(result.numel(), 1);
|
||||
ASSERT_TRUE(result.item<bool>());
|
||||
}
|
||||
|
||||
TEST(TensorAnyTest, AnyNoDimAllZero) {
|
||||
at::Tensor t = at::zeros({3, 3}, at::kFloat);
|
||||
|
||||
at::Tensor result = t.any();
|
||||
ASSERT_EQ(result.numel(), 1);
|
||||
ASSERT_FALSE(result.item<bool>());
|
||||
}
|
||||
|
||||
TEST(TensorAnyTest, AnyWithDim) {
|
||||
at::Tensor t = at::zeros({2, 3}, at::kFloat);
|
||||
t.data_ptr<float>()[0] = 1.0f; // First row has non-zero
|
||||
|
||||
at::Tensor result = t.any(0);
|
||||
ASSERT_EQ(result.numel(), 3);
|
||||
}
|
||||
|
||||
TEST(TensorAnyTest, AnyWithDimKeepdim) {
|
||||
at::Tensor t = at::zeros({2, 3}, at::kFloat);
|
||||
t.data_ptr<float>()[0] = 1.0f;
|
||||
|
||||
at::Tensor result = t.any(0, true);
|
||||
ASSERT_EQ(result.sizes().size(), 2);
|
||||
ASSERT_EQ(result.size(0), 1);
|
||||
ASSERT_EQ(result.size(1), 3);
|
||||
}
|
||||
|
||||
TEST(TensorAnyTest, AnyWithOptionalDim) {
|
||||
at::Tensor t = at::zeros({2, 3}, at::kFloat);
|
||||
t.data_ptr<float>()[3] = 1.0f; // Second row has non-zero
|
||||
|
||||
at::Tensor result = t.any(at::OptionalIntArrayRef(1));
|
||||
ASSERT_EQ(result.numel(), 2);
|
||||
}
|
||||
|
||||
TEST(TensorAnyTest, AnyInt) {
|
||||
at::Tensor t = at::zeros({2, 3}, at::kInt);
|
||||
t.data_ptr<int>()[0] = 1;
|
||||
|
||||
at::Tensor result = t.any(1);
|
||||
ASSERT_EQ(result.numel(), 2);
|
||||
}
|
||||
|
||||
TEST(TensorAnyTest, AnyBool) {
|
||||
at::Tensor t = at::zeros({3, 3}, at::kBool);
|
||||
t.data_ptr<bool>()[4] = true;
|
||||
|
||||
at::Tensor result = t.any();
|
||||
ASSERT_TRUE(result.item<bool>());
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/common/macros.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
COMMON_DECLARE_bool(use_stride_kernel);
|
||||
|
||||
namespace {
|
||||
|
||||
class TensorAsStridedTest : public ::testing::Test {};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(TensorAsStridedTest, AsStridedBasic) {
|
||||
// shape {2,3}, stride {3,1}: [[0,1,2],[3,4,5]]
|
||||
at::Tensor t = at::arange(12, at::kFloat);
|
||||
at::Tensor result = t.as_strided({2, 3}, {3, 1});
|
||||
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({2, 3}));
|
||||
float* data = result.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 0.0f);
|
||||
ASSERT_FLOAT_EQ(data[1], 1.0f);
|
||||
ASSERT_FLOAT_EQ(data[5], 5.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorAsStridedTest, AsStridedWithOffset) {
|
||||
// offset=2: [[2,3,4],[5,6,7]]
|
||||
at::Tensor t = at::arange(12, at::kFloat);
|
||||
at::Tensor result = t.as_strided({2, 3}, {3, 1}, 2);
|
||||
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({2, 3}));
|
||||
float* data = result.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[5], 7.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorAsStridedTest, AsStridedWithDifferentStrides) {
|
||||
// shape {4,2}, stride {2,1}: [[0,1],[2,3],[4,5],[6,7]]
|
||||
at::Tensor t = at::arange(12, at::kFloat);
|
||||
at::Tensor result = t.as_strided({4, 2}, {2, 1});
|
||||
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({4, 2}));
|
||||
float* data = result.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 0.0f);
|
||||
ASSERT_FLOAT_EQ(data[7], 7.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorAsStridedTest, AsStridedInplace) {
|
||||
// inplace: shape {12} -> {2,6}
|
||||
at::Tensor t = at::arange(12, at::kFloat);
|
||||
float* original_data_ptr = t.data_ptr<float>();
|
||||
|
||||
t.as_strided_({2, 6}, {6, 1});
|
||||
|
||||
ASSERT_EQ(t.sizes(), c10::IntArrayRef({2, 6}));
|
||||
ASSERT_EQ(t.data_ptr<float>(), original_data_ptr);
|
||||
|
||||
float* data = t.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 0.0f);
|
||||
ASSERT_FLOAT_EQ(data[11], 11.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorAsStridedTest, AsStridedInplaceWithOffset) {
|
||||
// inplace with offset=1: [[1,2,3],[4,5,6]]
|
||||
at::Tensor t = at::arange(12, at::kFloat);
|
||||
float* original_data_ptr = t.data_ptr<float>();
|
||||
|
||||
t.as_strided_({2, 3}, {3, 1}, 1);
|
||||
|
||||
ASSERT_EQ(t.sizes(), c10::IntArrayRef({2, 3}));
|
||||
ASSERT_NE(t.data_ptr<float>(), original_data_ptr);
|
||||
|
||||
float* data = t.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 1.0f);
|
||||
ASSERT_FLOAT_EQ(data[5], 6.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorAsStridedTest, AsStridedInplaceModifiesView) {
|
||||
// Modify view, verify original is affected
|
||||
at::Tensor t = at::arange(12, at::kFloat);
|
||||
at::Tensor view = t.as_strided({2, 3}, {3, 1});
|
||||
|
||||
view.data_ptr<float>()[0] = 99.0f;
|
||||
ASSERT_FLOAT_EQ(t.data_ptr<float>()[0], 99.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorAsStridedTest, AsStridedScatterBasic) {
|
||||
// Scatter 2x3 99s into t: [[99,99,99],[99,99,99],...]]
|
||||
at::Tensor t = at::arange(12, at::kFloat);
|
||||
at::Tensor src = at::full({2, 3}, 99.0f, at::kFloat);
|
||||
at::Tensor result = t.as_strided_scatter(src, {2, 3}, {3, 1});
|
||||
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({12}));
|
||||
float* data = result.data_ptr<float>();
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
ASSERT_FLOAT_EQ(data[i], 99.0f);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TensorAsStridedTest, AsStridedScatterOriginalUnchanged) {
|
||||
// Scatter returns new tensor, original unchanged
|
||||
at::Tensor t = at::arange(12, at::kFloat);
|
||||
at::Tensor src = at::full({2, 3}, 99.0f, at::kFloat);
|
||||
at::Tensor result = t.as_strided_scatter(src, {2, 3}, {3, 1});
|
||||
|
||||
ASSERT_FLOAT_EQ(t.data_ptr<float>()[0], 0.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorAsStridedTest, AsStridedScatterWithOffset) {
|
||||
// Scatter with offset=2: [[88,88],[88,88]]
|
||||
at::Tensor t = at::arange(12, at::kFloat);
|
||||
at::Tensor src = at::full({2, 2}, 88.0f, at::kFloat);
|
||||
at::Tensor result = t.as_strided_scatter(src, {2, 2}, {2, 1}, 2);
|
||||
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({12}));
|
||||
float* data = result.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[2], 88.0f);
|
||||
ASSERT_FLOAT_EQ(data[5], 88.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorAsStridedTest, AsStridedTranspose) {
|
||||
if (!FLAGS_use_stride_kernel) {
|
||||
return;
|
||||
}
|
||||
// Transpose: shape {2,3} -> {3,2}, stride {1,2}
|
||||
// [[0,1,2],[3,4,5]] -> [[0,3],[1,4],[2,5]]
|
||||
at::Tensor t = at::arange(6, at::kFloat).view({2, 3});
|
||||
at::Tensor result = t.as_strided({3, 2}, {1, 2});
|
||||
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({3, 2}));
|
||||
float* data = result.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 0.0f);
|
||||
ASSERT_FLOAT_EQ(data[5], 5.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorAsStridedTest, AsStridedContiguous) {
|
||||
if (!FLAGS_use_stride_kernel) {
|
||||
return;
|
||||
}
|
||||
at::Tensor t = at::arange(12, at::kFloat);
|
||||
|
||||
// Contiguous: {2,6}, stride {6,1}
|
||||
at::Tensor contig = t.as_strided({2, 6}, {6, 1});
|
||||
ASSERT_TRUE(contig.is_contiguous());
|
||||
|
||||
// Non-contiguous: {3,2}, stride {1,3}
|
||||
at::Tensor non_contig = t.as_strided({3, 2}, {1, 3});
|
||||
ASSERT_FALSE(non_contig.is_contiguous());
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/Layout.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include <ATen/ops/detach.h>
|
||||
#include <ATen/ops/reciprocal.h>
|
||||
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// Test detach member function: tensor.detach()
|
||||
TEST(TestDetach, MemberFunction) {
|
||||
at::Tensor tensor = at::ones({2, 3}, at::kFloat);
|
||||
|
||||
// Detach creates a new tensor that shares data but has no autograd history
|
||||
at::Tensor detached = tensor.detach();
|
||||
|
||||
ASSERT_EQ(detached.sizes(), tensor.sizes());
|
||||
ASSERT_EQ(detached.numel(), tensor.numel());
|
||||
ASSERT_EQ(detached.dtype(), tensor.dtype());
|
||||
|
||||
// Both tensors should share the same data
|
||||
float* original_ptr = tensor.data_ptr<float>();
|
||||
float* detached_ptr = detached.data_ptr<float>();
|
||||
ASSERT_EQ(original_ptr, detached_ptr);
|
||||
}
|
||||
|
||||
// Test detach free function: at::detach(tensor)
|
||||
TEST(TestDetach, FreeFunction) {
|
||||
at::Tensor tensor = at::ones({3, 4}, at::kFloat);
|
||||
at::Tensor detached = at::detach(tensor);
|
||||
|
||||
ASSERT_EQ(detached.sizes(), tensor.sizes());
|
||||
ASSERT_EQ(detached.numel(), tensor.numel());
|
||||
|
||||
// Verify data is shared
|
||||
float* original_ptr = tensor.data_ptr<float>();
|
||||
float* detached_ptr = detached.data_ptr<float>();
|
||||
ASSERT_EQ(original_ptr, detached_ptr);
|
||||
}
|
||||
|
||||
// Test that both methods produce identical results (shared implementation)
|
||||
TEST(TestDetach, SharedImplementation) {
|
||||
at::Tensor tensor = at::ones({2, 3, 4}, at::kFloat);
|
||||
|
||||
// Call both detach methods
|
||||
at::Tensor detached_member = tensor.detach();
|
||||
at::Tensor detached_free = at::detach(tensor);
|
||||
|
||||
// Both should have the same properties
|
||||
ASSERT_EQ(detached_member.sizes(), detached_free.sizes());
|
||||
ASSERT_EQ(detached_member.numel(), detached_free.numel());
|
||||
ASSERT_EQ(detached_member.dtype(), detached_free.dtype());
|
||||
|
||||
// All three should share the same data
|
||||
float* original_ptr = tensor.data_ptr<float>();
|
||||
float* member_ptr = detached_member.data_ptr<float>();
|
||||
float* free_ptr = detached_free.data_ptr<float>();
|
||||
|
||||
ASSERT_EQ(original_ptr, member_ptr);
|
||||
ASSERT_EQ(original_ptr, free_ptr);
|
||||
}
|
||||
|
||||
// Test detach_ in-place member function: tensor.detach_()
|
||||
TEST(TestDetach, InplaceMemberFunction) {
|
||||
at::Tensor tensor = at::ones({2, 3}, at::kFloat);
|
||||
void* original_ptr = tensor.data_ptr();
|
||||
|
||||
// detach_() modifies the tensor in-place
|
||||
at::Tensor& result = tensor.detach_();
|
||||
|
||||
// Should return reference to the same tensor
|
||||
ASSERT_EQ(&result, &tensor);
|
||||
ASSERT_EQ(result.data_ptr(), original_ptr);
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({2, 3}));
|
||||
}
|
||||
|
||||
// Test detach preserves data values
|
||||
TEST(TestDetach, PreservesData) {
|
||||
at::Tensor tensor = at::ones({2, 3}, at::kFloat);
|
||||
float* data = tensor.data_ptr<float>();
|
||||
data[0] = 1.0f;
|
||||
data[1] = 2.0f;
|
||||
data[2] = 3.0f;
|
||||
data[3] = 4.0f;
|
||||
data[4] = 5.0f;
|
||||
data[5] = 6.0f;
|
||||
|
||||
at::Tensor detached = tensor.detach();
|
||||
|
||||
// Verify data is preserved
|
||||
float* detached_data = detached.data_ptr<float>();
|
||||
ASSERT_EQ(detached_data[0], 1.0f);
|
||||
ASSERT_EQ(detached_data[1], 2.0f);
|
||||
ASSERT_EQ(detached_data[2], 3.0f);
|
||||
ASSERT_EQ(detached_data[3], 4.0f);
|
||||
ASSERT_EQ(detached_data[4], 5.0f);
|
||||
ASSERT_EQ(detached_data[5], 6.0f);
|
||||
}
|
||||
|
||||
// Test detach with different dtypes
|
||||
TEST(TestDetach, DifferentDtypes) {
|
||||
// Float32
|
||||
at::Tensor float_tensor = at::ones({2, 3}, at::kFloat);
|
||||
at::Tensor float_detached = float_tensor.detach();
|
||||
ASSERT_EQ(float_detached.dtype(), at::kFloat);
|
||||
ASSERT_EQ(float_detached.sizes(), float_tensor.sizes());
|
||||
|
||||
// Float64
|
||||
at::Tensor double_tensor = at::ones({2, 3}, at::kDouble);
|
||||
at::Tensor double_detached = double_tensor.detach();
|
||||
ASSERT_EQ(double_detached.dtype(), at::kDouble);
|
||||
ASSERT_EQ(double_detached.sizes(), double_tensor.sizes());
|
||||
|
||||
// Int32
|
||||
at::Tensor int_tensor = at::ones({2, 3}, at::kInt);
|
||||
at::Tensor int_detached = int_tensor.detach();
|
||||
ASSERT_EQ(int_detached.dtype(), at::kInt);
|
||||
ASSERT_EQ(int_detached.sizes(), int_tensor.sizes());
|
||||
|
||||
// Int64
|
||||
at::Tensor long_tensor = at::ones({2, 3}, at::kLong);
|
||||
at::Tensor long_detached = long_tensor.detach();
|
||||
ASSERT_EQ(long_detached.dtype(), at::kLong);
|
||||
ASSERT_EQ(long_detached.sizes(), long_tensor.sizes());
|
||||
}
|
||||
|
||||
// Test detach with various shapes
|
||||
TEST(TestDetach, VariousShapes) {
|
||||
// 1D tensor
|
||||
at::Tensor tensor_1d = at::ones({10}, at::kFloat);
|
||||
at::Tensor detached_1d = tensor_1d.detach();
|
||||
ASSERT_EQ(detached_1d.sizes(), c10::IntArrayRef({10}));
|
||||
|
||||
// 2D tensor
|
||||
at::Tensor tensor_2d = at::ones({3, 4}, at::kFloat);
|
||||
at::Tensor detached_2d = tensor_2d.detach();
|
||||
ASSERT_EQ(detached_2d.sizes(), c10::IntArrayRef({3, 4}));
|
||||
|
||||
// 3D tensor
|
||||
at::Tensor tensor_3d = at::ones({2, 3, 4}, at::kFloat);
|
||||
at::Tensor detached_3d = tensor_3d.detach();
|
||||
ASSERT_EQ(detached_3d.sizes(), c10::IntArrayRef({2, 3, 4}));
|
||||
|
||||
// 4D tensor
|
||||
at::Tensor tensor_4d = at::ones({2, 3, 4, 5}, at::kFloat);
|
||||
at::Tensor detached_4d = tensor_4d.detach();
|
||||
ASSERT_EQ(detached_4d.sizes(), c10::IntArrayRef({2, 3, 4, 5}));
|
||||
}
|
||||
|
||||
// Test modifications affect both tensors (shared data)
|
||||
TEST(TestDetach, SharedDataModification) {
|
||||
at::Tensor tensor = at::ones({2, 3}, at::kFloat);
|
||||
at::Tensor detached = tensor.detach();
|
||||
|
||||
// Modify original tensor
|
||||
float* tensor_data = tensor.data_ptr<float>();
|
||||
tensor_data[0] = 99.0f;
|
||||
|
||||
// Check that detached tensor sees the change
|
||||
float* detached_data = detached.data_ptr<float>();
|
||||
ASSERT_EQ(detached_data[0], 99.0f);
|
||||
|
||||
// Modify detached tensor
|
||||
detached_data[1] = 88.0f;
|
||||
|
||||
// Check that original tensor sees the change
|
||||
ASSERT_EQ(tensor_data[1], 88.0f);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Reciprocal Tests
|
||||
// ============================================================================
|
||||
|
||||
// Test reciprocal member function: tensor.reciprocal()
|
||||
TEST(TestReciprocal, MemberFunction) {
|
||||
at::Tensor tensor = at::full({2, 3}, 2.0f, at::kFloat);
|
||||
at::Tensor result = tensor.reciprocal();
|
||||
|
||||
ASSERT_EQ(result.sizes(), tensor.sizes());
|
||||
ASSERT_EQ(result.numel(), tensor.numel());
|
||||
|
||||
// Verify reciprocal calculation: 1/2 = 0.5
|
||||
float* result_data = result.data_ptr<float>();
|
||||
for (int i = 0; i < result.numel(); i++) {
|
||||
ASSERT_NEAR(result_data[i], 0.5f, 1e-6);
|
||||
}
|
||||
}
|
||||
|
||||
// Test reciprocal free function: at::reciprocal(tensor)
|
||||
TEST(TestReciprocal, FreeFunction) {
|
||||
at::Tensor tensor = at::full({3, 4}, 4.0f, at::kFloat);
|
||||
at::Tensor result = at::reciprocal(tensor);
|
||||
|
||||
ASSERT_EQ(result.sizes(), tensor.sizes());
|
||||
ASSERT_EQ(result.numel(), tensor.numel());
|
||||
|
||||
// Verify reciprocal calculation: 1/4 = 0.25
|
||||
float* result_data = result.data_ptr<float>();
|
||||
for (int i = 0; i < result.numel(); i++) {
|
||||
ASSERT_NEAR(result_data[i], 0.25f, 1e-6);
|
||||
}
|
||||
}
|
||||
|
||||
// Test that both methods produce identical results (shared implementation)
|
||||
TEST(TestReciprocal, SharedImplementation) {
|
||||
at::Tensor tensor = at::full({2, 3, 4}, 5.0f, at::kFloat);
|
||||
|
||||
// Call both reciprocal methods
|
||||
at::Tensor result_member = tensor.reciprocal();
|
||||
at::Tensor result_free = at::reciprocal(tensor);
|
||||
|
||||
// Both should have the same shape and values
|
||||
ASSERT_EQ(result_member.sizes(), result_free.sizes());
|
||||
ASSERT_EQ(result_member.numel(), result_free.numel());
|
||||
|
||||
// Verify both produce same values: 1/5 = 0.2
|
||||
float* member_data = result_member.data_ptr<float>();
|
||||
float* free_data = result_free.data_ptr<float>();
|
||||
for (int i = 0; i < result_member.numel(); i++) {
|
||||
ASSERT_NEAR(member_data[i], 0.2f, 1e-6);
|
||||
ASSERT_NEAR(free_data[i], 0.2f, 1e-6);
|
||||
ASSERT_EQ(member_data[i], free_data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Test reciprocal_ in-place member function: tensor.reciprocal_()
|
||||
TEST(TestReciprocal, InplaceMemberFunction) {
|
||||
at::Tensor tensor = at::full({2, 3}, 2.0f, at::kFloat);
|
||||
void* original_ptr = tensor.data_ptr();
|
||||
|
||||
// reciprocal_() modifies the tensor in-place
|
||||
at::Tensor& result = tensor.reciprocal_();
|
||||
|
||||
// Should return reference to the same tensor
|
||||
ASSERT_EQ(&result, &tensor);
|
||||
ASSERT_EQ(result.data_ptr(), original_ptr);
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({2, 3}));
|
||||
|
||||
// Verify reciprocal calculation: 1/2 = 0.5
|
||||
float* result_data = result.data_ptr<float>();
|
||||
for (int i = 0; i < result.numel(); i++) {
|
||||
ASSERT_NEAR(result_data[i], 0.5f, 1e-6);
|
||||
}
|
||||
}
|
||||
|
||||
// Test reciprocal with various input values
|
||||
TEST(TestReciprocal, VariousValues) {
|
||||
at::Tensor tensor = at::ones({5}, at::kFloat);
|
||||
float* data = tensor.data_ptr<float>();
|
||||
data[0] = 1.0f;
|
||||
data[1] = 2.0f;
|
||||
data[2] = 4.0f;
|
||||
data[3] = 0.5f;
|
||||
data[4] = 10.0f;
|
||||
|
||||
at::Tensor result = tensor.reciprocal();
|
||||
float* result_data = result.data_ptr<float>();
|
||||
|
||||
// Verify reciprocals
|
||||
ASSERT_NEAR(result_data[0], 1.0f, 1e-6); // 1/1 = 1
|
||||
ASSERT_NEAR(result_data[1], 0.5f, 1e-6); // 1/2 = 0.5
|
||||
ASSERT_NEAR(result_data[2], 0.25f, 1e-6); // 1/4 = 0.25
|
||||
ASSERT_NEAR(result_data[3], 2.0f, 1e-6); // 1/0.5 = 2
|
||||
ASSERT_NEAR(result_data[4], 0.1f, 1e-6); // 1/10 = 0.1
|
||||
}
|
||||
|
||||
// Test reciprocal with different dtypes
|
||||
TEST(TestReciprocal, DifferentDtypes) {
|
||||
// Float32
|
||||
at::Tensor float_tensor = at::full({2, 3}, 2.0f, at::kFloat);
|
||||
at::Tensor float_result = float_tensor.reciprocal();
|
||||
ASSERT_EQ(float_result.dtype(), at::kFloat);
|
||||
float* float_data = float_result.data_ptr<float>();
|
||||
ASSERT_NEAR(float_data[0], 0.5f, 1e-6);
|
||||
|
||||
// Float64
|
||||
at::Tensor double_tensor = at::full({2, 3}, 2.0, at::kDouble);
|
||||
at::Tensor double_result = double_tensor.reciprocal();
|
||||
ASSERT_EQ(double_result.dtype(), at::kDouble);
|
||||
double* double_data = double_result.data_ptr<double>();
|
||||
ASSERT_NEAR(double_data[0], 0.5, 1e-10);
|
||||
}
|
||||
|
||||
// Test reciprocal with various shapes
|
||||
TEST(TestReciprocal, VariousShapes) {
|
||||
// 1D tensor
|
||||
at::Tensor tensor_1d = at::full({10}, 2.0f, at::kFloat);
|
||||
at::Tensor result_1d = tensor_1d.reciprocal();
|
||||
ASSERT_EQ(result_1d.sizes(), c10::IntArrayRef({10}));
|
||||
ASSERT_NEAR(result_1d.data_ptr<float>()[0], 0.5f, 1e-6);
|
||||
|
||||
// 2D tensor
|
||||
at::Tensor tensor_2d = at::full({3, 4}, 2.0f, at::kFloat);
|
||||
at::Tensor result_2d = tensor_2d.reciprocal();
|
||||
ASSERT_EQ(result_2d.sizes(), c10::IntArrayRef({3, 4}));
|
||||
ASSERT_NEAR(result_2d.data_ptr<float>()[0], 0.5f, 1e-6);
|
||||
|
||||
// 3D tensor
|
||||
at::Tensor tensor_3d = at::full({2, 3, 4}, 2.0f, at::kFloat);
|
||||
at::Tensor result_3d = tensor_3d.reciprocal();
|
||||
ASSERT_EQ(result_3d.sizes(), c10::IntArrayRef({2, 3, 4}));
|
||||
ASSERT_NEAR(result_3d.data_ptr<float>()[0], 0.5f, 1e-6);
|
||||
|
||||
// 4D tensor
|
||||
at::Tensor tensor_4d = at::full({2, 3, 4, 5}, 2.0f, at::kFloat);
|
||||
at::Tensor result_4d = tensor_4d.reciprocal();
|
||||
ASSERT_EQ(result_4d.sizes(), c10::IntArrayRef({2, 3, 4, 5}));
|
||||
ASSERT_NEAR(result_4d.data_ptr<float>()[0], 0.5f, 1e-6);
|
||||
}
|
||||
|
||||
// Test reciprocal_ modifies original tensor
|
||||
TEST(TestReciprocal, InplaceModifiesOriginal) {
|
||||
at::Tensor tensor = at::full({3, 3}, 4.0f, at::kFloat);
|
||||
|
||||
// Store original data pointer
|
||||
void* original_ptr = tensor.data_ptr();
|
||||
|
||||
// Call in-place reciprocal
|
||||
tensor.reciprocal_();
|
||||
|
||||
// Same memory location
|
||||
ASSERT_EQ(tensor.data_ptr(), original_ptr);
|
||||
|
||||
// Values should be modified: 1/4 = 0.25
|
||||
float* data = tensor.data_ptr<float>();
|
||||
for (int i = 0; i < tensor.numel(); i++) {
|
||||
ASSERT_NEAR(data[i], 0.25f, 1e-6);
|
||||
}
|
||||
}
|
||||
|
||||
// Test reciprocal creates new tensor (non-inplace)
|
||||
TEST(TestReciprocal, CreatesNewTensor) {
|
||||
at::Tensor tensor = at::full({2, 3}, 2.0f, at::kFloat);
|
||||
void* original_ptr = tensor.data_ptr();
|
||||
|
||||
// Non-inplace reciprocal should create new tensor
|
||||
at::Tensor result = tensor.reciprocal();
|
||||
|
||||
// Different memory location
|
||||
ASSERT_NE(result.data_ptr(), original_ptr);
|
||||
|
||||
// Original tensor unchanged
|
||||
float* original_data = tensor.data_ptr<float>();
|
||||
ASSERT_NEAR(original_data[0], 2.0f, 1e-6);
|
||||
|
||||
// Result has reciprocal values
|
||||
float* result_data = result.data_ptr<float>();
|
||||
ASSERT_NEAR(result_data[0], 0.5f, 1e-6);
|
||||
}
|
||||
|
||||
// Test reciprocal with negative values
|
||||
TEST(TestReciprocal, NegativeValues) {
|
||||
at::Tensor tensor = at::ones({4}, at::kFloat);
|
||||
float* data = tensor.data_ptr<float>();
|
||||
data[0] = -1.0f;
|
||||
data[1] = -2.0f;
|
||||
data[2] = -0.5f;
|
||||
data[3] = -4.0f;
|
||||
|
||||
at::Tensor result = tensor.reciprocal();
|
||||
float* result_data = result.data_ptr<float>();
|
||||
|
||||
// Verify reciprocals of negative numbers
|
||||
ASSERT_NEAR(result_data[0], -1.0f, 1e-6); // 1/(-1) = -1
|
||||
ASSERT_NEAR(result_data[1], -0.5f, 1e-6); // 1/(-2) = -0.5
|
||||
ASSERT_NEAR(result_data[2], -2.0f, 1e-6); // 1/(-0.5) = -2
|
||||
ASSERT_NEAR(result_data[3], -0.25f, 1e-6); // 1/(-4) = -0.25
|
||||
}
|
||||
@@ -0,0 +1,518 @@
|
||||
// Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/Layout.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#ifdef PADDLE_WITH_XPU
|
||||
#include "paddle/phi/core/platform/device/xpu/xpu_info.h"
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/common/macros.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
COMMON_DECLARE_bool(use_stride_kernel);
|
||||
|
||||
TEST(TensorBaseTest, DataPtrAPIs) {
|
||||
// Test data_ptr() and const_data_ptr() APIs
|
||||
at::Tensor tensor = at::ones({2, 3}, at::kFloat);
|
||||
|
||||
// Test void* data_ptr()
|
||||
void* void_ptr = tensor.data_ptr();
|
||||
ASSERT_NE(void_ptr, nullptr);
|
||||
|
||||
// Test typed data_ptr<T>()
|
||||
float* float_ptr = tensor.data_ptr<float>();
|
||||
ASSERT_NE(float_ptr, nullptr);
|
||||
ASSERT_EQ(float_ptr, void_ptr);
|
||||
|
||||
// Test const_data_ptr()
|
||||
const float* const_float_ptr = tensor.const_data_ptr<float>();
|
||||
ASSERT_NE(const_float_ptr, nullptr);
|
||||
ASSERT_EQ(const_float_ptr, float_ptr);
|
||||
|
||||
// Test mutable_data_ptr()
|
||||
void* mutable_ptr = tensor.mutable_data_ptr();
|
||||
ASSERT_NE(mutable_ptr, nullptr);
|
||||
ASSERT_EQ(mutable_ptr, void_ptr);
|
||||
}
|
||||
TEST(TensorBaseTest, TypeDeviceAPIs) {
|
||||
// Test type and device related APIs
|
||||
at::TensorBase cpu_tensor = at::ones({2, 3}, at::kFloat);
|
||||
|
||||
// Test dtype()/scalar_type()
|
||||
ASSERT_EQ(cpu_tensor.dtype(), at::kFloat);
|
||||
ASSERT_EQ(cpu_tensor.scalar_type(), at::kFloat);
|
||||
|
||||
// Test device()
|
||||
ASSERT_EQ(cpu_tensor.device().type(), at::DeviceType::CPU);
|
||||
|
||||
// Test get_device()
|
||||
ASSERT_EQ(cpu_tensor.get_device(), -1); // CPU device index is -1
|
||||
|
||||
// Test is_cpu()/is_cuda()
|
||||
ASSERT_TRUE(cpu_tensor.is_cpu());
|
||||
ASSERT_FALSE(cpu_tensor.is_cuda());
|
||||
|
||||
// Test options()
|
||||
auto options = cpu_tensor.options();
|
||||
ASSERT_EQ(options.device().type(), at::DeviceType::CPU);
|
||||
}
|
||||
|
||||
TEST(TensorBaseTest, ModifyOperationAPIs) {
|
||||
if (!FLAGS_use_stride_kernel) {
|
||||
return;
|
||||
}
|
||||
// Test modify operation related APIs
|
||||
at::Tensor tensor = at::ones({2, 3}, at::kFloat);
|
||||
|
||||
// Test is_contiguous()
|
||||
ASSERT_TRUE(tensor.is_contiguous());
|
||||
|
||||
// Test is_contiguous_or_false()
|
||||
ASSERT_TRUE(tensor.is_contiguous_or_false());
|
||||
|
||||
// Test fill_()
|
||||
tensor.fill_(2.0);
|
||||
float* data = tensor.data_ptr<float>();
|
||||
for (int i = 0; i < tensor.numel(); i++) {
|
||||
ASSERT_EQ(data[i], 2.0f);
|
||||
}
|
||||
|
||||
// Test zero_()
|
||||
tensor.zero_();
|
||||
for (int i = 0; i < tensor.numel(); i++) {
|
||||
ASSERT_EQ(data[i], 0.0f);
|
||||
}
|
||||
|
||||
// Test copy_()
|
||||
at::Tensor src = at::ones({2, 3}, at::kFloat);
|
||||
tensor.copy_(src);
|
||||
for (int i = 0; i < tensor.numel(); i++) {
|
||||
ASSERT_EQ(data[i], 1.0f);
|
||||
}
|
||||
|
||||
// Test view()
|
||||
at::TensorBase viewed = tensor.view({6});
|
||||
ASSERT_EQ(viewed.sizes(), std::vector<int64_t>{6});
|
||||
ASSERT_EQ(viewed.strides(), std::vector<int64_t>{1});
|
||||
}
|
||||
|
||||
TEST(tensor_clone_test, BasicClone) {
|
||||
at::Tensor a = at::ones({2, 3}, at::kFloat);
|
||||
|
||||
at::Tensor b = a.clone();
|
||||
|
||||
ASSERT_EQ(a.sizes(), b.sizes());
|
||||
ASSERT_EQ(a.dtype(), b.dtype());
|
||||
ASSERT_EQ(a.device().type(), b.device().type());
|
||||
}
|
||||
|
||||
TEST(compat_basic_test, BasicCase) {
|
||||
at::Tensor a =
|
||||
at::ones({2, 3}, at::TensorOptions().dtype(at::kFloat).device(at::kCPU));
|
||||
at::Tensor b = at::full({2, 3}, 2, at::kFloat);
|
||||
double c = 10;
|
||||
|
||||
TORCH_CHECK(a.sizes() == b.sizes());
|
||||
TORCH_CHECK(a.dtype() == at::kFloat);
|
||||
TORCH_CHECK(b.dtype() == at::kFloat);
|
||||
TORCH_INTERNAL_ASSERT(a.device().type() == at::DeviceType::CPU);
|
||||
TORCH_INTERNAL_ASSERT(b.device().type() == at::DeviceType::CPU);
|
||||
at::Tensor a_contig = a.contiguous();
|
||||
at::Tensor b_contig = b.contiguous();
|
||||
at::Tensor result = at::empty(a_contig.sizes(), a_contig.options());
|
||||
const float* a_ptr = a_contig.data_ptr<float>();
|
||||
const float* b_ptr = b_contig.data_ptr<float>();
|
||||
float* result_ptr = result.data_ptr<float>();
|
||||
for (int64_t i = 0; i < a_contig.numel(); i++) {
|
||||
result_ptr[i] = a_ptr[i] * b_ptr[i] + c;
|
||||
}
|
||||
// Show result
|
||||
for (int64_t i = 0; i < a_contig.numel(); i++) {
|
||||
std::cout << "Result[" << i << "] = " << a_ptr[i] * b_ptr[i] + c
|
||||
<< std::endl;
|
||||
ASSERT_EQ(result_ptr[i], 12);
|
||||
}
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
// for test empty_cuda:
|
||||
at::Tensor bb =
|
||||
at::detail::empty_cuda(12, at::kFloat, at::kCUDA, std::nullopt);
|
||||
|
||||
// for test sizoof(at::Half):
|
||||
std::cout << sizeof(at::Half) << std::endl;
|
||||
at::Tensor num_non_exiting_ctas = at::empty(
|
||||
{}, at::TensorOptions().device(a.device()).dtype(at::ScalarType::Int));
|
||||
}
|
||||
{
|
||||
std::vector<int64_t> shape = {2, 3, 4, 5};
|
||||
size_t size_ =
|
||||
c10::elementSize(at::ScalarType::Float) * c10::multiply_integers(shape);
|
||||
std::cout << "multiply_integers out: " << size_ << std::endl;
|
||||
}
|
||||
{
|
||||
std::vector<int> shape = {2, 3, 4, 5};
|
||||
size_t size_ =
|
||||
c10::elementSize(at::ScalarType::Float) * c10::sum_integers(shape);
|
||||
std::cout << "sum_integers out: " << size_ << std::endl;
|
||||
}
|
||||
{
|
||||
auto stream = at::cuda::getCurrentCUDAStream();
|
||||
std::cout << "stream num: " << stream.stream() << std::endl;
|
||||
at::cuda::stream_synchronize(stream);
|
||||
at::Tensor bb =
|
||||
at::detail::empty_cuda(12, at::kFloat, at::kCUDA, std::nullopt);
|
||||
}
|
||||
{
|
||||
at::Tensor a = at::ones(
|
||||
{2, 3}, at::TensorOptions().dtype(at::kFloat).device(at::kCUDA));
|
||||
std::cout << "a.device() is at::kCUDA: " << (a.device().type() == at::kCUDA)
|
||||
<< std::endl;
|
||||
const c10::cuda::CUDAGuard device_guard(a.device());
|
||||
std::cout << "device_guard is at::kCUDA: "
|
||||
<< (device_guard.current_device().type() == at::kCUDA)
|
||||
<< std::endl;
|
||||
const c10::cuda::OptionalCUDAGuard device_guard_opt(a.device());
|
||||
std::cout << "device_guard is at::kCUDA: "
|
||||
<< (device_guard_opt.current_device().value().type() == at::kCUDA)
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
{
|
||||
std::cout << "num_tokens_per_rank.device() is at::kCUDA: " << std::endl;
|
||||
// for test empty:
|
||||
auto num_tokens_per_rank =
|
||||
torch::empty({3},
|
||||
dtype(torch::kInt32).device(torch::kCUDA),
|
||||
c10::MemoryFormat::Contiguous);
|
||||
std::cout << "num_tokens_per_rank.device() is at::kCUDA: "
|
||||
<< (num_tokens_per_rank.device().type() == at::kCUDA)
|
||||
<< std::endl;
|
||||
}
|
||||
{
|
||||
auto num_tokens_per_rank = torch::empty(
|
||||
{3}, dtype(torch::kInt32).device(torch::kCUDA), std::nullopt);
|
||||
std::cout << "num_tokens_per_rank.device() is at::kCUDA: "
|
||||
<< (num_tokens_per_rank.device().type() == at::kCUDA)
|
||||
<< std::endl;
|
||||
}
|
||||
#endif
|
||||
{
|
||||
int a = 10, b = 20, c = 30;
|
||||
int* p[] = {&a, &b, &c}; // int* array[3]
|
||||
int** pp = p;
|
||||
|
||||
torch::Tensor t =
|
||||
torch::from_blob(pp, {3}, torch::TensorOptions().dtype(torch::kInt64));
|
||||
|
||||
// Get original int**
|
||||
int** restored = reinterpret_cast<int**>(t.data_ptr<int64_t>());
|
||||
std::cout << *restored[0] << ", " << *restored[1] << ", " << *restored[2]
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TestDevice, DeviceAPIsOnCUDA) {
|
||||
// Test device related APIs on CUDA if available
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
if (at::cuda::is_available()) {
|
||||
at::TensorBase cuda_tensor = at::ones(
|
||||
{2, 3}, c10::TensorOptions().dtype(at::kFloat).device(at::kCUDA));
|
||||
|
||||
// Test device()
|
||||
ASSERT_EQ(cuda_tensor.device().type(), at::DeviceType::CUDA);
|
||||
|
||||
// Test get_device()
|
||||
ASSERT_EQ(cuda_tensor.get_device(), 0); // Assuming single GPU with index 0
|
||||
|
||||
// Test is_cpu()/is_cuda()
|
||||
ASSERT_FALSE(cuda_tensor.is_cpu());
|
||||
ASSERT_TRUE(cuda_tensor.is_cuda());
|
||||
|
||||
// Test options()
|
||||
auto options = cuda_tensor.options();
|
||||
ASSERT_EQ(options.device().type(), at::DeviceType::CUDA);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(TestDevice, DeviceAPIsOnCPU) {
|
||||
// Test device related APIs on CPU
|
||||
at::TensorBase cpu_tensor = at::ones({2, 3}, at::kFloat);
|
||||
|
||||
// Test device()
|
||||
ASSERT_EQ(cpu_tensor.device().type(), at::DeviceType::CPU);
|
||||
|
||||
// Test is_cpu()/is_cuda()
|
||||
ASSERT_TRUE(cpu_tensor.is_cpu());
|
||||
ASSERT_FALSE(cpu_tensor.is_cuda());
|
||||
|
||||
// Test options()
|
||||
auto options = cpu_tensor.options();
|
||||
ASSERT_EQ(options.device().type(), at::DeviceType::CPU);
|
||||
}
|
||||
|
||||
TEST(TestTranspose, TransposeAPI) {
|
||||
at::Tensor a = at::ones({4, 5, 6, 7, 8}, at::kFloat);
|
||||
at::Tensor b = a.transpose(2, 3);
|
||||
ASSERT_EQ(b.sizes(), c10::IntArrayRef({4, 5, 7, 6, 8}));
|
||||
}
|
||||
|
||||
TEST(TestSize, SizeNegativeIndex) {
|
||||
at::Tensor tensor = at::ones({2, 3, 4, 5}, at::kFloat);
|
||||
ASSERT_EQ(tensor.size(-1), 5);
|
||||
ASSERT_EQ(tensor.size(-2), 4);
|
||||
ASSERT_EQ(tensor.size(-3), 3);
|
||||
ASSERT_EQ(tensor.size(-4), 2);
|
||||
}
|
||||
|
||||
TEST(TestTensorOperators, SubScriptOperator) {
|
||||
const int M = 3;
|
||||
const int N = 4;
|
||||
const int K = 5;
|
||||
|
||||
at::Tensor tensor = at::arange(M * N * K, at::kFloat).reshape({M, N, K});
|
||||
|
||||
// Check tensor[0]
|
||||
at::Tensor tensor_0 = tensor[0];
|
||||
for (int i = 0; i < N * K; ++i) {
|
||||
ASSERT_EQ(tensor_0.data_ptr<float>()[i], static_cast<float>(i));
|
||||
}
|
||||
|
||||
// Check tensor[1]
|
||||
at::Tensor tensor_1 = tensor[1];
|
||||
int offset = N * K;
|
||||
for (int i = 0; i < N * K; ++i) {
|
||||
ASSERT_EQ(tensor_1.data_ptr<float>()[i], static_cast<float>(i + offset));
|
||||
}
|
||||
|
||||
// Check tensor[2]
|
||||
at::Tensor tensor_2 = tensor[2];
|
||||
offset = 2 * N * K;
|
||||
for (int i = 0; i < N * K; ++i) {
|
||||
ASSERT_EQ(tensor_2.data_ptr<float>()[i], static_cast<float>(i + offset));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TensorBaseTest, LayoutAPI) {
|
||||
// Test layout() API for strided tensors
|
||||
at::TensorBase tensor = at::ones({2, 3}, at::kFloat);
|
||||
|
||||
// Default tensor should have Strided layout
|
||||
ASSERT_EQ(tensor.layout(), c10::kStrided);
|
||||
|
||||
// Test layout output stream operator
|
||||
std::ostringstream oss;
|
||||
oss << tensor.layout();
|
||||
ASSERT_EQ(oss.str(), "Strided");
|
||||
}
|
||||
|
||||
TEST(TensorBaseTest, ResetAPI) {
|
||||
// Test reset() API
|
||||
at::TensorBase tensor = at::ones({2, 3}, at::kFloat);
|
||||
|
||||
// Verify tensor is defined before reset
|
||||
ASSERT_TRUE(tensor.defined());
|
||||
ASSERT_NE(tensor.data_ptr(), nullptr);
|
||||
ASSERT_EQ(tensor.numel(), 6);
|
||||
|
||||
// Call reset()
|
||||
tensor.reset();
|
||||
|
||||
// Verify tensor is no longer defined after reset
|
||||
ASSERT_FALSE(tensor.defined());
|
||||
|
||||
// Test reset on already undefined tensor (should not crash)
|
||||
at::TensorBase empty_tensor;
|
||||
ASSERT_FALSE(empty_tensor.defined());
|
||||
empty_tensor.reset();
|
||||
ASSERT_FALSE(empty_tensor.defined());
|
||||
|
||||
// Test reset on tensor after assignment
|
||||
at::TensorBase tensor2 = at::ones({3, 4}, at::kDouble);
|
||||
at::TensorBase tensor3 = tensor2;
|
||||
ASSERT_TRUE(tensor2.defined());
|
||||
ASSERT_TRUE(tensor3.defined());
|
||||
|
||||
tensor2.reset();
|
||||
ASSERT_FALSE(tensor2.defined());
|
||||
ASSERT_TRUE(tensor3.defined()); // tensor3 should still be valid
|
||||
}
|
||||
|
||||
TEST(TensorBaseTest, IsNonOverlappingAndDenseAPI) {
|
||||
if (!FLAGS_use_stride_kernel) {
|
||||
return;
|
||||
}
|
||||
// Test is_non_overlapping_and_dense() API
|
||||
|
||||
// Case 1: Contiguous tensor - should be non-overlapping and dense
|
||||
at::TensorBase contiguous_tensor = at::ones({2, 3, 4}, at::kFloat);
|
||||
ASSERT_TRUE(contiguous_tensor.is_contiguous());
|
||||
ASSERT_TRUE(contiguous_tensor.is_non_overlapping_and_dense());
|
||||
|
||||
// Case 2: Scalar tensor (numel == 1) - should be non-overlapping and dense
|
||||
at::TensorBase scalar_tensor = at::ones({}, at::kFloat);
|
||||
ASSERT_EQ(scalar_tensor.numel(), 1);
|
||||
ASSERT_TRUE(scalar_tensor.is_non_overlapping_and_dense());
|
||||
|
||||
// Case 3: Single element tensor - should be non-overlapping and dense
|
||||
at::TensorBase single_element = at::ones({1, 1, 1}, at::kFloat);
|
||||
ASSERT_EQ(single_element.numel(), 1);
|
||||
ASSERT_TRUE(single_element.is_non_overlapping_and_dense());
|
||||
|
||||
// Case 4: Transposed tensor - non-contiguous but still non-overlapping and
|
||||
// dense
|
||||
at::Tensor original = at::ones({3, 4}, at::kFloat);
|
||||
at::Tensor transposed = original.transpose(0, 1);
|
||||
ASSERT_FALSE(transposed.is_contiguous());
|
||||
ASSERT_TRUE(transposed.is_non_overlapping_and_dense());
|
||||
|
||||
// Case 5: Multi-dimensional transpose - still non-overlapping and dense
|
||||
at::Tensor tensor_3d = at::ones({2, 3, 4}, at::kFloat);
|
||||
at::Tensor transposed_3d = tensor_3d.transpose(0, 2);
|
||||
ASSERT_FALSE(transposed_3d.is_contiguous());
|
||||
ASSERT_TRUE(transposed_3d.is_non_overlapping_and_dense());
|
||||
|
||||
// Case 6: Tensor with size-1 dimensions
|
||||
at::TensorBase size_one_dims = at::ones({1, 3, 1, 4}, at::kFloat);
|
||||
ASSERT_TRUE(size_one_dims.is_non_overlapping_and_dense());
|
||||
|
||||
// Case 7: Empty tensor (numel == 0) - should be non-overlapping and dense
|
||||
at::TensorBase empty_tensor = at::ones({0, 3, 4}, at::kFloat);
|
||||
ASSERT_EQ(empty_tensor.numel(), 0);
|
||||
ASSERT_TRUE(empty_tensor.is_non_overlapping_and_dense());
|
||||
|
||||
// Case 8: Permuted tensor - still non-overlapping and dense
|
||||
at::Tensor tensor_4d = at::ones({2, 3, 4, 5}, at::kFloat);
|
||||
at::Tensor permuted = tensor_4d.permute({3, 1, 2, 0});
|
||||
ASSERT_FALSE(permuted.is_contiguous());
|
||||
ASSERT_TRUE(permuted.is_non_overlapping_and_dense());
|
||||
}
|
||||
|
||||
TEST(TensorBaseTest, UndefinedAndNonDenseBranchCoverage) {
|
||||
if (!FLAGS_use_stride_kernel) {
|
||||
return;
|
||||
}
|
||||
at::TensorBase undefined;
|
||||
ASSERT_EQ(undefined.toString(), std::string("UndefinedType"));
|
||||
ASSERT_EQ(undefined.data_ptr(), nullptr);
|
||||
ASSERT_FALSE(undefined.has_names());
|
||||
|
||||
at::Tensor non_dense = at::arange(6, at::TensorOptions().dtype(at::kFloat))
|
||||
.as_strided({2, 2}, {4, 1});
|
||||
ASSERT_FALSE(non_dense.is_non_overlapping_and_dense());
|
||||
}
|
||||
|
||||
TEST(TensorBodyTest, ToBackendUnsupportedBranch) {
|
||||
at::Tensor t = at::ones({1}, at::kFloat);
|
||||
ASSERT_THROW(t.toBackend(static_cast<c10::Backend>(-1)), ::std::exception);
|
||||
}
|
||||
|
||||
TEST(TensorBodyTest, ToBackendCpuBranchCoverage) {
|
||||
at::Tensor t = at::ones({1}, at::kFloat);
|
||||
at::Tensor cpu_t = t.toBackend(c10::Backend::CPU);
|
||||
|
||||
ASSERT_EQ(cpu_t.device().type(), c10::DeviceType::CPU);
|
||||
ASSERT_TRUE(cpu_t.equal(t));
|
||||
}
|
||||
|
||||
TEST(TensorBodyTest, ToBackendCudaBranchCoverage) {
|
||||
at::Tensor t = at::ones({1}, at::kFloat);
|
||||
|
||||
try {
|
||||
at::Tensor cuda_t = t.toBackend(c10::Backend::CUDA);
|
||||
ASSERT_EQ(cuda_t.device().type(), c10::DeviceType::CUDA);
|
||||
} catch (const std::exception&) {
|
||||
SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TensorBodyTest, ToBackendXpuBranchCoverage) {
|
||||
at::Tensor t = at::ones({1}, at::kFloat);
|
||||
|
||||
try {
|
||||
at::Tensor xpu_t = t.toBackend(c10::Backend::XPU);
|
||||
ASSERT_EQ(xpu_t.device().type(), c10::DeviceType::XPU);
|
||||
} catch (const std::exception&) {
|
||||
SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TensorBodyTest, ToBackendIpuBranchCoverage) {
|
||||
at::Tensor t = at::ones({1}, at::kFloat);
|
||||
|
||||
try {
|
||||
at::Tensor ipu_t = t.toBackend(c10::Backend::IPU);
|
||||
ASSERT_EQ(ipu_t.device().type(), c10::DeviceType::IPU);
|
||||
} catch (const std::exception&) {
|
||||
SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef PADDLE_WITH_XPU
|
||||
TEST(TensorBodyTest, ToBackendXpuUsesCurrentDevice) {
|
||||
if (paddle::platform::GetXPUDeviceCount() < 2) {
|
||||
return;
|
||||
}
|
||||
paddle::platform::XPUDeviceGuard guard(1);
|
||||
at::Tensor t = at::ones({1}, at::kFloat);
|
||||
at::Tensor xpu_t = t.toBackend(c10::Backend::XPU);
|
||||
|
||||
ASSERT_EQ(xpu_t.device().type(), c10::DeviceType::XPU);
|
||||
ASSERT_EQ(xpu_t.device().index(), 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(TensorBodyTest, MetaUnsupportedBranch) {
|
||||
at::Tensor t = at::ones({1}, at::kFloat);
|
||||
ASSERT_THROW((void)t.meta(), ::std::exception);
|
||||
}
|
||||
|
||||
TEST(TensorBaseTest, ToDeviceAndMemoryFormatUnsupportedBranches) {
|
||||
at::TensorBase base = at::ones({2, 2}, at::kFloat);
|
||||
|
||||
ASSERT_THROW(
|
||||
(void)base.to(at::TensorOptions().device(c10::Device(c10::kCPU))),
|
||||
::std::exception);
|
||||
|
||||
ASSERT_THROW((void)base.to(at::TensorOptions().dtype(at::kFloat),
|
||||
false,
|
||||
false,
|
||||
at::MemoryFormat::Contiguous),
|
||||
::std::exception);
|
||||
}
|
||||
|
||||
TEST(TensorBaseTest, ToDtypeCastsWhenSupported) {
|
||||
at::TensorBase base = at::ones({2, 2}, at::kFloat);
|
||||
at::TensorBase casted = base.to(at::TensorOptions().dtype(at::kDouble));
|
||||
ASSERT_EQ(casted.scalar_type(), at::kDouble);
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ======================== chunk tests ========================
|
||||
|
||||
TEST(TensorChunkTest, ChunkBasic) {
|
||||
at::Tensor t = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
|
||||
std::vector<at::Tensor> chunks = t.chunk(3, 0);
|
||||
|
||||
ASSERT_EQ(chunks.size(), 3);
|
||||
ASSERT_EQ(chunks[0].size(0), 1);
|
||||
ASSERT_EQ(chunks[1].size(0), 1);
|
||||
ASSERT_EQ(chunks[2].size(0), 1);
|
||||
}
|
||||
|
||||
TEST(TensorChunkTest, ChunkDim1) {
|
||||
at::Tensor t = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
|
||||
std::vector<at::Tensor> chunks = t.chunk(2, 1);
|
||||
|
||||
ASSERT_EQ(chunks.size(), 2);
|
||||
ASSERT_EQ(chunks[0].size(1), 2);
|
||||
ASSERT_EQ(chunks[1].size(1), 2);
|
||||
}
|
||||
|
||||
TEST(TensorChunkTest, ChunkUneven) {
|
||||
at::Tensor t = at::arange(10, at::kFloat).reshape({2, 5});
|
||||
|
||||
std::vector<at::Tensor> chunks = t.chunk(3, 1);
|
||||
|
||||
ASSERT_EQ(chunks.size(), 3);
|
||||
}
|
||||
|
||||
TEST(TensorChunkTest, ChunkMoreChunksThanSize) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
std::vector<at::Tensor> chunks = t.chunk(5, 0);
|
||||
|
||||
// PyTorch returns at most dim_size non-empty chunks when chunks > dim_size
|
||||
ASSERT_EQ(chunks.size(), 2);
|
||||
}
|
||||
|
||||
TEST(TensorChunkTest, ChunkDefaultDim) {
|
||||
at::Tensor t = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
|
||||
std::vector<at::Tensor> chunks = t.chunk(3);
|
||||
|
||||
ASSERT_EQ(chunks.size(), 3);
|
||||
ASSERT_EQ(chunks[0].size(0), 1);
|
||||
}
|
||||
|
||||
TEST(TensorChunkTest, ChunkIntType) {
|
||||
at::Tensor t = at::arange(12, at::kInt).reshape({3, 4});
|
||||
|
||||
std::vector<at::Tensor> chunks = t.chunk(3, 0);
|
||||
|
||||
ASSERT_EQ(chunks.size(), 3);
|
||||
ASSERT_EQ(chunks[0].dtype(), at::kInt);
|
||||
}
|
||||
|
||||
TEST(TensorChunkTest, ChunkZeroDim) {
|
||||
at::Tensor t = at::zeros({0, 4}, at::kFloat);
|
||||
|
||||
std::vector<at::Tensor> chunks = t.chunk(2, 0);
|
||||
|
||||
// PyTorch returns 'chunks' number of empty tensors when dim_size == 0
|
||||
ASSERT_EQ(chunks.size(), 2);
|
||||
ASSERT_EQ(chunks[0].size(0), 0);
|
||||
ASSERT_EQ(chunks[1].size(0), 0);
|
||||
}
|
||||
|
||||
TEST(TensorChunkTest, ChunkNegativeDim) {
|
||||
at::Tensor t = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
|
||||
// chunk(-1) should be equivalent to chunk(rank - 1) = chunk(1)
|
||||
std::vector<at::Tensor> chunks_neg = t.chunk(2, -1);
|
||||
std::vector<at::Tensor> chunks_pos = t.chunk(2, 1);
|
||||
|
||||
ASSERT_EQ(chunks_neg.size(), chunks_pos.size());
|
||||
for (size_t i = 0; i < chunks_neg.size(); ++i) {
|
||||
ASSERT_EQ(chunks_neg[i].sizes(), chunks_pos[i].sizes());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TensorChunkTest, ChunkOutOfRangeDim) {
|
||||
at::Tensor t = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
|
||||
ASSERT_THROW(t.chunk(2, 2), std::exception); // dim >= rank
|
||||
ASSERT_THROW(t.chunk(2, -3), std::exception); // dim < -rank
|
||||
}
|
||||
|
||||
TEST(TensorChunkTest, ChunkZeroRankTensor) {
|
||||
at::Tensor t = at::empty({}, at::kFloat); // 0-dim scalar tensor
|
||||
|
||||
ASSERT_THROW(t.chunk(2, 0), std::exception);
|
||||
}
|
||||
|
||||
TEST(TensorChunkTest, ChunkZeroChunks) {
|
||||
at::Tensor t = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
|
||||
ASSERT_THROW(t.chunk(0, 0), std::exception);
|
||||
ASSERT_THROW(t.chunk(-1, 0), std::exception);
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/cpp/prim/init_env_utils.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class TensorClampTest : public ::testing::Test {
|
||||
protected:
|
||||
static void SetUpTestSuite() { paddle::prim::InitTensorOperants(); }
|
||||
};
|
||||
|
||||
class TensorOperatorIndexTest : public ::testing::Test {
|
||||
protected:
|
||||
static void SetUpTestSuite() { paddle::prim::InitTensorOperants(); }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(TensorClampTest, ClampWithScalar) {
|
||||
// Create tensor with values [0, 1, 2, 3, 4, 5]
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
at::Tensor result = t.clamp(at::Scalar(1.0), at::Scalar(4.0));
|
||||
|
||||
float* data = result.data_ptr<float>();
|
||||
// Expected: [1, 1, 2, 3, 4, 4]
|
||||
ASSERT_FLOAT_EQ(data[0], 1.0f);
|
||||
ASSERT_FLOAT_EQ(data[1], 1.0f);
|
||||
ASSERT_FLOAT_EQ(data[2], 2.0f);
|
||||
ASSERT_FLOAT_EQ(data[3], 3.0f);
|
||||
ASSERT_FLOAT_EQ(data[4], 4.0f);
|
||||
ASSERT_FLOAT_EQ(data[5], 4.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampWithTensor) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
at::Tensor min_t = at::full({2, 3}, 1.0f, at::kFloat);
|
||||
at::Tensor max_t = at::full({2, 3}, 4.0f, at::kFloat);
|
||||
|
||||
at::Tensor result = t.clamp(::std::optional<at::Tensor>(min_t),
|
||||
::std::optional<at::Tensor>(max_t));
|
||||
|
||||
float* data = result.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 1.0f);
|
||||
ASSERT_FLOAT_EQ(data[5], 4.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampInplaceScalar) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
t.clamp_(at::Scalar(2.0), at::Scalar(3.0));
|
||||
|
||||
float* data = t.data_ptr<float>();
|
||||
// Expected: [2, 2, 2, 3, 3, 3]
|
||||
ASSERT_FLOAT_EQ(data[0], 2.0f);
|
||||
ASSERT_FLOAT_EQ(data[1], 2.0f);
|
||||
ASSERT_FLOAT_EQ(data[2], 2.0f);
|
||||
ASSERT_FLOAT_EQ(data[3], 3.0f);
|
||||
ASSERT_FLOAT_EQ(data[4], 3.0f);
|
||||
ASSERT_FLOAT_EQ(data[5], 3.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampInplaceTensor) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
at::Tensor min_t = at::full({2, 3}, 1.0f, at::kFloat);
|
||||
at::Tensor max_t = at::full({2, 3}, 4.0f, at::kFloat);
|
||||
|
||||
t.clamp_(::std::optional<at::Tensor>(min_t),
|
||||
::std::optional<at::Tensor>(max_t));
|
||||
|
||||
float* data = t.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 1.0f);
|
||||
ASSERT_FLOAT_EQ(data[5], 4.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampMaxScalar) {
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
at::Tensor result = t.clamp_max(at::Scalar(3.0));
|
||||
|
||||
float* data = result.data_ptr<float>();
|
||||
// Expected: [0, 1, 2, 3, 3, 3]
|
||||
ASSERT_FLOAT_EQ(data[4], 3.0f);
|
||||
ASSERT_FLOAT_EQ(data[5], 3.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampMaxTensor) {
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
at::Tensor max_t = at::full({6}, 3.0f, at::kFloat);
|
||||
at::Tensor result = t.clamp_max(max_t);
|
||||
|
||||
float* data = result.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[4], 3.0f);
|
||||
ASSERT_FLOAT_EQ(data[5], 3.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampMaxInplaceScalar) {
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
t.clamp_max_(at::Scalar(3.0));
|
||||
|
||||
float* data = t.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[4], 3.0f);
|
||||
ASSERT_FLOAT_EQ(data[5], 3.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampMaxInplaceTensor) {
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
at::Tensor max_t = at::full({6}, 3.0f, at::kFloat);
|
||||
t.clamp_max_(max_t);
|
||||
|
||||
float* data = t.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[4], 3.0f);
|
||||
ASSERT_FLOAT_EQ(data[5], 3.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampMinScalar) {
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
at::Tensor result = t.clamp_min(at::Scalar(2.0));
|
||||
|
||||
float* data = result.data_ptr<float>();
|
||||
// Expected: [2, 2, 2, 3, 4, 5]
|
||||
ASSERT_FLOAT_EQ(data[0], 2.0f);
|
||||
ASSERT_FLOAT_EQ(data[1], 2.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampMinTensor) {
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
at::Tensor min_t = at::full({6}, 2.0f, at::kFloat);
|
||||
at::Tensor result = t.clamp_min(min_t);
|
||||
|
||||
float* data = result.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 2.0f);
|
||||
ASSERT_FLOAT_EQ(data[1], 2.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampMinInplaceScalar) {
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
t.clamp_min_(at::Scalar(2.0));
|
||||
|
||||
float* data = t.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 2.0f);
|
||||
ASSERT_FLOAT_EQ(data[1], 2.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampMinInplaceTensor) {
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
at::Tensor min_t = at::full({6}, 2.0f, at::kFloat);
|
||||
t.clamp_min_(min_t);
|
||||
|
||||
float* data = t.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 2.0f);
|
||||
ASSERT_FLOAT_EQ(data[1], 2.0f);
|
||||
}
|
||||
|
||||
// ======================== operator[] tests ========================
|
||||
|
||||
TEST_F(TensorOperatorIndexTest, OperatorIndexBasic) {
|
||||
// Create tensor [[0,1,2],[3,4,5]]
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
// Test operator[](int64_t index) - returns first row
|
||||
at::Tensor result0 = t[0];
|
||||
ASSERT_EQ(result0.numel(), 3); // First row has 3 elements [0,1,2]
|
||||
ASSERT_FLOAT_EQ(result0.data_ptr<float>()[0],
|
||||
0.0f); // First element of the row
|
||||
|
||||
at::Tensor result1 = t[1];
|
||||
ASSERT_EQ(result1.numel(), 3); // Second row has 3 elements [3,4,5]
|
||||
ASSERT_FLOAT_EQ(result1.data_ptr<float>()[0],
|
||||
3.0f); // First element of the row
|
||||
}
|
||||
|
||||
TEST_F(TensorOperatorIndexTest, OperatorIndexOutOfBounds) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
// Test out of bounds index - should throw an exception
|
||||
// The test expects the code to handle this gracefully
|
||||
bool threw_exception = false;
|
||||
try {
|
||||
at::Tensor result = t[5];
|
||||
(void)result;
|
||||
} catch (...) {
|
||||
threw_exception = true;
|
||||
}
|
||||
// Note: Depending on implementation, this may or may not throw
|
||||
// We accept either behavior (return empty/invalid tensor or throw)
|
||||
(void)threw_exception; // Silence unused variable warning
|
||||
}
|
||||
|
||||
// ======================= Additional clamp edge case tests
|
||||
// =======================
|
||||
|
||||
TEST_F(TensorClampTest, ClampNoMinMax) {
|
||||
// Test clamp with no min and max (should be identity)
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
at::Tensor result = t.clamp(::std::optional<at::Scalar>(::std::nullopt),
|
||||
::std::optional<at::Scalar>(::std::nullopt));
|
||||
|
||||
ASSERT_EQ(result.numel(), 6);
|
||||
float* data = result.data_ptr<float>();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
ASSERT_FLOAT_EQ(data[i], static_cast<float>(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampOnlyMin) {
|
||||
// Test clamp with only min value
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
at::Tensor result =
|
||||
t.clamp(at::Scalar(2.5), ::std::optional<at::Scalar>(::std::nullopt));
|
||||
|
||||
float* data = result.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 2.5f); // 0 < 2.5 -> 2.5
|
||||
ASSERT_FLOAT_EQ(data[1], 2.5f); // 1 < 2.5 -> 2.5
|
||||
ASSERT_FLOAT_EQ(data[2], 2.5f); // 2 < 2.5 -> 2.5
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampOnlyMax) {
|
||||
// Test clamp with only max value
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
at::Tensor result =
|
||||
t.clamp(::std::optional<at::Scalar>(::std::nullopt), at::Scalar(2.5));
|
||||
|
||||
float* data = result.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 0.0f);
|
||||
ASSERT_FLOAT_EQ(data[1], 1.0f);
|
||||
ASSERT_FLOAT_EQ(data[2], 2.0f);
|
||||
ASSERT_FLOAT_EQ(data[3], 2.5f);
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampMinOnlyTensor) {
|
||||
// Test clamp_min with Tensor
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
at::Tensor min_t = at::full({6}, 2.5f, at::kFloat);
|
||||
at::Tensor result = t.clamp_min(min_t);
|
||||
|
||||
float* data = result.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 2.5f); // 0 < 2.5 -> 2.5
|
||||
ASSERT_FLOAT_EQ(data[1], 2.5f); // 1 < 2.5 -> 2.5
|
||||
ASSERT_FLOAT_EQ(data[2], 2.5f); // 2 < 2.5 -> 2.5
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampMaxOnlyTensor) {
|
||||
// Test clamp_max with Tensor
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
at::Tensor max_t = at::full({6}, 2.5f, at::kFloat);
|
||||
at::Tensor result = t.clamp_max(max_t);
|
||||
|
||||
float* data = result.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 0.0f);
|
||||
ASSERT_FLOAT_EQ(data[1], 1.0f);
|
||||
ASSERT_FLOAT_EQ(data[2], 2.0f);
|
||||
ASSERT_FLOAT_EQ(data[3], 2.5f);
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampWithTensorBothNone) {
|
||||
// Test clamp with both min and max as empty optional
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
at::Tensor result = t.clamp(::std::optional<at::Tensor>(::std::nullopt),
|
||||
::std::optional<at::Tensor>(::std::nullopt));
|
||||
|
||||
ASSERT_EQ(result.numel(), 6);
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampMinTensorMaxNone) {
|
||||
// Test clamp with min tensor, max none
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
at::Tensor min_t = at::full({6}, 2.0f, at::kFloat);
|
||||
at::Tensor result = t.clamp(::std::optional<at::Tensor>(min_t),
|
||||
::std::optional<at::Tensor>(::std::nullopt));
|
||||
|
||||
float* data = result.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 2.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampMinNoneMaxTensor) {
|
||||
// Test clamp with min none, max tensor
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
at::Tensor max_t = at::full({6}, 3.0f, at::kFloat);
|
||||
at::Tensor result = t.clamp(::std::optional<at::Tensor>(::std::nullopt),
|
||||
::std::optional<at::Tensor>(max_t));
|
||||
|
||||
float* data = result.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[3], 3.0f);
|
||||
ASSERT_FLOAT_EQ(data[4], 3.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampInplaceMinNoneMax) {
|
||||
// Test clamp_ with min none
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
t.clamp_(::std::optional<at::Scalar>(::std::nullopt), at::Scalar(2.5));
|
||||
|
||||
float* data = t.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[3], 2.5f);
|
||||
}
|
||||
|
||||
TEST_F(TensorClampTest, ClampInplaceMaxNoneMin) {
|
||||
// Test clamp_ with max none
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
t.clamp_(at::Scalar(2.0), ::std::optional<at::Scalar>(::std::nullopt));
|
||||
|
||||
float* data = t.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 2.0f);
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ============================================================
|
||||
// Tests for at::Tensor::coalesce() and at::Tensor::is_coalesced()
|
||||
// ============================================================
|
||||
|
||||
// Helper: build a 2-D sparse COO tensor from indices and values.
|
||||
// indices shape: [sparse_dim, nnz], values shape: [nnz]
|
||||
static at::Tensor make_sparse(at::Tensor indices,
|
||||
at::Tensor values,
|
||||
c10::IntArrayRef size) {
|
||||
return at::sparse_coo_tensor(indices, values, size);
|
||||
}
|
||||
|
||||
TEST(TensorCoalesceTest, NewSparseNotCoalesced) {
|
||||
// A freshly created sparse COO tensor reports is_coalesced() == false.
|
||||
at::Tensor indices =
|
||||
at::tensor({0, 0, 1, 1, 1, 2}, at::kLong).reshape({2, 3});
|
||||
at::Tensor values = at::tensor({1.0f, 2.0f, 3.0f}, at::kFloat);
|
||||
at::Tensor sparse = make_sparse(indices, values, {3, 3});
|
||||
|
||||
ASSERT_FALSE(sparse.is_coalesced());
|
||||
}
|
||||
|
||||
TEST(TensorCoalesceTest, CoalesceReturnsSparse) {
|
||||
// coalesce() returns a sparse COO tensor.
|
||||
at::Tensor indices =
|
||||
at::tensor({0, 0, 1, 1, 1, 2}, at::kLong).reshape({2, 3});
|
||||
at::Tensor values = at::tensor({1.0f, 2.0f, 3.0f}, at::kFloat);
|
||||
at::Tensor sparse = make_sparse(indices, values, {3, 3});
|
||||
|
||||
at::Tensor coalesced = sparse.coalesce();
|
||||
|
||||
ASSERT_EQ(coalesced.layout(), c10::kSparse);
|
||||
}
|
||||
|
||||
TEST(TensorCoalesceTest, CoalescedTensorIsCoalesced) {
|
||||
// After calling coalesce(), is_coalesced() must return true.
|
||||
at::Tensor indices =
|
||||
at::tensor({0, 0, 1, 1, 1, 2}, at::kLong).reshape({2, 3});
|
||||
at::Tensor values = at::tensor({1.0f, 2.0f, 3.0f}, at::kFloat);
|
||||
at::Tensor sparse = make_sparse(indices, values, {3, 3});
|
||||
|
||||
at::Tensor coalesced = sparse.coalesce();
|
||||
|
||||
ASSERT_TRUE(coalesced.is_coalesced());
|
||||
}
|
||||
|
||||
TEST(TensorCoalesceTest, CoalesceDuplicateIndices_SumsValues) {
|
||||
// Duplicate indices [(0,1) appears twice] are merged; values are summed.
|
||||
// indices = [[0,0],[1,1]] (both at (0,1))
|
||||
at::Tensor indices = at::tensor({0, 0, 1, 1}, at::kLong).reshape({2, 2});
|
||||
at::Tensor values = at::tensor({1.0f, 2.0f}, at::kFloat);
|
||||
at::Tensor sparse = make_sparse(indices, values, {3, 3});
|
||||
|
||||
at::Tensor coalesced = sparse.coalesce();
|
||||
ASSERT_TRUE(coalesced.is_coalesced());
|
||||
// After coalescing, nnz should be 1 (duplicates merged)
|
||||
ASSERT_EQ(coalesced._nnz(), 1);
|
||||
// The merged value at (0,1) should be 1+2 = 3
|
||||
ASSERT_FLOAT_EQ(coalesced._values()[0].item<float>(), 3.0f);
|
||||
}
|
||||
|
||||
TEST(TensorCoalesceTest, CoalesceIdempotent) {
|
||||
// Calling coalesce() on an already-coalesced tensor returns the same tensor.
|
||||
at::Tensor indices = at::tensor({0, 1, 1, 2}, at::kLong).reshape({2, 2});
|
||||
at::Tensor values = at::tensor({1.0f, 2.0f}, at::kFloat);
|
||||
at::Tensor sparse = make_sparse(indices, values, {3, 3});
|
||||
|
||||
at::Tensor coalesced1 = sparse.coalesce();
|
||||
at::Tensor coalesced2 = coalesced1.coalesce(); // already coalesced
|
||||
|
||||
ASSERT_TRUE(coalesced2.is_coalesced());
|
||||
}
|
||||
|
||||
TEST(TensorCoalesceTest, CoalesceOnDenseTensorThrows) {
|
||||
// coalesce() on a dense tensor must throw.
|
||||
at::Tensor dense = at::ones({3, 3}, at::kFloat);
|
||||
ASSERT_THROW(dense.coalesce(), std::exception);
|
||||
}
|
||||
|
||||
TEST(TensorCoalesceTest, IsCoalescedOnDenseTensorThrows) {
|
||||
// is_coalesced() on a dense tensor must throw.
|
||||
at::Tensor dense = at::ones({3, 3}, at::kFloat);
|
||||
ASSERT_THROW(dense.is_coalesced(), std::exception);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/DeviceType.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ============================================================
|
||||
// Tests for at::Tensor::cuda()
|
||||
// ============================================================
|
||||
|
||||
// After cuda(), the tensor should reside on a GPU device.
|
||||
TEST(TensorCudaTest, CpuTensorMovesToCuda) {
|
||||
at::Tensor cpu_t = at::tensor({1.0f, 2.0f, 3.0f}, at::kFloat);
|
||||
ASSERT_TRUE(cpu_t.is_cpu());
|
||||
|
||||
at::Tensor cuda_t = cpu_t.cuda();
|
||||
ASSERT_TRUE(cuda_t.is_cuda());
|
||||
ASSERT_FALSE(cuda_t.is_cpu());
|
||||
}
|
||||
|
||||
// dtype and numel must be preserved.
|
||||
TEST(TensorCudaTest, DtypeAndNumelPreserved) {
|
||||
at::Tensor cpu_t = at::tensor({1, 2, 3, 4}, at::kInt);
|
||||
at::Tensor cuda_t = cpu_t.cuda();
|
||||
|
||||
ASSERT_EQ(cuda_t.scalar_type(), at::kInt);
|
||||
ASSERT_EQ(cuda_t.numel(), 4);
|
||||
}
|
||||
|
||||
// Values should round-trip back to CPU intact.
|
||||
TEST(TensorCudaTest, ValuesPreservedAfterRoundTrip) {
|
||||
std::vector<float> data = {1.0f, 2.5f, -3.0f, 4.75f};
|
||||
at::Tensor cpu_t = at::tensor(data, at::kFloat);
|
||||
at::Tensor cuda_t = cpu_t.cuda();
|
||||
at::Tensor back = cuda_t.cpu();
|
||||
|
||||
ASSERT_EQ(back.numel(), static_cast<int64_t>(data.size()));
|
||||
for (int64_t i = 0; i < back.numel(); ++i) {
|
||||
ASSERT_NEAR(back[i].item<float>(), data[static_cast<size_t>(i)], 1e-5f);
|
||||
}
|
||||
}
|
||||
|
||||
// shape (sizes) should be preserved.
|
||||
TEST(TensorCudaTest, ShapePreserved) {
|
||||
at::Tensor cpu_t = at::zeros({2, 3, 4}, at::kFloat);
|
||||
at::Tensor cuda_t = cpu_t.cuda();
|
||||
|
||||
ASSERT_EQ(cuda_t.dim(), 3);
|
||||
ASSERT_EQ(cuda_t.size(0), 2);
|
||||
ASSERT_EQ(cuda_t.size(1), 3);
|
||||
ASSERT_EQ(cuda_t.size(2), 4);
|
||||
}
|
||||
|
||||
// An already-CUDA tensor should still be CUDA after another cuda() call.
|
||||
TEST(TensorCudaTest, AlreadyCudaTensorStaysCuda) {
|
||||
at::Tensor cpu_t = at::tensor({7.0f}, at::kFloat);
|
||||
at::Tensor cuda_t = cpu_t.cuda();
|
||||
at::Tensor cuda_t2 = cuda_t.cuda();
|
||||
|
||||
ASSERT_TRUE(cuda_t2.is_cuda());
|
||||
ASSERT_NEAR(cuda_t2.cpu().item<float>(), 7.0f, 1e-6f);
|
||||
}
|
||||
|
||||
// device() should report a CUDA device.
|
||||
TEST(TensorCudaTest, DeviceIsCuda) {
|
||||
at::Tensor cpu_t = at::tensor({0.0f}, at::kFloat);
|
||||
at::Tensor cuda_t = cpu_t.cuda();
|
||||
|
||||
ASSERT_EQ(cuda_t.device().type(), c10::DeviceType::CUDA);
|
||||
}
|
||||
|
||||
TEST(TensorCudaTest, DefaultCudaUsesCurrentDevice) {
|
||||
if (c10::cuda::device_count() < 2) {
|
||||
return;
|
||||
}
|
||||
c10::cuda::CUDAGuard guard(1);
|
||||
at::Tensor cpu_t = at::tensor({1.0f}, at::kFloat);
|
||||
at::Tensor cuda_t = cpu_t.cuda();
|
||||
|
||||
ASSERT_EQ(cuda_t.device().type(), c10::DeviceType::CUDA);
|
||||
ASSERT_EQ(cuda_t.device().index(), 1);
|
||||
}
|
||||
|
||||
// is_cuda() / is_cpu() are mutually exclusive.
|
||||
TEST(TensorCudaTest, IsCudaAndIsCpuMutuallyExclusive) {
|
||||
at::Tensor cpu_t = at::tensor({1.0f, 2.0f}, at::kFloat);
|
||||
at::Tensor cuda_t = cpu_t.cuda();
|
||||
|
||||
ASSERT_TRUE(cuda_t.is_cuda());
|
||||
ASSERT_FALSE(cuda_t.is_cpu());
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/Layout.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
#include "utils/dense_sparse_conversion.h"
|
||||
|
||||
// ============================================================
|
||||
// Tests for compat::_PD_ConvertToSparseIfNeeded()
|
||||
// ============================================================
|
||||
|
||||
// Helper: create a small 2-D dense float tensor.
|
||||
static paddle::Tensor make_dense_2d(int rows = 3, int cols = 3) {
|
||||
// Use at::ones, but get the underlying paddle::Tensor.
|
||||
at::Tensor t = at::ones({rows, cols}, at::kFloat);
|
||||
return t._PD_GetInner();
|
||||
}
|
||||
|
||||
// ---- kStrided -> dense (no conversion) ----
|
||||
|
||||
TEST(DenseSparseConversionTest, kStrided_ReturnsDense) {
|
||||
paddle::Tensor dense = make_dense_2d();
|
||||
at::Tensor result = compat::_PD_ConvertToSparseIfNeeded(dense, c10::kStrided);
|
||||
|
||||
ASSERT_EQ(result.layout(), c10::kStrided);
|
||||
}
|
||||
|
||||
// ---- unsupported layout throws ----
|
||||
|
||||
TEST(DenseSparseConversionTest, UnsupportedLayout_Throws) {
|
||||
paddle::Tensor dense = make_dense_2d();
|
||||
|
||||
// kSparseBsr is not handled in the switch => PD_CHECK(false, ...) fires.
|
||||
ASSERT_THROW(compat::_PD_ConvertToSparseIfNeeded(dense, c10::kSparseBsr),
|
||||
std::exception);
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/ops/empty.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#ifdef PADDLE_WITH_XPU
|
||||
#include "paddle/phi/core/platform/device/xpu/xpu_info.h"
|
||||
#endif
|
||||
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ======================== at::empty basic tests ========================
|
||||
|
||||
TEST(ATenEmptyTest, BasicShape) {
|
||||
at::Tensor t = at::empty({3, 4});
|
||||
ASSERT_EQ(t.sizes()[0], 3);
|
||||
ASSERT_EQ(t.sizes()[1], 4);
|
||||
}
|
||||
|
||||
TEST(ATenEmptyTest, DtypeFloat) {
|
||||
at::Tensor t = at::empty({2, 2}, at::TensorOptions().dtype(at::kFloat));
|
||||
ASSERT_EQ(t.scalar_type(), at::kFloat);
|
||||
}
|
||||
|
||||
TEST(ATenEmptyTest, DtypeDouble) {
|
||||
at::Tensor t = at::empty({4}, at::TensorOptions().dtype(at::kDouble));
|
||||
ASSERT_EQ(t.scalar_type(), at::kDouble);
|
||||
}
|
||||
|
||||
TEST(ATenEmptyTest, ExplicitArgsCpu) {
|
||||
// 6-argument overload: dtype, layout, device, pin_memory, memory_format
|
||||
at::Tensor t = at::empty(
|
||||
{2, 3}, at::kFloat, at::kStrided, at::kCPU, false, std::nullopt);
|
||||
ASSERT_EQ(t.sizes()[0], 2);
|
||||
ASSERT_EQ(t.sizes()[1], 3);
|
||||
ASSERT_EQ(t.scalar_type(), at::kFloat);
|
||||
ASSERT_FALSE(t.is_pinned());
|
||||
}
|
||||
|
||||
// ======================== pin_memory tests ========================
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
|
||||
// TensorOptions overload: pin_memory via options
|
||||
TEST(ATenEmptyTest, PinMemoryViaTensorOptions) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
at::TensorOptions opts =
|
||||
at::TensorOptions().dtype(at::kFloat).pinned_memory(true);
|
||||
at::Tensor t = at::empty({4, 4}, opts);
|
||||
ASSERT_TRUE(t.is_pinned())
|
||||
<< "Expected pinned memory tensor when TensorOptions.pinned_memory=true";
|
||||
}
|
||||
|
||||
// 6-argument overload: pin_memory = true (must use CPU device)
|
||||
TEST(ATenEmptyTest, PinMemoryViaExplicitArgs) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
at::Tensor t =
|
||||
at::empty({8}, at::kFloat, at::kStrided, at::kCPU, true, std::nullopt);
|
||||
ASSERT_TRUE(t.is_pinned())
|
||||
<< "Expected pinned memory tensor when pin_memory=true with CPU device";
|
||||
}
|
||||
|
||||
// pin_memory = false must NOT produce a pinned tensor
|
||||
TEST(ATenEmptyTest, NoPinMemoryViaExplicitArgs) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
at::Tensor t =
|
||||
at::empty({8}, at::kFloat, at::kStrided, at::kCUDA, false, std::nullopt);
|
||||
ASSERT_FALSE(t.is_pinned())
|
||||
<< "Expected non-pinned tensor when pin_memory=false";
|
||||
}
|
||||
|
||||
// Pinned tensor lives in pinned (host) memory, not on the GPU device itself
|
||||
TEST(ATenEmptyTest, PinnedTensorIsNotCuda) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
at::TensorOptions opts =
|
||||
at::TensorOptions().dtype(at::kFloat).pinned_memory(true);
|
||||
at::Tensor t = at::empty({16}, opts);
|
||||
ASSERT_TRUE(t.is_pinned());
|
||||
ASSERT_FALSE(t.is_cuda())
|
||||
<< "Pinned tensor should reside in host pinned memory, not on device";
|
||||
}
|
||||
|
||||
// Data pointer of a pinned tensor must be non-null
|
||||
TEST(ATenEmptyTest, PinnedTensorDataPtrNonNull) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
at::TensorOptions opts =
|
||||
at::TensorOptions().dtype(at::kFloat).pinned_memory(true);
|
||||
at::Tensor t = at::empty({32}, opts);
|
||||
ASSERT_TRUE(t.is_pinned());
|
||||
ASSERT_NE(t.data_ptr(), nullptr);
|
||||
}
|
||||
|
||||
TEST(ATenEmptyTest, DefaultCudaDeviceUsesCurrentDevice) {
|
||||
if (c10::cuda::device_count() < 2) {
|
||||
return;
|
||||
}
|
||||
c10::cuda::CUDAGuard guard(1);
|
||||
at::Tensor t =
|
||||
at::empty({8}, at::TensorOptions().dtype(at::kFloat).device(at::kCUDA));
|
||||
|
||||
ASSERT_TRUE(t.is_cuda());
|
||||
ASSERT_EQ(t.device().index(), 1);
|
||||
}
|
||||
|
||||
TEST(ATenEmptyTest, EmptyCudaHelperDefaultDeviceUsesCurrentDevice) {
|
||||
if (c10::cuda::device_count() < 2) {
|
||||
return;
|
||||
}
|
||||
c10::cuda::CUDAGuard guard(1);
|
||||
at::Tensor t = at::detail::empty_cuda(
|
||||
{8}, at::kFloat, at::Device(at::kCUDA), std::nullopt);
|
||||
|
||||
ASSERT_TRUE(t.is_cuda());
|
||||
ASSERT_EQ(t.device().index(), 1);
|
||||
}
|
||||
|
||||
TEST(ATenEmptyTest, EmptyCudaOptionsHelperDefaultDeviceUsesCurrentDevice) {
|
||||
if (c10::cuda::device_count() < 2) {
|
||||
return;
|
||||
}
|
||||
c10::cuda::CUDAGuard guard(1);
|
||||
at::Tensor t = at::detail::empty_cuda(
|
||||
{8}, at::TensorOptions().dtype(at::kFloat).device(at::kCUDA));
|
||||
|
||||
ASSERT_TRUE(t.is_cuda());
|
||||
ASSERT_EQ(t.device().index(), 1);
|
||||
}
|
||||
|
||||
#endif // PADDLE_WITH_CUDA || PADDLE_WITH_HIP
|
||||
|
||||
#ifdef PADDLE_WITH_XPU
|
||||
TEST(ATenEmptyTest, DefaultXpuDeviceUsesCurrentDevice) {
|
||||
if (paddle::platform::GetXPUDeviceCount() < 2) {
|
||||
return;
|
||||
}
|
||||
paddle::platform::XPUDeviceGuard guard(1);
|
||||
at::Tensor t =
|
||||
at::empty({8}, at::TensorOptions().dtype(at::kFloat).device(at::kXPU));
|
||||
|
||||
ASSERT_EQ(t.device().type(), c10::DeviceType::XPU);
|
||||
ASSERT_EQ(t.device().index(), 1);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <ATen/ops/equal.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
TEST(TensorEqualTest, DifferentShapeReturnsFalse) {
|
||||
at::Tensor a = at::ones({2, 2}, at::kFloat);
|
||||
at::Tensor b = at::ones({2, 3}, at::kFloat);
|
||||
|
||||
ASSERT_FALSE(at::equal(a, b));
|
||||
ASSERT_FALSE(a.equal(b));
|
||||
}
|
||||
|
||||
TEST(TensorEqualTest, DtypeMismatchCastsOtherTensor) {
|
||||
at::Tensor a = at::tensor({1.0f, 2.0f, 3.0f}, at::kFloat);
|
||||
at::Tensor b = at::tensor({1, 2, 3}, at::kInt);
|
||||
|
||||
ASSERT_TRUE(at::equal(a, b));
|
||||
ASSERT_TRUE(a.equal(b));
|
||||
}
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
TEST(TensorEqualTest, DeviceMismatchThrows) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
at::Tensor cpu = at::ones({2, 2}, at::kFloat);
|
||||
at::Tensor gpu =
|
||||
at::ones({2, 2}, at::TensorOptions().dtype(at::kFloat).device(at::kCUDA));
|
||||
|
||||
ASSERT_THROW((void)at::equal(cpu, gpu), std::exception);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,176 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/cpp/prim/init_env_utils.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class TensorExpandTest : public ::testing::Test {
|
||||
protected:
|
||||
static void SetUpTestSuite() { paddle::prim::InitTensorOperants(); }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// ======================== expand tests ========================
|
||||
|
||||
TEST_F(TensorExpandTest, ExpandBasic) {
|
||||
// {3}.expand({3,4}) - PyTorch rejects non-singleton expansion (3 != 4)
|
||||
at::Tensor t = at::arange(3, at::kFloat);
|
||||
ASSERT_THROW(t.expand({3, 4}), std::exception);
|
||||
}
|
||||
|
||||
TEST_F(TensorExpandTest, ExpandSingleDim) {
|
||||
at::Tensor t = at::full({1}, 5.0f, at::kFloat);
|
||||
|
||||
at::Tensor result = t.expand({5});
|
||||
|
||||
ASSERT_EQ(result.numel(), 5);
|
||||
}
|
||||
|
||||
TEST_F(TensorExpandTest, ExpandMultipleDims) {
|
||||
// {1,3}.expand({2,3,4}) - PyTorch rejects non-singleton expansion (3 != 4)
|
||||
at::Tensor t = at::full({1, 3}, 1.0f, at::kFloat);
|
||||
ASSERT_THROW(t.expand({2, 3, 4}), std::exception);
|
||||
}
|
||||
|
||||
TEST_F(TensorExpandTest, ExpandWithImplicit) {
|
||||
// {3}.expand({3,4}) - PyTorch rejects non-singleton expansion (3 != 4)
|
||||
at::Tensor t = at::arange(3, at::kFloat);
|
||||
ASSERT_THROW(t.expand({3, 4}, true), std::exception);
|
||||
}
|
||||
|
||||
TEST_F(TensorExpandTest, ExpandPreservesValue) {
|
||||
// {3}.expand({3,4}) - PyTorch rejects non-singleton expansion (3 != 4)
|
||||
at::Tensor t = at::full({3}, 7.0f, at::kFloat);
|
||||
ASSERT_THROW(t.expand({3, 4}), std::exception);
|
||||
}
|
||||
|
||||
// ======================== expand_as tests ========================
|
||||
|
||||
TEST_F(TensorExpandTest, ExpandAsBasic) {
|
||||
at::Tensor t = at::arange(3, at::kFloat).reshape({1, 3});
|
||||
at::Tensor other = at::zeros({2, 3}, at::kFloat);
|
||||
|
||||
at::Tensor result = t.expand_as(other);
|
||||
|
||||
ASSERT_EQ(result.sizes()[0], 2);
|
||||
ASSERT_EQ(result.sizes()[1], 3);
|
||||
}
|
||||
|
||||
TEST_F(TensorExpandTest, ExpandAsMatchSize) {
|
||||
at::Tensor t = at::full({1}, 7.0f, at::kFloat);
|
||||
at::Tensor other = at::zeros({3, 3, 3}, at::kFloat);
|
||||
|
||||
at::Tensor result = t.expand_as(other);
|
||||
|
||||
ASSERT_EQ(result.sizes().size(), 3);
|
||||
ASSERT_EQ(result.numel(), other.numel());
|
||||
}
|
||||
|
||||
TEST_F(TensorExpandTest, ExpandAsPreservesValue) {
|
||||
at::Tensor t = at::full({2, 1}, 5.0f, at::kFloat);
|
||||
at::Tensor other = at::zeros({2, 3}, at::kFloat);
|
||||
|
||||
at::Tensor result = t.expand_as(other);
|
||||
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[0], 5.0f);
|
||||
}
|
||||
|
||||
// ======================== Additional tests for coverage
|
||||
// ========================
|
||||
|
||||
// Test tile fallback path when input_rank < target_rank
|
||||
// This triggers lines 86-100 in expand.h
|
||||
TEST_F(TensorExpandTest, ExpandTileFallbackLowRank) {
|
||||
// {2,1}.expand({1,4}) - PyTorch rejects shrinking non-singleton dims
|
||||
at::Tensor t = at::full({2, 1}, 1.0f, at::kFloat);
|
||||
ASSERT_THROW(t.expand({1, 4}), std::exception);
|
||||
}
|
||||
|
||||
// Test tile fallback when input_rank == target_rank
|
||||
// This triggers lines 119-130 in expand.h
|
||||
TEST_F(TensorExpandTest, ExpandSameRankTileFallback) {
|
||||
// {2,3}.expand({2,6}) - PyTorch only allows expanding singleton dims
|
||||
at::Tensor t = at::full({2, 3}, 2.0f, at::kFloat);
|
||||
ASSERT_THROW(t.expand({2, 6}), std::exception);
|
||||
}
|
||||
|
||||
// Test zero dimension handling
|
||||
// This triggers lines 90-94 and 122-126 in expand.h
|
||||
TEST_F(TensorExpandTest, ExpandZeroDim) {
|
||||
// {0}.expand({0,3}) - PyTorch rejects non-singleton expansion (0 != 3)
|
||||
at::Tensor t = at::full({0}, 1.0f, at::kFloat);
|
||||
ASSERT_THROW(t.expand({0, 3}), std::exception);
|
||||
}
|
||||
|
||||
// Test input_rank > target_rank branch
|
||||
// This triggers lines 131-136 in expand.h
|
||||
TEST_F(TensorExpandTest, ExpandHighRankToLowRank) {
|
||||
// Input has more dimensions than target - PyTorch rejects this
|
||||
at::Tensor t = at::full({2, 3, 4}, 1.0f, at::kFloat);
|
||||
ASSERT_THROW(t.expand({3, 4}), std::exception);
|
||||
}
|
||||
|
||||
// Test expand_as with tile fallback
|
||||
TEST_F(TensorExpandTest, ExpandAsTileFallback) {
|
||||
// {2,1}.expand_as({1,4}) - PyTorch rejects shrinking non-singleton dims
|
||||
at::Tensor t = at::full({2, 1}, 3.0f, at::kFloat);
|
||||
at::Tensor other = at::zeros({1, 4}, at::kFloat);
|
||||
|
||||
ASSERT_THROW(t.expand_as(other), std::exception);
|
||||
}
|
||||
|
||||
// Test preserve non-singleton dimension (matching dimension)
|
||||
TEST_F(TensorExpandTest, ExpandPreserveNonSingleton) {
|
||||
// {3,1}.expand({3,4}) - dim 0 matches (3), dim 1 expands (1->4)
|
||||
at::Tensor t = at::full({3, 1}, 5.0f, at::kFloat);
|
||||
at::Tensor result = t.expand({3, 4});
|
||||
|
||||
ASSERT_EQ(result.sizes()[0], 3);
|
||||
ASSERT_EQ(result.sizes()[1], 4);
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[0], 5.0f);
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[3], 5.0f);
|
||||
}
|
||||
|
||||
// Test expand function (not member function)
|
||||
TEST_F(TensorExpandTest, ExpandFunction) {
|
||||
at::Tensor t = at::full({1}, 7.0f, at::kFloat);
|
||||
|
||||
at::Tensor result = at::expand(t, {3, 4});
|
||||
|
||||
ASSERT_EQ(result.sizes()[0], 3);
|
||||
ASSERT_EQ(result.sizes()[1], 4);
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[0], 7.0f);
|
||||
}
|
||||
|
||||
TEST_F(TensorExpandTest, ExpandAsMemberFunction) {
|
||||
at::Tensor t = at::full({1, 2}, 4.0f, at::kFloat);
|
||||
at::Tensor other = at::zeros({3, 2}, at::kFloat);
|
||||
|
||||
at::Tensor result = t.expand_as(other);
|
||||
|
||||
ASSERT_EQ(result.sizes()[0], 3);
|
||||
ASSERT_EQ(result.sizes()[1], 2);
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[0], 4.0f);
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ============================================================
|
||||
// Tests for at::eye()
|
||||
// ============================================================
|
||||
|
||||
// Helper: verify that a 2-D tensor is an identity-like matrix
|
||||
// (diagonal == 1, off-diagonal == 0).
|
||||
static void CheckEye(const at::Tensor& t, int64_t rows, int64_t cols) {
|
||||
ASSERT_EQ(t.dim(), 2);
|
||||
ASSERT_EQ(t.size(0), rows);
|
||||
ASSERT_EQ(t.size(1), cols);
|
||||
for (int64_t i = 0; i < rows; ++i) {
|
||||
for (int64_t j = 0; j < cols; ++j) {
|
||||
float expected = (i == j) ? 1.0f : 0.0f;
|
||||
ASSERT_FLOAT_EQ(t[i][j].item<float>(), expected)
|
||||
<< "Mismatch at (" << i << ", " << j << ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- eye(n) -------------------------------------------------------
|
||||
|
||||
TEST(ATenEyeTest, SquareDefaultDtype) {
|
||||
// eye(n) should produce an n×n float32 identity matrix.
|
||||
at::Tensor t = at::eye(4);
|
||||
ASSERT_EQ(t.scalar_type(), at::kFloat);
|
||||
CheckEye(t, 4, 4);
|
||||
}
|
||||
|
||||
TEST(ATenEyeTest, SquareTensorOptionsFloat) {
|
||||
// eye(n, TensorOptions) — explicit float32.
|
||||
at::Tensor t = at::eye(3, at::TensorOptions().dtype(at::kFloat));
|
||||
ASSERT_EQ(t.scalar_type(), at::kFloat);
|
||||
CheckEye(t, 3, 3);
|
||||
}
|
||||
|
||||
TEST(ATenEyeTest, SquareTensorOptionsDouble) {
|
||||
// eye(n, TensorOptions) — explicit float64.
|
||||
at::Tensor t = at::eye(5, at::TensorOptions().dtype(at::kDouble));
|
||||
ASSERT_EQ(t.scalar_type(), at::kDouble);
|
||||
ASSERT_EQ(t.size(0), 5);
|
||||
ASSERT_EQ(t.size(1), 5);
|
||||
for (int64_t i = 0; i < 5; ++i) {
|
||||
ASSERT_DOUBLE_EQ(t[i][i].item<double>(), 1.0);
|
||||
if (i + 1 < 5) {
|
||||
ASSERT_DOUBLE_EQ(t[i][i + 1].item<double>(), 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eye(n, dtype, layout, device, pin_memory) — separate-params overload
|
||||
|
||||
TEST(ATenEyeTest, SquareSeparateParamsFloat) {
|
||||
at::Tensor t =
|
||||
at::eye(4, at::kFloat, /*layout=*/std::nullopt, at::kCPU, false);
|
||||
ASSERT_EQ(t.scalar_type(), at::kFloat);
|
||||
CheckEye(t, 4, 4);
|
||||
}
|
||||
|
||||
TEST(ATenEyeTest, SquareSeparateParamsNulloptDtype) {
|
||||
// When dtype is nullopt the default dtype (float32) should be used.
|
||||
at::Tensor t =
|
||||
at::eye(3, std::nullopt, /*layout=*/std::nullopt, at::kCPU, false);
|
||||
ASSERT_EQ(t.scalar_type(), at::kFloat);
|
||||
CheckEye(t, 3, 3);
|
||||
}
|
||||
|
||||
// ---- eye(n, m) -------------------------------------------------------
|
||||
|
||||
TEST(ATenEyeTest, RectangularWiderThanTall) {
|
||||
// n < m: identity portion fits entirely within row range.
|
||||
at::Tensor t = at::eye(3, 5);
|
||||
ASSERT_EQ(t.scalar_type(), at::kFloat);
|
||||
CheckEye(t, 3, 5);
|
||||
}
|
||||
|
||||
TEST(ATenEyeTest, RectangularTallerThanWide) {
|
||||
// n > m: identity portion fits entirely within column range.
|
||||
at::Tensor t = at::eye(5, 3);
|
||||
ASSERT_EQ(t.scalar_type(), at::kFloat);
|
||||
CheckEye(t, 5, 3);
|
||||
}
|
||||
|
||||
TEST(ATenEyeTest, RectangularSquareEquivalent) {
|
||||
// eye(n, n) should behave like eye(n).
|
||||
at::Tensor t2 = at::eye(4, 4);
|
||||
at::Tensor t1 = at::eye(4);
|
||||
CheckEye(t2, 4, 4);
|
||||
for (int64_t i = 0; i < 4; ++i)
|
||||
for (int64_t j = 0; j < 4; ++j)
|
||||
ASSERT_FLOAT_EQ(t1[i][j].item<float>(), t2[i][j].item<float>());
|
||||
}
|
||||
|
||||
TEST(ATenEyeTest, RectangularTensorOptionsDouble) {
|
||||
// eye(n, m, TensorOptions) — float64.
|
||||
at::Tensor t = at::eye(2, 4, at::TensorOptions().dtype(at::kDouble));
|
||||
ASSERT_EQ(t.scalar_type(), at::kDouble);
|
||||
ASSERT_EQ(t.size(0), 2);
|
||||
ASSERT_EQ(t.size(1), 4);
|
||||
ASSERT_DOUBLE_EQ(t[0][0].item<double>(), 1.0);
|
||||
ASSERT_DOUBLE_EQ(t[1][1].item<double>(), 1.0);
|
||||
ASSERT_DOUBLE_EQ(t[0][1].item<double>(), 0.0);
|
||||
}
|
||||
|
||||
TEST(ATenEyeTest, RectangularSeparateParams) {
|
||||
// eye(n, m, dtype, layout, device, pin_memory)
|
||||
at::Tensor t =
|
||||
at::eye(3, 5, at::kDouble, /*layout=*/std::nullopt, at::kCPU, false);
|
||||
ASSERT_EQ(t.scalar_type(), at::kDouble);
|
||||
CheckEye(t, 3, 5);
|
||||
}
|
||||
|
||||
TEST(ATenEyeTest, RectangularSeparateParamsNulloptDtype) {
|
||||
at::Tensor t =
|
||||
at::eye(4, 6, std::nullopt, /*layout=*/std::nullopt, at::kCPU, false);
|
||||
ASSERT_EQ(t.scalar_type(), at::kFloat);
|
||||
CheckEye(t, 4, 6);
|
||||
}
|
||||
|
||||
// ---- 1×1 edge case -------------------------------------------------------
|
||||
|
||||
TEST(ATenEyeTest, OneByOne) {
|
||||
at::Tensor t = at::eye(1);
|
||||
ASSERT_EQ(t.numel(), 1);
|
||||
ASSERT_FLOAT_EQ(t[0][0].item<float>(), 1.0f);
|
||||
}
|
||||
|
||||
// ---- GPU tests (compiled only when CUDA / HIP is available) --------------
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
TEST(ATenEyeTest, SquareOnGPU) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
at::Tensor t =
|
||||
at::eye(4, at::TensorOptions().dtype(at::kFloat).device(at::kCUDA));
|
||||
at::Tensor t_cpu = t.to(at::kCPU);
|
||||
CheckEye(t_cpu, 4, 4);
|
||||
}
|
||||
|
||||
TEST(ATenEyeTest, RectangularOnGPU) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
at::Tensor t =
|
||||
at::eye(3, 5, at::TensorOptions().dtype(at::kFloat).device(at::kCUDA));
|
||||
at::Tensor t_cpu = t.to(at::kCPU);
|
||||
CheckEye(t_cpu, 3, 5);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,174 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/ops/arange.h>
|
||||
#include <ATen/ops/empty.h>
|
||||
#include <ATen/ops/full.h>
|
||||
#include <ATen/ops/ones.h>
|
||||
#include <ATen/ops/zeros.h>
|
||||
#include <c10/core/DefaultDtype.h>
|
||||
#include <c10/core/ScalarTypeToTypeMeta.h>
|
||||
#include <c10/core/SymIntArrayRef.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class DefaultDtypeGuard {
|
||||
public:
|
||||
explicit DefaultDtypeGuard(c10::ScalarType dtype)
|
||||
: previous_(c10::get_default_dtype()) {
|
||||
c10::set_default_dtype(c10::scalarTypeToTypeMeta(dtype));
|
||||
}
|
||||
|
||||
~DefaultDtypeGuard() { c10::set_default_dtype(previous_); }
|
||||
|
||||
private:
|
||||
caffe2::TypeMeta previous_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(ATenFactoryDefaultDtypeTest, EmptyNulloptDtypeUsesCurrentDefault) {
|
||||
DefaultDtypeGuard guard(at::kDouble);
|
||||
|
||||
at::Tensor tensor = at::empty(
|
||||
{2, 3}, std::nullopt, at::kStrided, at::kCPU, false, std::nullopt);
|
||||
|
||||
ASSERT_EQ(tensor.scalar_type(), at::kDouble);
|
||||
ASSERT_EQ(tensor.sizes(), c10::IntArrayRef({2, 3}));
|
||||
}
|
||||
|
||||
TEST(ATenFactoryDefaultDtypeTest, ArangeOmittedDtypeUsesLongForIntegralInputs) {
|
||||
DefaultDtypeGuard guard(at::kDouble);
|
||||
|
||||
at::Tensor end_only_default = at::arange(5);
|
||||
at::Tensor start_end_default = at::arange(1, 6);
|
||||
at::Tensor start_end_step_default = at::arange(1, 7, 2);
|
||||
at::Tensor end_only_nullopt =
|
||||
at::arange(5, std::nullopt, std::nullopt, at::kCPU, false);
|
||||
at::Tensor start_end_nullopt =
|
||||
at::arange(1, 6, std::nullopt, std::nullopt, at::kCPU, false);
|
||||
at::Tensor start_end_step_nullopt =
|
||||
at::arange(1, 7, 2, std::nullopt, std::nullopt, at::kCPU, false);
|
||||
|
||||
ASSERT_EQ(end_only_default.scalar_type(), at::kLong);
|
||||
ASSERT_EQ(start_end_default.scalar_type(), at::kLong);
|
||||
ASSERT_EQ(start_end_step_default.scalar_type(), at::kLong);
|
||||
ASSERT_EQ(end_only_nullopt.scalar_type(), at::kLong);
|
||||
ASSERT_EQ(start_end_nullopt.scalar_type(), at::kLong);
|
||||
ASSERT_EQ(start_end_step_nullopt.scalar_type(), at::kLong);
|
||||
ASSERT_EQ(end_only_default.data_ptr<int64_t>()[4], 4);
|
||||
ASSERT_EQ(start_end_default.data_ptr<int64_t>()[0], 1);
|
||||
ASSERT_EQ(start_end_step_default.data_ptr<int64_t>()[2], 5);
|
||||
ASSERT_EQ(end_only_nullopt.data_ptr<int64_t>()[4], 4);
|
||||
ASSERT_EQ(start_end_nullopt.data_ptr<int64_t>()[0], 1);
|
||||
ASSERT_EQ(start_end_step_nullopt.data_ptr<int64_t>()[2], 5);
|
||||
}
|
||||
|
||||
TEST(ATenFactoryDefaultDtypeTest,
|
||||
ArangeOmittedDtypeKeepsLargeInt64InputsExact) {
|
||||
constexpr int64_t kStart = (1LL << 53) + 1;
|
||||
constexpr int64_t kEnd = kStart + 4;
|
||||
|
||||
at::Tensor by_default = at::arange(kStart, kEnd);
|
||||
at::Tensor by_nullopt =
|
||||
at::arange(kStart, kEnd, std::nullopt, std::nullopt, at::kCPU, false);
|
||||
|
||||
ASSERT_EQ(by_default.scalar_type(), at::kLong);
|
||||
ASSERT_EQ(by_nullopt.scalar_type(), at::kLong);
|
||||
ASSERT_EQ(by_default.numel(), 4);
|
||||
ASSERT_EQ(by_nullopt.numel(), 4);
|
||||
|
||||
for (int64_t i = 0; i < 4; ++i) {
|
||||
ASSERT_EQ(by_default.data_ptr<int64_t>()[i], kStart + i);
|
||||
ASSERT_EQ(by_nullopt.data_ptr<int64_t>()[i], kStart + i);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ATenFactoryDefaultDtypeTest,
|
||||
ArangeOmittedDtypeUsesCurrentDefaultForFloatingInputs) {
|
||||
DefaultDtypeGuard guard(at::kDouble);
|
||||
|
||||
at::Tensor end_only_default = at::arange(5.0);
|
||||
at::Tensor start_end_default = at::arange(1.0, 6.0);
|
||||
at::Tensor start_end_step_default = at::arange(1.0, 7.0, 2.0);
|
||||
at::Tensor end_only_nullopt =
|
||||
at::arange(5.0, std::nullopt, std::nullopt, at::kCPU, false);
|
||||
at::Tensor start_end_nullopt =
|
||||
at::arange(1.0, 6.0, std::nullopt, std::nullopt, at::kCPU, false);
|
||||
at::Tensor start_end_step_nullopt =
|
||||
at::arange(1.0, 7.0, 2.0, std::nullopt, std::nullopt, at::kCPU, false);
|
||||
|
||||
ASSERT_EQ(end_only_default.scalar_type(), at::kDouble);
|
||||
ASSERT_EQ(start_end_default.scalar_type(), at::kDouble);
|
||||
ASSERT_EQ(start_end_step_default.scalar_type(), at::kDouble);
|
||||
ASSERT_EQ(end_only_nullopt.scalar_type(), at::kDouble);
|
||||
ASSERT_EQ(start_end_nullopt.scalar_type(), at::kDouble);
|
||||
ASSERT_EQ(start_end_step_nullopt.scalar_type(), at::kDouble);
|
||||
ASSERT_DOUBLE_EQ(end_only_default.data_ptr<double>()[4], 4.0);
|
||||
ASSERT_DOUBLE_EQ(start_end_default.data_ptr<double>()[0], 1.0);
|
||||
ASSERT_DOUBLE_EQ(start_end_step_default.data_ptr<double>()[2], 5.0);
|
||||
ASSERT_DOUBLE_EQ(end_only_nullopt.data_ptr<double>()[4], 4.0);
|
||||
ASSERT_DOUBLE_EQ(start_end_nullopt.data_ptr<double>()[0], 1.0);
|
||||
ASSERT_DOUBLE_EQ(start_end_step_nullopt.data_ptr<double>()[2], 5.0);
|
||||
}
|
||||
|
||||
TEST(ATenFactoryDefaultDtypeTest, FullNulloptDtypeUsesCurrentDefault) {
|
||||
DefaultDtypeGuard guard(at::kDouble);
|
||||
|
||||
at::Tensor tensor =
|
||||
at::full({2, 3}, 1.25, std::nullopt, std::nullopt, at::kCPU, false);
|
||||
at::Tensor symint_tensor = at::full_symint(c10::SymIntArrayRef({2, 3}),
|
||||
2.5,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
at::kCPU,
|
||||
false);
|
||||
|
||||
ASSERT_EQ(tensor.scalar_type(), at::kDouble);
|
||||
ASSERT_EQ(symint_tensor.scalar_type(), at::kDouble);
|
||||
ASSERT_DOUBLE_EQ(tensor.data_ptr<double>()[0], 1.25);
|
||||
ASSERT_DOUBLE_EQ(symint_tensor.data_ptr<double>()[0], 2.5);
|
||||
}
|
||||
|
||||
TEST(ATenFactoryDefaultDtypeTest, OnesNulloptDtypeUsesCurrentDefault) {
|
||||
DefaultDtypeGuard guard(at::kDouble);
|
||||
|
||||
at::Tensor tensor =
|
||||
at::ones({2, 3}, std::nullopt, std::nullopt, at::kCPU, false);
|
||||
at::Tensor symint_tensor = at::ones_symint(
|
||||
c10::SymIntArrayRef({2, 3}), std::nullopt, std::nullopt, at::kCPU, false);
|
||||
|
||||
ASSERT_EQ(tensor.scalar_type(), at::kDouble);
|
||||
ASSERT_EQ(symint_tensor.scalar_type(), at::kDouble);
|
||||
ASSERT_DOUBLE_EQ(tensor.data_ptr<double>()[0], 1.0);
|
||||
ASSERT_DOUBLE_EQ(symint_tensor.data_ptr<double>()[0], 1.0);
|
||||
}
|
||||
|
||||
TEST(ATenFactoryDefaultDtypeTest, ZerosNulloptDtypeUsesCurrentDefault) {
|
||||
DefaultDtypeGuard guard(at::kDouble);
|
||||
|
||||
at::Tensor tensor =
|
||||
at::zeros({2, 3}, std::nullopt, at::kStrided, at::kCPU, false);
|
||||
at::Tensor symint_tensor = at::zeros_symint(
|
||||
c10::SymIntArrayRef({2, 3}), std::nullopt, at::kStrided, at::kCPU, false);
|
||||
|
||||
ASSERT_EQ(tensor.scalar_type(), at::kDouble);
|
||||
ASSERT_EQ(symint_tensor.scalar_type(), at::kDouble);
|
||||
ASSERT_DOUBLE_EQ(tensor.data_ptr<double>()[0], 0.0);
|
||||
ASSERT_DOUBLE_EQ(symint_tensor.data_ptr<double>()[0], 0.0);
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ============================================================================
|
||||
// Flatten Tests
|
||||
// ============================================================================
|
||||
|
||||
TEST(TestFlatten, FlattenAllDims) {
|
||||
// Test flatten with start_dim=0, end_dim=-1
|
||||
// Flattens the entire tensor to 1D
|
||||
at::Tensor tensor = at::ones({2, 3, 4}, at::kFloat);
|
||||
at::Tensor flattened = tensor.flatten(0, -1);
|
||||
|
||||
ASSERT_EQ(flattened.sizes(), c10::IntArrayRef({24}));
|
||||
ASSERT_EQ(flattened.numel(), tensor.numel());
|
||||
}
|
||||
|
||||
TEST(TestFlatten, FlattenPartialDims) {
|
||||
// Test flatten with specific start and end dimensions
|
||||
at::Tensor tensor = at::ones({2, 3, 4, 5}, at::kFloat);
|
||||
|
||||
// Flatten dimensions 1 to 2 (3*4 = 12)
|
||||
at::Tensor flattened = tensor.flatten(1, 2);
|
||||
ASSERT_EQ(flattened.sizes(), c10::IntArrayRef({2, 12, 5}));
|
||||
ASSERT_EQ(flattened.numel(), tensor.numel());
|
||||
}
|
||||
|
||||
TEST(TestFlatten, FlattenSingleDim) {
|
||||
// Test flatten when start_dim == end_dim (should be no-op)
|
||||
at::Tensor tensor = at::ones({2, 3, 4}, at::kFloat);
|
||||
|
||||
at::Tensor flattened = tensor.flatten(1, 1);
|
||||
ASSERT_EQ(flattened.sizes(), c10::IntArrayRef({2, 3, 4}));
|
||||
}
|
||||
|
||||
TEST(TestFlatten, FlattenNegativeDims) {
|
||||
// Test flatten with negative dimension indices
|
||||
at::Tensor tensor = at::ones({2, 3, 4, 5}, at::kFloat);
|
||||
|
||||
// Flatten from -3 to -2 (dimensions 1 to 2)
|
||||
at::Tensor flattened = tensor.flatten(-3, -2);
|
||||
ASSERT_EQ(flattened.sizes(), c10::IntArrayRef({2, 12, 5}));
|
||||
}
|
||||
|
||||
TEST(TestFlatten, FlattenFirstTwoDims) {
|
||||
// Test flatten on first two dimensions
|
||||
at::Tensor tensor = at::ones({2, 3, 4}, at::kFloat);
|
||||
|
||||
at::Tensor flattened = tensor.flatten(0, 1);
|
||||
ASSERT_EQ(flattened.sizes(), c10::IntArrayRef({6, 4}));
|
||||
}
|
||||
|
||||
TEST(TestFlatten, FlattenLastTwoDims) {
|
||||
// Test flatten on last two dimensions
|
||||
at::Tensor tensor = at::ones({2, 3, 4}, at::kFloat);
|
||||
|
||||
at::Tensor flattened = tensor.flatten(1, 2);
|
||||
ASSERT_EQ(flattened.sizes(), c10::IntArrayRef({2, 12}));
|
||||
}
|
||||
|
||||
TEST(TestFlatten, FlattenDataIntegrity) {
|
||||
// Test that flatten preserves data
|
||||
at::Tensor tensor = at::arange(24, at::kFloat).reshape({2, 3, 4});
|
||||
at::Tensor flattened = tensor.flatten(0, -1);
|
||||
|
||||
const float* original_data = tensor.data_ptr<float>();
|
||||
const float* flattened_data = flattened.data_ptr<float>();
|
||||
|
||||
for (int64_t i = 0; i < tensor.numel(); ++i) {
|
||||
ASSERT_EQ(original_data[i], flattened_data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Unflatten Tests
|
||||
// ============================================================================
|
||||
|
||||
TEST(TestUnflatten, UnflattenBasic) {
|
||||
// Test basic unflatten operation
|
||||
at::Tensor tensor = at::ones({4, 6, 8}, at::kFloat);
|
||||
|
||||
// Unflatten dimension 1 (size 6) into (2, 3)
|
||||
at::Tensor unflattened = tensor.unflatten(1, c10::IntArrayRef({2, 3}));
|
||||
ASSERT_EQ(unflattened.sizes(), c10::IntArrayRef({4, 2, 3, 8}));
|
||||
ASSERT_EQ(unflattened.numel(), tensor.numel());
|
||||
}
|
||||
|
||||
TEST(TestUnflatten, UnflattenFirstDim) {
|
||||
// Test unflatten on first dimension
|
||||
at::Tensor tensor = at::ones({6, 4}, at::kFloat);
|
||||
|
||||
// Unflatten dimension 0 (size 6) into (2, 3)
|
||||
at::Tensor unflattened = tensor.unflatten(0, c10::IntArrayRef({2, 3}));
|
||||
ASSERT_EQ(unflattened.sizes(), c10::IntArrayRef({2, 3, 4}));
|
||||
}
|
||||
|
||||
TEST(TestUnflatten, UnflattenLastDim) {
|
||||
// Test unflatten on last dimension
|
||||
at::Tensor tensor = at::ones({2, 12}, at::kFloat);
|
||||
|
||||
// Unflatten dimension 1 (size 12) into (3, 4)
|
||||
at::Tensor unflattened = tensor.unflatten(1, c10::IntArrayRef({3, 4}));
|
||||
ASSERT_EQ(unflattened.sizes(), c10::IntArrayRef({2, 3, 4}));
|
||||
}
|
||||
|
||||
TEST(TestUnflatten, UnflattenNegativeDim) {
|
||||
// Test unflatten with negative dimension index
|
||||
at::Tensor tensor = at::ones({4, 6, 8}, at::kFloat);
|
||||
|
||||
// Unflatten dimension -1 (last dim, size 8) into (4, 2)
|
||||
at::Tensor unflattened = tensor.unflatten(-1, c10::IntArrayRef({4, 2}));
|
||||
ASSERT_EQ(unflattened.sizes(), c10::IntArrayRef({4, 6, 4, 2}));
|
||||
}
|
||||
|
||||
TEST(TestUnflatten, UnflattenSymInt) {
|
||||
// Test unflatten_symint (should behave same as unflatten)
|
||||
at::Tensor tensor = at::ones({4, 6, 8}, at::kFloat);
|
||||
|
||||
// Unflatten dimension 1 using symint version
|
||||
// Note: Must keep the underlying data alive
|
||||
std::vector<c10::SymInt> sizes_vec = {2, 3};
|
||||
c10::SymIntArrayRef sizes(sizes_vec);
|
||||
at::Tensor unflattened = tensor.unflatten_symint(1, sizes);
|
||||
ASSERT_EQ(unflattened.sizes(), c10::IntArrayRef({4, 2, 3, 8}));
|
||||
}
|
||||
|
||||
TEST(TestUnflatten, UnflattenDataIntegrity) {
|
||||
// Test that unflatten preserves data
|
||||
at::Tensor tensor = at::arange(24, at::kFloat).reshape({2, 12});
|
||||
at::Tensor unflattened = tensor.unflatten(1, c10::IntArrayRef({3, 4}));
|
||||
|
||||
// Verify shape
|
||||
ASSERT_EQ(unflattened.sizes(), c10::IntArrayRef({2, 3, 4}));
|
||||
|
||||
// Verify numel
|
||||
ASSERT_EQ(unflattened.numel(), tensor.numel());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Flatten and Unflatten Combined Tests
|
||||
// ============================================================================
|
||||
|
||||
TEST(TestFlattenUnflatten, RoundTrip) {
|
||||
// Test that flatten followed by unflatten restores original shape
|
||||
at::Tensor tensor = at::arange(24, at::kFloat).reshape({2, 3, 4});
|
||||
|
||||
// Flatten dimensions 1 and 2
|
||||
at::Tensor flattened = tensor.flatten(1, 2);
|
||||
ASSERT_EQ(flattened.sizes(), c10::IntArrayRef({2, 12}));
|
||||
|
||||
// Unflatten back to original shape
|
||||
at::Tensor unflattened = flattened.unflatten(1, c10::IntArrayRef({3, 4}));
|
||||
ASSERT_EQ(unflattened.sizes(), c10::IntArrayRef({2, 3, 4}));
|
||||
|
||||
// Verify data integrity
|
||||
ASSERT_EQ(tensor.numel(), unflattened.numel());
|
||||
}
|
||||
|
||||
TEST(TestFlattenUnflatten, MultipleOperations) {
|
||||
// Test multiple flatten/unflatten operations
|
||||
at::Tensor tensor = at::ones({2, 3, 4, 5}, at::kFloat);
|
||||
|
||||
// Flatten all dimensions
|
||||
at::Tensor flattened = tensor.flatten(0, -1);
|
||||
ASSERT_EQ(flattened.sizes(), c10::IntArrayRef({120}));
|
||||
|
||||
// Unflatten into different shape
|
||||
at::Tensor unflattened = flattened.unflatten(0, c10::IntArrayRef({6, 20}));
|
||||
ASSERT_EQ(unflattened.sizes(), c10::IntArrayRef({6, 20}));
|
||||
|
||||
// Unflatten again
|
||||
at::Tensor final_tensor = unflattened.unflatten(1, c10::IntArrayRef({4, 5}));
|
||||
ASSERT_EQ(final_tensor.sizes(), c10::IntArrayRef({6, 4, 5}));
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <ATen/ops/from_blob.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/common/macros.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
COMMON_DECLARE_bool(use_stride_kernel);
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA)
|
||||
#include <cuda_runtime.h>
|
||||
#elif defined(PADDLE_WITH_HIP)
|
||||
#include <hip/hip_runtime.h>
|
||||
#endif
|
||||
|
||||
// ======================== CPU place detection ========================
|
||||
|
||||
// No device specified: CPU pointer → tensor must be on CPU.
|
||||
TEST(ATenFromBlobTest, CpuPtrDefaultsToCpu) {
|
||||
float data[4] = {1.0f, 2.0f, 3.0f, 4.0f};
|
||||
at::Tensor t = at::from_blob(data, {4}, at::kFloat);
|
||||
ASSERT_TRUE(t.is_cpu());
|
||||
ASSERT_EQ(t.scalar_type(), at::kFloat);
|
||||
ASSERT_EQ(t.numel(), 4);
|
||||
}
|
||||
|
||||
// Explicitly pass CPU options: still CPU.
|
||||
TEST(ATenFromBlobTest, CpuPtrWithCpuOptions) {
|
||||
float data[3] = {1.0f, 2.0f, 3.0f};
|
||||
at::Tensor t = at::from_blob(
|
||||
data, {3}, at::TensorOptions().dtype(at::kFloat).device(at::kCPU));
|
||||
ASSERT_TRUE(t.is_cpu());
|
||||
}
|
||||
|
||||
// Data pointer must be preserved (no copy).
|
||||
TEST(ATenFromBlobTest, DataPtrPreserved) {
|
||||
float data[4] = {10.f, 20.f, 30.f, 40.f};
|
||||
at::Tensor t = at::from_blob(data, {4}, at::kFloat);
|
||||
ASSERT_EQ(t.data_ptr<float>(), data);
|
||||
}
|
||||
|
||||
// Shape and strides are correctly set.
|
||||
TEST(ATenFromBlobTest, ShapeAndStrides) {
|
||||
float data[6] = {};
|
||||
at::Tensor t = at::from_blob(data, {2, 3}, at::kFloat);
|
||||
ASSERT_EQ(t.sizes()[0], 2);
|
||||
ASSERT_EQ(t.sizes()[1], 3);
|
||||
// contiguous strides: [3, 1]
|
||||
ASSERT_EQ(t.strides()[0], 3);
|
||||
ASSERT_EQ(t.strides()[1], 1);
|
||||
}
|
||||
|
||||
// Explicit strides overload.
|
||||
TEST(ATenFromBlobTest, ExplicitStrides) {
|
||||
if (!FLAGS_use_stride_kernel) {
|
||||
return;
|
||||
}
|
||||
// Row-major 2×3 laid out in memory, but we interpret as column-major strides
|
||||
float data[6] = {1, 2, 3, 4, 5, 6};
|
||||
at::Tensor t = at::from_blob(data, {2, 3}, {1, 2}, at::kFloat);
|
||||
ASSERT_EQ(t.strides()[0], 1);
|
||||
ASSERT_EQ(t.strides()[1], 2);
|
||||
ASSERT_TRUE(t.is_cpu());
|
||||
}
|
||||
|
||||
// Deleter is called when the tensor is destroyed.
|
||||
TEST(ATenFromBlobTest, DeleterCalled) {
|
||||
bool deleted = false;
|
||||
{
|
||||
float* data = new float[4]{};
|
||||
at::Tensor t = at::from_blob(
|
||||
data,
|
||||
{4},
|
||||
[&deleted](void* p) {
|
||||
deleted = true;
|
||||
delete[] static_cast<float*>(p);
|
||||
},
|
||||
at::kFloat);
|
||||
ASSERT_FALSE(deleted);
|
||||
}
|
||||
ASSERT_TRUE(deleted);
|
||||
}
|
||||
|
||||
// Deleter + strides overload.
|
||||
TEST(ATenFromBlobTest, DeleterWithStrides) {
|
||||
bool deleted = false;
|
||||
{
|
||||
float* data = new float[6]{};
|
||||
at::Tensor t = at::from_blob(
|
||||
data,
|
||||
{2, 3},
|
||||
{3, 1},
|
||||
[&deleted](void* p) {
|
||||
deleted = true;
|
||||
delete[] static_cast<float*>(p);
|
||||
},
|
||||
at::kFloat);
|
||||
ASSERT_FALSE(deleted);
|
||||
ASSERT_TRUE(t.is_cpu());
|
||||
}
|
||||
ASSERT_TRUE(deleted);
|
||||
}
|
||||
|
||||
// ======================== GPU place detection ========================
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
|
||||
// No device specified: GPU pointer → tensor must be on CUDA automatically.
|
||||
TEST(ATenFromBlobTest, GpuPtrDefaultsToCuda) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
float* d_data = nullptr;
|
||||
#if defined(PADDLE_WITH_CUDA)
|
||||
cudaMalloc(&d_data, 4 * sizeof(float));
|
||||
#else
|
||||
hipMalloc(&d_data, 4 * sizeof(float));
|
||||
#endif
|
||||
|
||||
at::Tensor t = at::from_blob(d_data, {4}, at::kFloat);
|
||||
ASSERT_TRUE(t.is_cuda())
|
||||
<< "Expected GPU tensor when data pointer lives on device";
|
||||
ASSERT_FALSE(t.is_cpu());
|
||||
ASSERT_EQ(t.scalar_type(), at::kFloat);
|
||||
ASSERT_EQ(t.numel(), 4);
|
||||
ASSERT_EQ(t.data_ptr<float>(), d_data);
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA)
|
||||
cudaFree(d_data);
|
||||
#else
|
||||
hipFree(d_data);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Explicit CUDA device option + GPU pointer → still CUDA.
|
||||
TEST(ATenFromBlobTest, GpuPtrWithCudaOptions) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
float* d_data = nullptr;
|
||||
#if defined(PADDLE_WITH_CUDA)
|
||||
cudaMalloc(&d_data, 4 * sizeof(float));
|
||||
#else
|
||||
hipMalloc(&d_data, 4 * sizeof(float));
|
||||
#endif
|
||||
|
||||
at::Tensor t = at::from_blob(
|
||||
d_data, {4}, at::TensorOptions().dtype(at::kFloat).device(at::kCUDA, 0));
|
||||
ASSERT_TRUE(t.is_cuda());
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA)
|
||||
cudaFree(d_data);
|
||||
#else
|
||||
hipFree(d_data);
|
||||
#endif
|
||||
}
|
||||
|
||||
// target_device overrides auto-detection.
|
||||
TEST(ATenFromBlobTest, TargetDeviceOverride) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
float* d_data = nullptr;
|
||||
#if defined(PADDLE_WITH_CUDA)
|
||||
cudaMalloc(&d_data, 4 * sizeof(float));
|
||||
#else
|
||||
hipMalloc(&d_data, 4 * sizeof(float));
|
||||
#endif
|
||||
|
||||
at::Tensor t = at::for_blob(d_data, {4})
|
||||
.options(at::kFloat)
|
||||
.target_device(at::Device(at::kCUDA, 0))
|
||||
.make_tensor();
|
||||
ASSERT_TRUE(t.is_cuda());
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA)
|
||||
cudaFree(d_data);
|
||||
#else
|
||||
hipFree(d_data);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // PADDLE_WITH_CUDA || PADDLE_WITH_HIP
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ======================== register_hook tests ========================
|
||||
|
||||
TEST(TensorHookTest, RegisterHookThrows) {
|
||||
// register_hook should throw exception as Paddle doesn't support hooks
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
auto hook = [](const at::Tensor& grad) { return grad; };
|
||||
EXPECT_THROW(t.register_hook(hook), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(TensorHookTest, RegisterHookWithLambda) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
// Lambda that captures nothing
|
||||
EXPECT_THROW(t.register_hook([](const at::Tensor&) { return at::Tensor(); }),
|
||||
std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(TensorHookTest, RegisterHookWithMoveOnly) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
// Move-only lambda
|
||||
EXPECT_THROW(
|
||||
t.register_hook([](const at::Tensor&) mutable { return at::Tensor(); }),
|
||||
std::runtime_error);
|
||||
}
|
||||
@@ -0,0 +1,447 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/TensorIndexing.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/List.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/common/macros.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
COMMON_DECLARE_bool(use_stride_kernel);
|
||||
|
||||
// ======================== index tests ========================
|
||||
|
||||
TEST(TensorIndexTest, IndexWithSingleTensor) {
|
||||
// Create tensor [0, 10, 20, 30, 40]
|
||||
at::Tensor t = at::arange(5, at::kFloat);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
t.data_ptr<float>()[i] = static_cast<float>(i * 10);
|
||||
}
|
||||
|
||||
// Index with [0, 2, 4]
|
||||
at::Tensor idx = at::empty({3}, at::kLong);
|
||||
int64_t* idx_data = idx.data_ptr<int64_t>();
|
||||
idx_data[0] = 0;
|
||||
idx_data[1] = 2;
|
||||
idx_data[2] = 4;
|
||||
|
||||
c10::List<::std::optional<at::Tensor>> indices;
|
||||
indices.push_back(idx);
|
||||
|
||||
at::Tensor result = t.index(indices);
|
||||
ASSERT_EQ(result.numel(), 3);
|
||||
|
||||
float* result_data = result.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(result_data[0], 0.0f);
|
||||
ASSERT_FLOAT_EQ(result_data[1], 20.0f);
|
||||
ASSERT_FLOAT_EQ(result_data[2], 40.0f);
|
||||
}
|
||||
|
||||
TEST(TensorIndexTest, SliceKeepsStrideWithoutContiguousCopy) {
|
||||
if (!FLAGS_use_stride_kernel) {
|
||||
return;
|
||||
}
|
||||
at::Tensor base = at::arange(24, at::kFloat).reshape({4, 6});
|
||||
at::Tensor transposed = base.t(); // shape: [6, 4], strides: [1, 6]
|
||||
ASSERT_FALSE(transposed.is_contiguous());
|
||||
|
||||
at::Tensor sliced =
|
||||
transposed.index({at::indexing::Slice(1, 5), at::indexing::Slice(0, 3)});
|
||||
|
||||
ASSERT_EQ(sliced.sizes(), c10::IntArrayRef({4, 3}));
|
||||
ASSERT_EQ(sliced.strides(), c10::IntArrayRef({1, 6}));
|
||||
ASSERT_EQ(sliced.stride(0), transposed.stride(0));
|
||||
ASSERT_EQ(sliced.stride(1), transposed.stride(1));
|
||||
ASSERT_FALSE(sliced.is_contiguous());
|
||||
}
|
||||
|
||||
TEST(TensorIndexTest, IndexWithEmptyInitializerListReturnsSelf) {
|
||||
at::Tensor t = at::arange(5, at::kFloat);
|
||||
|
||||
// PyTorch throws for empty index list
|
||||
ASSERT_THROW(t.index(std::initializer_list<at::indexing::TensorIndex>{}),
|
||||
std::exception);
|
||||
}
|
||||
|
||||
TEST(TensorIndexTest, IndexWithTensorInitializerList) {
|
||||
at::Tensor t = at::arange(5, at::kFloat);
|
||||
|
||||
at::Tensor idx = at::empty({3}, at::kLong);
|
||||
int64_t* idx_data = idx.data_ptr<int64_t>();
|
||||
idx_data[0] = 0;
|
||||
idx_data[1] = 2;
|
||||
idx_data[2] = 4;
|
||||
|
||||
at::Tensor result = t.index({idx});
|
||||
|
||||
ASSERT_EQ(result.numel(), 3);
|
||||
float* result_data = result.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(result_data[0], 0.0f);
|
||||
ASSERT_FLOAT_EQ(result_data[1], 2.0f);
|
||||
ASSERT_FLOAT_EQ(result_data[2], 4.0f);
|
||||
}
|
||||
|
||||
TEST(TensorIndexTest, MemberIndexWithArrayRefTensorIndices) {
|
||||
if (!FLAGS_use_stride_kernel) {
|
||||
return;
|
||||
}
|
||||
at::Tensor base = at::arange(24, at::kFloat).reshape({4, 6});
|
||||
at::Tensor transposed = base.t();
|
||||
std::vector<at::indexing::TensorIndex> indices = {at::indexing::Slice(1, 5),
|
||||
at::indexing::Slice(0, 3)};
|
||||
|
||||
at::Tensor sliced = transposed.index(indices);
|
||||
|
||||
ASSERT_EQ(sliced.sizes(), c10::IntArrayRef({4, 3}));
|
||||
ASSERT_EQ(sliced.strides(), c10::IntArrayRef({1, 6}));
|
||||
}
|
||||
|
||||
TEST(TensorIndexTest, MixedSliceAndTensorIndicesThrows) {
|
||||
at::Tensor t = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
|
||||
at::Tensor idx = at::empty({2}, at::kLong);
|
||||
idx.data_ptr<int64_t>()[0] = 0;
|
||||
idx.data_ptr<int64_t>()[1] = 2;
|
||||
|
||||
ASSERT_THROW(t.index({at::indexing::Slice(0, 2), idx}), std::exception);
|
||||
}
|
||||
|
||||
// ======================== index_put_ tests ========================
|
||||
|
||||
TEST(TensorIndexPutTest, IndexPutInplaceWithTensor) {
|
||||
at::Tensor t = at::zeros({5}, at::kFloat);
|
||||
float* original_data_ptr = t.data_ptr<float>();
|
||||
|
||||
// Create index tensor [1, 3]
|
||||
at::Tensor idx = at::empty({2}, at::kLong);
|
||||
int64_t* idx_data = idx.data_ptr<int64_t>();
|
||||
idx_data[0] = 1;
|
||||
idx_data[1] = 3;
|
||||
|
||||
// Values to put
|
||||
at::Tensor values = at::full({2}, 99.0f, at::kFloat);
|
||||
|
||||
c10::List<::std::optional<at::Tensor>> indices;
|
||||
indices.push_back(idx);
|
||||
|
||||
t.index_put_(indices, values);
|
||||
|
||||
// Verify data pointer unchanged (inplace)
|
||||
ASSERT_EQ(t.data_ptr<float>(), original_data_ptr);
|
||||
|
||||
float* data = t.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 0.0f);
|
||||
ASSERT_FLOAT_EQ(data[1], 99.0f);
|
||||
ASSERT_FLOAT_EQ(data[2], 0.0f);
|
||||
ASSERT_FLOAT_EQ(data[3], 99.0f);
|
||||
ASSERT_FLOAT_EQ(data[4], 0.0f);
|
||||
}
|
||||
|
||||
TEST(TensorIndexPutTest, IndexPutInplaceWithScalar) {
|
||||
at::Tensor t = at::zeros({5}, at::kFloat);
|
||||
float* original_data_ptr = t.data_ptr<float>();
|
||||
|
||||
at::Tensor idx = at::empty({2}, at::kLong);
|
||||
int64_t* idx_data = idx.data_ptr<int64_t>();
|
||||
idx_data[0] = 0;
|
||||
idx_data[1] = 4;
|
||||
|
||||
t.index_put_({idx}, at::Scalar(7.0));
|
||||
|
||||
// Verify data pointer unchanged (inplace)
|
||||
ASSERT_EQ(t.data_ptr<float>(), original_data_ptr);
|
||||
|
||||
float* data = t.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 7.0f);
|
||||
ASSERT_FLOAT_EQ(data[1], 0.0f);
|
||||
ASSERT_FLOAT_EQ(data[4], 7.0f);
|
||||
}
|
||||
|
||||
TEST(TensorIndexPutTest, IndexPutNonInplace) {
|
||||
at::Tensor t = at::zeros({5}, at::kFloat);
|
||||
|
||||
at::Tensor idx = at::empty({2}, at::kLong);
|
||||
int64_t* idx_data = idx.data_ptr<int64_t>();
|
||||
idx_data[0] = 1;
|
||||
idx_data[1] = 3;
|
||||
|
||||
at::Tensor values = at::full({2}, 42.0f, at::kFloat);
|
||||
|
||||
c10::List<::std::optional<at::Tensor>> indices;
|
||||
indices.push_back(idx);
|
||||
|
||||
at::Tensor result = t.index_put(indices, values);
|
||||
|
||||
// Original should be unchanged
|
||||
ASSERT_FLOAT_EQ(t.data_ptr<float>()[1], 0.0f);
|
||||
|
||||
// Result should have the values
|
||||
float* rdata = result.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(rdata[1], 42.0f);
|
||||
ASSERT_FLOAT_EQ(rdata[3], 42.0f);
|
||||
}
|
||||
|
||||
// ======================= Additional index edge case tests
|
||||
// =======================
|
||||
|
||||
TEST(TensorIndexTest, IndexWithEmptyList) {
|
||||
// Test index with empty indices list (should return self)
|
||||
at::Tensor t = at::arange(5, at::kFloat);
|
||||
c10::List<::std::optional<at::Tensor>> indices;
|
||||
|
||||
at::Tensor result = t.index(indices);
|
||||
ASSERT_EQ(result.numel(), 5);
|
||||
}
|
||||
|
||||
TEST(TensorIndexTest, IndexWithMultipleIndices) {
|
||||
// Test index with multiple indices (2D indexing)
|
||||
at::Tensor t = at::arange(9, at::kFloat).reshape({3, 3});
|
||||
|
||||
at::Tensor idx0 = at::empty({2}, at::kLong);
|
||||
int64_t* idx0_data = idx0.data_ptr<int64_t>();
|
||||
idx0_data[0] = 0;
|
||||
idx0_data[1] = 1;
|
||||
|
||||
at::Tensor idx1 = at::empty({2}, at::kLong);
|
||||
int64_t* idx1_data = idx1.data_ptr<int64_t>();
|
||||
idx1_data[0] = 0;
|
||||
idx1_data[1] = 2;
|
||||
|
||||
c10::List<::std::optional<at::Tensor>> indices;
|
||||
indices.push_back(idx0);
|
||||
indices.push_back(idx1);
|
||||
|
||||
at::Tensor result = t.index(indices);
|
||||
ASSERT_EQ(result.numel(), 2);
|
||||
}
|
||||
|
||||
TEST(TensorIndexTest, IndexWithOptionalNone) {
|
||||
// Test index with optional None in indices
|
||||
// None means "select all" along that dimension
|
||||
at::Tensor t = at::arange(9, at::kFloat).reshape({3, 3});
|
||||
|
||||
at::Tensor idx = at::empty({2}, at::kLong);
|
||||
idx.data_ptr<int64_t>()[0] = 0;
|
||||
idx.data_ptr<int64_t>()[1] = 2;
|
||||
|
||||
c10::List<::std::optional<at::Tensor>> indices;
|
||||
indices.push_back(::std::nullopt); // None = select all rows
|
||||
indices.push_back(idx); // [0, 2] = select columns 0 and 2
|
||||
|
||||
at::Tensor result = t.index(indices);
|
||||
// Result should be shape {3, 2} = 6 elements
|
||||
// Columns 0 and 2 from all rows: [[0,2], [3,5], [6,8]]
|
||||
ASSERT_EQ(result.numel(), 6);
|
||||
}
|
||||
|
||||
TEST(TensorIndexTest, FreeIndexWithAllNoneReturnsSelf) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
c10::List<::std::optional<at::Tensor>> indices;
|
||||
indices.push_back(::std::nullopt);
|
||||
indices.push_back(::std::nullopt);
|
||||
|
||||
at::Tensor result = at::index(t, indices);
|
||||
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({2, 3}));
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[0], 0.0f);
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[5], 5.0f);
|
||||
}
|
||||
|
||||
TEST(TensorIndexTest, FreeIndexWithSingleLeadingTensor) {
|
||||
at::Tensor t = at::arange(9, at::kFloat).reshape({3, 3});
|
||||
at::Tensor idx = at::empty({2}, at::kLong);
|
||||
idx.data_ptr<int64_t>()[0] = 2;
|
||||
idx.data_ptr<int64_t>()[1] = 0;
|
||||
|
||||
c10::List<::std::optional<at::Tensor>> indices;
|
||||
indices.push_back(idx);
|
||||
|
||||
at::Tensor result = at::index(t, indices);
|
||||
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({2, 3}));
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[0], 6.0f);
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[1], 7.0f);
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[2], 8.0f);
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[3], 0.0f);
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[4], 1.0f);
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[5], 2.0f);
|
||||
}
|
||||
|
||||
TEST(TensorIndexTest, MixedTensorNoneFullSliceIndex) {
|
||||
at::Tensor base = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
at::Tensor idx = at::empty({2}, at::kLong);
|
||||
idx.data_ptr<int64_t>()[0] = 2;
|
||||
idx.data_ptr<int64_t>()[1] = 0;
|
||||
|
||||
at::Tensor result =
|
||||
base.index({idx, at::indexing::None, at::indexing::Slice()});
|
||||
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({2, 1, 4}));
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[0], 8.0f);
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[1], 9.0f);
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[2], 10.0f);
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[3], 11.0f);
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[4], 0.0f);
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[7], 3.0f);
|
||||
}
|
||||
|
||||
TEST(TensorIndexTest, MixedFullSliceWithMultipleTensorIndicesThrows) {
|
||||
at::Tensor base = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
at::Tensor idx0 = at::empty({2}, at::kLong);
|
||||
idx0.data_ptr<int64_t>()[0] = 0;
|
||||
idx0.data_ptr<int64_t>()[1] = 1;
|
||||
at::Tensor idx1 = at::empty({2}, at::kLong);
|
||||
idx1.data_ptr<int64_t>()[0] = 0;
|
||||
idx1.data_ptr<int64_t>()[1] = 1;
|
||||
|
||||
ASSERT_THROW(base.index({idx0, at::indexing::Slice(), idx1}), std::exception);
|
||||
}
|
||||
|
||||
TEST(TensorIndexPutTest, IndexPutAccumulate) {
|
||||
// Test index_put_ with accumulate=true
|
||||
at::Tensor t = at::zeros({5}, at::kFloat);
|
||||
float* original_data_ptr = t.data_ptr<float>();
|
||||
|
||||
at::Tensor idx = at::empty({2}, at::kLong);
|
||||
idx.data_ptr<int64_t>()[0] = 1;
|
||||
idx.data_ptr<int64_t>()[1] = 1;
|
||||
|
||||
at::Tensor values = at::full({2}, 5.0f, at::kFloat);
|
||||
|
||||
c10::List<::std::optional<at::Tensor>> indices;
|
||||
indices.push_back(idx);
|
||||
|
||||
t.index_put_(indices, values, true); // accumulate=true
|
||||
|
||||
// Verify data pointer unchanged (inplace)
|
||||
ASSERT_EQ(t.data_ptr<float>(), original_data_ptr);
|
||||
|
||||
float* data = t.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 0.0f);
|
||||
ASSERT_FLOAT_EQ(data[1], 10.0f); // 5 + 5 (accumulated)
|
||||
ASSERT_FLOAT_EQ(data[2], 0.0f);
|
||||
}
|
||||
|
||||
TEST(TensorIndexPutTest, IndexPutWith2D) {
|
||||
// Test index_put_ with 2D tensor
|
||||
at::Tensor t = at::zeros({3, 3}, at::kFloat);
|
||||
float* original_data_ptr = t.data_ptr<float>();
|
||||
|
||||
at::Tensor idx0 = at::arange(2, at::kLong);
|
||||
idx0.data_ptr<int64_t>()[0] = 0;
|
||||
idx0.data_ptr<int64_t>()[1] = 1;
|
||||
at::Tensor idx1 = at::arange(2, at::kLong);
|
||||
idx1.data_ptr<int64_t>()[0] = 0;
|
||||
idx1.data_ptr<int64_t>()[1] = 1;
|
||||
|
||||
c10::List<::std::optional<at::Tensor>> indices;
|
||||
indices.push_back(idx0);
|
||||
indices.push_back(idx1);
|
||||
|
||||
at::Tensor values = at::full({2}, 9.0f, at::kFloat);
|
||||
|
||||
t.index_put_(indices, values);
|
||||
|
||||
// Verify data pointer unchanged (inplace)
|
||||
ASSERT_EQ(t.data_ptr<float>(), original_data_ptr);
|
||||
|
||||
float* data = t.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 9.0f); // [0,0]
|
||||
ASSERT_FLOAT_EQ(data[4], 9.0f); // [1,1]
|
||||
}
|
||||
|
||||
TEST(TensorIndexPutTest, IndexPutNonInplaceAccumulate) {
|
||||
// Test index_put with accumulate=true (non-inplace)
|
||||
at::Tensor t = at::zeros({5}, at::kFloat);
|
||||
|
||||
at::Tensor idx = at::empty({2}, at::kLong);
|
||||
idx.data_ptr<int64_t>()[0] = 1;
|
||||
idx.data_ptr<int64_t>()[1] = 1;
|
||||
at::Tensor values = at::full({2}, 3.0f, at::kFloat);
|
||||
|
||||
c10::List<::std::optional<at::Tensor>> indices;
|
||||
indices.push_back(idx);
|
||||
|
||||
at::Tensor result = t.index_put(indices, values, true);
|
||||
|
||||
// Original unchanged
|
||||
ASSERT_FLOAT_EQ(t.data_ptr<float>()[1], 0.0f);
|
||||
// Result has accumulated
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[1], 6.0f);
|
||||
}
|
||||
|
||||
TEST(TensorIndexPutTest, IndexPutArrayRefWithTensorValue) {
|
||||
at::Tensor t = at::zeros({5}, at::kFloat);
|
||||
at::Tensor idx = at::empty({2}, at::kLong);
|
||||
idx.data_ptr<int64_t>()[0] = 1;
|
||||
idx.data_ptr<int64_t>()[1] = 4;
|
||||
at::Tensor values = at::full({2}, 13.0f, at::kFloat);
|
||||
|
||||
std::vector<at::indexing::TensorIndex> tensor_indices = {idx};
|
||||
t.index_put_(at::ArrayRef<at::indexing::TensorIndex>(tensor_indices), values);
|
||||
|
||||
ASSERT_FLOAT_EQ(t.data_ptr<float>()[0], 0.0f);
|
||||
ASSERT_FLOAT_EQ(t.data_ptr<float>()[1], 13.0f);
|
||||
ASSERT_FLOAT_EQ(t.data_ptr<float>()[2], 0.0f);
|
||||
ASSERT_FLOAT_EQ(t.data_ptr<float>()[4], 13.0f);
|
||||
}
|
||||
|
||||
TEST(TensorIndexPutTest, IndexPutArrayRefWithNoneValue) {
|
||||
at::Tensor t = at::zeros({2, 3}, at::kFloat);
|
||||
at::Tensor values = at::full({1, 2, 3}, 6.0f, at::kFloat);
|
||||
|
||||
t.index_put_({at::indexing::None}, values);
|
||||
|
||||
ASSERT_EQ(t.sizes(), c10::IntArrayRef({2, 3}));
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
ASSERT_FLOAT_EQ(t.data_ptr<float>()[i], 6.0f);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TensorIndexPutTest, IndexPutArrayRefWithTensorNoneAndSlice) {
|
||||
at::Tensor t = at::zeros({3, 4}, at::kFloat);
|
||||
at::Tensor idx = at::empty({2}, at::kLong);
|
||||
idx.data_ptr<int64_t>()[0] = 2;
|
||||
idx.data_ptr<int64_t>()[1] = 0;
|
||||
at::Tensor values = at::full({2, 1, 4}, 8.0f, at::kFloat);
|
||||
|
||||
t.index_put_({idx, at::indexing::None, at::indexing::Slice()}, values);
|
||||
|
||||
ASSERT_EQ(t.sizes(), c10::IntArrayRef({3, 4}));
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
ASSERT_FLOAT_EQ(t.data_ptr<float>()[i], 8.0f);
|
||||
ASSERT_FLOAT_EQ(t.data_ptr<float>()[8 + i], 8.0f);
|
||||
}
|
||||
for (int i = 4; i < 8; ++i) {
|
||||
ASSERT_FLOAT_EQ(t.data_ptr<float>()[i], 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TensorIndexPutTest, IndexPutArrayRefWithNoneScalarValue) {
|
||||
at::Tensor t = at::zeros({2, 3}, at::kFloat);
|
||||
|
||||
t.index_put_({at::indexing::None}, at::Scalar(4.0));
|
||||
|
||||
ASSERT_EQ(t.sizes(), c10::IntArrayRef({2, 3}));
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
ASSERT_FLOAT_EQ(t.data_ptr<float>()[i], 4.0f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ============================================================
|
||||
// Tests for at::Tensor::item() / at::Tensor::item<T>()
|
||||
// ============================================================
|
||||
|
||||
TEST(TensorItemTest, ItemFloat_ReturnsScalar) {
|
||||
// item() on a single-element float tensor returns an at::Scalar
|
||||
at::Tensor t = at::tensor({3.14f}, at::kFloat);
|
||||
at::Scalar s = t.item();
|
||||
|
||||
ASSERT_NEAR(s.to<float>(), 3.14f, 1e-5f);
|
||||
}
|
||||
|
||||
TEST(TensorItemTest, ItemDouble_ReturnsScalar) {
|
||||
// item() on a single-element double tensor
|
||||
at::Tensor t = at::tensor({2.718281828}, at::kDouble);
|
||||
at::Scalar s = t.item();
|
||||
|
||||
ASSERT_NEAR(s.to<double>(), 2.718281828, 1e-9);
|
||||
}
|
||||
|
||||
TEST(TensorItemTest, ItemInt32_ReturnsScalar) {
|
||||
// item() on a single-element int32 tensor
|
||||
at::Tensor t = at::tensor({42}, at::kInt);
|
||||
at::Scalar s = t.item();
|
||||
|
||||
ASSERT_EQ(s.to<int32_t>(), 42);
|
||||
}
|
||||
|
||||
TEST(TensorItemTest, ItemInt64_ReturnsScalar) {
|
||||
// item() on a single-element int64 tensor
|
||||
at::Tensor t = at::tensor({static_cast<int64_t>(1234567890)}, at::kLong);
|
||||
at::Scalar s = t.item();
|
||||
|
||||
ASSERT_EQ(s.to<int64_t>(), 1234567890LL);
|
||||
}
|
||||
|
||||
TEST(TensorItemTest, ItemTemplated_Float) {
|
||||
// item<float>() returns float directly
|
||||
at::Tensor t = at::tensor({1.5f}, at::kFloat);
|
||||
float val = t.item<float>();
|
||||
|
||||
ASSERT_FLOAT_EQ(val, 1.5f);
|
||||
}
|
||||
|
||||
TEST(TensorItemTest, ItemTemplated_Double) {
|
||||
// item<double>() returns double directly
|
||||
at::Tensor t = at::tensor({1.0 / 3.0}, at::kDouble);
|
||||
double val = t.item<double>();
|
||||
|
||||
ASSERT_NEAR(val, 1.0 / 3.0, 1e-15);
|
||||
}
|
||||
|
||||
TEST(TensorItemTest, ItemTemplated_Int32) {
|
||||
// item<int32_t>() on int32 tensor
|
||||
at::Tensor t = at::tensor({-7}, at::kInt);
|
||||
int32_t val = t.item<int32_t>();
|
||||
|
||||
ASSERT_EQ(val, -7);
|
||||
}
|
||||
|
||||
TEST(TensorItemTest, ItemFromSqueezed1D) {
|
||||
// item() works on a tensor that has been reshaped to single element via
|
||||
// squeeze / indexing
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
at::Tensor elem = t[1][2]; // value = 5.0
|
||||
|
||||
ASSERT_FLOAT_EQ(elem.item<float>(), 5.0f);
|
||||
}
|
||||
|
||||
TEST(TensorItemTest, ItemOnMultiElementTensorThrows) {
|
||||
// item() on a tensor with more than one element must throw.
|
||||
at::Tensor t = at::ones({2, 3}, at::kFloat);
|
||||
ASSERT_THROW(t.item(), std::exception);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Tests for at::Tensor::is_variable()
|
||||
// ============================================================
|
||||
|
||||
TEST(TensorIsVariableTest, AlwaysReturnsTrue) {
|
||||
// is_variable() is always true in the eager execution mode.
|
||||
at::Tensor t = at::ones({3, 4}, at::kFloat);
|
||||
ASSERT_TRUE(t.is_variable());
|
||||
}
|
||||
|
||||
TEST(TensorIsVariableTest, AlwaysTrueForScalarTensor) {
|
||||
at::Tensor t = at::tensor({1.0f}, at::kFloat);
|
||||
ASSERT_TRUE(t.is_variable());
|
||||
}
|
||||
|
||||
TEST(TensorIsVariableTest, AlwaysTrueFor1D) {
|
||||
at::Tensor t = at::arange(10, at::kFloat);
|
||||
ASSERT_TRUE(t.is_variable());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Tests for at::Tensor::item() — sparse tensor paths
|
||||
// ============================================================
|
||||
|
||||
TEST(TensorItemSparseTest, EmptySparseCOO_ItemReturnsZero) {
|
||||
// A sparse tensor with nnz == 0: item() must return zero (Scalar(0)).
|
||||
at::Tensor indices = at::zeros({2, 0}, at::kLong);
|
||||
at::Tensor values = at::zeros({0}, at::kFloat);
|
||||
// 1x1 empty sparse tensor
|
||||
at::Tensor sparse = at::sparse_coo_tensor(indices, values, {1, 1});
|
||||
sparse = sparse.coalesce();
|
||||
|
||||
at::Scalar s = sparse.item();
|
||||
|
||||
ASSERT_NEAR(s.to<float>(), 0.0f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(TensorItemSparseTest, CoalescedSparseCOO_SingleNonZero_ReturnsValue) {
|
||||
// 1x1 sparse COO with one non-zero at (0,0) = 5.0.
|
||||
at::Tensor indices = at::tensor({0, 0}, at::kLong).reshape({2, 1});
|
||||
at::Tensor values = at::tensor({5.0f}, at::kFloat);
|
||||
at::Tensor sparse = at::sparse_coo_tensor(indices, values, {1, 1});
|
||||
sparse = sparse.coalesce();
|
||||
|
||||
ASSERT_TRUE(sparse.is_coalesced());
|
||||
at::Scalar s = sparse.item();
|
||||
|
||||
ASSERT_NEAR(s.to<float>(), 5.0f, 1e-5f);
|
||||
}
|
||||
|
||||
TEST(TensorItemSparseTest, NonCoalescedSparseCOO_DuplicateIndices_SumsValues) {
|
||||
// Two entries both at (0,0): item() must sum them (3 + 7 = 10).
|
||||
at::Tensor indices = at::tensor({0, 0, 0, 0}, at::kLong).reshape({2, 2});
|
||||
at::Tensor values = at::tensor({3.0f, 7.0f}, at::kFloat);
|
||||
at::Tensor sparse = at::sparse_coo_tensor(indices, values, {1, 1});
|
||||
|
||||
// Do NOT coalesce — exercising the non-coalesced path.
|
||||
ASSERT_FALSE(sparse.is_coalesced());
|
||||
at::Scalar s = sparse.item();
|
||||
|
||||
ASSERT_NEAR(s.to<float>(), 10.0f, 1e-5f);
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/_local_scalar_dense.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/bfloat16.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ============================================================
|
||||
// Tests for at::_local_scalar_dense()
|
||||
// ============================================================
|
||||
|
||||
TEST(LocalScalarDenseTest, Float32_ReturnsCorrectValue) {
|
||||
at::Tensor t = at::tensor({2.5f}, at::kFloat);
|
||||
at::Scalar s = at::_local_scalar_dense(t);
|
||||
|
||||
ASSERT_NEAR(s.to<float>(), 2.5f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(LocalScalarDenseTest, Float64_ReturnsCorrectValue) {
|
||||
at::Tensor t = at::tensor({3.141592653589793}, at::kDouble);
|
||||
at::Scalar s = at::_local_scalar_dense(t);
|
||||
|
||||
ASSERT_NEAR(s.to<double>(), 3.141592653589793, 1e-12);
|
||||
}
|
||||
|
||||
TEST(LocalScalarDenseTest, Float16_ReturnsCorrectValue) {
|
||||
// Create FP16 tensor from float, then read back via _local_scalar_dense.
|
||||
at::Tensor t = at::tensor({1.5f}, at::kHalf);
|
||||
at::Scalar s = at::_local_scalar_dense(t);
|
||||
|
||||
// Float16 has ~3 significant decimal digits.
|
||||
ASSERT_NEAR(s.to<float>(), 1.5f, 1e-2f);
|
||||
}
|
||||
|
||||
TEST(LocalScalarDenseTest, BFloat16_ReturnsCorrectValue) {
|
||||
at::Tensor t = at::tensor({1.0f}, at::kBFloat16);
|
||||
at::Scalar s = at::_local_scalar_dense(t);
|
||||
|
||||
ASSERT_NEAR(s.to<float>(), 1.0f, 1e-2f);
|
||||
}
|
||||
|
||||
TEST(LocalScalarDenseTest, Int8_ReturnsCorrectValue) {
|
||||
at::Tensor t = at::tensor({static_cast<int8_t>(-7)}, at::kChar);
|
||||
at::Scalar s = at::_local_scalar_dense(t);
|
||||
|
||||
ASSERT_EQ(s.to<int8_t>(), static_cast<int8_t>(-7));
|
||||
}
|
||||
|
||||
TEST(LocalScalarDenseTest, Int16_ReturnsCorrectValue) {
|
||||
at::Tensor t = at::tensor({static_cast<int16_t>(300)}, at::kShort);
|
||||
at::Scalar s = at::_local_scalar_dense(t);
|
||||
|
||||
ASSERT_EQ(s.to<int16_t>(), static_cast<int16_t>(300));
|
||||
}
|
||||
|
||||
TEST(LocalScalarDenseTest, Int32_ReturnsCorrectValue) {
|
||||
at::Tensor t = at::tensor({42}, at::kInt);
|
||||
at::Scalar s = at::_local_scalar_dense(t);
|
||||
|
||||
ASSERT_EQ(s.to<int32_t>(), 42);
|
||||
}
|
||||
|
||||
TEST(LocalScalarDenseTest, Int64_ReturnsCorrectValue) {
|
||||
at::Tensor t = at::tensor({static_cast<int64_t>(9876543210LL)}, at::kLong);
|
||||
at::Scalar s = at::_local_scalar_dense(t);
|
||||
|
||||
ASSERT_EQ(s.to<int64_t>(), 9876543210LL);
|
||||
}
|
||||
|
||||
TEST(LocalScalarDenseTest, UInt8_ReturnsCorrectValue) {
|
||||
at::Tensor t = at::tensor({static_cast<uint8_t>(255)}, at::kByte);
|
||||
at::Scalar s = at::_local_scalar_dense(t);
|
||||
|
||||
ASSERT_EQ(s.to<uint8_t>(), static_cast<uint8_t>(255));
|
||||
}
|
||||
|
||||
TEST(LocalScalarDenseTest, Bool_True_ReturnsCorrectValue) {
|
||||
at::Tensor t = at::tensor({true}, at::kBool);
|
||||
at::Scalar s = at::_local_scalar_dense(t);
|
||||
|
||||
ASSERT_TRUE(s.to<bool>());
|
||||
}
|
||||
|
||||
TEST(LocalScalarDenseTest, Bool_False_ReturnsCorrectValue) {
|
||||
at::Tensor t = at::tensor({false}, at::kBool);
|
||||
at::Scalar s = at::_local_scalar_dense(t);
|
||||
|
||||
ASSERT_FALSE(s.to<bool>());
|
||||
}
|
||||
|
||||
TEST(LocalScalarDenseTest, NegativeValue_Float) {
|
||||
at::Tensor t = at::tensor({-99.0f}, at::kFloat);
|
||||
at::Scalar s = at::_local_scalar_dense(t);
|
||||
|
||||
ASSERT_NEAR(s.to<float>(), -99.0f, 1e-5f);
|
||||
}
|
||||
|
||||
TEST(LocalScalarDenseTest, ZeroValue_Int32) {
|
||||
at::Tensor t = at::tensor({0}, at::kInt);
|
||||
at::Scalar s = at::_local_scalar_dense(t);
|
||||
|
||||
ASSERT_EQ(s.to<int32_t>(), 0);
|
||||
}
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
TEST(LocalScalarDenseTest, GPU_Float32_ReturnsCorrectValue) {
|
||||
// _local_scalar_dense must copy to CPU when the tensor is on GPU.
|
||||
at::Tensor t = at::tensor(
|
||||
{7.0f},
|
||||
at::TensorOptions().dtype(at::kFloat).device(c10::Device(c10::kCUDA, 0)));
|
||||
at::Scalar s = at::_local_scalar_dense(t);
|
||||
|
||||
ASSERT_NEAR(s.to<float>(), 7.0f, 1e-5f);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(LocalScalarDenseTest, EmptyTensor_ThrowsCheck) {
|
||||
// Passing an empty tensor should trigger PD_CHECK in the implementation.
|
||||
at::Tensor t = at::empty({0}, at::kFloat);
|
||||
ASSERT_THROW(at::_local_scalar_dense(t), std::exception);
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/Layout.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ==================== is_pinned tests ====================
|
||||
|
||||
// Test is_pinned for CPU tensor (should be false)
|
||||
TEST(IsPinnedTest, CPUTensorNotPinned) {
|
||||
auto tensor = at::arange(10, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
EXPECT_FALSE(tensor.is_pinned());
|
||||
}
|
||||
|
||||
// Test is_pinned for empty tensor
|
||||
TEST(IsPinnedTest, EmptyTensorNotPinned) {
|
||||
auto tensor = at::empty({0}, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
EXPECT_FALSE(tensor.is_pinned());
|
||||
}
|
||||
|
||||
// Test is_pinned for multi-dimensional tensor
|
||||
TEST(IsPinnedTest, MultiDimTensorNotPinned) {
|
||||
auto tensor = at::empty({2, 3, 4}, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
EXPECT_FALSE(tensor.is_pinned());
|
||||
}
|
||||
|
||||
// ==================== data pointer tests ====================
|
||||
|
||||
TEST(TensorDataPtrTest, ConstDataPtrSupportsConstAndNonConstElementTypes) {
|
||||
auto tensor = at::ones({2, 3}, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
const void* void_ptr = tensor.const_data_ptr();
|
||||
const float* float_ptr = tensor.const_data_ptr<float>();
|
||||
const float* const_float_ptr = tensor.const_data_ptr<const float>();
|
||||
|
||||
EXPECT_NE(void_ptr, nullptr);
|
||||
EXPECT_EQ(static_cast<const void*>(float_ptr), void_ptr);
|
||||
EXPECT_EQ(static_cast<const void*>(const_float_ptr), void_ptr);
|
||||
EXPECT_FLOAT_EQ(float_ptr[0], 1.0f);
|
||||
EXPECT_FLOAT_EQ(const_float_ptr[0], 1.0f);
|
||||
}
|
||||
|
||||
// ==================== reciprocal tests ====================
|
||||
|
||||
// Test reciprocal for simple values
|
||||
TEST(ReciprocalTest, ReciprocalSimple) {
|
||||
auto tensor = at::empty({4}, at::TensorOptions().dtype(at::kFloat));
|
||||
tensor.data_ptr<float>()[0] = 1.0f;
|
||||
tensor.data_ptr<float>()[1] = 2.0f;
|
||||
tensor.data_ptr<float>()[2] = 4.0f;
|
||||
tensor.data_ptr<float>()[3] = 0.5f;
|
||||
|
||||
auto result = tensor.reciprocal();
|
||||
|
||||
// Check that original tensor is unchanged
|
||||
EXPECT_FLOAT_EQ(tensor.data_ptr<float>()[0], 1.0f);
|
||||
EXPECT_FLOAT_EQ(tensor.data_ptr<float>()[1], 2.0f);
|
||||
|
||||
// Check reciprocal values: 1/1=1, 1/2=0.5, 1/4=0.25, 1/0.5=2
|
||||
EXPECT_FLOAT_EQ(result.data_ptr<float>()[0], 1.0f);
|
||||
EXPECT_FLOAT_EQ(result.data_ptr<float>()[1], 0.5f);
|
||||
EXPECT_FLOAT_EQ(result.data_ptr<float>()[2], 0.25f);
|
||||
EXPECT_FLOAT_EQ(result.data_ptr<float>()[3], 2.0f);
|
||||
}
|
||||
|
||||
// Test reciprocal for 2D tensor
|
||||
TEST(ReciprocalTest, Reciprocal2D) {
|
||||
auto tensor = at::empty({2, 2}, at::TensorOptions().dtype(at::kFloat));
|
||||
tensor.data_ptr<float>()[0] = 1.0f;
|
||||
tensor.data_ptr<float>()[1] = 2.0f;
|
||||
tensor.data_ptr<float>()[2] = 5.0f;
|
||||
tensor.data_ptr<float>()[3] = 10.0f;
|
||||
|
||||
auto result = tensor.reciprocal();
|
||||
|
||||
EXPECT_EQ(result.dim(), 2);
|
||||
EXPECT_EQ(result.size(0), 2);
|
||||
EXPECT_EQ(result.size(1), 2);
|
||||
|
||||
EXPECT_FLOAT_EQ(result.data_ptr<float>()[0], 1.0f);
|
||||
EXPECT_FLOAT_EQ(result.data_ptr<float>()[1], 0.5f);
|
||||
EXPECT_FLOAT_EQ(result.data_ptr<float>()[2], 0.2f);
|
||||
EXPECT_FLOAT_EQ(result.data_ptr<float>()[3], 0.1f);
|
||||
}
|
||||
|
||||
// Test reciprocal with double dtype
|
||||
TEST(ReciprocalTest, ReciprocalDouble) {
|
||||
auto tensor = at::empty({3}, at::TensorOptions().dtype(at::kDouble));
|
||||
tensor.data_ptr<double>()[0] = 1.0;
|
||||
tensor.data_ptr<double>()[1] = 3.0;
|
||||
tensor.data_ptr<double>()[2] = 8.0;
|
||||
|
||||
auto result = tensor.reciprocal();
|
||||
|
||||
EXPECT_DOUBLE_EQ(result.data_ptr<double>()[0], 1.0);
|
||||
EXPECT_NEAR(result.data_ptr<double>()[1], 1.0 / 3.0, 1e-10);
|
||||
EXPECT_DOUBLE_EQ(result.data_ptr<double>()[2], 0.125);
|
||||
}
|
||||
|
||||
// Test reciprocal preserves dtype
|
||||
TEST(ReciprocalTest, ReciprocalPreservesDtype) {
|
||||
auto tensor_float = at::empty({2}, at::TensorOptions().dtype(at::kFloat));
|
||||
tensor_float.fill_(2.0f);
|
||||
|
||||
auto tensor_double = at::empty({2}, at::TensorOptions().dtype(at::kDouble));
|
||||
tensor_double.fill_(2.0);
|
||||
|
||||
auto result_float = tensor_float.reciprocal();
|
||||
auto result_double = tensor_double.reciprocal();
|
||||
|
||||
EXPECT_EQ(result_float.dtype(), at::kFloat);
|
||||
EXPECT_EQ(result_double.dtype(), at::kDouble);
|
||||
}
|
||||
|
||||
// ==================== reciprocal_ (in-place) tests ====================
|
||||
|
||||
// Test reciprocal_ modifies tensor in-place
|
||||
TEST(ReciprocalInplaceTest, ReciprocalInplaceSimple) {
|
||||
auto tensor = at::empty({4}, at::TensorOptions().dtype(at::kFloat));
|
||||
tensor.data_ptr<float>()[0] = 1.0f;
|
||||
tensor.data_ptr<float>()[1] = 2.0f;
|
||||
tensor.data_ptr<float>()[2] = 4.0f;
|
||||
tensor.data_ptr<float>()[3] = 0.5f;
|
||||
|
||||
void* original_ptr = tensor.data_ptr();
|
||||
|
||||
auto result = tensor.reciprocal_();
|
||||
|
||||
// Should return reference to same tensor
|
||||
EXPECT_EQ(result.data_ptr(), original_ptr);
|
||||
|
||||
// Check in-place modification: 1/1=1, 1/2=0.5, 1/4=0.25, 1/0.5=2
|
||||
EXPECT_FLOAT_EQ(tensor.data_ptr<float>()[0], 1.0f);
|
||||
EXPECT_FLOAT_EQ(tensor.data_ptr<float>()[1], 0.5f);
|
||||
EXPECT_FLOAT_EQ(tensor.data_ptr<float>()[2], 0.25f);
|
||||
EXPECT_FLOAT_EQ(tensor.data_ptr<float>()[3], 2.0f);
|
||||
}
|
||||
|
||||
// Test reciprocal_ on 2D tensor
|
||||
TEST(ReciprocalInplaceTest, ReciprocalInplace2D) {
|
||||
auto tensor = at::empty({2, 3}, at::TensorOptions().dtype(at::kFloat));
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
tensor.data_ptr<float>()[i] =
|
||||
static_cast<float>(i + 1); // [1, 2, 3, 4, 5, 6]
|
||||
}
|
||||
|
||||
tensor.reciprocal_();
|
||||
|
||||
EXPECT_FLOAT_EQ(tensor.data_ptr<float>()[0], 1.0f); // 1/1
|
||||
EXPECT_FLOAT_EQ(tensor.data_ptr<float>()[1], 0.5f); // 1/2
|
||||
EXPECT_NEAR(tensor.data_ptr<float>()[2], 1.0f / 3.0f, 1e-6); // 1/3
|
||||
EXPECT_FLOAT_EQ(tensor.data_ptr<float>()[3], 0.25f); // 1/4
|
||||
EXPECT_FLOAT_EQ(tensor.data_ptr<float>()[4], 0.2f); // 1/5
|
||||
EXPECT_NEAR(tensor.data_ptr<float>()[5], 1.0f / 6.0f, 1e-6); // 1/6
|
||||
}
|
||||
|
||||
// Test chaining reciprocal_ twice returns original values
|
||||
TEST(ReciprocalInplaceTest, ReciprocalInplaceTwice) {
|
||||
auto tensor = at::empty({3}, at::TensorOptions().dtype(at::kFloat));
|
||||
tensor.data_ptr<float>()[0] = 2.0f;
|
||||
tensor.data_ptr<float>()[1] = 4.0f;
|
||||
tensor.data_ptr<float>()[2] = 8.0f;
|
||||
|
||||
tensor.reciprocal_().reciprocal_();
|
||||
|
||||
// Should return to original values
|
||||
EXPECT_FLOAT_EQ(tensor.data_ptr<float>()[0], 2.0f);
|
||||
EXPECT_FLOAT_EQ(tensor.data_ptr<float>()[1], 4.0f);
|
||||
EXPECT_FLOAT_EQ(tensor.data_ptr<float>()[2], 8.0f);
|
||||
}
|
||||
|
||||
// ==================== detach tests ====================
|
||||
|
||||
// Test detach creates a new tensor sharing data
|
||||
TEST(DetachTest, DetachSharesData) {
|
||||
auto tensor = at::arange(5, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
auto detached = tensor.detach();
|
||||
|
||||
// Should have same shape and dtype
|
||||
EXPECT_EQ(detached.dim(), tensor.dim());
|
||||
EXPECT_EQ(detached.size(0), tensor.size(0));
|
||||
EXPECT_EQ(detached.dtype(), tensor.dtype());
|
||||
|
||||
// Should have same values
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
EXPECT_FLOAT_EQ(detached.data_ptr<float>()[i], tensor.data_ptr<float>()[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Test detach on 2D tensor
|
||||
TEST(DetachTest, Detach2D) {
|
||||
auto tensor =
|
||||
at::arange(12, at::TensorOptions().dtype(at::kFloat)).reshape({3, 4});
|
||||
|
||||
auto detached = tensor.detach();
|
||||
|
||||
EXPECT_EQ(detached.dim(), 2);
|
||||
EXPECT_EQ(detached.size(0), 3);
|
||||
EXPECT_EQ(detached.size(1), 4);
|
||||
EXPECT_EQ(detached.numel(), 12);
|
||||
}
|
||||
|
||||
// Test detach preserves device
|
||||
TEST(DetachTest, DetachPreservesDevice) {
|
||||
auto tensor = at::arange(5, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
auto detached = tensor.detach();
|
||||
|
||||
EXPECT_TRUE(tensor.is_cpu());
|
||||
EXPECT_TRUE(detached.is_cpu());
|
||||
}
|
||||
|
||||
// Test detach with different dtypes
|
||||
TEST(DetachTest, DetachDifferentDtypes) {
|
||||
auto tensor_float = at::arange(5, at::TensorOptions().dtype(at::kFloat));
|
||||
auto tensor_int = at::arange(5, at::TensorOptions().dtype(at::kInt));
|
||||
auto tensor_double = at::arange(5, at::TensorOptions().dtype(at::kDouble));
|
||||
|
||||
auto detached_float = tensor_float.detach();
|
||||
auto detached_int = tensor_int.detach();
|
||||
auto detached_double = tensor_double.detach();
|
||||
|
||||
EXPECT_EQ(detached_float.dtype(), at::kFloat);
|
||||
EXPECT_EQ(detached_int.dtype(), at::kInt);
|
||||
EXPECT_EQ(detached_double.dtype(), at::kDouble);
|
||||
}
|
||||
|
||||
// Test multiple detach calls
|
||||
TEST(DetachTest, DetachMultipleTimes) {
|
||||
auto tensor = at::arange(5, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
auto detached1 = tensor.detach();
|
||||
auto detached2 = detached1.detach();
|
||||
|
||||
EXPECT_EQ(detached2.numel(), 5);
|
||||
EXPECT_EQ(detached2.dtype(), at::kFloat);
|
||||
}
|
||||
|
||||
// ==================== detach_ (in-place) tests ====================
|
||||
|
||||
// Test detach_ returns reference to self
|
||||
TEST(DetachInplaceTest, DetachInplaceReturnsSelf) {
|
||||
auto tensor = at::arange(5, at::TensorOptions().dtype(at::kFloat));
|
||||
void* original_ptr = tensor.data_ptr();
|
||||
|
||||
auto result = tensor.detach_();
|
||||
|
||||
// Should return reference to same tensor
|
||||
EXPECT_EQ(result.data_ptr(), original_ptr);
|
||||
}
|
||||
|
||||
// Test detach_ preserves data
|
||||
TEST(DetachInplaceTest, DetachInplacePreservesData) {
|
||||
auto tensor = at::arange(5, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
tensor.detach_();
|
||||
|
||||
// Data should be unchanged
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
EXPECT_FLOAT_EQ(tensor.data_ptr<float>()[i], static_cast<float>(i));
|
||||
}
|
||||
}
|
||||
|
||||
// Test detach_ preserves shape
|
||||
TEST(DetachInplaceTest, DetachInplacePreservesShape) {
|
||||
auto tensor =
|
||||
at::arange(12, at::TensorOptions().dtype(at::kFloat)).reshape({3, 4});
|
||||
|
||||
tensor.detach_();
|
||||
|
||||
EXPECT_EQ(tensor.dim(), 2);
|
||||
EXPECT_EQ(tensor.size(0), 3);
|
||||
EXPECT_EQ(tensor.size(1), 4);
|
||||
}
|
||||
|
||||
// Test detach_ preserves dtype
|
||||
TEST(DetachInplaceTest, DetachInplacePreservesDtype) {
|
||||
auto tensor_float = at::empty({5}, at::TensorOptions().dtype(at::kFloat));
|
||||
auto tensor_double = at::empty({5}, at::TensorOptions().dtype(at::kDouble));
|
||||
|
||||
tensor_float.detach_();
|
||||
tensor_double.detach_();
|
||||
|
||||
EXPECT_EQ(tensor_float.dtype(), at::kFloat);
|
||||
EXPECT_EQ(tensor_double.dtype(), at::kDouble);
|
||||
}
|
||||
|
||||
// Test chaining detach_ calls
|
||||
TEST(DetachInplaceTest, DetachInplaceChained) {
|
||||
auto tensor = at::arange(5, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
tensor.detach_().detach_();
|
||||
|
||||
// Should still have valid data
|
||||
EXPECT_EQ(tensor.numel(), 5);
|
||||
EXPECT_FLOAT_EQ(tensor.data_ptr<float>()[0], 0.0f);
|
||||
EXPECT_FLOAT_EQ(tensor.data_ptr<float>()[4], 4.0f);
|
||||
}
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
// Test reciprocal on CUDA
|
||||
TEST(ReciprocalTest, ReciprocalCUDA) {
|
||||
auto tensor =
|
||||
at::empty({4}, at::TensorOptions().dtype(at::kFloat).device(at::kCUDA));
|
||||
auto cpu_tensor = at::empty({4}, at::TensorOptions().dtype(at::kFloat));
|
||||
cpu_tensor.data_ptr<float>()[0] = 1.0f;
|
||||
cpu_tensor.data_ptr<float>()[1] = 2.0f;
|
||||
cpu_tensor.data_ptr<float>()[2] = 4.0f;
|
||||
cpu_tensor.data_ptr<float>()[3] = 0.5f;
|
||||
tensor.copy_(cpu_tensor);
|
||||
|
||||
auto result = tensor.reciprocal();
|
||||
|
||||
EXPECT_TRUE(result.is_cuda());
|
||||
|
||||
auto cpu_result = result.cpu();
|
||||
EXPECT_FLOAT_EQ(cpu_result.data_ptr<float>()[0], 1.0f);
|
||||
EXPECT_FLOAT_EQ(cpu_result.data_ptr<float>()[1], 0.5f);
|
||||
EXPECT_FLOAT_EQ(cpu_result.data_ptr<float>()[2], 0.25f);
|
||||
EXPECT_FLOAT_EQ(cpu_result.data_ptr<float>()[3], 2.0f);
|
||||
}
|
||||
|
||||
// Test detach on CUDA
|
||||
TEST(DetachTest, DetachCUDA) {
|
||||
auto tensor =
|
||||
at::arange(5, at::TensorOptions().dtype(at::kFloat).device(at::kCUDA));
|
||||
|
||||
auto detached = tensor.detach();
|
||||
|
||||
EXPECT_TRUE(detached.is_cuda());
|
||||
EXPECT_EQ(detached.numel(), 5);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,208 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ============================================================================
|
||||
// Narrow Tests
|
||||
// ============================================================================
|
||||
|
||||
TEST(TestNarrow, NarrowBasic) {
|
||||
// Test basic narrow operation on dimension 0
|
||||
at::Tensor tensor = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
// tensor: [[0, 1, 2, 3],
|
||||
// [4, 5, 6, 7],
|
||||
// [8, 9, 10, 11]]
|
||||
|
||||
// Narrow on dim 0, start 1, length 2
|
||||
at::Tensor narrowed = tensor.narrow(0, 1, 2);
|
||||
ASSERT_EQ(narrowed.sizes(), c10::IntArrayRef({2, 4}));
|
||||
|
||||
// Verify data
|
||||
const float* data = narrowed.data_ptr<float>();
|
||||
ASSERT_EQ(data[0], 4.0f); // First element of row 1
|
||||
ASSERT_EQ(data[4], 8.0f); // First element of row 2
|
||||
}
|
||||
|
||||
TEST(TestNarrow, NarrowOnDim1) {
|
||||
// Test narrow on dimension 1
|
||||
at::Tensor tensor = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
|
||||
// Narrow on dim 1, start 1, length 2
|
||||
at::Tensor narrowed = tensor.narrow(1, 1, 2);
|
||||
ASSERT_EQ(narrowed.sizes(), c10::IntArrayRef({3, 2}));
|
||||
}
|
||||
|
||||
TEST(TestNarrow, NarrowStartZero) {
|
||||
// Test narrow starting from 0
|
||||
at::Tensor tensor = at::ones({5, 4}, at::kFloat);
|
||||
|
||||
at::Tensor narrowed = tensor.narrow(0, 0, 3);
|
||||
ASSERT_EQ(narrowed.sizes(), c10::IntArrayRef({3, 4}));
|
||||
ASSERT_EQ(narrowed.numel(), 12);
|
||||
}
|
||||
|
||||
TEST(TestNarrow, NarrowFullLength) {
|
||||
// Test narrow with full length (should return same shape on that dim)
|
||||
at::Tensor tensor = at::ones({3, 4}, at::kFloat);
|
||||
|
||||
at::Tensor narrowed = tensor.narrow(0, 0, 3);
|
||||
ASSERT_EQ(narrowed.sizes(), c10::IntArrayRef({3, 4}));
|
||||
}
|
||||
|
||||
TEST(TestNarrow, NarrowSingleElement) {
|
||||
// Test narrow with length 1
|
||||
at::Tensor tensor = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
|
||||
at::Tensor narrowed = tensor.narrow(0, 1, 1);
|
||||
ASSERT_EQ(narrowed.sizes(), c10::IntArrayRef({1, 4}));
|
||||
}
|
||||
|
||||
TEST(TestNarrow, NarrowHigherDim) {
|
||||
// Test narrow on 3D tensor
|
||||
at::Tensor tensor = at::ones({2, 3, 4}, at::kFloat);
|
||||
|
||||
// Narrow on dim 1
|
||||
at::Tensor narrowed = tensor.narrow(1, 1, 2);
|
||||
ASSERT_EQ(narrowed.sizes(), c10::IntArrayRef({2, 2, 4}));
|
||||
|
||||
// Narrow on dim 2
|
||||
at::Tensor narrowed2 = tensor.narrow(2, 0, 2);
|
||||
ASSERT_EQ(narrowed2.sizes(), c10::IntArrayRef({2, 3, 2}));
|
||||
}
|
||||
|
||||
TEST(TestNarrow, NarrowSymInt) {
|
||||
// Test narrow_symint (should behave same as narrow)
|
||||
at::Tensor tensor = at::ones({3, 4}, at::kFloat);
|
||||
|
||||
c10::SymInt start = 1;
|
||||
c10::SymInt length = 2;
|
||||
at::Tensor narrowed = tensor.narrow_symint(0, start, length);
|
||||
ASSERT_EQ(narrowed.sizes(), c10::IntArrayRef({2, 4}));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Narrow Copy Tests
|
||||
// ============================================================================
|
||||
|
||||
TEST(TestNarrowCopy, NarrowCopyBasic) {
|
||||
// Test narrow_copy returns a copy of the narrowed tensor
|
||||
at::Tensor tensor = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
|
||||
at::Tensor narrowed_copy = tensor.narrow_copy(0, 1, 2);
|
||||
ASSERT_EQ(narrowed_copy.sizes(), c10::IntArrayRef({2, 4}));
|
||||
|
||||
// Verify it's a copy (different data pointer)
|
||||
// Note: narrow_copy creates a contiguous copy
|
||||
ASSERT_EQ(narrowed_copy.numel(), 8);
|
||||
}
|
||||
|
||||
TEST(TestNarrowCopy, NarrowCopySymInt) {
|
||||
// Test narrow_copy_symint
|
||||
at::Tensor tensor = at::ones({3, 4}, at::kFloat);
|
||||
|
||||
c10::SymInt start = 0;
|
||||
c10::SymInt length = 2;
|
||||
at::Tensor narrowed_copy = tensor.narrow_copy_symint(0, start, length);
|
||||
ASSERT_EQ(narrowed_copy.sizes(), c10::IntArrayRef({2, 4}));
|
||||
}
|
||||
|
||||
TEST(TestNarrowCopy, NarrowCopyDataIntegrity) {
|
||||
// Test that narrow_copy preserves data correctly
|
||||
at::Tensor tensor = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
|
||||
at::Tensor narrowed_copy = tensor.narrow_copy(0, 1, 2);
|
||||
|
||||
// Verify data
|
||||
const float* data = narrowed_copy.data_ptr<float>();
|
||||
ASSERT_EQ(data[0], 4.0f);
|
||||
ASSERT_EQ(data[1], 5.0f);
|
||||
ASSERT_EQ(data[2], 6.0f);
|
||||
ASSERT_EQ(data[3], 7.0f);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Narrow with Tensor Start Tests
|
||||
// ============================================================================
|
||||
|
||||
TEST(TestNarrowTensor, NarrowWithTensorStart) {
|
||||
// Test narrow with tensor start parameter
|
||||
at::Tensor tensor = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
// Create start tensor with value 1 using ones
|
||||
at::Tensor start_tensor = at::ones({1}, at::kLong);
|
||||
|
||||
at::Tensor narrowed = tensor.narrow(0, start_tensor, 2);
|
||||
ASSERT_EQ(narrowed.sizes(), c10::IntArrayRef({2, 4}));
|
||||
}
|
||||
|
||||
TEST(TestNarrowTensor, NarrowSymIntWithTensorStart) {
|
||||
// Test narrow_symint with tensor start parameter
|
||||
at::Tensor tensor = at::ones({3, 4}, at::kFloat);
|
||||
// Create start tensor with value 0 using zeros
|
||||
at::Tensor start_tensor = at::zeros({1}, at::kLong);
|
||||
|
||||
c10::SymInt length = 2;
|
||||
at::Tensor narrowed = tensor.narrow_symint(0, start_tensor, length);
|
||||
ASSERT_EQ(narrowed.sizes(), c10::IntArrayRef({2, 4}));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Combined Tests
|
||||
// ============================================================================
|
||||
|
||||
TEST(TestNarrowCombined, NarrowMultipleDims) {
|
||||
// Test narrow on multiple dimensions sequentially
|
||||
at::Tensor tensor = at::ones({4, 5, 6}, at::kFloat);
|
||||
|
||||
// Narrow on each dimension
|
||||
at::Tensor n1 = tensor.narrow(0, 1, 2);
|
||||
ASSERT_EQ(n1.sizes(), c10::IntArrayRef({2, 5, 6}));
|
||||
|
||||
at::Tensor n2 = n1.narrow(1, 2, 3);
|
||||
ASSERT_EQ(n2.sizes(), c10::IntArrayRef({2, 3, 6}));
|
||||
|
||||
at::Tensor n3 = n2.narrow(2, 0, 4);
|
||||
ASSERT_EQ(n3.sizes(), c10::IntArrayRef({2, 3, 4}));
|
||||
}
|
||||
|
||||
TEST(TestNarrowCombined, NarrowDataVerification) {
|
||||
// Verify narrow selects correct data
|
||||
at::Tensor tensor = at::arange(20, at::kFloat).reshape({4, 5});
|
||||
// tensor: [[ 0, 1, 2, 3, 4],
|
||||
// [ 5, 6, 7, 8, 9],
|
||||
// [10, 11, 12, 13, 14],
|
||||
// [15, 16, 17, 18, 19]]
|
||||
|
||||
// Narrow rows 1-2, columns 2-3
|
||||
at::Tensor narrowed = tensor.narrow(0, 1, 2).narrow(1, 2, 2);
|
||||
ASSERT_EQ(narrowed.sizes(), c10::IntArrayRef({2, 2}));
|
||||
|
||||
// Expected: [[7, 8], [12, 13]]
|
||||
// Note: The actual verification depends on contiguous memory layout
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/ops/new_empty.h>
|
||||
#include <ATen/ops/new_full.h>
|
||||
#include <ATen/ops/new_ones.h>
|
||||
#include <ATen/ops/new_zeros.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ======================== new_empty tests ========================
|
||||
|
||||
TEST(TensorNewTest, NewEmptyBasic) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
at::Tensor result = t.new_empty({4, 5});
|
||||
|
||||
ASSERT_EQ(result.sizes()[0], 4);
|
||||
ASSERT_EQ(result.sizes()[1], 5);
|
||||
ASSERT_EQ(result.dtype(), at::kFloat);
|
||||
}
|
||||
|
||||
TEST(TensorNewTest, NewEmptyWithOptions) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
at::TensorOptions options = at::TensorOptions().dtype(at::kInt);
|
||||
at::Tensor result = t.new_empty({3, 4}, options);
|
||||
|
||||
ASSERT_EQ(result.sizes()[0], 3);
|
||||
ASSERT_EQ(result.dtype(), at::kInt);
|
||||
}
|
||||
|
||||
TEST(TensorNewTest, NewEmptyWithDtype) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
at::Tensor result = t.new_empty({2, 2}, at::kDouble);
|
||||
|
||||
ASSERT_EQ(result.dtype(), at::kDouble);
|
||||
}
|
||||
|
||||
TEST(TensorNewTest, NewEmptyWithDevice) {
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
|
||||
at::Tensor result = t.new_empty(
|
||||
{3, 3}, at::kFloat, ::std::nullopt, at::Device(at::kCPU), false);
|
||||
|
||||
ASSERT_EQ(result.device().type(), at::kCPU);
|
||||
}
|
||||
|
||||
TEST(TensorNewTest, NewEmptyNulloptDtypeInheritsSourceDtype) {
|
||||
at::Tensor t = at::arange(6, at::kInt);
|
||||
|
||||
at::Tensor result =
|
||||
t.new_empty({2, 2}, std::nullopt, std::nullopt, at::kCPU, false);
|
||||
|
||||
ASSERT_EQ(result.scalar_type(), at::kInt);
|
||||
}
|
||||
|
||||
// ======================== new_full tests ========================
|
||||
|
||||
TEST(TensorNewTest, NewFullBasic) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
at::Tensor result = t.new_full({3, 4}, 7.5f);
|
||||
|
||||
ASSERT_EQ(result.sizes()[0], 3);
|
||||
ASSERT_EQ(result.sizes()[1], 4);
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[0], 7.5f);
|
||||
}
|
||||
|
||||
TEST(TensorNewTest, NewFullWithOptions) {
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
|
||||
at::TensorOptions options = at::TensorOptions().dtype(at::kInt);
|
||||
at::Tensor result = t.new_full({2, 2}, 42, options);
|
||||
|
||||
ASSERT_EQ(result.dtype(), at::kInt);
|
||||
ASSERT_EQ(result.data_ptr<int>()[0], 42);
|
||||
}
|
||||
|
||||
TEST(TensorNewTest, NewFullWithDtype) {
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
|
||||
at::Tensor result = t.new_full({2, 2}, 100, at::kInt);
|
||||
|
||||
ASSERT_EQ(result.dtype(), at::kInt);
|
||||
}
|
||||
|
||||
TEST(TensorNewTest, NewFullScalarValue) {
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
|
||||
at::Scalar scalar_val(3.14);
|
||||
at::Tensor result = t.new_full({2, 2}, scalar_val);
|
||||
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[0], 3.14f);
|
||||
}
|
||||
|
||||
TEST(TensorNewTest, NewFullNulloptDtypeInheritsSourceDtype) {
|
||||
at::Tensor t = at::arange(6, at::kLong);
|
||||
|
||||
at::Tensor result =
|
||||
t.new_full({2, 2}, 9, std::nullopt, std::nullopt, at::kCPU, false);
|
||||
|
||||
ASSERT_EQ(result.scalar_type(), at::kLong);
|
||||
ASSERT_EQ(result.data_ptr<int64_t>()[0], 9);
|
||||
}
|
||||
|
||||
// ======================== new_zeros tests ========================
|
||||
|
||||
TEST(TensorNewTest, NewZerosBasic) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
at::Tensor result = t.new_zeros({4, 5});
|
||||
|
||||
ASSERT_EQ(result.sizes()[0], 4);
|
||||
ASSERT_EQ(result.sizes()[1], 5);
|
||||
ASSERT_EQ(result.dtype(), at::kFloat);
|
||||
}
|
||||
|
||||
TEST(TensorNewTest, NewZerosWithOptions) {
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
|
||||
at::TensorOptions options = at::TensorOptions().dtype(at::kInt);
|
||||
at::Tensor result = t.new_zeros({3, 3}, options);
|
||||
|
||||
ASSERT_EQ(result.dtype(), at::kInt);
|
||||
ASSERT_EQ(result.data_ptr<int>()[0], 0);
|
||||
}
|
||||
|
||||
TEST(TensorNewTest, NewZerosWithDtype) {
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
|
||||
at::Tensor result = t.new_zeros({2, 2}, at::kDouble);
|
||||
|
||||
ASSERT_EQ(result.dtype(), at::kDouble);
|
||||
}
|
||||
|
||||
TEST(TensorNewTest, NewZerosIntType) {
|
||||
at::Tensor t = at::arange(6, at::kInt);
|
||||
|
||||
at::Tensor result = t.new_zeros({3, 3});
|
||||
|
||||
ASSERT_EQ(result.dtype(), at::kInt);
|
||||
ASSERT_EQ(result.data_ptr<int>()[0], 0);
|
||||
}
|
||||
|
||||
TEST(TensorNewTest, NewZerosNulloptDtypeInheritsSourceDtype) {
|
||||
at::Tensor t = at::arange(6, at::kDouble);
|
||||
|
||||
at::Tensor result =
|
||||
t.new_zeros({2, 3}, std::nullopt, std::nullopt, at::kCPU, false);
|
||||
|
||||
ASSERT_EQ(result.scalar_type(), at::kDouble);
|
||||
ASSERT_EQ(result.data_ptr<double>()[0], 0.0);
|
||||
}
|
||||
|
||||
// ======================== new_ones tests ========================
|
||||
|
||||
TEST(TensorNewTest, NewOnesBasic) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
at::Tensor result = t.new_ones({4, 5});
|
||||
|
||||
ASSERT_EQ(result.sizes()[0], 4);
|
||||
ASSERT_EQ(result.sizes()[1], 5);
|
||||
ASSERT_EQ(result.dtype(), at::kFloat);
|
||||
}
|
||||
|
||||
TEST(TensorNewTest, NewOnesWithOptions) {
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
|
||||
at::TensorOptions options = at::TensorOptions().dtype(at::kInt);
|
||||
at::Tensor result = t.new_ones({3, 3}, options);
|
||||
|
||||
ASSERT_EQ(result.dtype(), at::kInt);
|
||||
ASSERT_EQ(result.data_ptr<int>()[0], 1);
|
||||
}
|
||||
|
||||
TEST(TensorNewTest, NewOnesWithDtype) {
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
|
||||
at::Tensor result = t.new_ones({2, 2}, at::kDouble);
|
||||
|
||||
ASSERT_EQ(result.dtype(), at::kDouble);
|
||||
ASSERT_EQ(result.data_ptr<double>()[0], 1.0);
|
||||
}
|
||||
|
||||
TEST(TensorNewTest, NewOnesIntType) {
|
||||
at::Tensor t = at::arange(6, at::kInt);
|
||||
|
||||
at::Tensor result = t.new_ones({3, 3});
|
||||
|
||||
ASSERT_EQ(result.dtype(), at::kInt);
|
||||
ASSERT_EQ(result.data_ptr<int>()[0], 1);
|
||||
}
|
||||
|
||||
TEST(TensorNewTest, NewOnesNulloptDtypeInheritsSourceDtype) {
|
||||
at::Tensor t = at::ones({6}, at::kDouble);
|
||||
|
||||
at::Tensor result =
|
||||
t.new_ones({2, 3}, std::nullopt, std::nullopt, at::kCPU, false);
|
||||
|
||||
ASSERT_EQ(result.scalar_type(), at::kDouble);
|
||||
ASSERT_EQ(result.data_ptr<double>()[0], 1.0);
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/_nnz.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/Layout.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ============================================================
|
||||
// Tests for at::Tensor::_nnz()
|
||||
// ============================================================
|
||||
|
||||
// ---- Sparse COO ----
|
||||
|
||||
TEST(TensorNNZTest, SparseCOO_NNZ_Correct) {
|
||||
// indices: [[0,1],[1,2]] -> 2 entries in a 3x3 matrix
|
||||
at::Tensor indices = at::tensor({0, 1, 1, 2}, at::kLong).reshape({2, 2});
|
||||
at::Tensor values = at::tensor({1.0f, 2.0f}, at::kFloat);
|
||||
at::Tensor sparse = at::sparse_coo_tensor(indices, values, {3, 3});
|
||||
|
||||
ASSERT_EQ(sparse._nnz(), 2);
|
||||
}
|
||||
|
||||
TEST(TensorNNZTest, SparseCOO_NNZ_ZeroWhenAllZero) {
|
||||
// Empty sparse tensor: 0 explicit entries.
|
||||
at::Tensor indices = at::zeros({2, 0}, at::kLong);
|
||||
at::Tensor values = at::zeros({0}, at::kFloat);
|
||||
at::Tensor sparse = at::sparse_coo_tensor(indices, values, {3, 3});
|
||||
|
||||
ASSERT_EQ(sparse._nnz(), 0);
|
||||
}
|
||||
|
||||
TEST(TensorNNZTest, SparseCOO_NNZ_AfterCoalesce_DuplicatesMerged) {
|
||||
// Two entries at the same position: after coalescing they become one.
|
||||
at::Tensor indices = at::tensor({0, 0, 1, 1}, at::kLong).reshape({2, 2});
|
||||
at::Tensor values = at::tensor({1.0f, 2.0f}, at::kFloat);
|
||||
at::Tensor sparse = at::sparse_coo_tensor(indices, values, {3, 3});
|
||||
|
||||
at::Tensor coalesced = sparse.coalesce();
|
||||
|
||||
ASSERT_EQ(coalesced._nnz(), 1);
|
||||
}
|
||||
|
||||
// ---- Sparse CSR ----
|
||||
|
||||
TEST(TensorNNZTest, SparseCsr_NNZ_Correct) {
|
||||
// 3x3 identity: 3 non-zeros.
|
||||
at::Tensor crow = at::tensor({0, 1, 2, 3}, at::kInt);
|
||||
at::Tensor col = at::tensor({0, 1, 2}, at::kInt);
|
||||
at::Tensor vals = at::tensor({1.0f, 1.0f, 1.0f}, at::kFloat);
|
||||
at::Tensor sparse_csr =
|
||||
at::sparse_csr_tensor(crow, col, vals, {3, 3}, at::TensorOptions());
|
||||
|
||||
ASSERT_EQ(sparse_csr._nnz(), 3);
|
||||
}
|
||||
|
||||
TEST(TensorNNZTest, SparseCsr_NNZ_SingleRow) {
|
||||
// 1x3 row with 2 non-zeros.
|
||||
at::Tensor crow = at::tensor({0, 2}, at::kInt);
|
||||
at::Tensor col = at::tensor({0, 2}, at::kInt);
|
||||
at::Tensor vals = at::tensor({5.0f, 7.0f}, at::kFloat);
|
||||
at::Tensor sparse_csr =
|
||||
at::sparse_csr_tensor(crow, col, vals, {1, 3}, at::TensorOptions());
|
||||
|
||||
ASSERT_EQ(sparse_csr._nnz(), 2);
|
||||
}
|
||||
|
||||
// ---- Dense tensor must throw ----
|
||||
|
||||
TEST(TensorNNZTest, DenseTensor_Throws) {
|
||||
at::Tensor dense = at::ones({3, 3}, at::kFloat);
|
||||
|
||||
ASSERT_THROW(dense._nnz(), std::exception);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Verify that including both headers in the same translation unit compiles
|
||||
// cleanly (no ODR violations) and that the canonical PhiloxCudaState
|
||||
// definition is consistent across both include paths.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <ATen/cuda/PhiloxCudaState.h>
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <ATen/cuda/CUDAGeneratorImpl.h>
|
||||
#include <ATen/cuda/PhiloxUtils.cuh>
|
||||
#endif
|
||||
|
||||
// offset_intragraph_ must be uint64_t to match PyTorch upstream.
|
||||
static_assert(std::is_same_v<decltype(at::PhiloxCudaState{}.offset_intragraph_),
|
||||
uint64_t>,
|
||||
"PhiloxCudaState::offset_intragraph_ must be uint64_t");
|
||||
|
||||
TEST(ATenPhiloxTest, TypeConsistency) {
|
||||
// The static_assert above already fires at compile time; this test
|
||||
// confirms the field size at runtime as well.
|
||||
EXPECT_EQ(sizeof(at::PhiloxCudaState{}.offset_intragraph_), sizeof(uint64_t));
|
||||
}
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
TEST(ATenPhiloxTest, UnpackNonCaptured) {
|
||||
constexpr uint64_t kSeed = 42ULL;
|
||||
constexpr uint64_t kOffset = 100ULL;
|
||||
at::PhiloxCudaState state(kSeed, kOffset);
|
||||
EXPECT_FALSE(state.captured_);
|
||||
auto [seed, offset] = at::cuda::philox::unpack(state);
|
||||
EXPECT_EQ(seed, kSeed);
|
||||
EXPECT_EQ(offset, kOffset);
|
||||
}
|
||||
|
||||
TEST(ATenPhiloxTest, DefaultConstructedNotCaptured) {
|
||||
at::PhiloxCudaState state;
|
||||
EXPECT_FALSE(state.captured_);
|
||||
EXPECT_EQ(state.offset_intragraph_, 0ULL);
|
||||
}
|
||||
|
||||
TEST(ATenPhiloxTest, NonCapturedOffsetIntragraphIgnored) {
|
||||
// In the non-captured path, offset_intragraph_ plays no role in unpack().
|
||||
at::PhiloxCudaState state(7ULL, 13ULL);
|
||||
state.offset_intragraph_ = 999ULL; // should be ignored
|
||||
auto [seed, offset] = at::cuda::philox::unpack(state);
|
||||
EXPECT_EQ(seed, 7ULL);
|
||||
EXPECT_EQ(offset, 13ULL);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,216 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/ops/arange.h>
|
||||
#include <ATen/ops/empty_like.h>
|
||||
#include <ATen/ops/eye.h>
|
||||
#include <ATen/ops/full.h>
|
||||
#include <ATen/ops/ones.h>
|
||||
#include <ATen/ops/zeros.h>
|
||||
#include <ATen/ops/zeros_like.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
|
||||
namespace {
|
||||
|
||||
void AssertPinned(const at::Tensor& t) {
|
||||
ASSERT_TRUE(t.is_pinned());
|
||||
ASSERT_FALSE(t.is_cuda());
|
||||
}
|
||||
|
||||
void AssertNotPinned(const at::Tensor& t) { ASSERT_FALSE(t.is_pinned()); }
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(ATenPinMemoryCreationTest, FullPinMemory) {
|
||||
// Test using TensorOptions with pinned_memory
|
||||
auto by_options = at::full(
|
||||
{2, 3}, 1.5f, at::TensorOptions().dtype(at::kFloat).pinned_memory(true));
|
||||
AssertPinned(by_options);
|
||||
|
||||
// Test using explicit arguments with CPU device (should succeed)
|
||||
auto by_args =
|
||||
at::full({2, 3}, 1.5f, at::kFloat, std::nullopt, at::kCPU, true);
|
||||
AssertPinned(by_args);
|
||||
|
||||
// Test without pin_memory
|
||||
auto no_pin =
|
||||
at::full({2, 3}, 1.5f, at::kFloat, std::nullopt, at::kCPU, false);
|
||||
AssertNotPinned(no_pin);
|
||||
}
|
||||
|
||||
TEST(ATenPinMemoryCreationTest, FullPinMemoryWithCUDADeviceErrors) {
|
||||
// Test that pin_memory=true with explicit CUDA device throws error
|
||||
ASSERT_THROW(
|
||||
at::full({2, 3}, 1.5f, at::kFloat, std::nullopt, at::kCUDA, true),
|
||||
std::exception);
|
||||
}
|
||||
|
||||
TEST(ATenPinMemoryCreationTest, OnesPinMemory) {
|
||||
auto by_options = at::ones(
|
||||
{4, 2}, at::TensorOptions().dtype(at::kFloat).pinned_memory(true));
|
||||
AssertPinned(by_options);
|
||||
|
||||
auto by_args = at::ones({4, 2}, at::kFloat, std::nullopt, at::kCPU, true);
|
||||
AssertPinned(by_args);
|
||||
|
||||
auto no_pin = at::ones({4, 2}, at::kFloat, std::nullopt, at::kCPU, false);
|
||||
AssertNotPinned(no_pin);
|
||||
}
|
||||
|
||||
TEST(ATenPinMemoryCreationTest, OnesPinMemoryWithCUDADeviceErrors) {
|
||||
ASSERT_THROW(at::ones({4, 2}, at::kFloat, std::nullopt, at::kCUDA, true),
|
||||
std::exception);
|
||||
}
|
||||
|
||||
TEST(ATenPinMemoryCreationTest, ZerosPinMemory) {
|
||||
auto by_options = at::zeros(
|
||||
{3, 5}, at::TensorOptions().dtype(at::kFloat).pinned_memory(true));
|
||||
AssertPinned(by_options);
|
||||
|
||||
auto by_args = at::zeros({3, 5}, at::kFloat, at::kStrided, at::kCPU, true);
|
||||
AssertPinned(by_args);
|
||||
|
||||
auto no_pin = at::zeros({3, 5}, at::kFloat, at::kStrided, at::kCPU, false);
|
||||
AssertNotPinned(no_pin);
|
||||
}
|
||||
|
||||
TEST(ATenPinMemoryCreationTest, ZerosPinMemoryWithCUDADeviceErrors) {
|
||||
ASSERT_THROW(at::zeros({3, 5}, at::kFloat, at::kStrided, at::kCUDA, true),
|
||||
std::exception);
|
||||
}
|
||||
|
||||
TEST(ATenPinMemoryCreationTest, EyePinMemory) {
|
||||
auto by_options =
|
||||
at::eye(6, at::TensorOptions().dtype(at::kFloat).pinned_memory(true));
|
||||
AssertPinned(by_options);
|
||||
|
||||
auto by_args = at::eye(6, at::kFloat, std::nullopt, at::kCPU, true);
|
||||
AssertPinned(by_args);
|
||||
|
||||
auto no_pin = at::eye(6, at::kFloat, std::nullopt, at::kCPU, false);
|
||||
AssertNotPinned(no_pin);
|
||||
}
|
||||
|
||||
TEST(ATenPinMemoryCreationTest, EyePinMemoryWithCUDADeviceErrors) {
|
||||
ASSERT_THROW(at::eye(6, at::kFloat, std::nullopt, at::kCUDA, true),
|
||||
std::exception);
|
||||
}
|
||||
|
||||
TEST(ATenPinMemoryCreationTest, ArangePinMemoryOverloads) {
|
||||
auto end_only_by_options =
|
||||
at::arange(10, at::TensorOptions().dtype(at::kFloat).pinned_memory(true));
|
||||
AssertPinned(end_only_by_options);
|
||||
|
||||
auto end_only_by_args =
|
||||
at::arange(10, at::kFloat, std::nullopt, at::kCPU, true);
|
||||
AssertPinned(end_only_by_args);
|
||||
|
||||
auto by_options = at::arange(
|
||||
0, 10, at::TensorOptions().dtype(at::kFloat).pinned_memory(true));
|
||||
AssertPinned(by_options);
|
||||
|
||||
auto by_args = at::arange(0, 10, at::kFloat, std::nullopt, at::kCPU, true);
|
||||
AssertPinned(by_args);
|
||||
|
||||
auto step_by_options = at::arange(
|
||||
0, 10, 2, at::TensorOptions().dtype(at::kFloat).pinned_memory(true));
|
||||
AssertPinned(step_by_options);
|
||||
|
||||
auto step_by_args =
|
||||
at::arange(0, 10, 2, at::kFloat, std::nullopt, at::kCPU, true);
|
||||
AssertPinned(step_by_args);
|
||||
|
||||
auto no_pin = at::arange(0, 10, at::kFloat, std::nullopt, at::kCPU, false);
|
||||
AssertNotPinned(no_pin);
|
||||
|
||||
auto step_no_pin =
|
||||
at::arange(0, 10, 2, at::kFloat, std::nullopt, at::kCPU, false);
|
||||
AssertNotPinned(step_no_pin);
|
||||
}
|
||||
|
||||
TEST(ATenPinMemoryCreationTest, ArangePinMemoryWithCUDADeviceErrors) {
|
||||
ASSERT_THROW(at::arange(10, at::kFloat, std::nullopt, at::kCUDA, true),
|
||||
std::exception);
|
||||
ASSERT_THROW(at::arange(0, 10, at::kFloat, std::nullopt, at::kCUDA, true),
|
||||
std::exception);
|
||||
ASSERT_THROW(at::arange(0, 10, 2, at::kFloat, std::nullopt, at::kCUDA, true),
|
||||
std::exception);
|
||||
}
|
||||
|
||||
TEST(ATenPinMemoryCreationTest, EmptyLikePinMemory) {
|
||||
auto base = at::ones({2, 4}, at::kFloat);
|
||||
|
||||
auto by_options =
|
||||
at::empty_like(base,
|
||||
at::TensorOptions().dtype(at::kFloat).pinned_memory(true),
|
||||
std::nullopt);
|
||||
AssertPinned(by_options);
|
||||
|
||||
auto by_args = at::empty_like(
|
||||
base, at::kFloat, at::kStrided, at::kCPU, true, std::nullopt);
|
||||
AssertPinned(by_args);
|
||||
|
||||
auto no_pin = at::empty_like(
|
||||
base, at::kFloat, at::kStrided, at::kCPU, false, std::nullopt);
|
||||
AssertNotPinned(no_pin);
|
||||
}
|
||||
|
||||
TEST(ATenPinMemoryCreationTest, EmptyLikePinMemoryWithCUDADeviceErrors) {
|
||||
auto base = at::ones({2, 4}, at::kFloat);
|
||||
ASSERT_THROW(at::empty_like(base,
|
||||
at::TensorOptions()
|
||||
.dtype(at::kFloat)
|
||||
.device(at::kCUDA)
|
||||
.pinned_memory(true),
|
||||
std::nullopt),
|
||||
std::exception);
|
||||
}
|
||||
|
||||
TEST(ATenPinMemoryCreationTest, ZerosLikePinMemory) {
|
||||
auto base = at::ones({2, 4}, at::kFloat);
|
||||
|
||||
auto by_options =
|
||||
at::zeros_like(base,
|
||||
at::TensorOptions().dtype(at::kFloat).pinned_memory(true),
|
||||
std::nullopt);
|
||||
AssertPinned(by_options);
|
||||
|
||||
auto by_args = at::zeros_like(
|
||||
base, at::kFloat, at::kStrided, at::kCPU, true, std::nullopt);
|
||||
AssertPinned(by_args);
|
||||
|
||||
auto no_pin = at::zeros_like(
|
||||
base, at::kFloat, at::kStrided, at::kCPU, false, std::nullopt);
|
||||
AssertNotPinned(no_pin);
|
||||
}
|
||||
|
||||
TEST(ATenPinMemoryCreationTest, ZerosLikePinMemoryWithCUDADeviceErrors) {
|
||||
auto base = at::ones({2, 4}, at::kFloat);
|
||||
ASSERT_THROW(at::zeros_like(base,
|
||||
at::TensorOptions()
|
||||
.dtype(at::kFloat)
|
||||
.device(at::kCUDA)
|
||||
.pinned_memory(true),
|
||||
std::nullopt),
|
||||
std::exception);
|
||||
}
|
||||
|
||||
#endif // PADDLE_WITH_CUDA || PADDLE_WITH_HIP
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <ATen/ops/record_stream.h>
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/Stream.h>
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "torch/all.h"
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
#endif
|
||||
|
||||
class RecordStreamTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
cpu_tensor =
|
||||
at::ones({4}, at::TensorOptions().dtype(at::kFloat).device(at::kCPU));
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
if (at::cuda::is_available()) {
|
||||
cuda_tensor = at::ones(
|
||||
{4}, at::TensorOptions().dtype(at::kFloat).device(at::kCUDA));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
at::Tensor cpu_tensor;
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
at::Tensor cuda_tensor;
|
||||
#endif
|
||||
};
|
||||
|
||||
// --- Happy path: CUDA tensor + current CUDA stream should succeed ---
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
using RecordStreamMethod = void (at::Tensor::*)(at::Stream) const;
|
||||
[[maybe_unused]] static RecordStreamMethod g_record_stream_method =
|
||||
&at::Tensor::record_stream;
|
||||
|
||||
TEST_F(RecordStreamTest, CudaTensorCurrentCudaStream) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
auto stream = at::cuda::getCurrentCUDAStream();
|
||||
// record_stream should not throw
|
||||
EXPECT_NO_THROW(cuda_tensor.record_stream(stream));
|
||||
}
|
||||
|
||||
// --- Happy path: CUDA tensor + default CUDA stream should succeed ---
|
||||
TEST_F(RecordStreamTest, CudaTensorDefaultCudaStream) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
c10::Stream default_stream = c10::cuda::getDefaultCUDAStream().unwrap();
|
||||
EXPECT_NO_THROW(cuda_tensor.record_stream(default_stream));
|
||||
}
|
||||
|
||||
#endif // PADDLE_WITH_CUDA || PADDLE_WITH_HIP
|
||||
|
||||
// --- Error path: CPU tensor + CPU stream (record_stream does not support CPU
|
||||
// tensors) ---
|
||||
TEST_F(RecordStreamTest, CpuTensorCpuStream) {
|
||||
c10::Stream cpu_stream(c10::Stream::DEFAULT,
|
||||
c10::Device(c10::DeviceType::CPU, 0));
|
||||
EXPECT_THROW(cpu_tensor.record_stream(cpu_stream), std::exception);
|
||||
}
|
||||
|
||||
// --- Error path: CPU tensor + CUDA stream (record_stream does not support CPU
|
||||
// tensors) ---
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
TEST_F(RecordStreamTest, CpuTensorCudaStream) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
auto cuda_stream = at::cuda::getCurrentCUDAStream();
|
||||
EXPECT_THROW(cpu_tensor.record_stream(cuda_stream), std::exception);
|
||||
}
|
||||
#endif // PADDLE_WITH_CUDA || PADDLE_WITH_HIP
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ======================== rename tests ========================
|
||||
|
||||
TEST(TensorRenameTest, RenameWithNames) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
std::vector<std::string> names = {"height", "width"};
|
||||
at::DimnameList name_list(names);
|
||||
at::Tensor result = t.rename(name_list);
|
||||
|
||||
ASSERT_EQ(result.sizes(), t.sizes());
|
||||
}
|
||||
|
||||
TEST(TensorRenameTest, RenameNone) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
at::Tensor result = t.rename(::std::nullopt);
|
||||
|
||||
ASSERT_EQ(result.sizes(), t.sizes());
|
||||
}
|
||||
|
||||
TEST(TensorRenameTest, RenamePreservesData) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
std::vector<std::string> names = {"a", "b"};
|
||||
at::Tensor result = t.rename(names);
|
||||
|
||||
// Data should be preserved
|
||||
ASSERT_EQ(result.numel(), t.numel());
|
||||
for (int i = 0; i < t.numel(); i++) {
|
||||
ASSERT_FLOAT_EQ(result.data_ptr<float>()[i], t.data_ptr<float>()[i]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/Layout.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
TEST(ATenReshapeTest, BasicReshape) {
|
||||
// create a 2x3 tensor
|
||||
auto tensor = torch::arange(6, torch::kFloat32).view({2, 3});
|
||||
|
||||
// use torch::reshape to reshape it to 3x2
|
||||
auto reshaped = torch::reshape(tensor, {3, 2});
|
||||
|
||||
// verify the shape
|
||||
EXPECT_EQ(reshaped.size(0), 3);
|
||||
EXPECT_EQ(reshaped.size(1), 2);
|
||||
|
||||
// verify the data content remains the same
|
||||
EXPECT_FLOAT_EQ(reshaped[0][0].item<float>(), 0.0f);
|
||||
EXPECT_FLOAT_EQ(reshaped[0][1].item<float>(), 1.0f);
|
||||
EXPECT_FLOAT_EQ(reshaped[1][0].item<float>(), 2.0f);
|
||||
EXPECT_FLOAT_EQ(reshaped[1][1].item<float>(), 3.0f);
|
||||
EXPECT_FLOAT_EQ(reshaped[2][0].item<float>(), 4.0f);
|
||||
EXPECT_FLOAT_EQ(reshaped[2][1].item<float>(), 5.0f);
|
||||
|
||||
// verify the total number of elements remains the same
|
||||
EXPECT_EQ(tensor.numel(), reshaped.numel());
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Define PADDLE_WITH_CUSTOM_KERNEL before any Paddle header so that
|
||||
// dense_tensor.inl is excluded from this translation unit. That hides
|
||||
// phi::DenseTensor::ResetHolder from the compiler, which makes the SFINAE
|
||||
// template overload of TensorBase::MaybeResetHolder drop out and forces the
|
||||
// fallback overload to be selected -- the same path external custom-kernel
|
||||
// extensions take. Guards PR #78826's fix to that fallback.
|
||||
#define PADDLE_WITH_CUSTOM_KERNEL
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/ops/ones.h>
|
||||
#include <ATen/ops/resize.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
TEST(ATenResizeCustomKernel, ResizeGrowsStorageInFallbackPath) {
|
||||
at::Tensor t = at::ones({2}, at::kInt);
|
||||
ASSERT_EQ(t.numel(), 2);
|
||||
ASSERT_EQ(t.storage().nbytes(), 8u);
|
||||
|
||||
t.resize_({4});
|
||||
|
||||
ASSERT_EQ(t.numel(), 4);
|
||||
// Without PR #78826 the storage().nbytes() stays at 8 because
|
||||
// SyncStorageFromTensor rebuilds StorageImpl from the stale holder.
|
||||
ASSERT_GE(t.storage().nbytes(), 16u);
|
||||
}
|
||||
|
||||
// Covers the numel() == 0 branch in MaybeResetHolder fallback.
|
||||
// at::empty({0}) creates a tensor whose initial holder is a plain
|
||||
// Allocation (not yet synced to StorageHolderView). During TensorBase
|
||||
// construction, SyncStorageFromTensor sees holder != compat_holder and
|
||||
// calls MaybeResetHolder with dense->numel() == 0, forcing the
|
||||
// offset-reset branch.
|
||||
TEST(ATenResizeCustomKernel, EmptyTensorOffsetResetInFallbackPath) {
|
||||
at::Tensor t = at::empty({0}, at::kInt);
|
||||
ASSERT_EQ(t.numel(), 0);
|
||||
|
||||
// storage() triggers SyncStorageFromTensor -> MaybeResetHolder
|
||||
// with dense->numel() == 0, covering the offset-reset branch.
|
||||
auto s = t.storage();
|
||||
ASSERT_TRUE(s.valid());
|
||||
ASSERT_EQ(t.numel(), 0);
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/ops/as_strided.h>
|
||||
#include <ATen/ops/resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ======================== resize_ tests ========================
|
||||
// Note: compat resize_ mutates the underlying DenseTensor directly so
|
||||
// shrink/grow round-trips preserve storage semantics without introducing new
|
||||
// memory_format hard errors in this split PR.
|
||||
|
||||
TEST(TensorResizeTest, ResizeBasic) {
|
||||
// Create a 2x3 tensor
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
// Resize to 3x2 (same 6 elements)
|
||||
t.resize_({3, 2});
|
||||
|
||||
// Verify the shape
|
||||
ASSERT_EQ(t.sizes()[0], 3);
|
||||
ASSERT_EQ(t.sizes()[1], 2);
|
||||
}
|
||||
|
||||
TEST(TensorResizeTest, ResizeFlatten) {
|
||||
// Create a 2x3 tensor
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
// Resize to flat 1D (same 6 elements)
|
||||
t.resize_({6});
|
||||
|
||||
ASSERT_EQ(t.sizes()[0], 6);
|
||||
}
|
||||
|
||||
TEST(TensorResizeTest, ResizeSameSize) {
|
||||
// Create a tensor
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
// Resize to same size
|
||||
t.resize_({2, 3});
|
||||
|
||||
ASSERT_EQ(t.sizes()[0], 2);
|
||||
ASSERT_EQ(t.sizes()[1], 3);
|
||||
}
|
||||
|
||||
TEST(TensorResizeTest, ResizeTo1D) {
|
||||
// Create a 2x3 tensor
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
// Resize to 1D (6 elements)
|
||||
t.resize_({6});
|
||||
|
||||
ASSERT_EQ(t.sizes()[0], 6);
|
||||
}
|
||||
|
||||
TEST(TensorResizeTest, ResizeTo2D) {
|
||||
// Create a 6-element tensor
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
|
||||
// Resize to 2x3 (6 elements)
|
||||
t.resize_({2, 3});
|
||||
|
||||
ASSERT_EQ(t.sizes()[0], 2);
|
||||
ASSERT_EQ(t.sizes()[1], 3);
|
||||
}
|
||||
|
||||
TEST(TensorResizeTest, ResizeSquare) {
|
||||
// Create a 2x3 tensor
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
// Resize to 1x6 (6 elements)
|
||||
t.resize_({1, 6});
|
||||
|
||||
ASSERT_EQ(t.sizes()[0], 1);
|
||||
ASSERT_EQ(t.sizes()[1], 6);
|
||||
}
|
||||
|
||||
TEST(TensorResizeTest, ResizePreservesData) {
|
||||
// Create tensor with known values
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
// Resize to 3x2
|
||||
t.resize_({3, 2});
|
||||
|
||||
// Verify data is preserved (in row-major order)
|
||||
float* data = t.data_ptr<float>();
|
||||
ASSERT_FLOAT_EQ(data[0], 0.0f);
|
||||
ASSERT_FLOAT_EQ(data[1], 1.0f);
|
||||
ASSERT_FLOAT_EQ(data[2], 2.0f);
|
||||
ASSERT_FLOAT_EQ(data[3], 3.0f);
|
||||
ASSERT_FLOAT_EQ(data[4], 4.0f);
|
||||
ASSERT_FLOAT_EQ(data[5], 5.0f);
|
||||
}
|
||||
|
||||
TEST(TensorResizeTest, ResizeShrinkDifferentNumel) {
|
||||
at::Tensor t = at::arange(24, at::kFloat).reshape({2, 3, 4});
|
||||
|
||||
t.resize_({4, 5});
|
||||
|
||||
ASSERT_EQ(t.sizes()[0], 4);
|
||||
ASSERT_EQ(t.sizes()[1], 5);
|
||||
|
||||
float* data = t.data_ptr<float>();
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
ASSERT_FLOAT_EQ(data[i], static_cast<float>(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TensorResizeTest, ResizeGrowDifferentNumelPreservesPrefix) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
|
||||
t.resize_({2, 5});
|
||||
|
||||
ASSERT_EQ(t.sizes()[0], 2);
|
||||
ASSERT_EQ(t.sizes()[1], 5);
|
||||
|
||||
float* data = t.data_ptr<float>();
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
ASSERT_FLOAT_EQ(data[i], static_cast<float>(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TensorResizeTest, ResizeShrinkGrowRoundTripPreservesTail) {
|
||||
at::Tensor t = at::arange(24, at::kFloat).reshape({2, 3, 4});
|
||||
|
||||
t.resize_({4, 5});
|
||||
t.resize_({2, 3, 4});
|
||||
|
||||
ASSERT_EQ(t.sizes()[0], 2);
|
||||
ASSERT_EQ(t.sizes()[1], 3);
|
||||
ASSERT_EQ(t.sizes()[2], 4);
|
||||
|
||||
float* data = t.data_ptr<float>();
|
||||
for (int i = 0; i < 24; ++i) {
|
||||
ASSERT_FLOAT_EQ(data[i], static_cast<float>(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TensorResizeTest, ResizeChannelsLastMemoryFormatDoesNotThrow) {
|
||||
at::Tensor t = at::arange(24, at::kFloat).reshape({1, 2, 3, 4});
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
t.resize_(std::vector<int64_t>{1, 3, 2, 4}, at::MemoryFormat::ChannelsLast);
|
||||
});
|
||||
|
||||
ASSERT_EQ(t.sizes()[0], 1);
|
||||
ASSERT_EQ(t.sizes()[1], 3);
|
||||
ASSERT_EQ(t.sizes()[2], 2);
|
||||
ASSERT_EQ(t.sizes()[3], 4);
|
||||
}
|
||||
|
||||
TEST(TensorResizeTest, ResizeChannelsLast3dMemoryFormatDoesNotThrow) {
|
||||
at::Tensor t = at::arange(24, at::kFloat).reshape({1, 2, 2, 2, 3});
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
t.resize_(std::vector<int64_t>{1, 2, 2, 3, 2},
|
||||
at::MemoryFormat::ChannelsLast3d);
|
||||
});
|
||||
|
||||
ASSERT_EQ(t.sizes()[0], 1);
|
||||
ASSERT_EQ(t.sizes()[1], 2);
|
||||
ASSERT_EQ(t.sizes()[2], 2);
|
||||
ASSERT_EQ(t.sizes()[3], 3);
|
||||
ASSERT_EQ(t.sizes()[4], 2);
|
||||
}
|
||||
|
||||
TEST(TensorResizeTest, ResizeRejectsNegativeDimension) {
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
auto bad_size = std::vector<int64_t>{2, -1};
|
||||
|
||||
EXPECT_THROW(t.resize_(bad_size), std::exception);
|
||||
}
|
||||
|
||||
TEST(TensorResizeTest, ResizeRejectsNumelOverflow) {
|
||||
at::Tensor t = at::arange(1, at::kFloat);
|
||||
auto huge_size = std::vector<int64_t>{std::numeric_limits<int64_t>::max(), 2};
|
||||
|
||||
EXPECT_THROW(t.resize_(huge_size), std::exception);
|
||||
}
|
||||
|
||||
TEST(TensorResizeTest, ResizeReturnReference) {
|
||||
// Create a tensor
|
||||
at::Tensor t = at::zeros({2, 3});
|
||||
|
||||
// Resize in-place, returns reference to same tensor
|
||||
const at::Tensor& result = t.resize_({3, 2});
|
||||
|
||||
// Verify returned reference points to same tensor
|
||||
ASSERT_EQ(result.sizes()[0], 3);
|
||||
ASSERT_EQ(result.sizes()[1], 2);
|
||||
|
||||
// Verify original tensor was modified
|
||||
ASSERT_EQ(t.sizes()[0], 3);
|
||||
ASSERT_EQ(t.sizes()[1], 2);
|
||||
}
|
||||
|
||||
TEST(TensorResizeTest, ResizePreserveDtype) {
|
||||
// Create an int tensor
|
||||
at::Tensor t = at::zeros({2, 3}, at::kInt);
|
||||
|
||||
// Resize to same element count (3x2)
|
||||
t.resize_({3, 2});
|
||||
|
||||
// Verify dtype is preserved
|
||||
ASSERT_EQ(t.dtype(), at::kInt);
|
||||
}
|
||||
|
||||
TEST(TensorResizeTest, ResizeLargeTensor) {
|
||||
// Create a larger tensor 4x5 = 20 elements
|
||||
at::Tensor t = at::arange(20, at::kFloat).reshape({4, 5});
|
||||
|
||||
// Resize to 2x10 (20 elements)
|
||||
t.resize_({2, 10});
|
||||
|
||||
ASSERT_EQ(t.sizes()[0], 2);
|
||||
ASSERT_EQ(t.sizes()[1], 10);
|
||||
}
|
||||
|
||||
TEST(TensorResizeTest, ResizeChain) {
|
||||
// Multiple consecutive resizes
|
||||
at::Tensor t = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
|
||||
// Resize to 4x3
|
||||
t.resize_({4, 3});
|
||||
ASSERT_EQ(t.sizes()[0], 4);
|
||||
ASSERT_EQ(t.sizes()[1], 3);
|
||||
|
||||
// Resize to 2x6
|
||||
t.resize_({2, 6});
|
||||
ASSERT_EQ(t.sizes()[0], 2);
|
||||
ASSERT_EQ(t.sizes()[1], 6);
|
||||
|
||||
// Resize back to 3x4
|
||||
t.resize_({3, 4});
|
||||
ASSERT_EQ(t.sizes()[0], 3);
|
||||
ASSERT_EQ(t.sizes()[1], 4);
|
||||
}
|
||||
|
||||
// Test that resizing a view with shared storage copies data from the start of
|
||||
// the storage, not from the view's offset, to preserve the original data and
|
||||
// storage semantics.
|
||||
TEST(TensorResizeTest, ResizeSliceSharedStorageCopiesFromStorageStart) {
|
||||
// ta = [1, 2, 3, 4], tb = [2, 3, 4]
|
||||
// Build tb through as_strided so it is a view with a non-zero storage
|
||||
// offset even when backend slice kernels materialize copies.
|
||||
at::Tensor ta = at::tensor({1, 2, 3, 4}, at::kInt);
|
||||
at::Tensor tb = ta.as_strided({3}, {1}, 1);
|
||||
|
||||
tb.resize_(4);
|
||||
|
||||
// After resize, tb[0] and ta[1] must point to the exact same address.
|
||||
ASSERT_EQ(tb.data_ptr<int>(), ta.data_ptr<int>() + 1);
|
||||
|
||||
// The original storage contents should remain unchanged.
|
||||
ASSERT_EQ(ta[0].item<int>(), 1);
|
||||
ASSERT_EQ(ta[1].item<int>(), 2);
|
||||
ASSERT_EQ(ta[2].item<int>(), 3);
|
||||
ASSERT_EQ(ta[3].item<int>(), 4);
|
||||
|
||||
// PyTorch only preserves the pre-existing prefix here. The newly exposed
|
||||
// tail element after resize_ is uninitialized and should not be asserted.
|
||||
ASSERT_EQ(tb[0].item<int>(), 2);
|
||||
ASSERT_EQ(tb[1].item<int>(), 3);
|
||||
ASSERT_EQ(tb[2].item<int>(), 4);
|
||||
ASSERT_EQ(tb.numel(), 4);
|
||||
}
|
||||
@@ -0,0 +1,449 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/Layout.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ==================== select tests ====================
|
||||
|
||||
// Test for select on 1D tensor
|
||||
TEST(SelectTest, Select1D) {
|
||||
auto tensor = at::arange(10, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
// Select element at index 5
|
||||
auto selected = tensor.select(0, 5);
|
||||
|
||||
// Result should be a scalar (0-dim tensor)
|
||||
EXPECT_EQ(selected.dim(), 0);
|
||||
EXPECT_FLOAT_EQ(selected.item<float>(), 5.0f);
|
||||
}
|
||||
|
||||
// Test for select on 2D tensor along dim 0
|
||||
TEST(SelectTest, Select2DDim0) {
|
||||
auto tensor =
|
||||
at::arange(12, at::TensorOptions().dtype(at::kFloat)).reshape({3, 4});
|
||||
|
||||
// Select row at index 1 (second row)
|
||||
auto selected = tensor.select(0, 1);
|
||||
|
||||
// Result should be 1D tensor of size 4
|
||||
EXPECT_EQ(selected.dim(), 1);
|
||||
EXPECT_EQ(selected.size(0), 4);
|
||||
|
||||
// Second row should be [4, 5, 6, 7]
|
||||
EXPECT_FLOAT_EQ(selected[0].item<float>(), 4.0f);
|
||||
EXPECT_FLOAT_EQ(selected[1].item<float>(), 5.0f);
|
||||
EXPECT_FLOAT_EQ(selected[2].item<float>(), 6.0f);
|
||||
EXPECT_FLOAT_EQ(selected[3].item<float>(), 7.0f);
|
||||
}
|
||||
|
||||
// Test for select on 2D tensor along dim 1
|
||||
TEST(SelectTest, Select2DDim1) {
|
||||
auto tensor =
|
||||
at::arange(12, at::TensorOptions().dtype(at::kFloat)).reshape({3, 4});
|
||||
|
||||
// Select column at index 2 (third column)
|
||||
auto selected = tensor.select(1, 2);
|
||||
|
||||
// Result should be 1D tensor of size 3
|
||||
EXPECT_EQ(selected.dim(), 1);
|
||||
EXPECT_EQ(selected.size(0), 3);
|
||||
|
||||
// Third column should be [2, 6, 10]
|
||||
EXPECT_FLOAT_EQ(selected[0].item<float>(), 2.0f);
|
||||
EXPECT_FLOAT_EQ(selected[1].item<float>(), 6.0f);
|
||||
EXPECT_FLOAT_EQ(selected[2].item<float>(), 10.0f);
|
||||
}
|
||||
|
||||
// Test for select on 3D tensor
|
||||
TEST(SelectTest, Select3D) {
|
||||
auto tensor =
|
||||
at::arange(24, at::TensorOptions().dtype(at::kFloat)).reshape({2, 3, 4});
|
||||
|
||||
// Select along dim 0
|
||||
auto selected_dim0 = tensor.select(0, 1);
|
||||
EXPECT_EQ(selected_dim0.dim(), 2);
|
||||
EXPECT_EQ(selected_dim0.size(0), 3);
|
||||
EXPECT_EQ(selected_dim0.size(1), 4);
|
||||
|
||||
// Select along dim 1
|
||||
auto selected_dim1 = tensor.select(1, 2);
|
||||
EXPECT_EQ(selected_dim1.dim(), 2);
|
||||
EXPECT_EQ(selected_dim1.size(0), 2);
|
||||
EXPECT_EQ(selected_dim1.size(1), 4);
|
||||
|
||||
// Select along dim 2
|
||||
auto selected_dim2 = tensor.select(2, 3);
|
||||
EXPECT_EQ(selected_dim2.dim(), 2);
|
||||
EXPECT_EQ(selected_dim2.size(0), 2);
|
||||
EXPECT_EQ(selected_dim2.size(1), 3);
|
||||
}
|
||||
|
||||
// Note: Negative index is not supported by Paddle's slice implementation
|
||||
// Test for select with last index using positive index
|
||||
TEST(SelectTest, SelectLastIndex) {
|
||||
auto tensor = at::arange(10, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
// Select last element using positive index (size - 1)
|
||||
auto selected = tensor.select(0, 9);
|
||||
|
||||
EXPECT_EQ(selected.dim(), 0);
|
||||
EXPECT_FLOAT_EQ(selected.item<float>(), 9.0f);
|
||||
}
|
||||
|
||||
// Test for select with first and last indices
|
||||
TEST(SelectTest, SelectBoundary) {
|
||||
auto tensor = at::arange(5, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
// Select first element
|
||||
auto first = tensor.select(0, 0);
|
||||
EXPECT_FLOAT_EQ(first.item<float>(), 0.0f);
|
||||
|
||||
// Select last element
|
||||
auto last = tensor.select(0, 4);
|
||||
EXPECT_FLOAT_EQ(last.item<float>(), 4.0f);
|
||||
}
|
||||
|
||||
// ==================== select_symint tests ====================
|
||||
|
||||
// Test for select_symint
|
||||
TEST(SelectTest, SelectSymInt) {
|
||||
auto tensor = at::arange(10, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
c10::SymInt index(5);
|
||||
auto selected = tensor.select_symint(0, index);
|
||||
|
||||
EXPECT_EQ(selected.dim(), 0);
|
||||
EXPECT_FLOAT_EQ(selected.item<float>(), 5.0f);
|
||||
}
|
||||
|
||||
// Test for select_symint on 2D tensor
|
||||
TEST(SelectTest, SelectSymInt2D) {
|
||||
auto tensor =
|
||||
at::arange(12, at::TensorOptions().dtype(at::kFloat)).reshape({3, 4});
|
||||
|
||||
c10::SymInt index(1);
|
||||
auto selected = tensor.select_symint(0, index);
|
||||
|
||||
EXPECT_EQ(selected.dim(), 1);
|
||||
EXPECT_EQ(selected.size(0), 4);
|
||||
EXPECT_FLOAT_EQ(selected[0].item<float>(), 4.0f);
|
||||
}
|
||||
|
||||
TEST(SelectTest, SelectNegativeIndexBranches) {
|
||||
auto tensor =
|
||||
at::arange(12, at::TensorOptions().dtype(at::kFloat)).reshape({3, 4});
|
||||
|
||||
auto selected = tensor.select(-1, -1);
|
||||
EXPECT_EQ(selected.dim(), 1);
|
||||
EXPECT_EQ(selected.size(0), 3);
|
||||
EXPECT_FLOAT_EQ(selected[0].item<float>(), 3.0f);
|
||||
EXPECT_FLOAT_EQ(selected[2].item<float>(), 11.0f);
|
||||
|
||||
c10::SymInt index(-1);
|
||||
auto selected_symint = tensor.select_symint(-1, index);
|
||||
EXPECT_EQ(selected_symint.size(0), 3);
|
||||
EXPECT_FLOAT_EQ(selected_symint[1].item<float>(), 7.0f);
|
||||
}
|
||||
|
||||
// ==================== index_select tests ====================
|
||||
|
||||
// Test for index_select on 1D tensor
|
||||
TEST(IndexSelectTest, IndexSelect1D) {
|
||||
auto tensor = at::arange(10, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
// Create index tensor [2, 5, 7]
|
||||
auto index = at::empty({3}, at::TensorOptions().dtype(at::kLong));
|
||||
index.data_ptr<int64_t>()[0] = 2;
|
||||
index.data_ptr<int64_t>()[1] = 5;
|
||||
index.data_ptr<int64_t>()[2] = 7;
|
||||
|
||||
auto selected = tensor.index_select(0, index);
|
||||
|
||||
EXPECT_EQ(selected.dim(), 1);
|
||||
EXPECT_EQ(selected.size(0), 3);
|
||||
EXPECT_FLOAT_EQ(selected[0].item<float>(), 2.0f);
|
||||
EXPECT_FLOAT_EQ(selected[1].item<float>(), 5.0f);
|
||||
EXPECT_FLOAT_EQ(selected[2].item<float>(), 7.0f);
|
||||
}
|
||||
|
||||
// Test for index_select on 2D tensor along dim 0 (select rows)
|
||||
TEST(IndexSelectTest, IndexSelect2DDim0) {
|
||||
auto tensor =
|
||||
at::arange(12, at::TensorOptions().dtype(at::kFloat)).reshape({3, 4});
|
||||
|
||||
// Select rows [0, 2]
|
||||
auto index = at::empty({2}, at::TensorOptions().dtype(at::kLong));
|
||||
index.data_ptr<int64_t>()[0] = 0;
|
||||
index.data_ptr<int64_t>()[1] = 2;
|
||||
|
||||
auto selected = tensor.index_select(0, index);
|
||||
|
||||
EXPECT_EQ(selected.dim(), 2);
|
||||
EXPECT_EQ(selected.size(0), 2);
|
||||
EXPECT_EQ(selected.size(1), 4);
|
||||
|
||||
// First selected row [0, 1, 2, 3]
|
||||
EXPECT_FLOAT_EQ(selected[0][0].item<float>(), 0.0f);
|
||||
EXPECT_FLOAT_EQ(selected[0][3].item<float>(), 3.0f);
|
||||
|
||||
// Second selected row [8, 9, 10, 11]
|
||||
EXPECT_FLOAT_EQ(selected[1][0].item<float>(), 8.0f);
|
||||
EXPECT_FLOAT_EQ(selected[1][3].item<float>(), 11.0f);
|
||||
}
|
||||
|
||||
// Test for index_select on 2D tensor along dim 1 (select columns)
|
||||
TEST(IndexSelectTest, IndexSelect2DDim1) {
|
||||
auto tensor =
|
||||
at::arange(12, at::TensorOptions().dtype(at::kFloat)).reshape({3, 4});
|
||||
|
||||
// Select columns [1, 3]
|
||||
auto index = at::empty({2}, at::TensorOptions().dtype(at::kLong));
|
||||
index.data_ptr<int64_t>()[0] = 1;
|
||||
index.data_ptr<int64_t>()[1] = 3;
|
||||
|
||||
auto selected = tensor.index_select(1, index);
|
||||
|
||||
EXPECT_EQ(selected.dim(), 2);
|
||||
EXPECT_EQ(selected.size(0), 3);
|
||||
EXPECT_EQ(selected.size(1), 2);
|
||||
|
||||
// Check values
|
||||
EXPECT_FLOAT_EQ(selected[0][0].item<float>(), 1.0f);
|
||||
EXPECT_FLOAT_EQ(selected[0][1].item<float>(), 3.0f);
|
||||
EXPECT_FLOAT_EQ(selected[1][0].item<float>(), 5.0f);
|
||||
EXPECT_FLOAT_EQ(selected[1][1].item<float>(), 7.0f);
|
||||
}
|
||||
|
||||
// Test for index_select with duplicate indices
|
||||
TEST(IndexSelectTest, IndexSelectDuplicateIndices) {
|
||||
auto tensor = at::arange(5, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
// Select with duplicate indices [1, 1, 3, 1]
|
||||
auto index = at::empty({4}, at::TensorOptions().dtype(at::kLong));
|
||||
index.data_ptr<int64_t>()[0] = 1;
|
||||
index.data_ptr<int64_t>()[1] = 1;
|
||||
index.data_ptr<int64_t>()[2] = 3;
|
||||
index.data_ptr<int64_t>()[3] = 1;
|
||||
|
||||
auto selected = tensor.index_select(0, index);
|
||||
|
||||
EXPECT_EQ(selected.size(0), 4);
|
||||
EXPECT_FLOAT_EQ(selected[0].item<float>(), 1.0f);
|
||||
EXPECT_FLOAT_EQ(selected[1].item<float>(), 1.0f);
|
||||
EXPECT_FLOAT_EQ(selected[2].item<float>(), 3.0f);
|
||||
EXPECT_FLOAT_EQ(selected[3].item<float>(), 1.0f);
|
||||
}
|
||||
|
||||
// Test for index_select on 3D tensor
|
||||
TEST(IndexSelectTest, IndexSelect3D) {
|
||||
auto tensor =
|
||||
at::arange(24, at::TensorOptions().dtype(at::kFloat)).reshape({2, 3, 4});
|
||||
|
||||
// Select along dim 1
|
||||
auto index = at::empty({2}, at::TensorOptions().dtype(at::kLong));
|
||||
index.data_ptr<int64_t>()[0] = 0;
|
||||
index.data_ptr<int64_t>()[1] = 2;
|
||||
|
||||
auto selected = tensor.index_select(1, index);
|
||||
|
||||
EXPECT_EQ(selected.dim(), 3);
|
||||
EXPECT_EQ(selected.size(0), 2);
|
||||
EXPECT_EQ(selected.size(1), 2);
|
||||
EXPECT_EQ(selected.size(2), 4);
|
||||
}
|
||||
|
||||
// ==================== masked_select tests ====================
|
||||
|
||||
// Test for masked_select on 1D tensor
|
||||
TEST(MaskedSelectTest, MaskedSelect1D) {
|
||||
auto tensor = at::arange(10, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
// Create mask for elements > 5
|
||||
auto mask = at::empty({10}, at::TensorOptions().dtype(at::kBool));
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
mask.data_ptr<bool>()[i] = (i > 5);
|
||||
}
|
||||
|
||||
auto selected = tensor.masked_select(mask);
|
||||
|
||||
// Should select [6, 7, 8, 9]
|
||||
EXPECT_EQ(selected.dim(), 1);
|
||||
EXPECT_EQ(selected.numel(), 4);
|
||||
EXPECT_FLOAT_EQ(selected[0].item<float>(), 6.0f);
|
||||
EXPECT_FLOAT_EQ(selected[1].item<float>(), 7.0f);
|
||||
EXPECT_FLOAT_EQ(selected[2].item<float>(), 8.0f);
|
||||
EXPECT_FLOAT_EQ(selected[3].item<float>(), 9.0f);
|
||||
}
|
||||
|
||||
// Test for masked_select on 2D tensor
|
||||
TEST(MaskedSelectTest, MaskedSelect2D) {
|
||||
auto tensor =
|
||||
at::arange(12, at::TensorOptions().dtype(at::kFloat)).reshape({3, 4});
|
||||
|
||||
// Create mask - select even numbers
|
||||
auto mask = at::empty({3, 4}, at::TensorOptions().dtype(at::kBool));
|
||||
for (int i = 0; i < 12; ++i) {
|
||||
mask.data_ptr<bool>()[i] = (i % 2 == 0);
|
||||
}
|
||||
|
||||
auto selected = tensor.masked_select(mask);
|
||||
|
||||
// Should select [0, 2, 4, 6, 8, 10]
|
||||
EXPECT_EQ(selected.dim(), 1); // Result is always 1D
|
||||
EXPECT_EQ(selected.numel(), 6);
|
||||
EXPECT_FLOAT_EQ(selected[0].item<float>(), 0.0f);
|
||||
EXPECT_FLOAT_EQ(selected[1].item<float>(), 2.0f);
|
||||
EXPECT_FLOAT_EQ(selected[2].item<float>(), 4.0f);
|
||||
EXPECT_FLOAT_EQ(selected[3].item<float>(), 6.0f);
|
||||
EXPECT_FLOAT_EQ(selected[4].item<float>(), 8.0f);
|
||||
EXPECT_FLOAT_EQ(selected[5].item<float>(), 10.0f);
|
||||
}
|
||||
|
||||
// Test for masked_select with all true mask
|
||||
TEST(MaskedSelectTest, MaskedSelectAllTrue) {
|
||||
auto tensor = at::arange(5, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
// All true mask
|
||||
auto mask = at::empty({5}, at::TensorOptions().dtype(at::kBool));
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
mask.data_ptr<bool>()[i] = true;
|
||||
}
|
||||
|
||||
auto selected = tensor.masked_select(mask);
|
||||
|
||||
EXPECT_EQ(selected.numel(), 5);
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
EXPECT_FLOAT_EQ(selected[i].item<float>(), static_cast<float>(i));
|
||||
}
|
||||
}
|
||||
|
||||
// Test for masked_select with all false mask
|
||||
TEST(MaskedSelectTest, MaskedSelectAllFalse) {
|
||||
auto tensor = at::arange(5, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
// All false mask
|
||||
auto mask = at::empty({5}, at::TensorOptions().dtype(at::kBool));
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
mask.data_ptr<bool>()[i] = false;
|
||||
}
|
||||
|
||||
auto selected = tensor.masked_select(mask);
|
||||
|
||||
EXPECT_EQ(selected.numel(), 0);
|
||||
}
|
||||
|
||||
// Test for masked_select with different dtypes
|
||||
TEST(MaskedSelectTest, MaskedSelectDifferentDtypes) {
|
||||
// Test with int64
|
||||
auto tensor_int = at::arange(10, at::TensorOptions().dtype(at::kLong));
|
||||
auto mask = at::empty({10}, at::TensorOptions().dtype(at::kBool));
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
mask.data_ptr<bool>()[i] = (i >= 7);
|
||||
}
|
||||
|
||||
auto selected = tensor_int.masked_select(mask);
|
||||
|
||||
EXPECT_EQ(selected.numel(), 3);
|
||||
EXPECT_EQ(selected[0].item<int64_t>(), 7);
|
||||
EXPECT_EQ(selected[1].item<int64_t>(), 8);
|
||||
EXPECT_EQ(selected[2].item<int64_t>(), 9);
|
||||
}
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
// Test for select on CUDA
|
||||
TEST(SelectTest, SelectCUDA) {
|
||||
auto tensor =
|
||||
at::arange(10, at::TensorOptions().dtype(at::kFloat).device(at::kCUDA));
|
||||
|
||||
auto selected = tensor.select(0, 5);
|
||||
|
||||
EXPECT_TRUE(selected.is_cuda());
|
||||
EXPECT_EQ(selected.dim(), 0);
|
||||
|
||||
auto cpu_selected = selected.cpu();
|
||||
EXPECT_FLOAT_EQ(cpu_selected.item<float>(), 5.0f);
|
||||
}
|
||||
|
||||
// Test for index_select on CUDA
|
||||
TEST(IndexSelectTest, IndexSelectCUDA) {
|
||||
auto tensor =
|
||||
at::arange(10, at::TensorOptions().dtype(at::kFloat).device(at::kCUDA));
|
||||
|
||||
auto index =
|
||||
at::empty({3}, at::TensorOptions().dtype(at::kLong).device(at::kCUDA));
|
||||
auto cpu_index = index.cpu();
|
||||
cpu_index.data_ptr<int64_t>()[0] = 1;
|
||||
cpu_index.data_ptr<int64_t>()[1] = 3;
|
||||
cpu_index.data_ptr<int64_t>()[2] = 5;
|
||||
index.copy_(cpu_index);
|
||||
|
||||
auto selected = tensor.index_select(0, index);
|
||||
|
||||
EXPECT_TRUE(selected.is_cuda());
|
||||
EXPECT_EQ(selected.size(0), 3);
|
||||
|
||||
auto cpu_selected = selected.cpu();
|
||||
EXPECT_FLOAT_EQ(cpu_selected[0].item<float>(), 1.0f);
|
||||
EXPECT_FLOAT_EQ(cpu_selected[1].item<float>(), 3.0f);
|
||||
EXPECT_FLOAT_EQ(cpu_selected[2].item<float>(), 5.0f);
|
||||
}
|
||||
|
||||
// Test for masked_select on CUDA
|
||||
TEST(MaskedSelectTest, MaskedSelectCUDA) {
|
||||
auto tensor =
|
||||
at::arange(10, at::TensorOptions().dtype(at::kFloat).device(at::kCUDA));
|
||||
|
||||
// Create mask on CUDA and copy data from CPU
|
||||
auto mask =
|
||||
at::empty({10}, at::TensorOptions().dtype(at::kBool).device(at::kCUDA));
|
||||
auto cpu_mask = mask.cpu();
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
cpu_mask.data_ptr<bool>()[i] = (i % 2 == 0);
|
||||
}
|
||||
mask.copy_(cpu_mask);
|
||||
|
||||
auto selected = tensor.masked_select(mask);
|
||||
|
||||
EXPECT_TRUE(selected.is_cuda());
|
||||
EXPECT_EQ(selected.numel(), 5);
|
||||
|
||||
auto cpu_selected = selected.cpu();
|
||||
float val0 = cpu_selected[0].item<float>();
|
||||
float val1 = cpu_selected[1].item<float>();
|
||||
float val2 = cpu_selected[2].item<float>();
|
||||
float val3 = cpu_selected[3].item<float>();
|
||||
float val4 = cpu_selected[4].item<float>();
|
||||
EXPECT_FLOAT_EQ(val0, 0.0f);
|
||||
EXPECT_FLOAT_EQ(val1, 2.0f);
|
||||
EXPECT_FLOAT_EQ(val2, 4.0f);
|
||||
EXPECT_FLOAT_EQ(val3, 6.0f);
|
||||
EXPECT_FLOAT_EQ(val4, 8.0f);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,344 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/Layout.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// Test for tensor_split with sections
|
||||
TEST(TensorSplitTest, TensorSplitWithSections) {
|
||||
// Create a test tensor [0, 1, 2, ..., 8] (9 elements, evenly divisible by 3)
|
||||
auto tensor = at::arange(9, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
// Split into 3 sections along dim 0
|
||||
auto splits = tensor.tensor_split(3, 0);
|
||||
|
||||
EXPECT_EQ(splits.size(), 3);
|
||||
EXPECT_EQ(splits[0].numel(), 3); // [0, 1, 2]
|
||||
EXPECT_EQ(splits[1].numel(), 3); // [3, 4, 5]
|
||||
EXPECT_EQ(splits[2].numel(), 3); // [6, 7, 8]
|
||||
|
||||
// Verify first split values
|
||||
EXPECT_FLOAT_EQ(splits[0][0].item<float>(), 0.0f);
|
||||
EXPECT_FLOAT_EQ(splits[0][2].item<float>(), 2.0f);
|
||||
}
|
||||
|
||||
// Test for tensor_split with indices (PyTorch semantics: indices are positions)
|
||||
TEST(TensorSplitTest, TensorSplitWithIndices) {
|
||||
auto tensor = at::arange(10, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
// Split at indices [2, 3, 5] -> [0:2], [2:3], [3:5], [5:10]
|
||||
std::vector<int64_t> indices = {2, 3, 5};
|
||||
auto splits = tensor.tensor_split(at::IntArrayRef(indices), 0);
|
||||
|
||||
EXPECT_EQ(splits.size(), 4); // 4 segments
|
||||
EXPECT_EQ(splits[0].numel(), 2); // [0, 1]
|
||||
EXPECT_EQ(splits[1].numel(), 1); // [2]
|
||||
EXPECT_EQ(splits[2].numel(), 2); // [3, 4]
|
||||
EXPECT_EQ(splits[3].numel(), 5); // [5, 6, 7, 8, 9]
|
||||
}
|
||||
|
||||
// Test for tensor_split with tensor indices (1D tensor = indices)
|
||||
TEST(TensorSplitTest, TensorSplitWithTensorIndices) {
|
||||
auto tensor = at::arange(10, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
// Create 1D tensor with indices [3, 7] -> split at positions 3 and 7
|
||||
// Result: [0:3], [3:7], [7:10] -> sizes: 3, 4, 3
|
||||
auto indices_tensor = at::empty({2}, at::TensorOptions().dtype(at::kLong));
|
||||
indices_tensor.data_ptr<int64_t>()[0] = 3;
|
||||
indices_tensor.data_ptr<int64_t>()[1] = 7;
|
||||
auto splits = tensor.tensor_split(indices_tensor, 0);
|
||||
|
||||
EXPECT_EQ(splits.size(), 3); // 3 segments
|
||||
EXPECT_EQ(splits[0].numel(), 3); // [0, 1, 2]
|
||||
EXPECT_EQ(splits[1].numel(), 4); // [3, 4, 5, 6]
|
||||
EXPECT_EQ(splits[2].numel(), 3); // [7, 8, 9]
|
||||
}
|
||||
|
||||
// Test for tensor_split with tensor scalar (0D tensor = number of sections)
|
||||
TEST(TensorSplitTest, TensorSplitWithTensorScalar) {
|
||||
auto tensor = at::arange(9, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
// Create 0D scalar tensor with value 3 -> split into 3 sections
|
||||
auto sections_tensor = at::empty({}, at::TensorOptions().dtype(at::kLong));
|
||||
sections_tensor.data_ptr<int64_t>()[0] = 3;
|
||||
auto splits = tensor.tensor_split(sections_tensor, 0);
|
||||
|
||||
EXPECT_EQ(splits.size(), 3);
|
||||
EXPECT_EQ(splits[0].numel(), 3); // [0, 1, 2]
|
||||
EXPECT_EQ(splits[1].numel(), 3); // [3, 4, 5]
|
||||
EXPECT_EQ(splits[2].numel(), 3); // [6, 7, 8]
|
||||
}
|
||||
|
||||
// Test for split with split_size
|
||||
TEST(SplitTest, SplitWithSize) {
|
||||
auto tensor = at::arange(10, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
// Split with size 3
|
||||
auto splits = tensor.split(3, 0);
|
||||
|
||||
EXPECT_EQ(splits.size(), 4);
|
||||
EXPECT_EQ(splits[0].numel(), 3); // [0, 1, 2]
|
||||
EXPECT_EQ(splits[1].numel(), 3); // [3, 4, 5]
|
||||
EXPECT_EQ(splits[2].numel(), 3); // [6, 7, 8]
|
||||
EXPECT_EQ(splits[3].numel(), 1); // [9]
|
||||
}
|
||||
|
||||
// Test for split with split_sizes
|
||||
TEST(SplitTest, SplitWithSizes) {
|
||||
auto tensor = at::arange(10, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
// Split with sizes [2, 3, 5]
|
||||
std::vector<int64_t> sizes = {2, 3, 5};
|
||||
auto splits = tensor.split(at::IntArrayRef(sizes), 0);
|
||||
|
||||
EXPECT_EQ(splits.size(), 3);
|
||||
EXPECT_EQ(splits[0].numel(), 2); // [0, 1]
|
||||
EXPECT_EQ(splits[1].numel(), 3); // [2, 3, 4]
|
||||
EXPECT_EQ(splits[2].numel(), 5); // [5, 6, 7, 8, 9]
|
||||
}
|
||||
|
||||
// Test for split_with_sizes
|
||||
TEST(SplitTest, SplitWithSizesMethod) {
|
||||
auto tensor = at::arange(12, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
std::vector<int64_t> sizes = {4, 4, 4};
|
||||
auto splits = tensor.split_with_sizes(at::IntArrayRef(sizes), 0);
|
||||
|
||||
EXPECT_EQ(splits.size(), 3);
|
||||
for (size_t i = 0; i < splits.size(); ++i) {
|
||||
EXPECT_EQ(splits[i].numel(), 4);
|
||||
}
|
||||
}
|
||||
|
||||
// Test for unsafe_split
|
||||
TEST(SplitTest, UnsafeSplit) {
|
||||
auto tensor = at::arange(10, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
auto splits = tensor.unsafe_split(4, 0);
|
||||
|
||||
EXPECT_EQ(splits.size(), 3);
|
||||
EXPECT_EQ(splits[0].numel(), 4);
|
||||
EXPECT_EQ(splits[1].numel(), 4);
|
||||
EXPECT_EQ(splits[2].numel(), 2);
|
||||
}
|
||||
|
||||
// Test for unsafe_split_with_sizes
|
||||
TEST(SplitTest, UnsafeSplitWithSizes) {
|
||||
auto tensor = at::arange(10, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
std::vector<int64_t> sizes = {3, 3, 4};
|
||||
auto splits = tensor.unsafe_split_with_sizes(at::IntArrayRef(sizes), 0);
|
||||
|
||||
EXPECT_EQ(splits.size(), 3);
|
||||
EXPECT_EQ(splits[0].numel(), 3);
|
||||
EXPECT_EQ(splits[1].numel(), 3);
|
||||
EXPECT_EQ(splits[2].numel(), 4);
|
||||
}
|
||||
|
||||
// Test for hsplit with 1D tensor
|
||||
TEST(SplitTest, HSplit1D) {
|
||||
auto tensor = at::arange(6, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
// For 1D tensor, hsplit splits along dim 0
|
||||
auto splits = tensor.hsplit(3);
|
||||
|
||||
EXPECT_EQ(splits.size(), 3);
|
||||
EXPECT_EQ(splits[0].numel(), 2);
|
||||
EXPECT_EQ(splits[1].numel(), 2);
|
||||
EXPECT_EQ(splits[2].numel(), 2);
|
||||
}
|
||||
|
||||
// Test for hsplit with 2D tensor
|
||||
TEST(SplitTest, HSplit2D) {
|
||||
auto tensor =
|
||||
at::arange(12, at::TensorOptions().dtype(at::kFloat)).reshape({3, 4});
|
||||
|
||||
// For 2D tensor, hsplit splits along dim 1
|
||||
auto splits = tensor.hsplit(2);
|
||||
|
||||
EXPECT_EQ(splits.size(), 2);
|
||||
EXPECT_EQ(splits[0].size(0), 3);
|
||||
EXPECT_EQ(splits[0].size(1), 2);
|
||||
EXPECT_EQ(splits[1].size(0), 3);
|
||||
EXPECT_EQ(splits[1].size(1), 2);
|
||||
}
|
||||
|
||||
// Test for hsplit with indices (PyTorch semantics: indices are positions)
|
||||
TEST(SplitTest, HSplitWithIndices) {
|
||||
auto tensor = at::arange(10, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
// Split at indices [2, 5] -> [0:2], [2:5], [5:10]
|
||||
std::vector<int64_t> indices = {2, 5};
|
||||
auto splits = tensor.hsplit(at::IntArrayRef(indices));
|
||||
|
||||
EXPECT_EQ(splits.size(), 3);
|
||||
EXPECT_EQ(splits[0].numel(), 2); // [0, 1]
|
||||
EXPECT_EQ(splits[1].numel(), 3); // [2, 3, 4]
|
||||
EXPECT_EQ(splits[2].numel(), 5); // [5, 6, 7, 8, 9]
|
||||
}
|
||||
|
||||
// Test for vsplit
|
||||
TEST(SplitTest, VSplit) {
|
||||
auto tensor =
|
||||
at::arange(12, at::TensorOptions().dtype(at::kFloat)).reshape({6, 2});
|
||||
|
||||
// vsplit splits along dim 0
|
||||
auto splits = tensor.vsplit(3);
|
||||
|
||||
EXPECT_EQ(splits.size(), 3);
|
||||
EXPECT_EQ(splits[0].size(0), 2);
|
||||
EXPECT_EQ(splits[1].size(0), 2);
|
||||
EXPECT_EQ(splits[2].size(0), 2);
|
||||
}
|
||||
|
||||
// Test for vsplit with indices (PyTorch semantics: indices are positions)
|
||||
TEST(SplitTest, VSplitWithIndices) {
|
||||
auto tensor =
|
||||
at::arange(12, at::TensorOptions().dtype(at::kFloat)).reshape({6, 2});
|
||||
|
||||
// Split at indices [2, 4] along dim 0 -> [0:2], [2:4], [4:6]
|
||||
std::vector<int64_t> indices = {2, 4};
|
||||
auto splits = tensor.vsplit(at::IntArrayRef(indices));
|
||||
|
||||
EXPECT_EQ(splits.size(), 3);
|
||||
EXPECT_EQ(splits[0].size(0), 2);
|
||||
EXPECT_EQ(splits[1].size(0), 2);
|
||||
EXPECT_EQ(splits[2].size(0), 2);
|
||||
}
|
||||
|
||||
// Test for dsplit
|
||||
TEST(SplitTest, DSplit) {
|
||||
auto tensor =
|
||||
at::arange(24, at::TensorOptions().dtype(at::kFloat)).reshape({2, 3, 4});
|
||||
|
||||
// dsplit splits along dim 2
|
||||
auto splits = tensor.dsplit(2);
|
||||
|
||||
EXPECT_EQ(splits.size(), 2);
|
||||
EXPECT_EQ(splits[0].size(2), 2);
|
||||
EXPECT_EQ(splits[1].size(2), 2);
|
||||
}
|
||||
|
||||
// Test for dsplit with indices (PyTorch semantics: indices are positions)
|
||||
TEST(SplitTest, DSplitWithIndices) {
|
||||
auto tensor =
|
||||
at::arange(30, at::TensorOptions().dtype(at::kFloat)).reshape({2, 3, 5});
|
||||
|
||||
// Split at indices [2, 3] along dim 2 -> [0:2], [2:3], [3:5]
|
||||
std::vector<int64_t> indices = {2, 3};
|
||||
auto splits = tensor.dsplit(at::IntArrayRef(indices));
|
||||
|
||||
EXPECT_EQ(splits.size(), 3);
|
||||
EXPECT_EQ(splits[0].size(2), 2);
|
||||
EXPECT_EQ(splits[1].size(2), 1);
|
||||
EXPECT_EQ(splits[2].size(2), 2);
|
||||
}
|
||||
|
||||
// Test for split along different dimensions
|
||||
TEST(SplitTest, SplitAlongDifferentDims) {
|
||||
auto tensor =
|
||||
at::arange(24, at::TensorOptions().dtype(at::kFloat)).reshape({2, 3, 4});
|
||||
|
||||
// Split along dim 1
|
||||
auto splits_dim1 = tensor.split(2, 1);
|
||||
EXPECT_EQ(splits_dim1.size(), 2);
|
||||
EXPECT_EQ(splits_dim1[0].size(1), 2);
|
||||
EXPECT_EQ(splits_dim1[1].size(1), 1);
|
||||
|
||||
// Split along dim 2
|
||||
auto splits_dim2 = tensor.split(2, 2);
|
||||
EXPECT_EQ(splits_dim2.size(), 2);
|
||||
EXPECT_EQ(splits_dim2[0].size(2), 2);
|
||||
EXPECT_EQ(splits_dim2[1].size(2), 2);
|
||||
}
|
||||
|
||||
// Test for tensor_split_symint
|
||||
TEST(TensorSplitTest, TensorSplitSymInt) {
|
||||
// Use 9 elements to be evenly divisible by 3
|
||||
auto tensor = at::arange(9, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
c10::SymInt sections(3);
|
||||
auto splits = tensor.tensor_split_symint(sections, 0);
|
||||
|
||||
EXPECT_EQ(splits.size(), 3);
|
||||
EXPECT_EQ(splits[0].numel(), 3);
|
||||
EXPECT_EQ(splits[1].numel(), 3);
|
||||
EXPECT_EQ(splits[2].numel(), 3);
|
||||
}
|
||||
|
||||
// Test for split_symint
|
||||
TEST(SplitTest, SplitSymInt) {
|
||||
auto tensor = at::arange(10, at::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
c10::SymInt split_size(3);
|
||||
auto splits = tensor.split_symint(split_size, 0);
|
||||
|
||||
EXPECT_EQ(splits.size(), 4);
|
||||
EXPECT_EQ(splits[0].numel(), 3);
|
||||
EXPECT_EQ(splits[1].numel(), 3);
|
||||
EXPECT_EQ(splits[2].numel(), 3);
|
||||
EXPECT_EQ(splits[3].numel(), 1);
|
||||
}
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
// Test for split on CUDA
|
||||
TEST(SplitTest, SplitCUDA) {
|
||||
auto tensor =
|
||||
at::arange(10, at::TensorOptions().dtype(at::kFloat).device(at::kCUDA));
|
||||
|
||||
auto splits = tensor.split(3, 0);
|
||||
|
||||
EXPECT_EQ(splits.size(), 4);
|
||||
EXPECT_TRUE(splits[0].is_cuda());
|
||||
EXPECT_EQ(splits[0].numel(), 3);
|
||||
|
||||
// Copy to CPU and verify
|
||||
auto cpu_split = splits[0].cpu();
|
||||
EXPECT_FLOAT_EQ(cpu_split[0].item<float>(), 0.0f);
|
||||
EXPECT_FLOAT_EQ(cpu_split[1].item<float>(), 1.0f);
|
||||
EXPECT_FLOAT_EQ(cpu_split[2].item<float>(), 2.0f);
|
||||
}
|
||||
|
||||
// Test for tensor_split on CUDA
|
||||
TEST(TensorSplitTest, TensorSplitCUDA) {
|
||||
auto tensor =
|
||||
at::arange(12, at::TensorOptions().dtype(at::kFloat).device(at::kCUDA));
|
||||
|
||||
auto splits = tensor.tensor_split(4, 0);
|
||||
|
||||
EXPECT_EQ(splits.size(), 4);
|
||||
for (const auto& split : splits) {
|
||||
EXPECT_TRUE(split.is_cuda());
|
||||
}
|
||||
EXPECT_EQ(splits[0].numel(), 3);
|
||||
EXPECT_EQ(splits[1].numel(), 3);
|
||||
EXPECT_EQ(splits[2].numel(), 3);
|
||||
EXPECT_EQ(splits[3].numel(), 3);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,203 @@
|
||||
// Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
TEST(TestSqueeze, SqueezeNoArg) {
|
||||
// Test squeeze() without arguments - removes all dimensions of size 1
|
||||
at::Tensor tensor = at::ones({1, 3, 1, 4, 1}, at::kFloat);
|
||||
at::Tensor squeezed = tensor.squeeze();
|
||||
|
||||
ASSERT_EQ(squeezed.sizes(), c10::IntArrayRef({3, 4}));
|
||||
ASSERT_EQ(squeezed.numel(), tensor.numel());
|
||||
}
|
||||
|
||||
TEST(TestSqueeze, SqueezeSingleDim) {
|
||||
// Test squeeze(int64_t dim) - removes specific dimension if size is 1
|
||||
at::Tensor tensor = at::ones({1, 3, 1, 4}, at::kFloat);
|
||||
|
||||
// Squeeze dimension 0 (size 1)
|
||||
at::Tensor squeezed_0 = tensor.squeeze(0);
|
||||
ASSERT_EQ(squeezed_0.sizes(), c10::IntArrayRef({3, 1, 4}));
|
||||
|
||||
// Squeeze dimension 2 (size 1)
|
||||
at::Tensor squeezed_2 = tensor.squeeze(2);
|
||||
ASSERT_EQ(squeezed_2.sizes(), c10::IntArrayRef({1, 3, 4}));
|
||||
|
||||
// Squeeze dimension 1 (size 3, should not change)
|
||||
at::Tensor squeezed_1 = tensor.squeeze(1);
|
||||
ASSERT_EQ(squeezed_1.sizes(), c10::IntArrayRef({1, 3, 1, 4}));
|
||||
}
|
||||
|
||||
TEST(TestSqueeze, SqueezeMultipleDims) {
|
||||
// Test squeeze(IntArrayRef dim) - removes specified dimensions if size is 1
|
||||
at::Tensor tensor = at::ones({1, 3, 1, 4, 1}, at::kFloat);
|
||||
|
||||
// Squeeze dimensions 0 and 2
|
||||
at::Tensor squeezed = tensor.squeeze(c10::IntArrayRef({0, 2}));
|
||||
ASSERT_EQ(squeezed.sizes(), c10::IntArrayRef({3, 4, 1}));
|
||||
|
||||
// Squeeze all size-1 dimensions
|
||||
at::Tensor squeezed_all = tensor.squeeze(c10::IntArrayRef({0, 2, 4}));
|
||||
ASSERT_EQ(squeezed_all.sizes(), c10::IntArrayRef({3, 4}));
|
||||
}
|
||||
|
||||
TEST(TestSqueeze, SqueezeInplaceNoArg) {
|
||||
// Test squeeze_() without arguments - in-place operation
|
||||
at::Tensor tensor = at::ones({1, 3, 1, 4, 1}, at::kFloat);
|
||||
void* original_ptr = tensor.data_ptr();
|
||||
|
||||
tensor.squeeze_();
|
||||
|
||||
ASSERT_EQ(tensor.sizes(), c10::IntArrayRef({3, 4}));
|
||||
ASSERT_EQ(tensor.data_ptr(), original_ptr); // Same data pointer
|
||||
}
|
||||
|
||||
TEST(TestSqueeze, SqueezeInplaceSingleDim) {
|
||||
// Test squeeze_(int64_t dim) - in-place operation on specific dimension
|
||||
at::Tensor tensor = at::ones({1, 3, 1, 4}, at::kFloat);
|
||||
void* original_ptr = tensor.data_ptr();
|
||||
|
||||
tensor.squeeze_(2);
|
||||
|
||||
ASSERT_EQ(tensor.sizes(), c10::IntArrayRef({1, 3, 4}));
|
||||
ASSERT_EQ(tensor.data_ptr(), original_ptr);
|
||||
}
|
||||
|
||||
TEST(TestSqueeze, SqueezeInplaceMultipleDims) {
|
||||
// Test squeeze_(IntArrayRef dim) - in-place operation on multiple dimensions
|
||||
at::Tensor tensor = at::ones({1, 3, 1, 4, 1}, at::kFloat);
|
||||
void* original_ptr = tensor.data_ptr();
|
||||
|
||||
tensor.squeeze_(c10::IntArrayRef({0, 2, 4}));
|
||||
|
||||
ASSERT_EQ(tensor.sizes(), c10::IntArrayRef({3, 4}));
|
||||
ASSERT_EQ(tensor.data_ptr(), original_ptr);
|
||||
}
|
||||
|
||||
TEST(TestUnsqueeze, UnsqueezeLeadingDim) {
|
||||
at::Tensor tensor = at::ones({3, 4}, at::kFloat);
|
||||
at::Tensor unsqueezed = tensor.unsqueeze(0);
|
||||
|
||||
ASSERT_EQ(unsqueezed.sizes(), c10::IntArrayRef({1, 3, 4}));
|
||||
}
|
||||
|
||||
TEST(TestUnsqueeze, UnsqueezeSingleDim) {
|
||||
// Test unsqueeze(int64_t dim) - adds dimension at specified position
|
||||
at::Tensor tensor = at::ones({3, 4}, at::kFloat);
|
||||
|
||||
// Unsqueeze at dimension 0
|
||||
at::Tensor unsqueezed_0 = tensor.unsqueeze(0);
|
||||
ASSERT_EQ(unsqueezed_0.sizes(), c10::IntArrayRef({1, 3, 4}));
|
||||
|
||||
// Unsqueeze at dimension 1
|
||||
at::Tensor unsqueezed_1 = tensor.unsqueeze(1);
|
||||
ASSERT_EQ(unsqueezed_1.sizes(), c10::IntArrayRef({3, 1, 4}));
|
||||
|
||||
// Unsqueeze at dimension 2
|
||||
at::Tensor unsqueezed_2 = tensor.unsqueeze(2);
|
||||
ASSERT_EQ(unsqueezed_2.sizes(), c10::IntArrayRef({3, 4, 1}));
|
||||
|
||||
// Unsqueeze at negative dimension
|
||||
at::Tensor unsqueezed_neg = tensor.unsqueeze(-1);
|
||||
ASSERT_EQ(unsqueezed_neg.sizes(), c10::IntArrayRef({3, 4, 1}));
|
||||
}
|
||||
|
||||
TEST(TestUnsqueeze, UnsqueezeChainedDims) {
|
||||
at::Tensor tensor = at::ones({3, 4}, at::kFloat);
|
||||
|
||||
at::Tensor unsqueezed = tensor.unsqueeze(0).unsqueeze(2);
|
||||
ASSERT_EQ(unsqueezed.sizes(), c10::IntArrayRef({1, 3, 1, 4}));
|
||||
ASSERT_EQ(unsqueezed.numel(), tensor.numel());
|
||||
}
|
||||
|
||||
TEST(TestUnsqueeze, UnsqueezeInplaceLeadingDim) {
|
||||
at::Tensor tensor = at::ones({3, 4}, at::kFloat);
|
||||
void* original_ptr = tensor.data_ptr();
|
||||
|
||||
tensor.unsqueeze_(0);
|
||||
|
||||
ASSERT_EQ(tensor.sizes(), c10::IntArrayRef({1, 3, 4}));
|
||||
ASSERT_EQ(tensor.data_ptr(), original_ptr);
|
||||
}
|
||||
|
||||
TEST(TestUnsqueeze, UnsqueezeInplaceSingleDim) {
|
||||
// Test unsqueeze_(int64_t dim) - in-place operation
|
||||
at::Tensor tensor = at::ones({3, 4}, at::kFloat);
|
||||
void* original_ptr = tensor.data_ptr();
|
||||
|
||||
tensor.unsqueeze_(1);
|
||||
|
||||
ASSERT_EQ(tensor.sizes(), c10::IntArrayRef({3, 1, 4}));
|
||||
ASSERT_EQ(tensor.data_ptr(), original_ptr);
|
||||
}
|
||||
|
||||
TEST(TestUnsqueeze, UnsqueezeInplaceChainedDims) {
|
||||
at::Tensor tensor = at::ones({3, 4}, at::kFloat);
|
||||
void* original_ptr = tensor.data_ptr();
|
||||
|
||||
tensor.unsqueeze_(0);
|
||||
tensor.unsqueeze_(2);
|
||||
|
||||
ASSERT_EQ(tensor.sizes(), c10::IntArrayRef({1, 3, 1, 4}));
|
||||
ASSERT_EQ(tensor.numel(), 12);
|
||||
ASSERT_EQ(tensor.data_ptr(), original_ptr);
|
||||
}
|
||||
|
||||
TEST(TestSqueezeUnsqueeze, CombinedOperations) {
|
||||
// Test combining squeeze and unsqueeze operations
|
||||
at::Tensor tensor = at::ones({2, 3, 4}, at::kFloat);
|
||||
|
||||
// Add a dimension then remove it
|
||||
at::Tensor unsqueezed = tensor.unsqueeze(1);
|
||||
ASSERT_EQ(unsqueezed.sizes(), c10::IntArrayRef({2, 1, 3, 4}));
|
||||
|
||||
at::Tensor squeezed = unsqueezed.squeeze(1);
|
||||
ASSERT_EQ(squeezed.sizes(), c10::IntArrayRef({2, 3, 4}));
|
||||
|
||||
// Verify data integrity
|
||||
ASSERT_EQ(tensor.numel(), squeezed.numel());
|
||||
}
|
||||
|
||||
TEST(TestSqueezeUnsqueeze, DataIntegrity) {
|
||||
// Test that squeeze and unsqueeze preserve data
|
||||
at::Tensor tensor = at::arange(24, at::kFloat).reshape({2, 3, 4});
|
||||
|
||||
// Unsqueeze and squeeze
|
||||
at::Tensor unsqueezed = tensor.unsqueeze(0);
|
||||
at::Tensor squeezed = unsqueezed.squeeze(0);
|
||||
|
||||
// Check data is preserved
|
||||
const float* original_data = tensor.data_ptr<float>();
|
||||
const float* result_data = squeezed.data_ptr<float>();
|
||||
|
||||
for (int64_t i = 0; i < tensor.numel(); ++i) {
|
||||
ASSERT_EQ(original_data[i], result_data[i]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#include <cmath>
|
||||
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ======================== std tests ========================
|
||||
|
||||
TEST(TensorStdTest, StdDefault) {
|
||||
// Create tensor [1, 2, 3, 4, 5, 6]
|
||||
at::Tensor t = at::arange(1, 7, at::kFloat);
|
||||
at::Tensor result = t.std();
|
||||
|
||||
// std of [1,2,3,4,5,6] with unbiased=true (ddof=1) = sqrt(3.5)
|
||||
ASSERT_EQ(result.numel(), 1);
|
||||
float val = result.item<float>();
|
||||
ASSERT_NEAR(val, std::sqrt(3.5f), 1e-4);
|
||||
}
|
||||
|
||||
TEST(TensorStdTest, StdBiased) {
|
||||
at::Tensor t = at::arange(1, 7, at::kFloat);
|
||||
at::Tensor result = t.std(false); // unbiased=false
|
||||
|
||||
// std with ddof=0: sqrt(sum((x-mean)^2)/N)
|
||||
// mean=3.5, sum_sq_diff = 17.5, var=17.5/6, std=sqrt(17.5/6)
|
||||
ASSERT_EQ(result.numel(), 1);
|
||||
float val = result.item<float>();
|
||||
ASSERT_NEAR(val, std::sqrt(17.5f / 6.0f), 1e-4);
|
||||
}
|
||||
|
||||
TEST(TensorStdTest, StdWithDim) {
|
||||
// Create 2x3 tensor
|
||||
at::Tensor t = at::arange(1, 7, at::kFloat).reshape({2, 3});
|
||||
at::Tensor result =
|
||||
t.std(at::OptionalIntArrayRef({1}), /*unbiased=*/true, /*keepdim=*/false);
|
||||
|
||||
ASSERT_EQ(result.numel(), 2);
|
||||
}
|
||||
|
||||
TEST(TensorStdTest, StdWithDimAndCorrection) {
|
||||
at::Tensor t = at::arange(1, 7, at::kFloat).reshape({2, 3});
|
||||
at::Tensor result = t.std(
|
||||
at::OptionalIntArrayRef({1}), ::std::optional<at::Scalar>(1.0), false);
|
||||
|
||||
ASSERT_EQ(result.numel(), 2);
|
||||
}
|
||||
|
||||
TEST(TensorStdTest, StdSingleDim) {
|
||||
at::Tensor t = at::arange(1, 7, at::kFloat).reshape({2, 3});
|
||||
at::Tensor result = t.std(1);
|
||||
|
||||
ASSERT_EQ(result.numel(), 2);
|
||||
}
|
||||
|
||||
// ======================== var tests ========================
|
||||
|
||||
TEST(TensorVarTest, VarDefault) {
|
||||
at::Tensor t = at::arange(1, 7, at::kFloat);
|
||||
at::Tensor result = t.var();
|
||||
|
||||
// var of [1,2,3,4,5,6] with unbiased=true: 17.5/5 = 3.5
|
||||
float val = result.item<float>();
|
||||
ASSERT_NEAR(val, 3.5f, 1e-4);
|
||||
}
|
||||
|
||||
TEST(TensorVarTest, VarBiased) {
|
||||
at::Tensor t = at::arange(1, 7, at::kFloat);
|
||||
at::Tensor result = t.var(false);
|
||||
|
||||
// var with unbiased=false: 17.5/6
|
||||
float val = result.item<float>();
|
||||
ASSERT_NEAR(val, 17.5f / 6.0f, 1e-4);
|
||||
}
|
||||
|
||||
TEST(TensorVarTest, VarWithDim) {
|
||||
at::Tensor t = at::arange(1, 7, at::kFloat).reshape({2, 3});
|
||||
at::Tensor result =
|
||||
t.var(at::OptionalIntArrayRef({1}), /*unbiased=*/true, /*keepdim=*/false);
|
||||
|
||||
ASSERT_EQ(result.numel(), 2);
|
||||
}
|
||||
|
||||
TEST(TensorVarTest, VarWithCorrection) {
|
||||
at::Tensor t = at::arange(1, 7, at::kFloat).reshape({2, 3});
|
||||
at::Tensor result = t.var(
|
||||
at::OptionalIntArrayRef({0}), ::std::optional<at::Scalar>(1.0), false);
|
||||
|
||||
ASSERT_EQ(result.numel(), 3);
|
||||
}
|
||||
|
||||
TEST(TensorVarTest, VarSingleDim) {
|
||||
at::Tensor t = at::arange(1, 7, at::kFloat).reshape({2, 3});
|
||||
at::Tensor result = t.var(0);
|
||||
|
||||
ASSERT_EQ(result.numel(), 3);
|
||||
}
|
||||
|
||||
// ======================= Additional std edge case tests
|
||||
// ========================
|
||||
|
||||
TEST(TensorStdTest, StdWithKeepdim) {
|
||||
at::Tensor t = at::arange(1, 7, at::kFloat).reshape({2, 3});
|
||||
at::Tensor result =
|
||||
t.std(at::OptionalIntArrayRef({1}), /*unbiased=*/true, /*keepdim=*/true);
|
||||
|
||||
// keepdim should preserve dimension
|
||||
ASSERT_EQ(result.sizes().size(), 2);
|
||||
ASSERT_EQ(result.size(0), 2);
|
||||
ASSERT_EQ(result.size(1), 1);
|
||||
}
|
||||
|
||||
TEST(TensorStdTest, StdWithMultipleDims) {
|
||||
at::Tensor t = at::arange(1, 13, at::kFloat).reshape({2, 2, 3});
|
||||
at::Tensor result = t.std(
|
||||
at::OptionalIntArrayRef({0, 2}), /*unbiased=*/true, /*keepdim=*/false);
|
||||
|
||||
ASSERT_EQ(result.numel(), 2);
|
||||
}
|
||||
|
||||
TEST(TensorStdTest, StdWithCorrectionValue) {
|
||||
at::Tensor t = at::arange(1, 7, at::kFloat);
|
||||
// Test with custom correction value (ddof)
|
||||
at::Tensor result = t.std(
|
||||
at::OptionalIntArrayRef({}), ::std::optional<at::Scalar>(2.0), false);
|
||||
|
||||
ASSERT_EQ(result.numel(), 1);
|
||||
}
|
||||
|
||||
TEST(TensorStdTest, StdNegativeDim) {
|
||||
at::Tensor t = at::arange(1, 7, at::kFloat).reshape({2, 3});
|
||||
// Test with negative dimension (-1 means last dimension)
|
||||
at::Tensor result = t.std(-1);
|
||||
|
||||
ASSERT_EQ(result.numel(), 2);
|
||||
}
|
||||
|
||||
TEST(TensorVarTest, VarWithKeepdim) {
|
||||
at::Tensor t = at::arange(1, 7, at::kFloat).reshape({2, 3});
|
||||
at::Tensor result =
|
||||
t.var(at::OptionalIntArrayRef({1}), /*unbiased=*/true, /*keepdim=*/true);
|
||||
|
||||
ASSERT_EQ(result.sizes().size(), 2);
|
||||
ASSERT_EQ(result.size(0), 2);
|
||||
ASSERT_EQ(result.size(1), 1);
|
||||
}
|
||||
|
||||
TEST(TensorVarTest, VarWithMultipleDims) {
|
||||
at::Tensor t = at::arange(1, 13, at::kFloat).reshape({2, 2, 3});
|
||||
at::Tensor result = t.var(
|
||||
at::OptionalIntArrayRef({0, 2}), /*unbiased=*/true, /*keepdim=*/false);
|
||||
|
||||
ASSERT_EQ(result.numel(), 2);
|
||||
}
|
||||
|
||||
TEST(TensorVarTest, VarWithCorrectionValue) {
|
||||
at::Tensor t = at::arange(1, 7, at::kFloat);
|
||||
at::Tensor result = t.var(
|
||||
at::OptionalIntArrayRef({}), ::std::optional<at::Scalar>(2.0), false);
|
||||
|
||||
ASSERT_EQ(result.numel(), 1);
|
||||
}
|
||||
|
||||
TEST(TensorVarTest, VarNegativeDim) {
|
||||
at::Tensor t = at::arange(1, 7, at::kFloat).reshape({2, 3});
|
||||
at::Tensor result = t.var(-1);
|
||||
|
||||
ASSERT_EQ(result.numel(), 2);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ============================================================
|
||||
// Tests for at::Tensor::sum()
|
||||
// ============================================================
|
||||
|
||||
TEST(TensorSumTest, SumAllElementsNoArgs) {
|
||||
// sum() without arguments: sum of all elements, keeps original dtype
|
||||
at::Tensor t = at::ones({2, 3}, at::kFloat);
|
||||
at::Tensor result = t.sum();
|
||||
|
||||
ASSERT_EQ(result.numel(), 1);
|
||||
ASSERT_FLOAT_EQ(result.item<float>(), 6.0f);
|
||||
}
|
||||
|
||||
TEST(TensorSumTest, SumAllElementsWithDtype) {
|
||||
// sum(dtype): reduce all elements, cast result to given dtype
|
||||
at::Tensor t = at::ones({4, 4}, at::kFloat);
|
||||
at::Tensor result = t.sum(at::kDouble);
|
||||
|
||||
ASSERT_EQ(result.numel(), 1);
|
||||
ASSERT_EQ(result.scalar_type(), at::kDouble);
|
||||
ASSERT_DOUBLE_EQ(result.item<double>(), 16.0);
|
||||
}
|
||||
|
||||
TEST(TensorSumTest, SumAlongDim0) {
|
||||
// sum(dim={0}): reduce along first dimension
|
||||
at::Tensor t = at::ones({3, 4}, at::kFloat);
|
||||
at::Tensor result = t.sum(at::IntArrayRef{0}, /*keepdim=*/false);
|
||||
|
||||
ASSERT_EQ(result.dim(), 1);
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({4}));
|
||||
for (int64_t i = 0; i < 4; ++i) {
|
||||
ASSERT_FLOAT_EQ(result[i].item<float>(), 3.0f);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TensorSumTest, SumAlongDim1) {
|
||||
// sum(dim={1}): reduce along second dimension
|
||||
at::Tensor t = at::ones({3, 4}, at::kFloat);
|
||||
at::Tensor result = t.sum(at::IntArrayRef{1}, /*keepdim=*/false);
|
||||
|
||||
ASSERT_EQ(result.dim(), 1);
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({3}));
|
||||
for (int64_t i = 0; i < 3; ++i) {
|
||||
ASSERT_FLOAT_EQ(result[i].item<float>(), 4.0f);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TensorSumTest, SumAlongDimKeepDim) {
|
||||
// sum(dim, keepdim=true): result keeps reduced dimension as size 1
|
||||
at::Tensor t = at::ones({3, 4}, at::kFloat);
|
||||
at::Tensor result = t.sum(at::IntArrayRef{1}, /*keepdim=*/true);
|
||||
|
||||
ASSERT_EQ(result.dim(), 2);
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({3, 1}));
|
||||
for (int64_t i = 0; i < 3; ++i) {
|
||||
ASSERT_FLOAT_EQ(result[i][0].item<float>(), 4.0f);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TensorSumTest, SumAlongDimWithDtypeCast) {
|
||||
// sum(dim, keepdim, dtype): reduce and cast to specified dtype
|
||||
at::Tensor t = at::ones({2, 5}, at::kFloat);
|
||||
at::Tensor result =
|
||||
t.sum(at::IntArrayRef{0}, /*keepdim=*/false, /*dtype=*/at::kDouble);
|
||||
|
||||
ASSERT_EQ(result.scalar_type(), at::kDouble);
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({5}));
|
||||
for (int64_t i = 0; i < 5; ++i) {
|
||||
ASSERT_DOUBLE_EQ(result[i].item<double>(), 2.0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TensorSumTest, SumPreservesNumel) {
|
||||
// Verify that sum of known values is correct
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
// t = [[0,1,2],[3,4,5]], total = 15
|
||||
at::Tensor result = t.sum();
|
||||
ASSERT_FLOAT_EQ(result.item<float>(), 15.0f);
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ============================================================
|
||||
// Tests for at::Tensor::t() and at::Tensor::t_()
|
||||
// ============================================================
|
||||
|
||||
TEST(TensorTTest, T1D_ReturnsSameShape) {
|
||||
// t() on a 1D tensor: transposing a 1D tensor returns itself (same shape)
|
||||
at::Tensor t = at::arange(5, at::kFloat);
|
||||
at::Tensor result = t.t();
|
||||
|
||||
ASSERT_EQ(result.dim(), 1);
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({5}));
|
||||
ASSERT_EQ(result.numel(), t.numel());
|
||||
}
|
||||
|
||||
TEST(TensorTTest, T2D_TransposesShape) {
|
||||
// t() on a 2D tensor: returns transposed shape
|
||||
at::Tensor t = at::ones({3, 4}, at::kFloat);
|
||||
at::Tensor result = t.t();
|
||||
|
||||
ASSERT_EQ(result.dim(), 2);
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({4, 3}));
|
||||
ASSERT_EQ(result.numel(), t.numel());
|
||||
}
|
||||
|
||||
TEST(TensorTTest, T2D_PreservesValues) {
|
||||
// t() on 2D tensor: verify element access after transpose
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
// t = [[0,1,2],[3,4,5]]
|
||||
at::Tensor result = t.t();
|
||||
// result = [[0,3],[1,4],[2,5]]
|
||||
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({3, 2}));
|
||||
// Check [0][0] == 0, [1][0] == 1, [0][1] == 3
|
||||
ASSERT_FLOAT_EQ(result[0][0].item<float>(), 0.0f);
|
||||
ASSERT_FLOAT_EQ(result[1][0].item<float>(), 1.0f);
|
||||
ASSERT_FLOAT_EQ(result[0][1].item<float>(), 3.0f);
|
||||
ASSERT_FLOAT_EQ(result[2][1].item<float>(), 5.0f);
|
||||
}
|
||||
|
||||
TEST(TensorTTest, TInplace1D_DoesNotChangeShape) {
|
||||
// t_() on a 1D tensor: shape remains the same, returns self
|
||||
at::Tensor t = at::arange(5, at::kFloat);
|
||||
void* original_ptr = t.data_ptr();
|
||||
at::Tensor& ref = t.t_();
|
||||
|
||||
ASSERT_EQ(t.dim(), 1);
|
||||
ASSERT_EQ(t.sizes(), c10::IntArrayRef({5}));
|
||||
// Must return *this by reference
|
||||
ASSERT_EQ(&ref, &t);
|
||||
// Data must remain in place
|
||||
ASSERT_EQ(t.data_ptr(), original_ptr);
|
||||
}
|
||||
|
||||
TEST(TensorTTest, TInplace2D_TransposesInPlace) {
|
||||
// t_() on 2D tensor: shape becomes transposed, data pointer unchanged
|
||||
at::Tensor t = at::ones({3, 4}, at::kFloat);
|
||||
void* original_ptr = t.data_ptr();
|
||||
t.t_();
|
||||
|
||||
ASSERT_EQ(t.dim(), 2);
|
||||
ASSERT_EQ(t.sizes(), c10::IntArrayRef({4, 3}));
|
||||
ASSERT_EQ(t.data_ptr(), original_ptr);
|
||||
}
|
||||
|
||||
TEST(TensorTTest, TInplace2D_PreservesValues) {
|
||||
// t_() on 2D tensor: values are correct after in-place transpose
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
// t = [[0,1,2],[3,4,5]]
|
||||
t.t_();
|
||||
// After t_: shape is {3,2}, t = [[0,3],[1,4],[2,5]]
|
||||
|
||||
ASSERT_EQ(t.sizes(), c10::IntArrayRef({3, 2}));
|
||||
ASSERT_FLOAT_EQ(t[0][0].item<float>(), 0.0f);
|
||||
ASSERT_FLOAT_EQ(t[0][1].item<float>(), 3.0f);
|
||||
ASSERT_FLOAT_EQ(t[2][1].item<float>(), 5.0f);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// High-dimensional tests (dim > 2):
|
||||
// t() / t_() always swap axes 0 and 1 only; remaining axes stay in place.
|
||||
// ============================================================
|
||||
|
||||
TEST(TensorTTest, T3D_SwapsOnlyDim0AndDim1) {
|
||||
// For a 3D tensor {A, B, C}, t() should produce shape {B, A, C}.
|
||||
// The innermost axis (dim 2) must NOT be touched.
|
||||
at::Tensor t = at::ones({2, 3, 4}, at::kFloat);
|
||||
at::Tensor result = t.t();
|
||||
|
||||
ASSERT_EQ(result.dim(), 3);
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({3, 2, 4}));
|
||||
}
|
||||
|
||||
TEST(TensorTTest, T3D_PreservesValues) {
|
||||
// Verify that element access is consistent after transposing a 3D tensor.
|
||||
// t = arange(24).reshape({2,3,4})
|
||||
// t[i][j][k] = i*12 + j*4 + k
|
||||
// After t(): result[j][i][k] should still equal i*12 + j*4 + k.
|
||||
at::Tensor t = at::arange(24, at::kFloat).reshape({2, 3, 4});
|
||||
at::Tensor r = t.t();
|
||||
|
||||
ASSERT_EQ(r.sizes(), c10::IntArrayRef({3, 2, 4}));
|
||||
// r[j][i][k] == t[i][j][k]
|
||||
for (int64_t i = 0; i < 2; ++i) {
|
||||
for (int64_t j = 0; j < 3; ++j) {
|
||||
for (int64_t k = 0; k < 4; ++k) {
|
||||
ASSERT_FLOAT_EQ(r[j][i][k].item<float>(), t[i][j][k].item<float>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TensorTTest, T4D_SwapsOnlyDim0AndDim1) {
|
||||
// For a 4D tensor {A, B, C, D}, t() should produce shape {B, A, C, D}.
|
||||
at::Tensor t = at::ones({2, 5, 3, 4}, at::kFloat);
|
||||
at::Tensor result = t.t();
|
||||
|
||||
ASSERT_EQ(result.dim(), 4);
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({5, 2, 3, 4}));
|
||||
}
|
||||
|
||||
TEST(TensorTTest, TInplace3D_SwapsOnlyDim0AndDim1) {
|
||||
// t_() on a 3D tensor: shape {A,B,C} -> {B,A,C}, data pointer unchanged.
|
||||
at::Tensor t = at::ones({2, 3, 4}, at::kFloat);
|
||||
void* original_ptr = t.data_ptr();
|
||||
t.t_();
|
||||
|
||||
ASSERT_EQ(t.dim(), 3);
|
||||
ASSERT_EQ(t.sizes(), c10::IntArrayRef({3, 2, 4}));
|
||||
ASSERT_EQ(t.data_ptr(), original_ptr);
|
||||
}
|
||||
|
||||
TEST(TensorTTest, TInplace3D_HigherDimsUnchanged) {
|
||||
// After t_() on a 3D tensor, verify that dim 2 is not touched.
|
||||
at::Tensor t = at::arange(24, at::kFloat).reshape({2, 3, 4});
|
||||
t.t_();
|
||||
// Shape must be {3, 2, 4}: C=4 must be preserved.
|
||||
ASSERT_EQ(t.size(2), 4);
|
||||
}
|
||||
|
||||
TEST(TensorTTest, TInplace4D_SwapsOnlyDim0AndDim1) {
|
||||
// t_() on a 4D tensor: shape {A,B,C,D} -> {B,A,C,D}.
|
||||
at::Tensor t = at::ones({2, 5, 3, 4}, at::kFloat);
|
||||
void* original_ptr = t.data_ptr();
|
||||
t.t_();
|
||||
|
||||
ASSERT_EQ(t.sizes(), c10::IntArrayRef({5, 2, 3, 4}));
|
||||
ASSERT_EQ(t.data_ptr(), original_ptr);
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ======================== tensor_data / variable_data tests
|
||||
// ========================
|
||||
|
||||
TEST(TensorDataTest, TensorData) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
at::Tensor td = t.tensor_data();
|
||||
|
||||
ASSERT_EQ(td.sizes(), t.sizes());
|
||||
ASSERT_EQ(td.dtype(), t.dtype());
|
||||
ASSERT_EQ(td.numel(), t.numel());
|
||||
|
||||
// Values should be the same
|
||||
float* orig = t.data_ptr<float>();
|
||||
float* copy = td.data_ptr<float>();
|
||||
for (int i = 0; i < t.numel(); i++) {
|
||||
ASSERT_FLOAT_EQ(orig[i], copy[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TensorDataTest, VariableData) {
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
at::Tensor vd = t.variable_data();
|
||||
|
||||
ASSERT_EQ(vd.sizes(), t.sizes());
|
||||
ASSERT_EQ(vd.dtype(), t.dtype());
|
||||
ASSERT_EQ(vd.numel(), t.numel());
|
||||
|
||||
float* orig = t.data_ptr<float>();
|
||||
float* copy = vd.data_ptr<float>();
|
||||
for (int i = 0; i < t.numel(); i++) {
|
||||
ASSERT_FLOAT_EQ(orig[i], copy[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== item tests ========================
|
||||
|
||||
TEST(TensorItemTest, ItemScalar) {
|
||||
at::Tensor t = at::full({}, 3.14f, at::kFloat);
|
||||
at::Scalar s = t.item();
|
||||
ASSERT_NEAR(s.to<float>(), 3.14f, 1e-5);
|
||||
}
|
||||
|
||||
TEST(TensorItemTest, ItemTyped) {
|
||||
at::Tensor t = at::full({1}, 42.0f, at::kFloat);
|
||||
float val = t.item<float>();
|
||||
ASSERT_FLOAT_EQ(val, 42.0f);
|
||||
}
|
||||
|
||||
TEST(TensorItemTest, ItemInt) {
|
||||
at::Tensor t = at::full({1}, 7, at::kInt);
|
||||
at::Scalar s = t.item();
|
||||
ASSERT_EQ(s.to<int>(), 7);
|
||||
}
|
||||
|
||||
TEST(TensorItemTest, ItemDouble) {
|
||||
at::Tensor t = at::full({1}, 2.718, at::kDouble);
|
||||
at::Scalar s = t.item();
|
||||
ASSERT_NEAR(s.to<double>(), 2.718, 1e-6);
|
||||
}
|
||||
|
||||
TEST(TensorItemTest, ItemInt64) {
|
||||
at::Tensor t = at::full({1}, 12345, at::kLong);
|
||||
at::Scalar s = t.item();
|
||||
ASSERT_EQ(s.to<int64_t>(), 12345);
|
||||
}
|
||||
|
||||
TEST(TensorItemTest, ItemBool) {
|
||||
at::Tensor t = at::full({1}, true, at::kBool);
|
||||
at::Scalar s = t.item();
|
||||
ASSERT_TRUE(s.to<bool>());
|
||||
}
|
||||
|
||||
TEST(TensorItemTest, ItemInt8) {
|
||||
at::Tensor t = at::full({1}, 5, at::kChar);
|
||||
at::Scalar s = t.item();
|
||||
ASSERT_EQ(s.to<int8_t>(), 5);
|
||||
}
|
||||
|
||||
TEST(TensorItemTest, ItemUint8) {
|
||||
at::Tensor t = at::full({1}, 200, at::kByte);
|
||||
at::Scalar s = t.item();
|
||||
ASSERT_EQ(s.to<uint8_t>(), 200);
|
||||
}
|
||||
|
||||
TEST(TensorItemTest, ItemInt16) {
|
||||
at::Tensor t = at::full({1}, 300, at::kShort);
|
||||
at::Scalar s = t.item();
|
||||
ASSERT_EQ(s.to<int16_t>(), 300);
|
||||
}
|
||||
|
||||
TEST(TensorItemTest, ItemFloat16) {
|
||||
at::Tensor t = at::full({1}, 1.5f, at::kHalf);
|
||||
at::Scalar s = t.item();
|
||||
ASSERT_NEAR(s.to<float>(), 1.5f, 1e-3);
|
||||
}
|
||||
|
||||
TEST(TensorItemTest, ItemBFloat16) {
|
||||
at::Tensor t = at::full({1}, 2.5f, at::kBFloat16);
|
||||
at::Scalar s = t.item();
|
||||
ASSERT_NEAR(s.to<float>(), 2.5f, 1e-2);
|
||||
}
|
||||
|
||||
// ======================= Additional tensor_data edge cases
|
||||
// =======================
|
||||
|
||||
TEST(TensorDataTest, TensorDataUninitialized) {
|
||||
// Test tensor_data on uninitialized tensor
|
||||
at::Tensor t;
|
||||
at::Tensor td = t.tensor_data();
|
||||
ASSERT_FALSE(td.defined());
|
||||
}
|
||||
|
||||
TEST(TensorDataTest, VariableDataUninitialized) {
|
||||
// Test variable_data on uninitialized tensor
|
||||
at::Tensor t;
|
||||
at::Tensor vd = t.variable_data();
|
||||
ASSERT_FALSE(vd.defined());
|
||||
}
|
||||
|
||||
TEST(TensorDataTest, TensorDataNonContiguous) {
|
||||
// Test tensor_data on non-contiguous tensor
|
||||
at::Tensor t = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
at::Tensor t_transposed = t.transpose(0, 1);
|
||||
at::Tensor td = t_transposed.tensor_data();
|
||||
|
||||
ASSERT_EQ(td.sizes()[0], 4);
|
||||
ASSERT_EQ(td.sizes()[1], 3);
|
||||
}
|
||||
|
||||
// ======================== data_ptr tests ========================
|
||||
|
||||
TEST(TensorDataPtrTest, DataPtrBasic) {
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
void* ptr = t.data_ptr();
|
||||
ASSERT_NE(ptr, nullptr);
|
||||
}
|
||||
|
||||
TEST(TensorDataPtrTest, DataPtrTyped) {
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
float* ptr = t.data_ptr<float>();
|
||||
ASSERT_NE(ptr, nullptr);
|
||||
ASSERT_FLOAT_EQ(ptr[0], 0.0f);
|
||||
}
|
||||
|
||||
TEST(TensorDataPtrTest, DataPtrInt) {
|
||||
at::Tensor t = at::arange(6, at::kInt);
|
||||
int* ptr = t.data_ptr<int>();
|
||||
ASSERT_NE(ptr, nullptr);
|
||||
ASSERT_EQ(ptr[0], 0);
|
||||
}
|
||||
|
||||
TEST(TensorDataPtrTest, DataPtrLong) {
|
||||
at::Tensor t = at::arange(6, at::kLong);
|
||||
int64_t* ptr = t.data_ptr<int64_t>();
|
||||
ASSERT_NE(ptr, nullptr);
|
||||
ASSERT_EQ(ptr[0], 0);
|
||||
}
|
||||
|
||||
TEST(TensorDataPtrTest, DataPtrDouble) {
|
||||
at::Tensor t = at::full({1}, 3.14159, at::kDouble);
|
||||
double* ptr = t.data_ptr<double>();
|
||||
ASSERT_NE(ptr, nullptr);
|
||||
ASSERT_NEAR(ptr[0], 3.14159, 1e-5);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/Layout.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
TEST(TensorBaseTest, ToStringAPI) {
|
||||
// Test toString() API
|
||||
at::TensorBase cpu_float_tensor = at::ones({2, 3}, at::kFloat);
|
||||
std::string cpu_float_str = cpu_float_tensor.toString();
|
||||
ASSERT_EQ(cpu_float_str, "CPUFloatType");
|
||||
|
||||
at::TensorBase cpu_double_tensor = at::ones({2, 3}, at::kDouble);
|
||||
std::string cpu_double_str = cpu_double_tensor.toString();
|
||||
ASSERT_EQ(cpu_double_str, "CPUDoubleType");
|
||||
|
||||
at::TensorBase cpu_int_tensor = at::ones({2, 3}, at::kInt);
|
||||
std::string cpu_int_str = cpu_int_tensor.toString();
|
||||
ASSERT_EQ(cpu_int_str, "CPUIntType");
|
||||
|
||||
at::TensorBase cpu_long_tensor = at::ones({2, 3}, at::kLong);
|
||||
std::string cpu_long_str = cpu_long_tensor.toString();
|
||||
ASSERT_EQ(cpu_long_str, "CPULongType");
|
||||
|
||||
at::TensorBase cpu_bool_tensor = at::ones({2, 3}, at::kBool);
|
||||
std::string cpu_bool_str = cpu_bool_tensor.toString();
|
||||
ASSERT_EQ(cpu_bool_str, "CPUBoolType");
|
||||
|
||||
// Test undefined tensor
|
||||
at::TensorBase undefined_tensor;
|
||||
std::string undefined_str = undefined_tensor.toString();
|
||||
ASSERT_EQ(undefined_str, "UndefinedType");
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
// Test CUDA tensor if available
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
at::TensorBase cuda_float_tensor = at::ones(
|
||||
{2, 3},
|
||||
at::TensorOptions().dtype(at::kFloat).device(at::Device(at::kCUDA, 0)));
|
||||
std::string cuda_float_str = cuda_float_tensor.toString();
|
||||
ASSERT_EQ(cuda_float_str, "CUDAFloatType");
|
||||
|
||||
at::TensorBase cuda_double_tensor = at::ones(
|
||||
{2, 3},
|
||||
at::TensorOptions().dtype(at::kDouble).device(at::Device(at::kCUDA, 0)));
|
||||
std::string cuda_double_str = cuda_double_tensor.toString();
|
||||
ASSERT_EQ(cuda_double_str, "CUDADoubleType");
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#ifdef PADDLE_WITH_XPU
|
||||
#include "paddle/phi/core/platform/device/xpu/xpu_info.h"
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ============================================================
|
||||
// Tests for at::Tensor::to() overloads
|
||||
// ============================================================
|
||||
|
||||
// ---- Overload 4: to(ScalarType) ----
|
||||
|
||||
TEST(TensorToTest, ToDtype_FloatToDouble) {
|
||||
at::Tensor t = at::tensor({1.0f, 2.0f, 3.0f}, at::kFloat);
|
||||
at::Tensor result = t.to(at::kDouble);
|
||||
|
||||
ASSERT_EQ(result.scalar_type(), at::kDouble);
|
||||
ASSERT_EQ(result.numel(), 3);
|
||||
ASSERT_NEAR(result[0].item<double>(), 1.0, 1e-10);
|
||||
ASSERT_NEAR(result[2].item<double>(), 3.0, 1e-10);
|
||||
}
|
||||
|
||||
TEST(TensorToTest, ToDtype_DoubleToFloat) {
|
||||
at::Tensor t = at::tensor({1.5, 2.5}, at::kDouble);
|
||||
at::Tensor result = t.to(at::kFloat);
|
||||
|
||||
ASSERT_EQ(result.scalar_type(), at::kFloat);
|
||||
ASSERT_NEAR(result[0].item<float>(), 1.5f, 1e-5f);
|
||||
}
|
||||
|
||||
TEST(TensorToTest, ToDtype_FloatToInt32) {
|
||||
at::Tensor t = at::tensor({1.9f, 2.1f, 3.7f}, at::kFloat);
|
||||
at::Tensor result = t.to(at::kInt);
|
||||
|
||||
ASSERT_EQ(result.scalar_type(), at::kInt);
|
||||
}
|
||||
|
||||
TEST(TensorToTest, ToDtype_SameType_NoAllocation) {
|
||||
// When target dtype == current dtype and copy=false, returns self.
|
||||
at::Tensor t = at::tensor({4.0f}, at::kFloat);
|
||||
at::Tensor result = t.to(at::kFloat, /*non_blocking=*/false, /*copy=*/false);
|
||||
|
||||
ASSERT_EQ(result.scalar_type(), at::kFloat);
|
||||
ASSERT_NEAR(result.item<float>(), 4.0f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(TensorToTest, ToDtype_Int32ToInt64) {
|
||||
at::Tensor t = at::tensor({10, 20, 30}, at::kInt);
|
||||
at::Tensor result = t.to(at::kLong);
|
||||
|
||||
ASSERT_EQ(result.scalar_type(), at::kLong);
|
||||
ASSERT_EQ(result[1].item<int64_t>(), 20LL);
|
||||
}
|
||||
|
||||
TEST(TensorToTest, ToDtype_FloatToHalf) {
|
||||
at::Tensor t = at::tensor({1.0f, 2.0f}, at::kFloat);
|
||||
at::Tensor result = t.to(at::kHalf);
|
||||
|
||||
ASSERT_EQ(result.scalar_type(), at::kHalf);
|
||||
}
|
||||
|
||||
// ---- Overload 1: to(TensorOptions) ----
|
||||
|
||||
TEST(TensorToTest, ToOptions_DtypeOnly) {
|
||||
at::Tensor t = at::tensor({5.0f}, at::kFloat);
|
||||
at::TensorOptions opts = at::TensorOptions().dtype(at::kDouble);
|
||||
|
||||
at::Tensor result = t.to(opts);
|
||||
|
||||
ASSERT_EQ(result.scalar_type(), at::kDouble);
|
||||
ASSERT_NEAR(result.item<double>(), 5.0, 1e-9);
|
||||
}
|
||||
|
||||
TEST(TensorToTest, ToOptions_DeviceCPU) {
|
||||
at::Tensor t = at::tensor({3.0f}, at::kFloat);
|
||||
at::TensorOptions opts = at::TensorOptions().device(c10::Device(c10::kCPU));
|
||||
|
||||
at::Tensor result = t.to(opts);
|
||||
|
||||
ASSERT_EQ(result.device().type(), c10::DeviceType::CPU);
|
||||
}
|
||||
|
||||
// ---- Overload 2: to(optional<ScalarType>, optional<Layout>, ...) ----
|
||||
|
||||
TEST(TensorToTest, ToOptionalArgs_DtypeSet) {
|
||||
at::Tensor t = at::ones({3}, at::kFloat);
|
||||
at::Tensor result = t.to(at::kDouble,
|
||||
/*layout=*/std::nullopt,
|
||||
/*device=*/std::nullopt,
|
||||
/*pin_memory=*/std::nullopt,
|
||||
/*non_blocking=*/false,
|
||||
/*copy=*/false,
|
||||
/*memory_format=*/std::nullopt);
|
||||
|
||||
ASSERT_EQ(result.scalar_type(), at::kDouble);
|
||||
}
|
||||
|
||||
TEST(TensorToTest, ToOptionalArgs_NothingSet_ReturnsSameType) {
|
||||
at::Tensor t = at::ones({3}, at::kFloat);
|
||||
at::Tensor result = t.to(std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
/*non_blocking=*/false,
|
||||
/*copy=*/false,
|
||||
std::nullopt);
|
||||
|
||||
ASSERT_EQ(result.scalar_type(), at::kFloat);
|
||||
}
|
||||
|
||||
TEST(TensorToTest, ToCopyAndUnsupportedDeviceBranches) {
|
||||
at::Tensor t = at::ones({2, 3}, at::kFloat);
|
||||
|
||||
at::Tensor copied =
|
||||
t.to(at::TensorOptions().dtype(at::kFloat), false, true, std::nullopt);
|
||||
EXPECT_TRUE(copied.equal(t));
|
||||
|
||||
at::Tensor pinned = t.to(std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
std::nullopt);
|
||||
EXPECT_TRUE(pinned.equal(t));
|
||||
|
||||
EXPECT_THROW(t.to(at::TensorOptions().device(
|
||||
c10::Device(static_cast<c10::DeviceType>(-1), 0))),
|
||||
::std::exception);
|
||||
}
|
||||
|
||||
// ---- Overload 3: to(Device, ScalarType) ----
|
||||
|
||||
TEST(TensorToTest, ToDeviceAndDtype) {
|
||||
at::Tensor t = at::tensor({1.0f, 2.0f}, at::kFloat);
|
||||
at::Tensor result = t.to(c10::Device(c10::kCPU),
|
||||
at::kDouble,
|
||||
/*non_blocking=*/false,
|
||||
/*copy=*/false);
|
||||
|
||||
ASSERT_EQ(result.scalar_type(), at::kDouble);
|
||||
ASSERT_EQ(result.device().type(), c10::DeviceType::CPU);
|
||||
}
|
||||
|
||||
// ---- Overload 5: to(const Tensor& other) ----
|
||||
|
||||
TEST(TensorToTest, ToOtherTensor_MatchesDtype) {
|
||||
at::Tensor src = at::ones({2, 3}, at::kFloat);
|
||||
at::Tensor target_template = at::zeros({1}, at::kDouble);
|
||||
|
||||
at::Tensor result = src.to(target_template);
|
||||
|
||||
ASSERT_EQ(result.scalar_type(), at::kDouble);
|
||||
}
|
||||
|
||||
TEST(TensorToTest, ToOtherTensor_MatchesDevice) {
|
||||
at::Tensor src = at::ones({3}, at::kFloat);
|
||||
at::Tensor target_template =
|
||||
at::zeros({1}, at::TensorOptions().dtype(at::kFloat).device(c10::kCPU));
|
||||
|
||||
at::Tensor result = src.to(target_template);
|
||||
|
||||
ASSERT_EQ(result.device().type(), c10::DeviceType::CPU);
|
||||
}
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
TEST(TensorToTest, ToDtype_GPU_FloatToDouble) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
at::Tensor t = at::tensor(
|
||||
{1.0f, 2.0f},
|
||||
at::TensorOptions().dtype(at::kFloat).device(c10::Device(c10::kCUDA, 0)));
|
||||
at::Tensor result = t.to(at::kDouble);
|
||||
|
||||
ASSERT_EQ(result.scalar_type(), at::kDouble);
|
||||
ASSERT_EQ(result.device().type(), c10::DeviceType::CUDA);
|
||||
}
|
||||
|
||||
TEST(TensorToTest, ToDevice_CPUToGPU) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
at::Tensor t = at::tensor({5.0f}, at::kFloat);
|
||||
at::Tensor result = t.to(c10::Device(c10::kCUDA, 0),
|
||||
at::kFloat,
|
||||
/*non_blocking=*/false,
|
||||
/*copy=*/false);
|
||||
|
||||
ASSERT_EQ(result.device().type(), c10::DeviceType::CUDA);
|
||||
}
|
||||
|
||||
TEST(TensorToTest, ToDevice_GPUToCPU) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
at::Tensor t = at::tensor(
|
||||
{7.0f},
|
||||
at::TensorOptions().dtype(at::kFloat).device(c10::Device(c10::kCUDA, 0)));
|
||||
at::Tensor result = t.to(at::TensorOptions().device(c10::Device(c10::kCPU)));
|
||||
|
||||
ASSERT_EQ(result.device().type(), c10::DeviceType::CPU);
|
||||
ASSERT_NEAR(result.item<float>(), 7.0f, 1e-5f);
|
||||
}
|
||||
|
||||
TEST(TensorToTest, ToDeviceWithoutIndexUsesCurrentCudaDevice) {
|
||||
if (c10::cuda::device_count() < 2) {
|
||||
return;
|
||||
}
|
||||
c10::cuda::CUDAGuard guard(1);
|
||||
at::Tensor t = at::tensor({5.0f}, at::kFloat);
|
||||
at::Tensor result = t.to(c10::Device(c10::kCUDA),
|
||||
at::kFloat,
|
||||
/*non_blocking=*/false,
|
||||
/*copy=*/false);
|
||||
|
||||
ASSERT_EQ(result.device().type(), c10::DeviceType::CUDA);
|
||||
ASSERT_EQ(result.device().index(), 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PADDLE_WITH_XPU
|
||||
TEST(TensorToTest, ToDevice_CPUToXPU) {
|
||||
if (paddle::platform::GetXPUDeviceCount() == 0) {
|
||||
return;
|
||||
}
|
||||
at::Tensor t = at::tensor({5.0f}, at::kFloat);
|
||||
at::Tensor result = t.to(c10::Device(c10::kXPU, 0),
|
||||
at::kFloat,
|
||||
/*non_blocking=*/false,
|
||||
/*copy=*/false);
|
||||
|
||||
ASSERT_EQ(result.device().type(), c10::DeviceType::XPU);
|
||||
ASSERT_EQ(result.device().index(), 0);
|
||||
}
|
||||
|
||||
TEST(TensorToTest, ToDeviceWithoutIndexUsesCurrentXpuDevice) {
|
||||
if (paddle::platform::GetXPUDeviceCount() < 2) {
|
||||
return;
|
||||
}
|
||||
paddle::platform::XPUDeviceGuard guard(1);
|
||||
at::Tensor t = at::tensor({5.0f}, at::kFloat);
|
||||
at::Tensor result = t.to(c10::Device(c10::kXPU),
|
||||
at::kFloat,
|
||||
/*non_blocking=*/false,
|
||||
/*copy=*/false);
|
||||
|
||||
ASSERT_EQ(result.device().type(), c10::DeviceType::XPU);
|
||||
ASSERT_EQ(result.device().index(), 1);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/common/macros.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
COMMON_DECLARE_bool(use_stride_kernel);
|
||||
|
||||
// ============================================================
|
||||
// Tests for at::Tensor::transpose_(int64_t dim0, int64_t dim1)
|
||||
// (in-place variant; out-of-place transpose is a separate path)
|
||||
// ============================================================
|
||||
|
||||
TEST(TensorTransposeInplaceTest, Transpose2D_SwapDims) {
|
||||
// transpose_(0, 1) on a 2D tensor: shape becomes transposed in-place
|
||||
at::Tensor t = at::ones({3, 4}, at::kFloat);
|
||||
void* original_ptr = t.data_ptr();
|
||||
at::Tensor& ref = t.transpose_(0, 1);
|
||||
|
||||
ASSERT_EQ(t.dim(), 2);
|
||||
ASSERT_EQ(t.sizes(), c10::IntArrayRef({4, 3}));
|
||||
// Returns *this by reference
|
||||
ASSERT_EQ(&ref, &t);
|
||||
ASSERT_EQ(t.data_ptr(), original_ptr);
|
||||
}
|
||||
|
||||
TEST(TensorTransposeInplaceTest, Transpose3D_SwapFirstTwo) {
|
||||
// transpose_(0, 1) on a 3D tensor swaps first two axes in-place
|
||||
at::Tensor t = at::ones({2, 3, 4}, at::kFloat);
|
||||
void* original_ptr = t.data_ptr();
|
||||
t.transpose_(0, 1);
|
||||
|
||||
ASSERT_EQ(t.dim(), 3);
|
||||
ASSERT_EQ(t.sizes(), c10::IntArrayRef({3, 2, 4}));
|
||||
ASSERT_EQ(t.data_ptr(), original_ptr);
|
||||
}
|
||||
|
||||
TEST(TensorTransposeInplaceTest, Transpose3D_SwapLastTwo) {
|
||||
// transpose_(1, 2) on a 3D tensor swaps last two axes in-place
|
||||
at::Tensor t = at::ones({2, 3, 4}, at::kFloat);
|
||||
void* original_ptr = t.data_ptr();
|
||||
t.transpose_(1, 2);
|
||||
|
||||
ASSERT_EQ(t.sizes(), c10::IntArrayRef({2, 4, 3}));
|
||||
ASSERT_EQ(t.data_ptr(), original_ptr);
|
||||
}
|
||||
|
||||
TEST(TensorTransposeInplaceTest, TransposeInplace_PreservesValues) {
|
||||
if (!FLAGS_use_stride_kernel) {
|
||||
return;
|
||||
}
|
||||
// Verify values are correctly accessed after in-place transpose
|
||||
at::Tensor t = at::arange(6, at::kFloat).reshape({2, 3});
|
||||
// t = [[0,1,2],[3,4,5]]
|
||||
t.transpose_(0, 1);
|
||||
// After t: shape {3,2}, layout: [[0,3],[1,4],[2,5]]
|
||||
|
||||
ASSERT_EQ(t.sizes(), c10::IntArrayRef({3, 2}));
|
||||
ASSERT_FLOAT_EQ(t[0][0].item<float>(), 0.0f);
|
||||
ASSERT_FLOAT_EQ(t[0][1].item<float>(), 3.0f);
|
||||
ASSERT_FLOAT_EQ(t[2][0].item<float>(), 2.0f);
|
||||
ASSERT_FLOAT_EQ(t[2][1].item<float>(), 5.0f);
|
||||
}
|
||||
|
||||
TEST(TensorTransposeInplaceTest, TransposeInplace_SameDim_NoOp) {
|
||||
// transpose_(i, i) is a no-op; shape and data pointer are unchanged
|
||||
at::Tensor t = at::ones({3, 4}, at::kFloat);
|
||||
void* original_ptr = t.data_ptr();
|
||||
t.transpose_(0, 0);
|
||||
|
||||
ASSERT_EQ(t.sizes(), c10::IntArrayRef({3, 4}));
|
||||
ASSERT_EQ(t.data_ptr(), original_ptr);
|
||||
}
|
||||
|
||||
TEST(TensorTransposeInplaceTest,
|
||||
TransposeInplace_DoubleTranspose_RestoresShape) {
|
||||
// Two consecutive in-place transposes on same dims restore original shape
|
||||
at::Tensor t = at::ones({5, 7}, at::kFloat);
|
||||
t.transpose_(0, 1);
|
||||
ASSERT_EQ(t.sizes(), c10::IntArrayRef({7, 5}));
|
||||
t.transpose_(0, 1);
|
||||
ASSERT_EQ(t.sizes(), c10::IntArrayRef({5, 7}));
|
||||
}
|
||||
|
||||
TEST(TensorTransposeTest, TransposeLargePositiveDimThrows) {
|
||||
at::Tensor t = at::ones({2, 3}, at::kFloat);
|
||||
ASSERT_ANY_THROW((void)at::transpose(t, 1LL << 32, 1));
|
||||
}
|
||||
|
||||
TEST(TensorTransposeTest, TransposeLargeNegativeDimThrows) {
|
||||
at::Tensor t = at::ones({2, 3}, at::kFloat);
|
||||
ASSERT_ANY_THROW((void)t.transpose(0, -(1LL << 32)));
|
||||
}
|
||||
|
||||
TEST(TensorTransposeInplaceTest, TransposeInplaceLargeDimThrowsUnchanged) {
|
||||
at::Tensor t = at::ones({2, 3}, at::kFloat);
|
||||
ASSERT_ANY_THROW((void)t.transpose_(1LL << 32, 1));
|
||||
ASSERT_EQ(t.sizes(), c10::IntArrayRef({2, 3}));
|
||||
}
|
||||
|
||||
TEST(TensorTransposeTest, TransposeLegalNegativeDims) {
|
||||
at::Tensor t = at::ones({2, 3}, at::kFloat);
|
||||
at::Tensor result = at::transpose(t, -1, -2);
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({3, 2}));
|
||||
|
||||
t.transpose_(-1, -2);
|
||||
ASSERT_EQ(t.sizes(), c10::IntArrayRef({3, 2}));
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/_values.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/Layout.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ============================================================
|
||||
// Tests for at::Tensor::_values()
|
||||
// ============================================================
|
||||
|
||||
// Helper: build 2-D sparse COO tensor from dense indices + float values.
|
||||
static at::Tensor make_sparse_coo(at::Tensor indices,
|
||||
at::Tensor values,
|
||||
c10::IntArrayRef size) {
|
||||
return at::sparse_coo_tensor(indices, values, size);
|
||||
}
|
||||
|
||||
// ---- COO sparse ----
|
||||
|
||||
TEST(TensorValuesTest, SparseCOO_ValuesHasCorrectNumel) {
|
||||
// 3x3 matrix, 2 non-zeros at (0,1)=1.5 and (2,0)=3.0
|
||||
at::Tensor indices =
|
||||
at::tensor({0, 2, 1, 0}, at::kLong).reshape({2, 2}); // [sparse_dim, nnz]
|
||||
at::Tensor values = at::tensor({1.5f, 3.0f}, at::kFloat);
|
||||
at::Tensor sparse = make_sparse_coo(indices, values, {3, 3});
|
||||
|
||||
at::Tensor vals = sparse._values();
|
||||
|
||||
ASSERT_EQ(vals.numel(), 2);
|
||||
}
|
||||
|
||||
TEST(TensorValuesTest, SparseCOO_ValuesCorrectContent) {
|
||||
at::Tensor indices = at::tensor({0, 2, 1, 0}, at::kLong).reshape({2, 2});
|
||||
at::Tensor values = at::tensor({1.5f, 3.0f}, at::kFloat);
|
||||
at::Tensor sparse = make_sparse_coo(indices, values, {3, 3});
|
||||
|
||||
at::Tensor vals = sparse.coalesce()._values();
|
||||
|
||||
ASSERT_NEAR(vals[0].item<float>(), 1.5f, 1e-5f);
|
||||
ASSERT_NEAR(vals[1].item<float>(), 3.0f, 1e-5f);
|
||||
}
|
||||
|
||||
TEST(TensorValuesTest, SparseCOO_ValuesIsDense) {
|
||||
// The values() tensor of a sparse tensor is itself a dense (strided) tensor.
|
||||
at::Tensor indices = at::tensor({0, 0, 1, 1}, at::kLong).reshape({2, 2});
|
||||
at::Tensor values = at::tensor({7.0f, 8.0f}, at::kFloat);
|
||||
at::Tensor sparse = make_sparse_coo(indices, values, {3, 3});
|
||||
|
||||
at::Tensor vals = sparse._values();
|
||||
|
||||
ASSERT_EQ(vals.layout(), c10::kStrided);
|
||||
}
|
||||
|
||||
TEST(TensorValuesTest, SparseCOO_ValuesScalarType) {
|
||||
at::Tensor indices = at::tensor({0, 0, 1, 2}, at::kLong).reshape({2, 2});
|
||||
at::Tensor values = at::tensor({1, 2}, at::kInt);
|
||||
at::Tensor sparse = make_sparse_coo(indices, values, {3, 3});
|
||||
|
||||
at::Tensor vals = sparse._values();
|
||||
|
||||
ASSERT_EQ(vals.scalar_type(), at::kInt);
|
||||
}
|
||||
|
||||
// ---- Dense tensor must throw ----
|
||||
|
||||
TEST(TensorValuesTest, DenseTensor_Throws) {
|
||||
at::Tensor dense = at::ones({3, 3}, at::kFloat);
|
||||
|
||||
ASSERT_THROW(dense._values(), std::exception);
|
||||
}
|
||||
|
||||
// ---- CSR sparse ----
|
||||
|
||||
TEST(TensorValuesTest, SparseCsr_ValuesCorrect) {
|
||||
// 3x3 identity in CSR: values=[1,1,1], col_indices=[0,1,2],
|
||||
// row_ptrs=[0,1,2,3]
|
||||
at::Tensor crow = at::tensor({0, 1, 2, 3}, at::kInt);
|
||||
at::Tensor col = at::tensor({0, 1, 2}, at::kInt);
|
||||
at::Tensor vals_in = at::tensor({1.0f, 1.0f, 1.0f}, at::kFloat);
|
||||
at::Tensor sparse_csr =
|
||||
at::sparse_csr_tensor(crow, col, vals_in, {3, 3}, at::TensorOptions());
|
||||
|
||||
// PyTorch does not dispatch _values for SparseCsr tensors
|
||||
ASSERT_THROW(sparse_csr._values(), std::exception);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/common/macros.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
COMMON_DECLARE_bool(use_stride_kernel);
|
||||
|
||||
// ============================================================
|
||||
// Tests for at::Tensor::view_as(const at::Tensor& other)
|
||||
// ============================================================
|
||||
|
||||
TEST(TensorViewAsTest, ViewAsSameShape) {
|
||||
// view_as with same shape: result has identical shape
|
||||
at::Tensor t = at::arange(12, at::kFloat).reshape({3, 4});
|
||||
at::Tensor other = at::zeros({3, 4}, at::kFloat);
|
||||
at::Tensor result = t.view_as(other);
|
||||
|
||||
ASSERT_EQ(result.sizes(), other.sizes());
|
||||
ASSERT_EQ(result.numel(), t.numel());
|
||||
}
|
||||
|
||||
TEST(TensorViewAsTest, ViewAsDifferentShape_CompatibleNumel) {
|
||||
if (!FLAGS_use_stride_kernel) {
|
||||
return;
|
||||
}
|
||||
// view_as with a different but numel-compatible shape
|
||||
at::Tensor t = at::arange(12, at::kFloat);
|
||||
at::Tensor other = at::zeros({3, 4}, at::kFloat);
|
||||
at::Tensor result = t.view_as(other);
|
||||
|
||||
ASSERT_EQ(result.dim(), 2);
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({3, 4}));
|
||||
}
|
||||
|
||||
TEST(TensorViewAsTest, ViewAsPreservesData) {
|
||||
if (!FLAGS_use_stride_kernel) {
|
||||
return;
|
||||
}
|
||||
// Elements are accessible with the new shape and preserve original values
|
||||
at::Tensor t = at::arange(6, at::kFloat);
|
||||
// t = [0, 1, 2, 3, 4, 5]
|
||||
at::Tensor other = at::zeros({2, 3}, at::kFloat);
|
||||
at::Tensor result = t.view_as(other);
|
||||
|
||||
// result[0] = [0,1,2], result[1] = [3,4,5]
|
||||
ASSERT_FLOAT_EQ(result[0][0].item<float>(), 0.0f);
|
||||
ASSERT_FLOAT_EQ(result[0][2].item<float>(), 2.0f);
|
||||
ASSERT_FLOAT_EQ(result[1][0].item<float>(), 3.0f);
|
||||
ASSERT_FLOAT_EQ(result[1][2].item<float>(), 5.0f);
|
||||
}
|
||||
|
||||
TEST(TensorViewAsTest, ViewAs1D_Flattens) {
|
||||
if (!FLAGS_use_stride_kernel) {
|
||||
return;
|
||||
}
|
||||
// view_as a 1-D tensor to flatten a higher-rank tensor
|
||||
at::Tensor t = at::ones({2, 3, 4}, at::kFloat);
|
||||
at::Tensor flat_ref = at::zeros({24}, at::kFloat);
|
||||
at::Tensor result = t.view_as(flat_ref);
|
||||
|
||||
ASSERT_EQ(result.dim(), 1);
|
||||
ASSERT_EQ(result.sizes(), c10::IntArrayRef({24}));
|
||||
}
|
||||
|
||||
TEST(TensorViewAsTest, ViewAs_SameDataPointer) {
|
||||
// view_as should share the underlying data (no copy)
|
||||
at::Tensor t = at::arange(12, at::kFloat);
|
||||
at::Tensor other = at::zeros({3, 4}, at::kFloat);
|
||||
at::Tensor result = t.view_as(other);
|
||||
|
||||
ASSERT_EQ(result.data_ptr(), t.data_ptr());
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
# c10 core tests (CPU compatible)
|
||||
cc_test(c10_Device_test SRCS c10_Device_test.cc)
|
||||
cc_test(c10_DispatchKeySet_test SRCS c10_DispatchKeySet_test.cc)
|
||||
cc_test(c10_DispatchKey_test SRCS c10_DispatchKey_test.cc)
|
||||
cc_test(c10_MemoryFormat_test SRCS c10_MemoryFormat_test.cc)
|
||||
cc_test(c10_ScalarType_test SRCS c10_ScalarType_test.cc)
|
||||
cc_test(c10_SizesAndStrides_test SRCS c10_SizesAndStrides_test.cc)
|
||||
cc_test(c10_TensorOptions_test SRCS c10_TensorOptions_test.cc)
|
||||
cc_test(c10_TypeMeta_test SRCS c10_TypeMeta_test.cc)
|
||||
cc_test(c10_intrusive_ptr_lifecycle_test
|
||||
SRCS c10_intrusive_ptr_lifecycle_test.cc)
|
||||
cc_test(c10_layout_test SRCS c10_layout_test.cc)
|
||||
cc_test(c10_ptr_test SRCS c10_ptr_test.cc)
|
||||
cc_test(c10_storage_test SRCS c10_storage_test.cc)
|
||||
|
||||
# ATen core tests (CPU compatible)
|
||||
cc_test(ATen_all_test SRCS ATen_all_test.cc)
|
||||
cc_test(ATen_any_test SRCS ATen_any_test.cc)
|
||||
cc_test(ATen_as_strided_test SRCS ATen_as_strided_test.cc)
|
||||
cc_test(ATen_autograd_test SRCS ATen_autograd_test.cc)
|
||||
cc_test(ATen_chunk_test SRCS ATen_chunk_test.cc)
|
||||
cc_test(ATen_clamp_test SRCS ATen_clamp_test.cc)
|
||||
cc_test(ATen_coalesce_test SRCS ATen_coalesce_test.cc)
|
||||
cc_test(ATen_dense_sparse_conversion_test
|
||||
SRCS ATen_dense_sparse_conversion_test.cc)
|
||||
cc_test(ATen_empty_test SRCS ATen_empty_test.cc)
|
||||
cc_test(ATen_equal_test SRCS ATen_equal_test.cc)
|
||||
cc_test(ATen_expand_test SRCS ATen_expand_test.cc)
|
||||
cc_test(ATen_eye_test SRCS ATen_eye_test.cc)
|
||||
cc_test(ATen_factory_default_dtype_test SRCS ATen_factory_default_dtype_test.cc)
|
||||
cc_test(ATen_flatten_test SRCS ATen_flatten_test.cc)
|
||||
cc_test(ATen_from_blob_test SRCS ATen_from_blob_test.cc)
|
||||
cc_test(ATen_hook_test SRCS ATen_hook_test.cc)
|
||||
cc_test(ATen_index_test SRCS ATen_index_test.cc)
|
||||
cc_test(ATen_item_test SRCS ATen_item_test.cc)
|
||||
cc_test(ATen_narrow_test SRCS ATen_narrow_test.cc)
|
||||
cc_test(ATen_new_test SRCS ATen_new_test.cc)
|
||||
cc_test(ATen_nnz_test SRCS ATen_nnz_test.cc)
|
||||
cc_test(ATen_rename_test SRCS ATen_rename_test.cc)
|
||||
cc_test(ATen_reshape_test SRCS ATen_reshape_test.cc)
|
||||
cc_test(ATen_resize_test SRCS ATen_resize_test.cc)
|
||||
cc_test(ATen_resize_custom_kernel_test SRCS ATen_resize_custom_kernel_test.cc)
|
||||
cc_test(ATen_squeeze_test SRCS ATen_squeeze_test.cc)
|
||||
cc_test(ATen_std_var_test SRCS ATen_std_var_test.cc)
|
||||
cc_test(ATen_sum_test SRCS ATen_sum_test.cc)
|
||||
cc_test(ATen_t_test SRCS ATen_t_test.cc)
|
||||
cc_test(ATen_tensor_data_test SRCS ATen_tensor_data_test.cc)
|
||||
cc_test(ATen_toString_test SRCS ATen_toString_test.cc)
|
||||
cc_test(ATen_to_test SRCS ATen_to_test.cc)
|
||||
cc_test(ATen_transpose_test SRCS ATen_transpose_test.cc)
|
||||
cc_test(ATen_Utils_test SRCS ATen_Utils_test.cc)
|
||||
cc_test(ATen_values_test SRCS ATen_values_test.cc)
|
||||
cc_test(ATen_viewAs_test SRCS ATen_viewAs_test.cc)
|
||||
|
||||
# torch library tests (CPU compatible)
|
||||
cc_test(torch_library_test SRCS torch_library_test.cc)
|
||||
cc_test(torch_library_dispatch_test SRCS torch_library_dispatch_test.cc)
|
||||
|
||||
# GPU-runtime compat tests are not fully audited on ROCm/DCU yet.
|
||||
# Keep the DCU surface limited to the cases adapted in this PR.
|
||||
if(WITH_ROCM)
|
||||
cc_test(ATen_CUDAContext_test SRCS ATen_CUDAContext_test.cc)
|
||||
cc_test(ATen_record_stream_test SRCS ATen_record_stream_test.cc)
|
||||
cc_test(c10_Event_test SRCS c10_Event_test.cc)
|
||||
cc_test(c10_Stream_test SRCS c10_Stream_test.cc)
|
||||
else()
|
||||
cc_test(ATen_TensorAccessor_test SRCS ATen_TensorAccessor_test.cc)
|
||||
cc_test(ATen_basic_test SRCS ATen_basic_test.cc)
|
||||
cc_test(ATen_local_scalar_dense_test SRCS ATen_local_scalar_dense_test.cc)
|
||||
cc_test(ATen_memory_test SRCS ATen_memory_test.cc)
|
||||
cc_test(ATen_pin_memory_creation_test SRCS ATen_pin_memory_creation_test.cc)
|
||||
cc_test(ATen_record_stream_test SRCS ATen_record_stream_test.cc)
|
||||
cc_test(ATen_select_test SRCS ATen_select_test.cc)
|
||||
cc_test(ATen_split_test SRCS ATen_split_test.cc)
|
||||
|
||||
cc_test(ATen_CUDAContext_test SRCS ATen_CUDAContext_test.cc)
|
||||
cc_test(ATen_philox_test SRCS ATen_philox_test.cc)
|
||||
cc_test(c10_Event_test SRCS c10_Event_test.cc)
|
||||
cc_test(c10_Stream_test SRCS c10_Stream_test.cc)
|
||||
endif()
|
||||
|
||||
if(WITH_GPU)
|
||||
nv_test(ATen_CUDABlas_test SRCS ATen_CUDABlas_test.cc)
|
||||
nv_test(ATen_cuda_test SRCS ATen_cuda_test.cc)
|
||||
nv_test(c10_cuda_generator_test SRCS c10_cuda_generator_test.cc)
|
||||
nv_test(c10_generator_impl_test SRCS c10_generator_impl_test.cc)
|
||||
endif()
|
||||
cc_test(schema_parser_type_test SRCS schema_parser_type_test.cc)
|
||||
|
||||
add_subdirectory(torch)
|
||||
@@ -0,0 +1,208 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/DeviceType.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#ifdef PADDLE_WITH_XPU
|
||||
#include "paddle/phi/core/platform/device/xpu/xpu_info.h"
|
||||
#endif
|
||||
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
TEST(DeviceTypeCompatTest, DeviceTypeConversionAndStreamOperator) {
|
||||
EXPECT_EQ(c10::DeviceTypeToPhi(c10::DeviceType::CPU),
|
||||
phi::AllocationType::CPU);
|
||||
EXPECT_EQ(c10::DeviceTypeToPhi(c10::DeviceType::CUDA),
|
||||
phi::AllocationType::GPU);
|
||||
EXPECT_EQ(c10::DeviceTypeToPhi(c10::DeviceType::XPU),
|
||||
phi::AllocationType::XPU);
|
||||
EXPECT_EQ(c10::DeviceTypeToPhi(c10::DeviceType::IPU),
|
||||
phi::AllocationType::IPU);
|
||||
EXPECT_EQ(c10::DeviceTypeToPhi(c10::DeviceType::CUSTOM),
|
||||
phi::AllocationType::CUSTOM);
|
||||
EXPECT_EQ(c10::DeviceTypeToPhi(static_cast<c10::DeviceType>(-1)),
|
||||
phi::AllocationType::UNDEFINED);
|
||||
|
||||
EXPECT_EQ(c10::PhiToDeviceType(phi::AllocationType::CPU),
|
||||
c10::DeviceType::CPU);
|
||||
EXPECT_EQ(c10::PhiToDeviceType(phi::AllocationType::GPU),
|
||||
c10::DeviceType::CUDA);
|
||||
EXPECT_EQ(c10::PhiToDeviceType(phi::AllocationType::XPU),
|
||||
c10::DeviceType::XPU);
|
||||
EXPECT_EQ(c10::PhiToDeviceType(phi::AllocationType::IPU),
|
||||
c10::DeviceType::IPU);
|
||||
EXPECT_EQ(c10::PhiToDeviceType(phi::AllocationType::CUSTOM),
|
||||
c10::DeviceType::CUSTOM);
|
||||
EXPECT_EQ(c10::PhiToDeviceType(phi::AllocationType::UNDEFINED),
|
||||
c10::DeviceType::CPU);
|
||||
|
||||
EXPECT_TRUE(c10::isValidDeviceType(c10::DeviceType::CPU));
|
||||
EXPECT_TRUE(c10::isValidDeviceType(c10::DeviceType::CUSTOM));
|
||||
EXPECT_FALSE(c10::isValidDeviceType(static_cast<c10::DeviceType>(-9)));
|
||||
|
||||
std::ostringstream cpu_os;
|
||||
cpu_os << c10::DeviceType::CPU;
|
||||
EXPECT_EQ(cpu_os.str(), "cpu");
|
||||
std::ostringstream cuda_os;
|
||||
cuda_os << c10::DeviceType::CUDA;
|
||||
EXPECT_EQ(cuda_os.str(), "cuda");
|
||||
std::ostringstream xpu_os;
|
||||
xpu_os << c10::DeviceType::XPU;
|
||||
EXPECT_EQ(xpu_os.str(), "xpu");
|
||||
std::ostringstream ipu_os;
|
||||
ipu_os << c10::DeviceType::IPU;
|
||||
EXPECT_EQ(ipu_os.str(), "ipu");
|
||||
std::ostringstream custom_os;
|
||||
custom_os << c10::DeviceType::CUSTOM;
|
||||
EXPECT_EQ(custom_os.str(), "privateuseone");
|
||||
std::ostringstream invalid_os;
|
||||
invalid_os << static_cast<c10::DeviceType>(99);
|
||||
EXPECT_TRUE(invalid_os.str().empty());
|
||||
|
||||
EXPECT_EQ(c10::DeviceType::PrivateUse1, c10::DeviceType::CUSTOM);
|
||||
EXPECT_EQ(c10::kPrivateUse1, c10::DeviceType::PrivateUse1);
|
||||
}
|
||||
|
||||
TEST(DeviceCompatTest, DeviceParseAndPlaceBranches) {
|
||||
c10::Device cpu("cpu");
|
||||
EXPECT_TRUE(cpu.is_cpu());
|
||||
EXPECT_FALSE(cpu.has_index());
|
||||
EXPECT_EQ(cpu.str(), "cpu");
|
||||
|
||||
c10::Device cuda("cuda:3");
|
||||
EXPECT_TRUE(cuda.is_cuda());
|
||||
EXPECT_TRUE(cuda.has_index());
|
||||
EXPECT_EQ(cuda.index(), 3);
|
||||
EXPECT_EQ(cuda.str(), "cuda:3");
|
||||
|
||||
c10::Device xpu("xpu:1");
|
||||
EXPECT_EQ(xpu.type(), c10::DeviceType::XPU);
|
||||
EXPECT_EQ(xpu.index(), 1);
|
||||
EXPECT_EQ(xpu.str(), "xpu:1");
|
||||
|
||||
c10::Device ipu("ipu:2");
|
||||
EXPECT_EQ(ipu.type(), c10::DeviceType::IPU);
|
||||
EXPECT_EQ(ipu.index(), 2);
|
||||
EXPECT_EQ(ipu.str(), "ipu:2");
|
||||
|
||||
EXPECT_THROW(c10::Device(""), ::std::exception);
|
||||
EXPECT_THROW(c10::Device("npu:0"), ::std::exception);
|
||||
EXPECT_THROW(c10::Device("cuda:abc"), ::std::exception);
|
||||
EXPECT_THROW(c10::Device("cuda:9999999999999999999999"), ::std::exception);
|
||||
|
||||
c10::Device custom(c10::DeviceType::CUSTOM, 5, "npu");
|
||||
phi::Place custom_place = custom._PD_GetInner();
|
||||
EXPECT_EQ(custom_place.GetType(), phi::AllocationType::CUSTOM);
|
||||
EXPECT_EQ(custom_place.GetDeviceId(), 5);
|
||||
EXPECT_EQ(custom.str(), "privateuseone:5");
|
||||
|
||||
c10::Device cuda_no_index(c10::DeviceType::CUDA);
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
auto device_count = c10::cuda::device_count();
|
||||
if (device_count == 0) {
|
||||
return;
|
||||
}
|
||||
EXPECT_EQ(cuda_no_index._PD_GetInner().GetType(), phi::AllocationType::GPU);
|
||||
if (device_count >= 2) {
|
||||
c10::cuda::CUDAGuard guard(1);
|
||||
EXPECT_EQ(c10::Device(c10::DeviceType::CUDA)._PD_GetInner().GetDeviceId(),
|
||||
1);
|
||||
} else {
|
||||
EXPECT_EQ(cuda_no_index._PD_GetInner().GetDeviceId(), 0);
|
||||
}
|
||||
#else
|
||||
EXPECT_EQ(cuda_no_index._PD_GetInner().GetType(), phi::AllocationType::GPU);
|
||||
EXPECT_EQ(cuda_no_index._PD_GetInner().GetDeviceId(), 0);
|
||||
#endif
|
||||
c10::Device xpu_no_index(c10::DeviceType::XPU);
|
||||
#ifdef PADDLE_WITH_XPU
|
||||
auto xpu_device_count = paddle::platform::GetXPUDeviceCount();
|
||||
if (xpu_device_count > 0) {
|
||||
EXPECT_EQ(xpu_no_index._PD_GetInner().GetType(), phi::AllocationType::XPU);
|
||||
}
|
||||
if (xpu_device_count >= 2) {
|
||||
paddle::platform::XPUDeviceGuard guard(1);
|
||||
EXPECT_EQ(c10::Device(c10::DeviceType::XPU)._PD_GetInner().GetDeviceId(),
|
||||
1);
|
||||
} else if (xpu_device_count == 1) {
|
||||
EXPECT_EQ(xpu_no_index._PD_GetInner().GetDeviceId(), 0);
|
||||
}
|
||||
#else
|
||||
EXPECT_EQ(xpu_no_index._PD_GetInner().GetType(), phi::AllocationType::XPU);
|
||||
EXPECT_EQ(xpu_no_index._PD_GetInner().GetDeviceId(), 0);
|
||||
#endif
|
||||
c10::Device ipu_no_index(c10::DeviceType::IPU);
|
||||
EXPECT_EQ(ipu_no_index._PD_GetInner().GetType(), phi::AllocationType::IPU);
|
||||
EXPECT_EQ(ipu_no_index._PD_GetInner().GetDeviceId(), 0);
|
||||
|
||||
c10::Device invalid(static_cast<c10::DeviceType>(-1), 0);
|
||||
phi::Place fallback_place = invalid._PD_GetInner();
|
||||
EXPECT_EQ(fallback_place.GetType(), phi::AllocationType::CPU);
|
||||
EXPECT_EQ(invalid.str(), "cpu:0");
|
||||
|
||||
std::ostringstream os;
|
||||
os << cuda;
|
||||
EXPECT_EQ(os.str(), "cuda:3");
|
||||
}
|
||||
|
||||
TEST(DeviceCompatTest, DeviceInterfaceParity) {
|
||||
c10::Device cpu(c10::kCPU);
|
||||
c10::Device cuda(c10::kCUDA, 0);
|
||||
c10::Device xpu(c10::kXPU, 1);
|
||||
c10::Device ipu(c10::kIPU, 2);
|
||||
c10::Device privateuse(c10::kPrivateUse1, 4);
|
||||
|
||||
EXPECT_TRUE(cpu.is_cpu());
|
||||
EXPECT_TRUE(cuda.is_cuda());
|
||||
EXPECT_TRUE(xpu.is_xpu());
|
||||
EXPECT_TRUE(ipu.is_ipu());
|
||||
EXPECT_TRUE(privateuse.is_privateuseone());
|
||||
EXPECT_FALSE(privateuse.is_mps());
|
||||
EXPECT_FALSE(privateuse.is_hip());
|
||||
EXPECT_FALSE(privateuse.is_meta());
|
||||
EXPECT_TRUE(cpu.supports_as_strided());
|
||||
EXPECT_FALSE(ipu.supports_as_strided());
|
||||
|
||||
c10::Device cpu_with_index(c10::kCPU);
|
||||
cpu_with_index.set_index(0);
|
||||
EXPECT_EQ(cpu_with_index.index(), 0);
|
||||
EXPECT_EQ(cpu_with_index.str(), "cpu:0");
|
||||
|
||||
c10::Device cuda_with_index(c10::kCUDA);
|
||||
cuda_with_index.set_index(2);
|
||||
EXPECT_EQ(cuda_with_index.index(), 2);
|
||||
EXPECT_EQ(cuda_with_index.str(), "cuda:2");
|
||||
|
||||
EXPECT_NE(cpu, cuda);
|
||||
EXPECT_EQ(cuda, c10::Device(c10::kCUDA, 0));
|
||||
EXPECT_EQ(privateuse.str(), "privateuseone:4");
|
||||
EXPECT_TRUE(c10::Device("privateuseone:7").is_privateuseone());
|
||||
|
||||
EXPECT_THROW(c10::Device("cuda:-1"), ::std::exception);
|
||||
EXPECT_THROW(c10::Device("cuda:01"), ::std::exception);
|
||||
EXPECT_THROW(c10::Device("cuda:1:2"), ::std::exception);
|
||||
|
||||
std::unordered_map<c10::Device, int> device_map;
|
||||
device_map.emplace(c10::Device(c10::kCUDA, 0), 7);
|
||||
device_map.emplace(c10::Device(c10::kCPU), 3);
|
||||
EXPECT_EQ(device_map.at(c10::Device(c10::kCUDA, 0)), 7);
|
||||
EXPECT_EQ(device_map.at(c10::Device(c10::kCPU)), 3);
|
||||
}
|
||||
@@ -0,0 +1,439 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <c10/core/DispatchKeySet.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
// Use EXPECT_TRUE with == instead of EXPECT_EQ for enum types
|
||||
// because gtest's EXPECT_EQ needs operator<< which is declared
|
||||
// but not yet implemented for DispatchKey/BackendComponent.
|
||||
#define EXPECT_DK_EQ(a, b) EXPECT_TRUE((a) == (b))
|
||||
#define EXPECT_BC_EQ(a, b) EXPECT_TRUE((a) == (b))
|
||||
|
||||
// ==========================================================================
|
||||
// Tests for c10::DispatchKeySet inline/constexpr methods
|
||||
// ==========================================================================
|
||||
|
||||
// ---------- Constructors ----------------------------------------------------
|
||||
|
||||
TEST(DispatchKeySetTest, DefaultConstructorEmpty) {
|
||||
c10::DispatchKeySet ks;
|
||||
EXPECT_TRUE(ks.empty());
|
||||
EXPECT_EQ(ks.raw_repr(), 0u);
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, ConstructFromDispatchKeyUndefined) {
|
||||
c10::DispatchKeySet ks(c10::DispatchKey::Undefined);
|
||||
EXPECT_TRUE(ks.empty());
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, ConstructFromFunctionalityKey) {
|
||||
c10::DispatchKeySet ks(c10::DispatchKey::Dense);
|
||||
EXPECT_FALSE(ks.empty());
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::Dense));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, ConstructFromRuntimeBackendKey) {
|
||||
// CPU is a per-backend key (Dense + CPUBit).
|
||||
c10::DispatchKeySet ks(c10::DispatchKey::CPU);
|
||||
EXPECT_FALSE(ks.empty());
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::CPU));
|
||||
EXPECT_TRUE(ks.has_backend(c10::BackendComponent::CPUBit));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, ConstructFromBackendComponent) {
|
||||
c10::DispatchKeySet ks(c10::BackendComponent::CUDABit);
|
||||
EXPECT_TRUE(ks.has_backend(c10::BackendComponent::CUDABit));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, ConstructFromBackendComponentInvalid) {
|
||||
c10::DispatchKeySet ks(c10::BackendComponent::InvalidBit);
|
||||
EXPECT_TRUE(ks.empty());
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, ConstructFromInitializerList) {
|
||||
c10::DispatchKeySet ks({c10::DispatchKey::Dense, c10::DispatchKey::Sparse});
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::Dense));
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::Sparse));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, ConstructFromBackendInitializerList) {
|
||||
c10::DispatchKeySet ks(
|
||||
{c10::BackendComponent::CPUBit, c10::BackendComponent::CUDABit});
|
||||
EXPECT_TRUE(ks.has_backend(c10::BackendComponent::CPUBit));
|
||||
EXPECT_TRUE(ks.has_backend(c10::BackendComponent::CUDABit));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, ConstructFull) {
|
||||
c10::DispatchKeySet ks(c10::DispatchKeySet::FULL);
|
||||
EXPECT_FALSE(ks.empty());
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, ConstructFullAfter) {
|
||||
c10::DispatchKeySet ks(c10::DispatchKeySet::FULL_AFTER,
|
||||
c10::DispatchKey::AutogradOther);
|
||||
EXPECT_FALSE(ks.empty());
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, ConstructRaw) {
|
||||
c10::DispatchKeySet ks(c10::DispatchKeySet::RAW, 0x42);
|
||||
EXPECT_EQ(ks.raw_repr(), 0x42u);
|
||||
}
|
||||
|
||||
// ---------- Key beyond runtime range ----------------------------------------
|
||||
|
||||
TEST(DispatchKeySetTest, ConstructFromKeyBeyondRuntime) {
|
||||
// Keys beyond EndOfRuntimeBackendKeys should produce an empty set.
|
||||
c10::DispatchKeySet ks(c10::DispatchKey::Autograd);
|
||||
EXPECT_TRUE(ks.empty());
|
||||
}
|
||||
|
||||
// ---------- has / has_all / has_any / has_backend ----------------------------
|
||||
|
||||
TEST(DispatchKeySetTest, HasAll) {
|
||||
c10::DispatchKeySet ks({c10::DispatchKey::Dense, c10::DispatchKey::Sparse});
|
||||
c10::DispatchKeySet sub(c10::DispatchKey::Dense);
|
||||
EXPECT_TRUE(ks.has_all(sub));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, HasAnyFunctionality) {
|
||||
c10::DispatchKeySet ks({c10::DispatchKey::Dense, c10::DispatchKey::Sparse});
|
||||
c10::DispatchKeySet query(c10::DispatchKey::Sparse);
|
||||
EXPECT_TRUE(ks.has_any(query));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, IsSupersetOf) {
|
||||
c10::DispatchKeySet full({c10::DispatchKey::Dense, c10::DispatchKey::Sparse});
|
||||
c10::DispatchKeySet sub(c10::DispatchKey::Dense);
|
||||
EXPECT_TRUE(full.isSupersetOf(sub));
|
||||
EXPECT_FALSE(sub.isSupersetOf(full));
|
||||
}
|
||||
|
||||
// ---------- Operators -------------------------------------------------------
|
||||
|
||||
TEST(DispatchKeySetTest, OperatorOr) {
|
||||
c10::DispatchKeySet a(c10::DispatchKey::Dense);
|
||||
c10::DispatchKeySet b(c10::DispatchKey::Sparse);
|
||||
auto combined = a | b;
|
||||
EXPECT_TRUE(combined.has(c10::DispatchKey::Dense));
|
||||
EXPECT_TRUE(combined.has(c10::DispatchKey::Sparse));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, OperatorAnd) {
|
||||
c10::DispatchKeySet a({c10::DispatchKey::Dense, c10::DispatchKey::Sparse});
|
||||
c10::DispatchKeySet b(c10::DispatchKey::Dense);
|
||||
auto result = a & b;
|
||||
EXPECT_TRUE(result.has(c10::DispatchKey::Dense));
|
||||
EXPECT_FALSE(result.has(c10::DispatchKey::Sparse));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, OperatorXor) {
|
||||
c10::DispatchKeySet a({c10::DispatchKey::Dense, c10::DispatchKey::Sparse});
|
||||
c10::DispatchKeySet b(c10::DispatchKey::Dense);
|
||||
auto result = a ^ b;
|
||||
EXPECT_FALSE(result.has(c10::DispatchKey::Dense));
|
||||
EXPECT_TRUE(result.has(c10::DispatchKey::Sparse));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, OperatorMinus) {
|
||||
c10::DispatchKeySet a({c10::DispatchKey::Dense, c10::DispatchKey::Sparse});
|
||||
c10::DispatchKeySet b(c10::DispatchKey::Dense);
|
||||
auto result = a - b;
|
||||
EXPECT_FALSE(result.has(c10::DispatchKey::Dense));
|
||||
// Sparse should remain.
|
||||
EXPECT_TRUE(result.has(c10::DispatchKey::Sparse));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, OperatorEqNeq) {
|
||||
c10::DispatchKeySet a(c10::DispatchKey::Dense);
|
||||
c10::DispatchKeySet b(c10::DispatchKey::Dense);
|
||||
c10::DispatchKeySet c(c10::DispatchKey::Sparse);
|
||||
EXPECT_TRUE(a == b);
|
||||
EXPECT_FALSE(a != b);
|
||||
EXPECT_TRUE(a != c);
|
||||
EXPECT_FALSE(a == c);
|
||||
}
|
||||
|
||||
// ---------- add / remove / remove_backend -----------------------------------
|
||||
|
||||
TEST(DispatchKeySetTest, Add) {
|
||||
c10::DispatchKeySet ks(c10::DispatchKey::Dense);
|
||||
auto result = ks.add(c10::DispatchKey::Sparse);
|
||||
EXPECT_TRUE(result.has(c10::DispatchKey::Dense));
|
||||
EXPECT_TRUE(result.has(c10::DispatchKey::Sparse));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AddDispatchKeySet) {
|
||||
c10::DispatchKeySet ks(c10::DispatchKey::Dense);
|
||||
auto result = ks.add(c10::DispatchKeySet(c10::DispatchKey::Sparse));
|
||||
EXPECT_TRUE(result.has(c10::DispatchKey::Sparse));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, Remove) {
|
||||
c10::DispatchKeySet ks({c10::DispatchKey::Dense, c10::DispatchKey::Sparse});
|
||||
auto result = ks.remove(c10::DispatchKey::Dense);
|
||||
EXPECT_FALSE(result.has(c10::DispatchKey::Dense));
|
||||
EXPECT_TRUE(result.has(c10::DispatchKey::Sparse));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, RemoveBackend) {
|
||||
c10::DispatchKeySet ks(c10::DispatchKey::CPU);
|
||||
auto result = ks.remove_backend(c10::BackendComponent::CPUBit);
|
||||
EXPECT_FALSE(result.has_backend(c10::BackendComponent::CPUBit));
|
||||
}
|
||||
|
||||
// ---------- empty / raw_repr / from_raw_repr --------------------------------
|
||||
|
||||
TEST(DispatchKeySetTest, FromRawRepr) {
|
||||
auto ks = c10::DispatchKeySet::from_raw_repr(0xFF);
|
||||
EXPECT_EQ(ks.raw_repr(), 0xFFu);
|
||||
}
|
||||
|
||||
// ---------- highestFunctionalityKey / highestBackendKey ----------------------
|
||||
|
||||
TEST(DispatchKeySetTest, HighestFunctionalityKey) {
|
||||
c10::DispatchKeySet ks(c10::DispatchKey::Dense);
|
||||
EXPECT_DK_EQ(ks.highestFunctionalityKey(), c10::DispatchKey::Dense);
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, HighestFunctionalityKeyEmpty) {
|
||||
c10::DispatchKeySet ks;
|
||||
EXPECT_DK_EQ(ks.highestFunctionalityKey(), c10::DispatchKey::Undefined);
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, HighestBackendKey) {
|
||||
c10::DispatchKeySet ks(c10::DispatchKey::CPU);
|
||||
EXPECT_BC_EQ(ks.highestBackendKey(), c10::BackendComponent::CPUBit);
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, HighestBackendKeyNoBackend) {
|
||||
c10::DispatchKeySet ks(c10::DispatchKey::Dense);
|
||||
EXPECT_BC_EQ(ks.highestBackendKey(), c10::BackendComponent::InvalidBit);
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, HighestPriorityTypeId) {
|
||||
c10::DispatchKeySet ks(c10::DispatchKey::CPU);
|
||||
EXPECT_DK_EQ(ks.highestPriorityTypeId(), c10::DispatchKey::CPU);
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, HighestPriorityTypeIdNonPerBackend) {
|
||||
c10::DispatchKeySet ks(c10::DispatchKey::BackendSelect);
|
||||
// BackendSelect is not per-backend, maps directly.
|
||||
EXPECT_DK_EQ(ks.highestPriorityTypeId(), c10::DispatchKey::BackendSelect);
|
||||
}
|
||||
|
||||
// ---------- indexOfHighestBit ------------------------------------------------
|
||||
|
||||
TEST(DispatchKeySetTest, IndexOfHighestBitEmpty) {
|
||||
c10::DispatchKeySet ks;
|
||||
EXPECT_EQ(ks.indexOfHighestBit(), 0u);
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, IndexOfHighestBitNonEmpty) {
|
||||
c10::DispatchKeySet ks(c10::DispatchKeySet::RAW, 0x8);
|
||||
EXPECT_EQ(ks.indexOfHighestBit(), 4u); // bit 3 is set (0-indexed)
|
||||
}
|
||||
|
||||
// ---------- getBackendIndex -------------------------------------------------
|
||||
|
||||
TEST(DispatchKeySetTest, GetBackendIndex) {
|
||||
c10::DispatchKeySet ks(c10::DispatchKey::CUDA);
|
||||
// CUDA should have a non-zero backend index (CPUBit=0, CUDABit=1).
|
||||
EXPECT_GT(ks.getBackendIndex(), 0u);
|
||||
}
|
||||
|
||||
// ---------- getAutogradRelatedKeySetFromBackend ------------------------------
|
||||
|
||||
TEST(DispatchKeySetTest, AutogradRelatedKeySetCPU) {
|
||||
auto ks =
|
||||
c10::getAutogradRelatedKeySetFromBackend(c10::BackendComponent::CPUBit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::ADInplaceOrView));
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::AutogradCPU));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutogradRelatedKeySetCUDA) {
|
||||
auto ks =
|
||||
c10::getAutogradRelatedKeySetFromBackend(c10::BackendComponent::CUDABit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::ADInplaceOrView));
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::AutogradCUDA));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutogradRelatedKeySetXPU) {
|
||||
auto ks =
|
||||
c10::getAutogradRelatedKeySetFromBackend(c10::BackendComponent::XPUBit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::ADInplaceOrView));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutogradRelatedKeySetXLA) {
|
||||
auto ks =
|
||||
c10::getAutogradRelatedKeySetFromBackend(c10::BackendComponent::XLABit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::ADInplaceOrView));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutogradRelatedKeySetLazy) {
|
||||
auto ks =
|
||||
c10::getAutogradRelatedKeySetFromBackend(c10::BackendComponent::LazyBit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::ADInplaceOrView));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutogradRelatedKeySetMeta) {
|
||||
auto ks =
|
||||
c10::getAutogradRelatedKeySetFromBackend(c10::BackendComponent::MetaBit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::ADInplaceOrView));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutogradRelatedKeySetMPS) {
|
||||
auto ks =
|
||||
c10::getAutogradRelatedKeySetFromBackend(c10::BackendComponent::MPSBit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::ADInplaceOrView));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutogradRelatedKeySetHPU) {
|
||||
auto ks =
|
||||
c10::getAutogradRelatedKeySetFromBackend(c10::BackendComponent::HPUBit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::ADInplaceOrView));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutogradRelatedKeySetIPU) {
|
||||
auto ks =
|
||||
c10::getAutogradRelatedKeySetFromBackend(c10::BackendComponent::IPUBit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::ADInplaceOrView));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutogradRelatedKeySetMTIA) {
|
||||
auto ks =
|
||||
c10::getAutogradRelatedKeySetFromBackend(c10::BackendComponent::MTIABit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::ADInplaceOrView));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutogradRelatedKeySetMAIA) {
|
||||
auto ks =
|
||||
c10::getAutogradRelatedKeySetFromBackend(c10::BackendComponent::MAIABit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::ADInplaceOrView));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutogradRelatedKeySetPrivateUse1) {
|
||||
auto ks = c10::getAutogradRelatedKeySetFromBackend(
|
||||
c10::BackendComponent::PrivateUse1Bit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::ADInplaceOrView));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutogradRelatedKeySetPrivateUse2) {
|
||||
auto ks = c10::getAutogradRelatedKeySetFromBackend(
|
||||
c10::BackendComponent::PrivateUse2Bit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::ADInplaceOrView));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutogradRelatedKeySetPrivateUse3) {
|
||||
auto ks = c10::getAutogradRelatedKeySetFromBackend(
|
||||
c10::BackendComponent::PrivateUse3Bit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::ADInplaceOrView));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutogradRelatedKeySetDefault) {
|
||||
// InvalidBit falls through to default: inplace_or_view_ks |
|
||||
// autograd_other_ks.
|
||||
auto ks = c10::getAutogradRelatedKeySetFromBackend(
|
||||
c10::BackendComponent::InvalidBit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::ADInplaceOrView));
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::AutogradOther));
|
||||
}
|
||||
|
||||
// ---------- getAutocastRelatedKeySetFromBackend ------------------------------
|
||||
|
||||
TEST(DispatchKeySetTest, AutocastRelatedKeySetCPU) {
|
||||
auto ks =
|
||||
c10::getAutocastRelatedKeySetFromBackend(c10::BackendComponent::CPUBit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::AutocastCPU));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutocastRelatedKeySetCUDA) {
|
||||
auto ks =
|
||||
c10::getAutocastRelatedKeySetFromBackend(c10::BackendComponent::CUDABit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::AutocastCUDA));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutocastRelatedKeySetXPU) {
|
||||
auto ks =
|
||||
c10::getAutocastRelatedKeySetFromBackend(c10::BackendComponent::XPUBit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::AutocastXPU));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutocastRelatedKeySetIPU) {
|
||||
auto ks =
|
||||
c10::getAutocastRelatedKeySetFromBackend(c10::BackendComponent::IPUBit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::AutocastIPU));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutocastRelatedKeySetHPU) {
|
||||
auto ks =
|
||||
c10::getAutocastRelatedKeySetFromBackend(c10::BackendComponent::HPUBit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::AutocastHPU));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutocastRelatedKeySetXLA) {
|
||||
auto ks =
|
||||
c10::getAutocastRelatedKeySetFromBackend(c10::BackendComponent::XLABit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::AutocastXLA));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutocastRelatedKeySetMPS) {
|
||||
auto ks =
|
||||
c10::getAutocastRelatedKeySetFromBackend(c10::BackendComponent::MPSBit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::AutocastMPS));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutocastRelatedKeySetMTIA) {
|
||||
auto ks =
|
||||
c10::getAutocastRelatedKeySetFromBackend(c10::BackendComponent::MTIABit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::AutocastMTIA));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutocastRelatedKeySetMAIA) {
|
||||
auto ks =
|
||||
c10::getAutocastRelatedKeySetFromBackend(c10::BackendComponent::MAIABit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::AutocastMAIA));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutocastRelatedKeySetPrivateUse1) {
|
||||
auto ks = c10::getAutocastRelatedKeySetFromBackend(
|
||||
c10::BackendComponent::PrivateUse1Bit);
|
||||
EXPECT_TRUE(ks.has(c10::DispatchKey::AutocastPrivateUse1));
|
||||
}
|
||||
|
||||
TEST(DispatchKeySetTest, AutocastRelatedKeySetDefault) {
|
||||
// Backends without a dedicated autocast key return empty.
|
||||
auto ks =
|
||||
c10::getAutocastRelatedKeySetFromBackend(c10::BackendComponent::LazyBit);
|
||||
EXPECT_TRUE(ks.empty());
|
||||
}
|
||||
|
||||
// ---------- highestPriorityBackendTypeId ------------------------------------
|
||||
|
||||
TEST(DispatchKeySetTest, HighestPriorityBackendTypeId) {
|
||||
c10::DispatchKeySet ks(c10::DispatchKey::CPU);
|
||||
auto key = c10::highestPriorityBackendTypeId(ks);
|
||||
EXPECT_DK_EQ(key, c10::DispatchKey::CPU);
|
||||
}
|
||||
|
||||
// ---------- legacyExtractDispatchKey ----------------------------------------
|
||||
|
||||
TEST(DispatchKeySetTest, LegacyExtractDispatchKey) {
|
||||
c10::DispatchKeySet ks(c10::DispatchKey::CPU);
|
||||
auto key = c10::legacyExtractDispatchKey(ks);
|
||||
EXPECT_DK_EQ(key, c10::DispatchKey::CPU);
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <c10/core/DispatchKey.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
// Use EXPECT_TRUE with == instead of EXPECT_EQ for enum types
|
||||
// because gtest's EXPECT_EQ needs operator<< which is declared
|
||||
// but not yet implemented for DispatchKey/BackendComponent.
|
||||
#define EXPECT_DK_EQ(a, b) EXPECT_TRUE((a) == (b))
|
||||
#define EXPECT_BC_EQ(a, b) EXPECT_TRUE((a) == (b))
|
||||
|
||||
// Helper: wrap in a non-constexpr call to force runtime evaluation.
|
||||
template <typename F, typename... Args>
|
||||
auto runtime_call(F f, Args... args) -> decltype(f(args...)) {
|
||||
return f(args...);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Tests for constexpr/inline functions in c10::DispatchKey.h
|
||||
// ==========================================================================
|
||||
|
||||
// ---------- isAliasDispatchKey
|
||||
// ------------------------------------------------
|
||||
|
||||
TEST(DispatchKeyTest, IsAliasDispatchKey) {
|
||||
// Alias keys: Autograd .. CompositeExplicitAutogradNonFunctional
|
||||
c10::DispatchKey alias = c10::DispatchKey::Autograd;
|
||||
EXPECT_TRUE(c10::isAliasDispatchKey(alias));
|
||||
|
||||
alias = c10::DispatchKey::CompositeImplicitAutograd;
|
||||
EXPECT_TRUE(c10::isAliasDispatchKey(alias));
|
||||
|
||||
alias = c10::DispatchKey::CompositeExplicitAutograd;
|
||||
EXPECT_TRUE(c10::isAliasDispatchKey(alias));
|
||||
|
||||
alias = c10::DispatchKey::CompositeExplicitAutogradNonFunctional;
|
||||
EXPECT_TRUE(c10::isAliasDispatchKey(alias));
|
||||
|
||||
// Non-alias keys
|
||||
c10::DispatchKey non_alias = c10::DispatchKey::CPU;
|
||||
EXPECT_FALSE(c10::isAliasDispatchKey(non_alias));
|
||||
|
||||
non_alias = c10::DispatchKey::Dense;
|
||||
EXPECT_FALSE(c10::isAliasDispatchKey(non_alias));
|
||||
|
||||
non_alias = c10::DispatchKey::Undefined;
|
||||
EXPECT_FALSE(c10::isAliasDispatchKey(non_alias));
|
||||
}
|
||||
|
||||
// ---------- isPerBackendFunctionalityKey -------------------------------------
|
||||
|
||||
TEST(DispatchKeyTest, IsPerBackendFunctionalityKey) {
|
||||
// Per-backend keys: Dense, Quantized, Sparse, SparseCsr,
|
||||
// AutogradFunctionality, NestedTensor
|
||||
c10::DispatchKey k = c10::DispatchKey::Dense;
|
||||
EXPECT_TRUE(c10::isPerBackendFunctionalityKey(k));
|
||||
|
||||
k = c10::DispatchKey::Quantized;
|
||||
EXPECT_TRUE(c10::isPerBackendFunctionalityKey(k));
|
||||
|
||||
k = c10::DispatchKey::Sparse;
|
||||
EXPECT_TRUE(c10::isPerBackendFunctionalityKey(k));
|
||||
|
||||
k = c10::DispatchKey::SparseCsr;
|
||||
EXPECT_TRUE(c10::isPerBackendFunctionalityKey(k));
|
||||
|
||||
k = c10::DispatchKey::AutogradFunctionality;
|
||||
EXPECT_TRUE(c10::isPerBackendFunctionalityKey(k));
|
||||
|
||||
k = c10::DispatchKey::NestedTensor;
|
||||
EXPECT_TRUE(c10::isPerBackendFunctionalityKey(k));
|
||||
|
||||
// Non per-backend keys
|
||||
k = c10::DispatchKey::BackendSelect;
|
||||
EXPECT_FALSE(c10::isPerBackendFunctionalityKey(k));
|
||||
|
||||
k = c10::DispatchKey::Python;
|
||||
EXPECT_FALSE(c10::isPerBackendFunctionalityKey(k));
|
||||
|
||||
k = c10::DispatchKey::Undefined;
|
||||
EXPECT_FALSE(c10::isPerBackendFunctionalityKey(k));
|
||||
}
|
||||
|
||||
// ---------- toBackendComponent(DispatchKey)
|
||||
// -----------------------------------
|
||||
|
||||
TEST(DispatchKeyTest, ToBackendComponentDense) {
|
||||
c10::DispatchKey k = c10::DispatchKey::CPU;
|
||||
c10::BackendComponent bc = c10::toBackendComponent(k);
|
||||
EXPECT_BC_EQ(bc, c10::BackendComponent::CPUBit);
|
||||
}
|
||||
|
||||
TEST(DispatchKeyTest, ToBackendComponentQuantized) {
|
||||
c10::DispatchKey k = c10::DispatchKey::QuantizedCPU;
|
||||
c10::BackendComponent bc = c10::toBackendComponent(k);
|
||||
EXPECT_BC_EQ(bc, c10::BackendComponent::CPUBit);
|
||||
}
|
||||
|
||||
TEST(DispatchKeyTest, ToBackendComponentSparse) {
|
||||
c10::DispatchKey k = c10::DispatchKey::SparseCPU;
|
||||
c10::BackendComponent bc = c10::toBackendComponent(k);
|
||||
EXPECT_BC_EQ(bc, c10::BackendComponent::CPUBit);
|
||||
}
|
||||
|
||||
TEST(DispatchKeyTest, ToBackendComponentSparseCsr) {
|
||||
c10::DispatchKey k = c10::DispatchKey::SparseCsrCPU;
|
||||
c10::BackendComponent bc = c10::toBackendComponent(k);
|
||||
EXPECT_BC_EQ(bc, c10::BackendComponent::CPUBit);
|
||||
}
|
||||
|
||||
TEST(DispatchKeyTest, ToBackendComponentNestedTensor) {
|
||||
c10::DispatchKey k = c10::DispatchKey::NestedTensorCPU;
|
||||
c10::BackendComponent bc = c10::toBackendComponent(k);
|
||||
EXPECT_BC_EQ(bc, c10::BackendComponent::CPUBit);
|
||||
}
|
||||
|
||||
TEST(DispatchKeyTest, ToBackendComponentAutograd) {
|
||||
c10::DispatchKey k = c10::DispatchKey::AutogradCPU;
|
||||
c10::BackendComponent bc = c10::toBackendComponent(k);
|
||||
EXPECT_BC_EQ(bc, c10::BackendComponent::CPUBit);
|
||||
}
|
||||
|
||||
TEST(DispatchKeyTest, ToBackendComponentInvalid) {
|
||||
// A functionality-only key (no backend range) → InvalidBit
|
||||
c10::DispatchKey k = c10::DispatchKey::BackendSelect;
|
||||
c10::BackendComponent bc = c10::toBackendComponent(k);
|
||||
EXPECT_BC_EQ(bc, c10::BackendComponent::InvalidBit);
|
||||
}
|
||||
|
||||
// ---------- toFunctionalityKey -----------------------------------------------
|
||||
|
||||
TEST(DispatchKeyTest, ToFunctionalityKeyPure) {
|
||||
// A pure functionality key maps to itself.
|
||||
c10::DispatchKey k = c10::DispatchKey::Dense;
|
||||
EXPECT_DK_EQ(c10::toFunctionalityKey(k), c10::DispatchKey::Dense);
|
||||
|
||||
k = c10::DispatchKey::BackendSelect;
|
||||
EXPECT_DK_EQ(c10::toFunctionalityKey(k), c10::DispatchKey::BackendSelect);
|
||||
}
|
||||
|
||||
TEST(DispatchKeyTest, ToFunctionalityKeyDenseBackend) {
|
||||
c10::DispatchKey k = c10::DispatchKey::CPU;
|
||||
EXPECT_DK_EQ(c10::toFunctionalityKey(k), c10::DispatchKey::Dense);
|
||||
|
||||
k = c10::DispatchKey::CUDA;
|
||||
EXPECT_DK_EQ(c10::toFunctionalityKey(k), c10::DispatchKey::Dense);
|
||||
}
|
||||
|
||||
TEST(DispatchKeyTest, ToFunctionalityKeyQuantizedBackend) {
|
||||
c10::DispatchKey k = c10::DispatchKey::QuantizedCPU;
|
||||
EXPECT_DK_EQ(c10::toFunctionalityKey(k), c10::DispatchKey::Quantized);
|
||||
}
|
||||
|
||||
TEST(DispatchKeyTest, ToFunctionalityKeySparseBackend) {
|
||||
c10::DispatchKey k = c10::DispatchKey::SparseCPU;
|
||||
EXPECT_DK_EQ(c10::toFunctionalityKey(k), c10::DispatchKey::Sparse);
|
||||
}
|
||||
|
||||
TEST(DispatchKeyTest, ToFunctionalityKeySparseCsrBackend) {
|
||||
c10::DispatchKey k = c10::DispatchKey::SparseCsrCPU;
|
||||
EXPECT_DK_EQ(c10::toFunctionalityKey(k), c10::DispatchKey::SparseCsr);
|
||||
}
|
||||
|
||||
TEST(DispatchKeyTest, ToFunctionalityKeyNestedTensorBackend) {
|
||||
c10::DispatchKey k = c10::DispatchKey::NestedTensorCPU;
|
||||
EXPECT_DK_EQ(c10::toFunctionalityKey(k), c10::DispatchKey::NestedTensor);
|
||||
}
|
||||
|
||||
TEST(DispatchKeyTest, ToFunctionalityKeyAutogradBackend) {
|
||||
c10::DispatchKey k = c10::DispatchKey::AutogradCPU;
|
||||
EXPECT_DK_EQ(c10::toFunctionalityKey(k),
|
||||
c10::DispatchKey::AutogradFunctionality);
|
||||
}
|
||||
|
||||
TEST(DispatchKeyTest, ToFunctionalityKeyBeyondRuntime) {
|
||||
// Keys beyond EndOfRuntimeBackendKeys map to Undefined.
|
||||
c10::DispatchKey k = c10::DispatchKey::Autograd;
|
||||
EXPECT_DK_EQ(c10::toFunctionalityKey(k), c10::DispatchKey::Undefined);
|
||||
}
|
||||
|
||||
// ---------- toRuntimePerBackendFunctionalityKey ------------------------------
|
||||
|
||||
TEST(DispatchKeyTest, ToRuntimeKeyDense) {
|
||||
c10::DispatchKey k = c10::toRuntimePerBackendFunctionalityKey(
|
||||
c10::DispatchKey::Dense, c10::BackendComponent::CPUBit);
|
||||
EXPECT_DK_EQ(k, c10::DispatchKey::CPU);
|
||||
}
|
||||
|
||||
TEST(DispatchKeyTest, ToRuntimeKeySparse) {
|
||||
c10::DispatchKey k = c10::toRuntimePerBackendFunctionalityKey(
|
||||
c10::DispatchKey::Sparse, c10::BackendComponent::CPUBit);
|
||||
EXPECT_DK_EQ(k, c10::DispatchKey::SparseCPU);
|
||||
}
|
||||
|
||||
TEST(DispatchKeyTest, ToRuntimeKeySparseCsr) {
|
||||
c10::DispatchKey k = c10::toRuntimePerBackendFunctionalityKey(
|
||||
c10::DispatchKey::SparseCsr, c10::BackendComponent::CPUBit);
|
||||
EXPECT_DK_EQ(k, c10::DispatchKey::SparseCsrCPU);
|
||||
}
|
||||
|
||||
TEST(DispatchKeyTest, ToRuntimeKeyQuantized) {
|
||||
c10::DispatchKey k = c10::toRuntimePerBackendFunctionalityKey(
|
||||
c10::DispatchKey::Quantized, c10::BackendComponent::CPUBit);
|
||||
EXPECT_DK_EQ(k, c10::DispatchKey::QuantizedCPU);
|
||||
}
|
||||
|
||||
TEST(DispatchKeyTest, ToRuntimeKeyNestedTensor) {
|
||||
c10::DispatchKey k = c10::toRuntimePerBackendFunctionalityKey(
|
||||
c10::DispatchKey::NestedTensor, c10::BackendComponent::CPUBit);
|
||||
EXPECT_DK_EQ(k, c10::DispatchKey::NestedTensorCPU);
|
||||
}
|
||||
|
||||
TEST(DispatchKeyTest, ToRuntimeKeyAutograd) {
|
||||
c10::DispatchKey k = c10::toRuntimePerBackendFunctionalityKey(
|
||||
c10::DispatchKey::AutogradFunctionality, c10::BackendComponent::CPUBit);
|
||||
EXPECT_DK_EQ(k, c10::DispatchKey::AutogradCPU);
|
||||
}
|
||||
|
||||
TEST(DispatchKeyTest, ToRuntimeKeyNonPerBackend) {
|
||||
// A key that is not per-backend should map to Undefined.
|
||||
c10::DispatchKey k = c10::toRuntimePerBackendFunctionalityKey(
|
||||
c10::DispatchKey::BackendSelect, c10::BackendComponent::CPUBit);
|
||||
EXPECT_DK_EQ(k, c10::DispatchKey::Undefined);
|
||||
}
|
||||
|
||||
// ---------- std::hash<DispatchKey> -------------------------------------------
|
||||
|
||||
TEST(DispatchKeyTest, Hash) {
|
||||
std::hash<c10::DispatchKey> hasher;
|
||||
c10::DispatchKey k = c10::DispatchKey::CPU;
|
||||
EXPECT_EQ(hasher(k), static_cast<size_t>(k));
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <c10/core/Event.h>
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
#endif
|
||||
|
||||
TEST(EventTest, CpuEventDefaultProperties) {
|
||||
c10::Event event(c10::DeviceType::CPU);
|
||||
EXPECT_EQ(event.device_type(), c10::DeviceType::CPU);
|
||||
EXPECT_EQ(event.device_index(), -1);
|
||||
EXPECT_EQ(event.flag(), c10::EventFlag::PYTORCH_DEFAULT);
|
||||
EXPECT_FALSE(event.was_marked_for_recording());
|
||||
EXPECT_TRUE(event.query());
|
||||
EXPECT_EQ(event.eventId(), nullptr);
|
||||
}
|
||||
|
||||
TEST(EventTest, CpuEventRecordThrows) {
|
||||
c10::Event event(c10::DeviceType::CPU);
|
||||
c10::Stream stream(c10::Stream::DEFAULT,
|
||||
c10::Device(c10::DeviceType::CPU, 0));
|
||||
EXPECT_THROW(event.record(stream), std::exception);
|
||||
EXPECT_THROW(event.recordOnce(stream), std::exception);
|
||||
}
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
TEST(EventTest, CudaEventLazyCreateAndRecord) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
c10::Event event(c10::DeviceType::CUDA);
|
||||
auto stream = c10::cuda::getCurrentCUDAStream();
|
||||
|
||||
EXPECT_EQ(event.device_index(), -1);
|
||||
EXPECT_EQ(event.eventId(), nullptr);
|
||||
EXPECT_FALSE(event.was_marked_for_recording());
|
||||
|
||||
EXPECT_NO_THROW(event.record(stream));
|
||||
EXPECT_EQ(event.device_index(), stream.device_index());
|
||||
EXPECT_NE(event.eventId(), nullptr);
|
||||
EXPECT_TRUE(event.was_marked_for_recording());
|
||||
|
||||
EXPECT_NO_THROW(event.synchronize());
|
||||
EXPECT_TRUE(event.query());
|
||||
}
|
||||
|
||||
TEST(EventTest, CudaEventElapsedTimeRequiresTimingFlag) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
auto stream = c10::cuda::getCurrentCUDAStream();
|
||||
c10::Event start(c10::DeviceType::CUDA);
|
||||
c10::Event end(c10::DeviceType::CUDA);
|
||||
|
||||
start.record(stream);
|
||||
end.record(stream);
|
||||
end.synchronize();
|
||||
|
||||
EXPECT_THROW(start.elapsedTime(end), std::exception);
|
||||
}
|
||||
|
||||
TEST(EventTest, CudaEventElapsedTimeWithTimingEnabled) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
auto stream = c10::cuda::getCurrentCUDAStream();
|
||||
c10::Event start(c10::DeviceType::CUDA, c10::EventFlag::BACKEND_DEFAULT);
|
||||
c10::Event end(c10::DeviceType::CUDA, c10::EventFlag::BACKEND_DEFAULT);
|
||||
|
||||
start.record(stream);
|
||||
end.record(stream);
|
||||
end.synchronize();
|
||||
|
||||
double elapsed_ms = -1.0;
|
||||
EXPECT_NO_THROW(elapsed_ms = start.elapsedTime(end));
|
||||
EXPECT_GE(elapsed_ms, 0.0);
|
||||
}
|
||||
|
||||
TEST(EventTest, CudaEventRejectsDifferentDeviceRecord) {
|
||||
if (c10::cuda::device_count() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
c10::Event event(c10::DeviceType::CUDA, c10::EventFlag::BACKEND_DEFAULT);
|
||||
auto stream0 = c10::cuda::getDefaultCUDAStream(0);
|
||||
auto stream1 = c10::cuda::getDefaultCUDAStream(1);
|
||||
|
||||
EXPECT_NO_THROW(event.record(stream0));
|
||||
EXPECT_THROW(event.record(stream1), std::exception);
|
||||
}
|
||||
#endif // PADDLE_WITH_CUDA || PADDLE_WITH_HIP
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/Layout.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
TEST(TensorBaseTest, IsContiguousOrFalseAPI) {
|
||||
// Test with regular contiguous tensor
|
||||
at::Tensor contiguous_tensor = at::ones({2, 3, 4}, at::kFloat);
|
||||
ASSERT_TRUE(contiguous_tensor.is_contiguous_or_false());
|
||||
ASSERT_EQ(contiguous_tensor.is_contiguous_or_false(),
|
||||
contiguous_tensor.is_contiguous());
|
||||
|
||||
// Test with view tensor (should still be contiguous if strides allow)
|
||||
at::TensorBase view_tensor = contiguous_tensor.view({6, 4});
|
||||
ASSERT_TRUE(view_tensor.is_contiguous_or_false());
|
||||
ASSERT_EQ(view_tensor.is_contiguous_or_false(), view_tensor.is_contiguous());
|
||||
|
||||
// Test with different shapes
|
||||
at::TensorBase flat_tensor = at::ones({24}, at::kFloat);
|
||||
ASSERT_TRUE(flat_tensor.is_contiguous_or_false());
|
||||
|
||||
at::TensorBase multi_dim_tensor = at::ones({2, 3, 4, 5}, at::kFloat);
|
||||
ASSERT_TRUE(multi_dim_tensor.is_contiguous_or_false());
|
||||
|
||||
// Test with contiguous() method
|
||||
at::TensorBase made_contiguous = contiguous_tensor.contiguous();
|
||||
ASSERT_TRUE(made_contiguous.is_contiguous_or_false());
|
||||
|
||||
// Test consistency between is_contiguous() and is_contiguous_or_false()
|
||||
// They should return the same value for all cases
|
||||
at::TensorBase tensor1 = at::ones({5, 6}, at::kDouble);
|
||||
ASSERT_EQ(tensor1.is_contiguous(), tensor1.is_contiguous_or_false());
|
||||
|
||||
at::TensorBase tensor2 = at::ones({3, 4, 5}, at::kInt);
|
||||
ASSERT_EQ(tensor2.is_contiguous(), tensor2.is_contiguous_or_false());
|
||||
|
||||
// Test with different dtypes
|
||||
at::TensorBase bool_tensor = at::ones({2, 3}, at::kBool);
|
||||
ASSERT_TRUE(bool_tensor.is_contiguous_or_false());
|
||||
|
||||
at::TensorBase long_tensor = at::ones({2, 3}, at::kLong);
|
||||
ASSERT_TRUE(long_tensor.is_contiguous_or_false());
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/Layout.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
TEST(TensorBaseTest, TypeCheckingAPIs) {
|
||||
// Test is_complex()
|
||||
at::TensorBase complex64_tensor = at::ones({2, 3}, at::kComplexFloat);
|
||||
at::TensorBase complex128_tensor = at::ones({2, 3}, at::kComplexDouble);
|
||||
at::TensorBase float_tensor = at::ones({2, 3}, at::kFloat);
|
||||
at::TensorBase int_tensor = at::ones({2, 3}, at::kInt);
|
||||
|
||||
ASSERT_TRUE(complex64_tensor.is_complex());
|
||||
ASSERT_TRUE(complex128_tensor.is_complex());
|
||||
ASSERT_FALSE(float_tensor.is_complex());
|
||||
ASSERT_FALSE(int_tensor.is_complex());
|
||||
|
||||
// Test is_floating_point()
|
||||
at::TensorBase float16_tensor = at::ones({2, 3}, at::kHalf);
|
||||
at::TensorBase bfloat16_tensor = at::ones({2, 3}, at::kBFloat16);
|
||||
at::TensorBase float32_tensor = at::ones({2, 3}, at::kFloat);
|
||||
at::TensorBase float64_tensor = at::ones({2, 3}, at::kDouble);
|
||||
at::TensorBase float8_e5m2_tensor = at::ones({2, 3}, at::kFloat8_e5m2);
|
||||
at::TensorBase float8_e4m3fn_tensor = at::ones({2, 3}, at::kFloat8_e4m3fn);
|
||||
at::TensorBase bool_tensor = at::ones({2, 3}, at::kBool);
|
||||
|
||||
ASSERT_TRUE(float16_tensor.is_floating_point());
|
||||
ASSERT_TRUE(bfloat16_tensor.is_floating_point());
|
||||
ASSERT_TRUE(float32_tensor.is_floating_point());
|
||||
ASSERT_TRUE(float64_tensor.is_floating_point());
|
||||
ASSERT_TRUE(float8_e5m2_tensor.is_floating_point());
|
||||
ASSERT_TRUE(float8_e4m3fn_tensor.is_floating_point());
|
||||
ASSERT_FALSE(int_tensor.is_floating_point());
|
||||
ASSERT_FALSE(bool_tensor.is_floating_point());
|
||||
ASSERT_FALSE(complex64_tensor.is_floating_point());
|
||||
|
||||
// Test is_signed()
|
||||
at::TensorBase int8_tensor = at::ones({2, 3}, at::kChar);
|
||||
at::TensorBase int16_tensor = at::ones({2, 3}, at::kShort);
|
||||
at::TensorBase int32_tensor = at::ones({2, 3}, at::kInt);
|
||||
at::TensorBase int64_tensor = at::ones({2, 3}, at::kLong);
|
||||
at::TensorBase uint8_tensor = at::ones({2, 3}, at::kByte);
|
||||
|
||||
// Signed integer types
|
||||
ASSERT_TRUE(int8_tensor.is_signed());
|
||||
ASSERT_TRUE(int16_tensor.is_signed());
|
||||
ASSERT_TRUE(int32_tensor.is_signed());
|
||||
ASSERT_TRUE(int64_tensor.is_signed());
|
||||
|
||||
// Floating point types are signed
|
||||
ASSERT_TRUE(float16_tensor.is_signed());
|
||||
ASSERT_TRUE(bfloat16_tensor.is_signed());
|
||||
ASSERT_TRUE(float32_tensor.is_signed());
|
||||
ASSERT_TRUE(float64_tensor.is_signed());
|
||||
ASSERT_TRUE(float8_e5m2_tensor.is_signed());
|
||||
ASSERT_TRUE(float8_e4m3fn_tensor.is_signed());
|
||||
|
||||
// Complex types are signed
|
||||
ASSERT_TRUE(complex64_tensor.is_signed());
|
||||
ASSERT_TRUE(complex128_tensor.is_signed());
|
||||
|
||||
// Unsigned types
|
||||
ASSERT_FALSE(uint8_tensor.is_signed());
|
||||
ASSERT_FALSE(bool_tensor.is_signed());
|
||||
}
|
||||
|
||||
TEST(ScalarTypeTest, RestoredCompatScalarTypesKeepSourceLevelSemantics) {
|
||||
EXPECT_EQ(static_cast<int>(c10::ScalarType::ComplexHalf), 8);
|
||||
EXPECT_EQ(static_cast<int>(c10::ScalarType::QInt8), 12);
|
||||
EXPECT_EQ(static_cast<int>(c10::ScalarType::Bits16), 22);
|
||||
EXPECT_EQ(static_cast<int>(c10::ScalarType::Float8_e5m2fnuz), 25);
|
||||
EXPECT_EQ(static_cast<int>(c10::ScalarType::Float4_e2m1fn_x2), 45);
|
||||
EXPECT_EQ(c10::NumScalarTypes, 47);
|
||||
|
||||
EXPECT_EQ(c10::kComplexHalf, c10::ScalarType::ComplexHalf);
|
||||
EXPECT_EQ(c10::kQInt8, c10::ScalarType::QInt8);
|
||||
EXPECT_EQ(c10::kBits16, c10::ScalarType::Bits16);
|
||||
EXPECT_EQ(c10::kFloat8_e4m3fnuz, c10::ScalarType::Float8_e4m3fnuz);
|
||||
EXPECT_EQ(c10::kFloat8_e8m0fnu, c10::ScalarType::Float8_e8m0fnu);
|
||||
EXPECT_EQ(c10::kFloat4_e2m1fn_x2, c10::ScalarType::Float4_e2m1fn_x2);
|
||||
EXPECT_EQ(c10::kUndefined, c10::ScalarType::Undefined);
|
||||
|
||||
EXPECT_STREQ(c10::toString(c10::ScalarType::ComplexHalf), "ComplexHalf");
|
||||
EXPECT_STREQ(c10::toString(c10::ScalarType::QInt8), "QInt8");
|
||||
EXPECT_STREQ(c10::toString(c10::ScalarType::QUInt8), "QUInt8");
|
||||
EXPECT_STREQ(c10::toString(c10::ScalarType::QInt32), "QInt32");
|
||||
EXPECT_STREQ(c10::toString(c10::ScalarType::QUInt4x2), "QUInt4x2");
|
||||
EXPECT_STREQ(c10::toString(c10::ScalarType::QUInt2x4), "QUInt2x4");
|
||||
EXPECT_STREQ(c10::toString(c10::ScalarType::Bits1x8), "Bits1x8");
|
||||
EXPECT_STREQ(c10::toString(c10::ScalarType::Bits2x4), "Bits2x4");
|
||||
EXPECT_STREQ(c10::toString(c10::ScalarType::Bits4x2), "Bits4x2");
|
||||
EXPECT_STREQ(c10::toString(c10::ScalarType::Bits8), "Bits8");
|
||||
EXPECT_STREQ(c10::toString(c10::ScalarType::Bits16), "Bits16");
|
||||
EXPECT_STREQ(c10::toString(c10::ScalarType::Float8_e5m2fnuz),
|
||||
"Float8_e5m2fnuz");
|
||||
EXPECT_STREQ(c10::toString(c10::ScalarType::Float8_e4m3fnuz),
|
||||
"Float8_e4m3fnuz");
|
||||
EXPECT_STREQ(c10::toString(c10::ScalarType::Float8_e8m0fnu),
|
||||
"Float8_e8m0fnu");
|
||||
EXPECT_STREQ(c10::toString(c10::ScalarType::Float4_e2m1fn_x2),
|
||||
"Float4_e2m1fn_x2");
|
||||
EXPECT_STREQ(c10::toString(c10::ScalarType::Undefined), "Undefined");
|
||||
|
||||
EXPECT_EQ(c10::elementSize(c10::ScalarType::ComplexHalf),
|
||||
sizeof(at::Half) * 2);
|
||||
EXPECT_EQ(c10::elementSize(c10::ScalarType::QInt8), 1U);
|
||||
EXPECT_EQ(c10::elementSize(c10::ScalarType::QUInt8), 1U);
|
||||
EXPECT_EQ(c10::elementSize(c10::ScalarType::QInt32), 4U);
|
||||
EXPECT_EQ(c10::elementSize(c10::ScalarType::QUInt4x2), 1U);
|
||||
EXPECT_EQ(c10::elementSize(c10::ScalarType::QUInt2x4), 1U);
|
||||
EXPECT_EQ(c10::elementSize(c10::ScalarType::Bits1x8), 1U);
|
||||
EXPECT_EQ(c10::elementSize(c10::ScalarType::Bits2x4), 1U);
|
||||
EXPECT_EQ(c10::elementSize(c10::ScalarType::Bits4x2), 1U);
|
||||
EXPECT_EQ(c10::elementSize(c10::ScalarType::Bits8), 1U);
|
||||
EXPECT_EQ(c10::elementSize(c10::ScalarType::Bits16), 2U);
|
||||
EXPECT_EQ(c10::elementSize(c10::ScalarType::Float8_e5m2fnuz), 1U);
|
||||
EXPECT_EQ(c10::elementSize(c10::ScalarType::Float8_e4m3fnuz), 1U);
|
||||
EXPECT_EQ(c10::elementSize(c10::ScalarType::Float8_e8m0fnu), 1U);
|
||||
EXPECT_EQ(c10::elementSize(c10::ScalarType::Float4_e2m1fn_x2), 1U);
|
||||
|
||||
EXPECT_TRUE(c10::isComplexType(c10::ScalarType::ComplexHalf));
|
||||
EXPECT_TRUE(c10::isFloat8Type(c10::ScalarType::Float8_e5m2fnuz));
|
||||
EXPECT_TRUE(c10::isFloat8Type(c10::ScalarType::Float8_e4m3fnuz));
|
||||
EXPECT_TRUE(c10::isFloat8Type(c10::ScalarType::Float8_e8m0fnu));
|
||||
EXPECT_TRUE(c10::isReducedFloatingType(c10::ScalarType::Float4_e2m1fn_x2));
|
||||
}
|
||||
|
||||
TEST(ScalarTypeTest, HelperPredicatesAndConversionsMatchPyTorchBehavior) {
|
||||
EXPECT_TRUE(c10::isQIntType(c10::ScalarType::QInt8));
|
||||
EXPECT_TRUE(c10::isQIntType(c10::ScalarType::QUInt8));
|
||||
EXPECT_TRUE(c10::isQIntType(c10::ScalarType::QInt32));
|
||||
EXPECT_TRUE(c10::isQIntType(c10::ScalarType::QUInt4x2));
|
||||
EXPECT_TRUE(c10::isQIntType(c10::ScalarType::QUInt2x4));
|
||||
EXPECT_FALSE(c10::isQIntType(c10::ScalarType::Float));
|
||||
|
||||
EXPECT_TRUE(c10::isBitsType(c10::ScalarType::Bits1x8));
|
||||
EXPECT_TRUE(c10::isBitsType(c10::ScalarType::Bits2x4));
|
||||
EXPECT_TRUE(c10::isBitsType(c10::ScalarType::Bits4x2));
|
||||
EXPECT_TRUE(c10::isBitsType(c10::ScalarType::Bits8));
|
||||
EXPECT_TRUE(c10::isBitsType(c10::ScalarType::Bits16));
|
||||
EXPECT_FALSE(c10::isBitsType(c10::ScalarType::Int));
|
||||
|
||||
EXPECT_TRUE(c10::isBarebonesUnsignedType(c10::ScalarType::UInt1));
|
||||
EXPECT_TRUE(c10::isBarebonesUnsignedType(c10::ScalarType::UInt7));
|
||||
EXPECT_TRUE(c10::isBarebonesUnsignedType(c10::ScalarType::UInt16));
|
||||
EXPECT_TRUE(c10::isBarebonesUnsignedType(c10::ScalarType::UInt64));
|
||||
EXPECT_FALSE(c10::isBarebonesUnsignedType(c10::ScalarType::Byte));
|
||||
EXPECT_FALSE(c10::isBarebonesUnsignedType(c10::ScalarType::Int));
|
||||
|
||||
EXPECT_EQ(c10::toQIntType(c10::ScalarType::Byte), c10::ScalarType::QUInt8);
|
||||
EXPECT_EQ(c10::toQIntType(c10::ScalarType::Char), c10::ScalarType::QInt8);
|
||||
EXPECT_EQ(c10::toQIntType(c10::ScalarType::Int), c10::ScalarType::QInt32);
|
||||
EXPECT_EQ(c10::toQIntType(c10::ScalarType::Float), c10::ScalarType::Float);
|
||||
|
||||
EXPECT_EQ(c10::toUnderlying(c10::ScalarType::QUInt8), c10::ScalarType::Byte);
|
||||
EXPECT_EQ(c10::toUnderlying(c10::ScalarType::QUInt4x2),
|
||||
c10::ScalarType::Byte);
|
||||
EXPECT_EQ(c10::toUnderlying(c10::ScalarType::QUInt2x4),
|
||||
c10::ScalarType::Byte);
|
||||
EXPECT_EQ(c10::toUnderlying(c10::ScalarType::QInt8), c10::ScalarType::Char);
|
||||
EXPECT_EQ(c10::toUnderlying(c10::ScalarType::QInt32), c10::ScalarType::Int);
|
||||
EXPECT_EQ(c10::toUnderlying(c10::ScalarType::Float), c10::ScalarType::Float);
|
||||
|
||||
EXPECT_TRUE(
|
||||
c10::isUnderlying(c10::ScalarType::Byte, c10::ScalarType::QUInt8));
|
||||
EXPECT_TRUE(c10::isUnderlying(c10::ScalarType::Char, c10::ScalarType::QInt8));
|
||||
EXPECT_TRUE(c10::isUnderlying(c10::ScalarType::Int, c10::ScalarType::QInt32));
|
||||
EXPECT_FALSE(
|
||||
c10::isUnderlying(c10::ScalarType::Byte, c10::ScalarType::QInt8));
|
||||
|
||||
EXPECT_EQ(c10::toRealValueType(c10::ScalarType::ComplexHalf),
|
||||
c10::ScalarType::Half);
|
||||
EXPECT_EQ(c10::toRealValueType(c10::ScalarType::ComplexFloat),
|
||||
c10::ScalarType::Float);
|
||||
EXPECT_EQ(c10::toRealValueType(c10::ScalarType::ComplexDouble),
|
||||
c10::ScalarType::Double);
|
||||
EXPECT_EQ(c10::toRealValueType(c10::ScalarType::Int), c10::ScalarType::Int);
|
||||
|
||||
EXPECT_EQ(c10::toComplexType(c10::ScalarType::Half),
|
||||
c10::ScalarType::ComplexHalf);
|
||||
EXPECT_EQ(c10::toComplexType(c10::ScalarType::Float),
|
||||
c10::ScalarType::ComplexFloat);
|
||||
EXPECT_EQ(c10::toComplexType(c10::ScalarType::Double),
|
||||
c10::ScalarType::ComplexDouble);
|
||||
EXPECT_EQ(c10::toComplexType(c10::ScalarType::BFloat16),
|
||||
c10::ScalarType::ComplexFloat);
|
||||
EXPECT_EQ(c10::toComplexType(c10::ScalarType::ComplexFloat),
|
||||
c10::ScalarType::ComplexFloat);
|
||||
|
||||
EXPECT_TRUE(c10::canCast(c10::ScalarType::Int, c10::ScalarType::Long));
|
||||
EXPECT_TRUE(c10::canCast(c10::ScalarType::Float, c10::ScalarType::Double));
|
||||
EXPECT_TRUE(c10::canCast(c10::ScalarType::ComplexFloat,
|
||||
c10::ScalarType::ComplexDouble));
|
||||
EXPECT_TRUE(c10::canCast(c10::ScalarType::Bool, c10::ScalarType::Int));
|
||||
|
||||
EXPECT_FALSE(
|
||||
c10::canCast(c10::ScalarType::ComplexFloat, c10::ScalarType::Float));
|
||||
EXPECT_FALSE(c10::canCast(c10::ScalarType::Float, c10::ScalarType::Int));
|
||||
EXPECT_FALSE(c10::canCast(c10::ScalarType::Double, c10::ScalarType::Long));
|
||||
EXPECT_FALSE(c10::canCast(c10::ScalarType::Int, c10::ScalarType::Bool));
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
// Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
TEST(TensorBaseTest, DimensionAPIs) {
|
||||
// Test dimension related APIs
|
||||
at::TensorBase tensor = at::ones({2, 3, 4}, at::kFloat);
|
||||
|
||||
// Test sizes()
|
||||
auto sizes = tensor.sizes();
|
||||
ASSERT_EQ(sizes.size(), 3);
|
||||
ASSERT_EQ(sizes[0], 2);
|
||||
ASSERT_EQ(sizes[1], 3);
|
||||
ASSERT_EQ(sizes[2], 4);
|
||||
|
||||
// Test size(dim)
|
||||
ASSERT_EQ(tensor.size(0), 2);
|
||||
ASSERT_EQ(tensor.size(1), 3);
|
||||
ASSERT_EQ(tensor.size(2), 4);
|
||||
|
||||
// Test strides()
|
||||
auto strides = tensor.strides();
|
||||
ASSERT_EQ(strides.size(), 3);
|
||||
ASSERT_EQ(strides[0], 12); // 3*4
|
||||
ASSERT_EQ(strides[1], 4); // 4
|
||||
ASSERT_EQ(strides[2], 1); // contiguous
|
||||
|
||||
// Test stride(dim)
|
||||
ASSERT_EQ(tensor.stride(0), 12);
|
||||
ASSERT_EQ(tensor.stride(1), 4);
|
||||
ASSERT_EQ(tensor.stride(2), 1);
|
||||
|
||||
// Test numel()
|
||||
ASSERT_EQ(tensor.numel(), 24); // 2*3*4
|
||||
|
||||
// Test dim()/ndimension()
|
||||
ASSERT_EQ(tensor.dim(), 3);
|
||||
ASSERT_EQ(tensor.ndimension(), 3);
|
||||
}
|
||||
|
||||
TEST(TestSymbolicInt, SymSizeAPI) {
|
||||
// Test sym_size() API
|
||||
at::TensorBase tensor = at::ones({2, 3, 4}, at::kFloat);
|
||||
|
||||
// Test sym_size(dim) returns c10::SymInt
|
||||
c10::SymInt sym_size_0 = tensor.sym_size(0);
|
||||
c10::SymInt sym_size_1 = tensor.sym_size(1);
|
||||
c10::SymInt sym_size_2 = tensor.sym_size(2);
|
||||
|
||||
ASSERT_EQ(sym_size_0, 2);
|
||||
ASSERT_EQ(sym_size_1, 3);
|
||||
ASSERT_EQ(sym_size_2, 4);
|
||||
|
||||
// Test sym_size with negative index
|
||||
c10::SymInt sym_size_neg1 = tensor.sym_size(-1);
|
||||
c10::SymInt sym_size_neg2 = tensor.sym_size(-2);
|
||||
c10::SymInt sym_size_neg3 = tensor.sym_size(-3);
|
||||
|
||||
ASSERT_EQ(sym_size_neg1, 4);
|
||||
ASSERT_EQ(sym_size_neg2, 3);
|
||||
ASSERT_EQ(sym_size_neg3, 2);
|
||||
}
|
||||
|
||||
TEST(TestSymbolicInt, SymSizesAPI) {
|
||||
// Test sym_sizes() API
|
||||
at::TensorBase tensor = at::ones({2, 3, 4, 5}, at::kFloat);
|
||||
|
||||
// Test sym_sizes() returns c10::SymIntArrayRef
|
||||
c10::SymIntArrayRef sym_sizes = tensor.sym_sizes();
|
||||
|
||||
ASSERT_EQ(sym_sizes.size(), 4);
|
||||
ASSERT_EQ(sym_sizes[0], 2);
|
||||
ASSERT_EQ(sym_sizes[1], 3);
|
||||
ASSERT_EQ(sym_sizes[2], 4);
|
||||
ASSERT_EQ(sym_sizes[3], 5);
|
||||
}
|
||||
|
||||
TEST(TestSymbolicInt, SymStrideAPI) {
|
||||
// Test sym_stride() API
|
||||
at::TensorBase tensor = at::ones({2, 3, 4}, at::kFloat);
|
||||
|
||||
// Test sym_stride(dim) returns c10::SymInt
|
||||
c10::SymInt sym_stride_0 = tensor.sym_stride(0);
|
||||
c10::SymInt sym_stride_1 = tensor.sym_stride(1);
|
||||
c10::SymInt sym_stride_2 = tensor.sym_stride(2);
|
||||
|
||||
ASSERT_EQ(sym_stride_0, 12); // 3*4
|
||||
ASSERT_EQ(sym_stride_1, 4); // 4
|
||||
ASSERT_EQ(sym_stride_2, 1); // contiguous
|
||||
|
||||
// Test sym_stride with negative index
|
||||
c10::SymInt sym_stride_neg1 = tensor.sym_stride(-1);
|
||||
c10::SymInt sym_stride_neg2 = tensor.sym_stride(-2);
|
||||
|
||||
ASSERT_EQ(sym_stride_neg1, 1);
|
||||
ASSERT_EQ(sym_stride_neg2, 4);
|
||||
}
|
||||
|
||||
TEST(TestSymbolicInt, SymStridesAPI) {
|
||||
// Test sym_strides() API
|
||||
at::TensorBase tensor = at::ones({2, 3, 4}, at::kFloat);
|
||||
|
||||
// Test sym_strides() returns c10::SymIntArrayRef
|
||||
c10::SymIntArrayRef sym_strides = tensor.sym_strides();
|
||||
|
||||
ASSERT_EQ(sym_strides.size(), 3);
|
||||
ASSERT_EQ(sym_strides[0], 12); // 3*4
|
||||
ASSERT_EQ(sym_strides[1], 4); // 4
|
||||
ASSERT_EQ(sym_strides[2], 1); // contiguous
|
||||
}
|
||||
|
||||
TEST(TestSymbolicInt, SymNumelAPI) {
|
||||
// Test sym_numel() API
|
||||
at::TensorBase tensor = at::ones({2, 3, 4}, at::kFloat);
|
||||
|
||||
// Test sym_numel() returns c10::SymInt
|
||||
c10::SymInt sym_numel = tensor.sym_numel();
|
||||
|
||||
ASSERT_EQ(sym_numel, 24); // 2*3*4
|
||||
|
||||
// Test with different shape
|
||||
at::TensorBase tensor2 = at::ones({5, 6, 7, 8}, at::kFloat);
|
||||
c10::SymInt sym_numel2 = tensor2.sym_numel();
|
||||
|
||||
ASSERT_EQ(sym_numel2, 1680); // 5*6*7*8
|
||||
}
|
||||
|
||||
TEST(TestSymbolicInt, SymAPIsConsistency) {
|
||||
// Test that sym_* APIs return values consistent with non-sym APIs
|
||||
at::TensorBase tensor = at::ones({3, 4, 5, 6}, at::kFloat);
|
||||
|
||||
// Test sym_size vs size
|
||||
for (int64_t i = 0; i < tensor.dim(); ++i) {
|
||||
ASSERT_EQ(tensor.sym_size(i), tensor.size(i));
|
||||
}
|
||||
|
||||
// Test sym_stride vs stride
|
||||
for (int64_t i = 0; i < tensor.dim(); ++i) {
|
||||
ASSERT_EQ(tensor.sym_stride(i), tensor.stride(i));
|
||||
}
|
||||
|
||||
// Test sym_numel vs numel
|
||||
ASSERT_EQ(tensor.sym_numel(), tensor.numel());
|
||||
|
||||
// Test sym_sizes vs sizes
|
||||
auto sizes = tensor.sizes();
|
||||
auto sym_sizes = tensor.sym_sizes();
|
||||
ASSERT_EQ(sizes.size(), sym_sizes.size());
|
||||
for (size_t i = 0; i < sizes.size(); ++i) {
|
||||
ASSERT_EQ(sym_sizes[i], sizes[i]);
|
||||
}
|
||||
|
||||
// Test sym_strides vs strides
|
||||
auto strides = tensor.strides();
|
||||
auto sym_strides = tensor.sym_strides();
|
||||
ASSERT_EQ(strides.size(), sym_strides.size());
|
||||
for (size_t i = 0; i < strides.size(); ++i) {
|
||||
ASSERT_EQ(sym_strides[i], strides[i]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <c10/core/Stream.h>
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/api/include/context_pool.h"
|
||||
#include "paddle/phi/backends/gpu/gpu_context.h"
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
namespace {
|
||||
|
||||
using StreamCallbackGate = std::atomic<bool>;
|
||||
|
||||
#ifdef PADDLE_WITH_HIP
|
||||
void BlockingStreamCallback(hipStream_t /*stream*/,
|
||||
hipError_t /*status*/,
|
||||
void* user_data) {
|
||||
auto* gate = static_cast<StreamCallbackGate*>(user_data);
|
||||
while (!gate->load(std::memory_order_acquire)) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
void CreateRawStream(hipStream_t* stream) {
|
||||
C10_CUDA_CHECK(hipStreamCreate(stream));
|
||||
}
|
||||
|
||||
void DestroyRawStream(hipStream_t stream) {
|
||||
C10_CUDA_CHECK(hipStreamDestroy(stream));
|
||||
}
|
||||
|
||||
void ClearLastStreamError() { (void)hipGetLastError(); }
|
||||
#else
|
||||
void CUDART_CB BlockingStreamCallback(void* user_data) {
|
||||
auto* gate = static_cast<StreamCallbackGate*>(user_data);
|
||||
while (!gate->load(std::memory_order_acquire)) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
void ClearLastStreamError() { (void)cudaGetLastError(); }
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
// Test device_count() works in both CPU and CUDA builds
|
||||
TEST(StreamTest, DeviceCount) {
|
||||
c10::DeviceIndex count = c10::cuda::device_count();
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
// In CUDA builds, should return actual device count (>= 0)
|
||||
EXPECT_GE(count, 0);
|
||||
#else
|
||||
// In CPU-only builds, should return 0
|
||||
EXPECT_EQ(count, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
// ==================== native_handle ====================
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
// CUDA stream: native_handle() should return the underlying cudaStream_t
|
||||
// encoded as void*. For the default (null) stream the id is 0, so the
|
||||
// pointer is nullptr; for a real stream it must be non-null.
|
||||
TEST(StreamTest, NativeHandleCudaDefaultStream) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
c10::Stream s = c10::cuda::getDefaultCUDAStream().unwrap();
|
||||
// Default stream encodes nullptr (id == 0), so native_handle() == nullptr.
|
||||
EXPECT_EQ(s.native_handle(), nullptr);
|
||||
}
|
||||
|
||||
TEST(StreamTest, NativeHandleCudaCurrentStream) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
auto cuda_stream = c10::cuda::getCurrentCUDAStream();
|
||||
c10::Stream s = cuda_stream.unwrap();
|
||||
// getCurrentCUDAStream wraps the real phi stream handle; calling
|
||||
// native_handle() must not throw.
|
||||
EXPECT_NO_THROW({ (void)s.native_handle(); });
|
||||
}
|
||||
#endif // PADDLE_WITH_CUDA || PADDLE_WITH_HIP
|
||||
|
||||
// CPU stream: native_handle() is not supported and must throw.
|
||||
TEST(StreamTest, NativeHandleCpuStreamThrows) {
|
||||
c10::Stream cpu_stream(c10::Stream::DEFAULT,
|
||||
c10::Device(c10::DeviceType::CPU, 0));
|
||||
EXPECT_THROW({ (void)cpu_stream.native_handle(); }, std::exception);
|
||||
}
|
||||
|
||||
// ==================== query ====================
|
||||
|
||||
// CPU stream is always ready.
|
||||
TEST(StreamTest, QueryCpuStreamReturnsTrue) {
|
||||
c10::Stream cpu_stream(c10::Stream::DEFAULT,
|
||||
c10::Device(c10::DeviceType::CPU, 0));
|
||||
EXPECT_TRUE(cpu_stream.query());
|
||||
}
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
// A freshly-obtained CUDA stream with no pending work must report ready.
|
||||
TEST(StreamTest, QueryCudaStreamReady) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
auto cuda_stream = c10::cuda::getCurrentCUDAStream();
|
||||
c10::Stream s = cuda_stream.unwrap();
|
||||
// synchronize first to ensure no pending work, then query should be true.
|
||||
EXPECT_NO_THROW(s.synchronize());
|
||||
EXPECT_TRUE(s.query());
|
||||
}
|
||||
|
||||
#endif // PADDLE_WITH_CUDA || PADDLE_WITH_HIP
|
||||
|
||||
// ==================== synchronize ====================
|
||||
|
||||
// CPU stream: synchronize() is a no-op and must not throw.
|
||||
TEST(StreamTest, SynchronizeCpuStream) {
|
||||
c10::Stream cpu_stream(c10::Stream::DEFAULT,
|
||||
c10::Device(c10::DeviceType::CPU, 0));
|
||||
EXPECT_NO_THROW(cpu_stream.synchronize());
|
||||
}
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
// CUDA stream: synchronize() must complete without error.
|
||||
TEST(StreamTest, SynchronizeCudaStream) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
auto cuda_stream = c10::cuda::getCurrentCUDAStream();
|
||||
c10::Stream s = cuda_stream.unwrap();
|
||||
EXPECT_NO_THROW(s.synchronize());
|
||||
}
|
||||
#endif // PADDLE_WITH_CUDA || PADDLE_WITH_HIP
|
||||
|
||||
// ==================== getDefaultCUDAStream ====================
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
// getDefaultCUDAStream must always return the null stream (id == 0),
|
||||
// which corresponds to cudaStreamDefault on the device.
|
||||
TEST(CUDAStreamTest, DefaultStreamIsNullStream) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
auto default_stream = c10::cuda::getDefaultCUDAStream();
|
||||
// id == 0 encodes cudaStreamDefault (the null stream, handle nullptr).
|
||||
EXPECT_EQ(default_stream.id(), static_cast<c10::StreamId>(0));
|
||||
}
|
||||
|
||||
// getDefaultCUDAStream must be stable: calling it twice returns equal streams.
|
||||
TEST(CUDAStreamTest, DefaultStreamIsStable) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
auto s1 = c10::cuda::getDefaultCUDAStream();
|
||||
auto s2 = c10::cuda::getDefaultCUDAStream();
|
||||
EXPECT_EQ(s1, s2);
|
||||
}
|
||||
|
||||
TEST(CUDAStreamTest, GetStreamFromPoolBoolOverloadPreservesHighPriority) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
auto low_priority_stream =
|
||||
c10::cuda::getStreamFromPool(/*isHighPriority=*/false);
|
||||
auto high_priority_stream =
|
||||
c10::cuda::getStreamFromPool(/*isHighPriority=*/true);
|
||||
auto explicit_high_priority_stream = c10::cuda::getStreamFromPool(-1);
|
||||
|
||||
const int low_priority = low_priority_stream.priority();
|
||||
const int high_priority = high_priority_stream.priority();
|
||||
const int explicit_high_priority = explicit_high_priority_stream.priority();
|
||||
|
||||
if (low_priority == explicit_high_priority) {
|
||||
return;
|
||||
}
|
||||
|
||||
EXPECT_EQ(high_priority, explicit_high_priority);
|
||||
EXPECT_NE(high_priority, low_priority);
|
||||
}
|
||||
|
||||
// After setCurrentCUDAStream redirects the current stream,
|
||||
// getDefaultCUDAStream must still return the null stream.
|
||||
TEST(CUDAStreamTest, DefaultStreamUnaffectedBySetCurrentCUDAStream) {
|
||||
if (!at::cuda::is_available()) {
|
||||
return;
|
||||
}
|
||||
// Snapshot the current stream before we touch it so we can
|
||||
// restore it afterward and avoid polluting subsequent tests.
|
||||
auto original_stream = c10::cuda::getCurrentCUDAStream();
|
||||
|
||||
// Obtain a non-default stream from the pool.
|
||||
auto pool_stream = c10::cuda::getStreamFromPool(/*isHighPriority=*/false);
|
||||
|
||||
// Redirect the current stream.
|
||||
c10::cuda::setCurrentCUDAStream(pool_stream);
|
||||
|
||||
auto default_stream = c10::cuda::getDefaultCUDAStream();
|
||||
auto current_stream = c10::cuda::getCurrentCUDAStream();
|
||||
auto place = phi::GPUPlace(current_stream.device_index());
|
||||
|
||||
// Default stream is still null; current stream has changed.
|
||||
EXPECT_EQ(default_stream.id(), static_cast<c10::StreamId>(0));
|
||||
EXPECT_NE(default_stream, current_stream);
|
||||
EXPECT_EQ(paddle::GetCurrentCUDAStream(place)->raw_stream(),
|
||||
current_stream.stream());
|
||||
|
||||
// Restore the original current stream.
|
||||
c10::cuda::setCurrentCUDAStream(original_stream);
|
||||
EXPECT_EQ(paddle::GetCurrentCUDAStream(place)->raw_stream(),
|
||||
original_stream.stream());
|
||||
}
|
||||
|
||||
#endif // PADDLE_WITH_CUDA || PADDLE_WITH_HIP
|
||||
@@ -0,0 +1,445 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <c10/core/DefaultDtype.h>
|
||||
#include <c10/core/Device.h>
|
||||
#include <c10/core/Layout.h>
|
||||
#include <c10/core/MemoryFormat.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/ScalarTypeToTypeMeta.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
// ============================================================
|
||||
// Tests for c10::TensorOptions
|
||||
// ============================================================
|
||||
|
||||
namespace {
|
||||
|
||||
class DefaultDtypeGuard {
|
||||
public:
|
||||
explicit DefaultDtypeGuard(c10::ScalarType dtype)
|
||||
: previous_(c10::get_default_dtype()) {
|
||||
c10::set_default_dtype(c10::scalarTypeToTypeMeta(dtype));
|
||||
}
|
||||
|
||||
~DefaultDtypeGuard() { c10::set_default_dtype(previous_); }
|
||||
|
||||
private:
|
||||
caffe2::TypeMeta previous_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// Default-constructed TensorOptions has no device/dtype/layout/grad fields set
|
||||
TEST(TensorOptionsTest, DefaultConstructor_NothingSet) {
|
||||
c10::TensorOptions opts;
|
||||
|
||||
ASSERT_FALSE(opts.has_device());
|
||||
ASSERT_FALSE(opts.has_dtype());
|
||||
ASSERT_FALSE(opts.has_layout());
|
||||
ASSERT_FALSE(opts.has_requires_grad());
|
||||
ASSERT_FALSE(opts.has_pinned_memory());
|
||||
ASSERT_FALSE(opts.has_memory_format());
|
||||
}
|
||||
|
||||
// Default dtype falls back to Float, device to CPU, layout to kStrided
|
||||
TEST(TensorOptionsTest, DefaultConstructor_Defaults) {
|
||||
c10::TensorOptions opts;
|
||||
|
||||
ASSERT_EQ(opts.dtype(), c10::ScalarType::Float);
|
||||
ASSERT_EQ(opts.device(), c10::Device(c10::kCPU));
|
||||
ASSERT_EQ(opts.layout(), c10::kStrided);
|
||||
ASSERT_FALSE(opts.requires_grad());
|
||||
ASSERT_FALSE(opts.pinned_memory());
|
||||
}
|
||||
|
||||
// ---- dtype ----
|
||||
|
||||
TEST(TensorOptionsTest, SetDtype_HasDtypeTrue) {
|
||||
c10::TensorOptions opts = c10::TensorOptions().dtype(c10::kFloat);
|
||||
|
||||
ASSERT_TRUE(opts.has_dtype());
|
||||
ASSERT_EQ(opts.dtype(), c10::kFloat);
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, SetDtype_Double) {
|
||||
c10::TensorOptions opts = c10::TensorOptions().dtype(c10::kDouble);
|
||||
|
||||
ASSERT_EQ(opts.dtype(), c10::kDouble);
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, SetDtype_Int32) {
|
||||
c10::TensorOptions opts = c10::TensorOptions().dtype(c10::kInt);
|
||||
|
||||
ASSERT_EQ(opts.dtype(), c10::kInt);
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, SetDtype_Bool) {
|
||||
c10::TensorOptions opts = c10::TensorOptions().dtype(c10::kBool);
|
||||
|
||||
ASSERT_EQ(opts.dtype(), c10::kBool);
|
||||
}
|
||||
|
||||
// Implicit construction from ScalarType
|
||||
TEST(TensorOptionsTest, ImplicitFromScalarType) {
|
||||
c10::TensorOptions opts(c10::kHalf);
|
||||
|
||||
ASSERT_TRUE(opts.has_dtype());
|
||||
ASSERT_EQ(opts.dtype(), c10::kHalf);
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, ImplicitFromTypeMeta) {
|
||||
c10::TensorOptions opts(caffe2::TypeMeta::Make<double>());
|
||||
|
||||
ASSERT_TRUE(opts.has_dtype());
|
||||
ASSERT_EQ(opts.dtype(), caffe2::TypeMeta::Make<double>());
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, SetDtype_TypeMetaOptional) {
|
||||
c10::TensorOptions opts = c10::TensorOptions().dtype(
|
||||
std::make_optional(caffe2::TypeMeta::Make<int>()));
|
||||
|
||||
ASSERT_TRUE(opts.has_dtype());
|
||||
ASSERT_EQ(opts.dtype(), caffe2::TypeMeta::Make<int>());
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, SetDtype_TypeMetaTemplateMember) {
|
||||
c10::TensorOptions opts;
|
||||
opts.dtype<int64_t>();
|
||||
|
||||
ASSERT_TRUE(opts.has_dtype());
|
||||
ASSERT_EQ(opts.dtype(), caffe2::TypeMeta::Make<int64_t>());
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, ClearDtypeWithNullopt) {
|
||||
c10::TensorOptions opts = c10::TensorOptions().dtype(
|
||||
std::make_optional(caffe2::TypeMeta::Make<float>()));
|
||||
opts = opts.dtype(std::optional<caffe2::TypeMeta>{});
|
||||
|
||||
ASSERT_FALSE(opts.has_dtype());
|
||||
ASSERT_FALSE(opts.dtype_opt().has_value());
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, DtypeOptReturnsTypeMeta) {
|
||||
c10::TensorOptions opts =
|
||||
c10::TensorOptions().dtype(caffe2::TypeMeta::Make<bool>());
|
||||
|
||||
ASSERT_TRUE(opts.dtype_opt().has_value());
|
||||
ASSERT_EQ(opts.dtype_opt().value(), caffe2::TypeMeta::Make<bool>());
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, DtypeDefaultTracksGlobalDefaultDtype) {
|
||||
DefaultDtypeGuard guard(c10::kDouble);
|
||||
c10::TensorOptions opts;
|
||||
|
||||
ASSERT_EQ(opts.dtype(), caffe2::TypeMeta::Make<double>());
|
||||
ASSERT_EQ(c10::dtype_or_default(std::optional<caffe2::TypeMeta>{}),
|
||||
caffe2::TypeMeta::Make<double>());
|
||||
ASSERT_EQ(c10::dtype_or_default(std::optional<c10::ScalarType>{}),
|
||||
c10::kDouble);
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, DefaultComplexDtypeTracksGlobalDefaultDtype) {
|
||||
{
|
||||
DefaultDtypeGuard guard(c10::kHalf);
|
||||
|
||||
ASSERT_EQ(c10::get_default_dtype_as_scalartype(), c10::kHalf);
|
||||
ASSERT_EQ(c10::get_default_complex_dtype().toScalarType(),
|
||||
c10::ScalarType::ComplexHalf);
|
||||
}
|
||||
|
||||
{
|
||||
DefaultDtypeGuard guard(c10::kDouble);
|
||||
|
||||
ASSERT_EQ(c10::get_default_dtype_as_scalartype(), c10::kDouble);
|
||||
ASSERT_EQ(c10::get_default_complex_dtype().toScalarType(),
|
||||
c10::ScalarType::ComplexDouble);
|
||||
}
|
||||
|
||||
{
|
||||
DefaultDtypeGuard guard(c10::kFloat);
|
||||
|
||||
ASSERT_EQ(c10::get_default_dtype_as_scalartype(), c10::kFloat);
|
||||
ASSERT_EQ(c10::get_default_complex_dtype().toScalarType(),
|
||||
c10::ScalarType::ComplexFloat);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- device ----
|
||||
|
||||
TEST(TensorOptionsTest, SetDevice_CPU) {
|
||||
c10::TensorOptions opts = c10::TensorOptions().device(c10::Device(c10::kCPU));
|
||||
|
||||
ASSERT_TRUE(opts.has_device());
|
||||
ASSERT_EQ(opts.device().type(), c10::DeviceType::CPU);
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, SetDevice_CUDA) {
|
||||
c10::TensorOptions opts =
|
||||
c10::TensorOptions().device(c10::Device(c10::kCUDA, 0));
|
||||
|
||||
ASSERT_TRUE(opts.has_device());
|
||||
ASSERT_EQ(opts.device().type(), c10::DeviceType::CUDA);
|
||||
ASSERT_EQ(opts.device().index(), 0);
|
||||
}
|
||||
|
||||
// Helper function: c10::device()
|
||||
TEST(TensorOptionsTest, HelperFunction_device) {
|
||||
c10::TensorOptions opts = c10::device(c10::Device(c10::kCPU));
|
||||
|
||||
ASSERT_TRUE(opts.has_device());
|
||||
ASSERT_EQ(opts.device().type(), c10::DeviceType::CPU);
|
||||
}
|
||||
|
||||
// ---- layout ----
|
||||
|
||||
TEST(TensorOptionsTest, SetLayout_kStrided) {
|
||||
c10::TensorOptions opts = c10::TensorOptions().layout(c10::kStrided);
|
||||
|
||||
ASSERT_TRUE(opts.has_layout());
|
||||
ASSERT_EQ(opts.layout(), c10::kStrided);
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, SetLayout_kSparse) {
|
||||
c10::TensorOptions opts = c10::TensorOptions().layout(c10::kSparse);
|
||||
|
||||
ASSERT_TRUE(opts.has_layout());
|
||||
ASSERT_EQ(opts.layout(), c10::kSparse);
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, SetLayout_kSparseCsr) {
|
||||
c10::TensorOptions opts = c10::TensorOptions().layout(c10::kSparseCsr);
|
||||
|
||||
ASSERT_TRUE(opts.has_layout());
|
||||
ASSERT_EQ(opts.layout(), c10::kSparseCsr);
|
||||
}
|
||||
|
||||
// Implicit construction from Layout
|
||||
TEST(TensorOptionsTest, ImplicitFromLayout) {
|
||||
c10::TensorOptions opts(c10::kSparse);
|
||||
|
||||
ASSERT_TRUE(opts.has_layout());
|
||||
ASSERT_EQ(opts.layout(), c10::kSparse);
|
||||
}
|
||||
|
||||
// Helper function: c10::layout()
|
||||
TEST(TensorOptionsTest, HelperFunction_layout) {
|
||||
c10::TensorOptions opts = c10::layout(c10::kSparse);
|
||||
|
||||
ASSERT_TRUE(opts.has_layout());
|
||||
ASSERT_EQ(opts.layout(), c10::kSparse);
|
||||
}
|
||||
|
||||
// ---- is_sparse / is_sparse_csr / is_sparse_compressed ----
|
||||
|
||||
TEST(TensorOptionsTest, IsSparse_kSparse) {
|
||||
c10::TensorOptions opts = c10::TensorOptions().layout(c10::kSparse);
|
||||
|
||||
ASSERT_TRUE(opts.is_sparse());
|
||||
ASSERT_FALSE(opts.is_sparse_csr());
|
||||
ASSERT_FALSE(opts.is_sparse_compressed());
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, IsSparse_kSparseCsr) {
|
||||
c10::TensorOptions opts = c10::TensorOptions().layout(c10::kSparseCsr);
|
||||
|
||||
ASSERT_FALSE(opts.is_sparse());
|
||||
ASSERT_TRUE(opts.is_sparse_csr());
|
||||
ASSERT_TRUE(opts.is_sparse_compressed());
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, IsSparse_kSparseCsc) {
|
||||
c10::TensorOptions opts = c10::TensorOptions().layout(c10::kSparseCsc);
|
||||
|
||||
ASSERT_FALSE(opts.is_sparse());
|
||||
ASSERT_FALSE(opts.is_sparse_csr());
|
||||
ASSERT_TRUE(opts.is_sparse_compressed());
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, IsSparse_kStrided) {
|
||||
c10::TensorOptions opts = c10::TensorOptions().layout(c10::kStrided);
|
||||
|
||||
ASSERT_FALSE(opts.is_sparse());
|
||||
ASSERT_FALSE(opts.is_sparse_csr());
|
||||
ASSERT_FALSE(opts.is_sparse_compressed());
|
||||
}
|
||||
|
||||
// ---- requires_grad ----
|
||||
|
||||
TEST(TensorOptionsTest, SetRequiresGrad_True) {
|
||||
c10::TensorOptions opts = c10::TensorOptions().requires_grad(true);
|
||||
|
||||
ASSERT_TRUE(opts.has_requires_grad());
|
||||
ASSERT_TRUE(opts.requires_grad());
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, SetRequiresGrad_False) {
|
||||
c10::TensorOptions opts = c10::TensorOptions().requires_grad(false);
|
||||
|
||||
ASSERT_TRUE(opts.has_requires_grad());
|
||||
ASSERT_FALSE(opts.requires_grad());
|
||||
}
|
||||
|
||||
// Helper function: c10::requires_grad()
|
||||
TEST(TensorOptionsTest, HelperFunction_requires_grad) {
|
||||
c10::TensorOptions opts = c10::requires_grad(true);
|
||||
|
||||
ASSERT_TRUE(opts.has_requires_grad());
|
||||
ASSERT_TRUE(opts.requires_grad());
|
||||
}
|
||||
|
||||
// ---- pinned_memory ----
|
||||
|
||||
TEST(TensorOptionsTest, SetPinnedMemory_True) {
|
||||
c10::TensorOptions opts = c10::TensorOptions().pinned_memory(true);
|
||||
|
||||
ASSERT_TRUE(opts.has_pinned_memory());
|
||||
ASSERT_TRUE(opts.pinned_memory());
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, SetPinnedMemory_False) {
|
||||
c10::TensorOptions opts = c10::TensorOptions().pinned_memory(false);
|
||||
|
||||
ASSERT_TRUE(opts.has_pinned_memory());
|
||||
ASSERT_FALSE(opts.pinned_memory());
|
||||
}
|
||||
|
||||
// ---- memory_format ----
|
||||
|
||||
TEST(TensorOptionsTest, SetMemoryFormat_Contiguous) {
|
||||
c10::TensorOptions opts =
|
||||
c10::TensorOptions().memory_format(c10::MemoryFormat::Contiguous);
|
||||
|
||||
ASSERT_TRUE(opts.has_memory_format());
|
||||
ASSERT_EQ(opts.memory_format_opt().value(), c10::MemoryFormat::Contiguous);
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, SetMemoryFormat_ChannelsLast) {
|
||||
c10::TensorOptions opts =
|
||||
c10::TensorOptions().memory_format(c10::MemoryFormat::ChannelsLast);
|
||||
|
||||
ASSERT_TRUE(opts.has_memory_format());
|
||||
ASSERT_EQ(opts.memory_format_opt().value(), c10::MemoryFormat::ChannelsLast);
|
||||
}
|
||||
|
||||
// Helper function: c10::memory_format()
|
||||
TEST(TensorOptionsTest, HelperFunction_memory_format) {
|
||||
c10::TensorOptions opts = c10::memory_format(c10::MemoryFormat::Contiguous);
|
||||
|
||||
ASSERT_TRUE(opts.has_memory_format());
|
||||
}
|
||||
|
||||
// ---- merge_memory_format ----
|
||||
|
||||
TEST(TensorOptionsTest, MergeMemoryFormat_Overrides) {
|
||||
c10::TensorOptions base =
|
||||
c10::TensorOptions().memory_format(c10::MemoryFormat::Contiguous);
|
||||
c10::TensorOptions merged =
|
||||
base.merge_memory_format(c10::MemoryFormat::ChannelsLast);
|
||||
|
||||
ASSERT_EQ(merged.memory_format_opt().value(),
|
||||
c10::MemoryFormat::ChannelsLast);
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, MergeMemoryFormat_NulloptKeepsOriginal) {
|
||||
c10::TensorOptions base =
|
||||
c10::TensorOptions().memory_format(c10::MemoryFormat::Contiguous);
|
||||
c10::TensorOptions merged = base.merge_memory_format(std::nullopt);
|
||||
|
||||
ASSERT_EQ(merged.memory_format_opt().value(), c10::MemoryFormat::Contiguous);
|
||||
}
|
||||
|
||||
// ---- chaining multiple options ----
|
||||
|
||||
TEST(TensorOptionsTest, ChainMultipleOptions) {
|
||||
c10::TensorOptions opts = c10::TensorOptions()
|
||||
.dtype(c10::kDouble)
|
||||
.device(c10::Device(c10::kCPU))
|
||||
.layout(c10::kStrided)
|
||||
.requires_grad(true);
|
||||
|
||||
ASSERT_EQ(opts.dtype(), c10::kDouble);
|
||||
ASSERT_EQ(opts.device().type(), c10::DeviceType::CPU);
|
||||
ASSERT_EQ(opts.layout(), c10::kStrided);
|
||||
ASSERT_TRUE(opts.requires_grad());
|
||||
}
|
||||
|
||||
// ---- opt() accessor returns nullopt when not set ----
|
||||
|
||||
TEST(TensorOptionsTest, DeviceOpt_NulloptWhenNotSet) {
|
||||
c10::TensorOptions opts;
|
||||
ASSERT_FALSE(opts.device_opt().has_value());
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, DtypeOpt_NulloptWhenNotSet) {
|
||||
c10::TensorOptions opts;
|
||||
ASSERT_FALSE(opts.dtype_opt().has_value());
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, LayoutOpt_NulloptWhenNotSet) {
|
||||
c10::TensorOptions opts;
|
||||
ASSERT_FALSE(opts.layout_opt().has_value());
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, RequiresGradOpt_NulloptWhenNotSet) {
|
||||
c10::TensorOptions opts;
|
||||
ASSERT_FALSE(opts.requires_grad_opt().has_value());
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, PinnedMemoryOpt_NulloptWhenNotSet) {
|
||||
c10::TensorOptions opts;
|
||||
ASSERT_FALSE(opts.pinned_memory_opt().has_value());
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, MemoryFormatOpt_NulloptWhenNotSet) {
|
||||
c10::TensorOptions opts;
|
||||
ASSERT_FALSE(opts.memory_format_opt().has_value());
|
||||
}
|
||||
|
||||
// ---- Implicit construction from MemoryFormat ----
|
||||
|
||||
TEST(TensorOptionsTest, ImplicitFromMemoryFormat) {
|
||||
c10::TensorOptions opts(c10::MemoryFormat::ChannelsLast);
|
||||
|
||||
ASSERT_TRUE(opts.has_memory_format());
|
||||
ASSERT_EQ(opts.memory_format_opt().value(), c10::MemoryFormat::ChannelsLast);
|
||||
}
|
||||
|
||||
// ---- Helper free functions ----
|
||||
|
||||
TEST(TensorOptionsTest, HelperFunction_dtype) {
|
||||
c10::TensorOptions opts = c10::dtype(c10::kLong);
|
||||
|
||||
ASSERT_TRUE(opts.has_dtype());
|
||||
ASSERT_EQ(opts.dtype(), c10::kLong);
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, HelperFunction_dtypeTypeMeta) {
|
||||
c10::TensorOptions opts = c10::dtype(caffe2::TypeMeta::Make<int16_t>());
|
||||
|
||||
ASSERT_TRUE(opts.has_dtype());
|
||||
ASSERT_EQ(opts.dtype(), caffe2::TypeMeta::Make<int16_t>());
|
||||
}
|
||||
|
||||
TEST(TensorOptionsTest, HelperFunction_dtypeTemplate) {
|
||||
c10::TensorOptions opts = c10::dtype<uint8_t>();
|
||||
|
||||
ASSERT_TRUE(opts.has_dtype());
|
||||
ASSERT_EQ(opts.dtype(), caffe2::TypeMeta::Make<uint8_t>());
|
||||
ASSERT_EQ(c10::typeMetaToScalarType(opts.dtype()), c10::kByte);
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/ScalarTypeToTypeMeta.h>
|
||||
#include <c10/util/typeid.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct LifecycleTrackedType {
|
||||
LifecycleTrackedType() = default;
|
||||
explicit LifecycleTrackedType(int new_value) : value(new_value) {}
|
||||
|
||||
int value{7};
|
||||
};
|
||||
|
||||
struct NonDefaultConstructibleType {
|
||||
explicit NonDefaultConstructibleType(int new_value) : value(new_value) {}
|
||||
|
||||
int value;
|
||||
};
|
||||
|
||||
struct NonCopyAssignableType {
|
||||
NonCopyAssignableType() = default;
|
||||
explicit NonCopyAssignableType(int new_value) : value(new_value) {}
|
||||
NonCopyAssignableType(const NonCopyAssignableType&) = default;
|
||||
NonCopyAssignableType& operator=(const NonCopyAssignableType&) = delete;
|
||||
|
||||
int value{5};
|
||||
};
|
||||
|
||||
caffe2::TypeIdentifier GetIntTypeIdentifierFromHelper() {
|
||||
return caffe2::TypeIdentifier::Get<int>();
|
||||
}
|
||||
|
||||
caffe2::TypeMeta MakeStdStringTypeMetaFromHelper() {
|
||||
return caffe2::TypeMeta::Make<std::string>();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace caffe2 {
|
||||
|
||||
CAFFE_KNOWN_TYPE_NOEXPORT(::LifecycleTrackedType)
|
||||
CAFFE_KNOWN_TYPE_NOEXPORT(::NonDefaultConstructibleType)
|
||||
CAFFE_KNOWN_TYPE_NOEXPORT(::NonCopyAssignableType)
|
||||
|
||||
} // namespace caffe2
|
||||
|
||||
TEST(TypeIdentifierCompatTest, SameTypeHasStableId) {
|
||||
const auto id1 = caffe2::TypeIdentifier::Get<int>();
|
||||
const auto id2 = caffe2::TypeIdentifier::Get<int>();
|
||||
const auto id3 = GetIntTypeIdentifierFromHelper();
|
||||
|
||||
EXPECT_EQ(id1, id2);
|
||||
EXPECT_EQ(id1, id3);
|
||||
}
|
||||
|
||||
TEST(TypeIdentifierCompatTest, DifferentTypeHasDifferentId) {
|
||||
const auto int_id = caffe2::TypeIdentifier::Get<int>();
|
||||
const auto float_id = caffe2::TypeIdentifier::Get<float>();
|
||||
|
||||
EXPECT_NE(int_id, float_id);
|
||||
}
|
||||
|
||||
TEST(TypeIdentifierCompatTest, OrderingHashAndStreamWork) {
|
||||
const auto int_id = caffe2::TypeIdentifier::Get<int>();
|
||||
const auto string_id = caffe2::TypeIdentifier::Get<std::string>();
|
||||
const auto uninitialized = caffe2::TypeIdentifier::uninitialized();
|
||||
|
||||
EXPECT_EQ(uninitialized.underlyingId(), 0U);
|
||||
EXPECT_NE(std::hash<caffe2::TypeIdentifier>{}(int_id),
|
||||
std::hash<caffe2::TypeIdentifier>{}(uninitialized));
|
||||
EXPECT_TRUE(uninitialized < int_id || int_id < uninitialized);
|
||||
|
||||
std::ostringstream stream;
|
||||
stream << int_id;
|
||||
EXPECT_FALSE(stream.str().empty());
|
||||
EXPECT_NE(stream.str(), "0");
|
||||
EXPECT_NE(int_id, string_id);
|
||||
}
|
||||
|
||||
TEST(TypeMetaCompatTest, ScalarTypeRoundTrip) {
|
||||
const std::array<c10::ScalarType, 6> dtypes = {
|
||||
c10::ScalarType::Bool,
|
||||
c10::ScalarType::Half,
|
||||
c10::ScalarType::Float,
|
||||
c10::ScalarType::Double,
|
||||
c10::ScalarType::Int,
|
||||
c10::ScalarType::Long,
|
||||
};
|
||||
|
||||
for (const auto dtype : dtypes) {
|
||||
const auto type_meta = caffe2::TypeMeta::fromScalarType(dtype);
|
||||
EXPECT_TRUE(type_meta.isScalarType(dtype));
|
||||
EXPECT_EQ(type_meta.toScalarType(), dtype);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TypeMetaCompatTest, ScalarTypeHelperConversionsAndComparisons) {
|
||||
const auto type_meta = c10::scalarTypeToTypeMeta(c10::kDouble);
|
||||
|
||||
EXPECT_EQ(c10::typeMetaToScalarType(type_meta), c10::kDouble);
|
||||
EXPECT_EQ(c10::optTypeMetaToScalarType(std::make_optional(type_meta)),
|
||||
std::make_optional(c10::kDouble));
|
||||
EXPECT_EQ(c10::optTypeMetaToScalarType(std::optional<caffe2::TypeMeta>{}),
|
||||
std::nullopt);
|
||||
EXPECT_TRUE(type_meta == c10::kDouble);
|
||||
EXPECT_TRUE(c10::kDouble == type_meta);
|
||||
EXPECT_TRUE(type_meta != c10::kFloat);
|
||||
EXPECT_TRUE(c10::kFloat != type_meta);
|
||||
}
|
||||
|
||||
TEST(TypeMetaCompatTest, BuiltinKnownTypeIsStableAcrossTranslationUnits) {
|
||||
const auto local_meta = caffe2::TypeMeta::Make<std::string>();
|
||||
const auto helper_meta = MakeStdStringTypeMetaFromHelper();
|
||||
|
||||
EXPECT_EQ(local_meta, helper_meta);
|
||||
EXPECT_EQ(local_meta.id(), helper_meta.id());
|
||||
}
|
||||
|
||||
TEST(TypeMetaCompatTest, NonScalarTypeToScalarTypeThrows) {
|
||||
const auto non_scalar_meta = caffe2::TypeMeta::Make<std::string>();
|
||||
|
||||
EXPECT_FALSE(non_scalar_meta.isScalarType());
|
||||
EXPECT_THROW(
|
||||
{
|
||||
const auto dtype = non_scalar_meta.toScalarType();
|
||||
(void)dtype;
|
||||
},
|
||||
std::exception);
|
||||
}
|
||||
|
||||
TEST(TypeMetaCompatTest, BuiltinKnownTypeRepeatRegistrationIsStable) {
|
||||
const auto vector_meta_1 = caffe2::TypeMeta::Make<std::vector<int64_t>>();
|
||||
const auto vector_meta_2 = caffe2::TypeMeta::Make<std::vector<int64_t>>();
|
||||
|
||||
EXPECT_EQ(vector_meta_1, vector_meta_2);
|
||||
EXPECT_EQ(vector_meta_1.id(), vector_meta_2.id());
|
||||
}
|
||||
|
||||
TEST(TypeMetaCompatTest, DefaultConstructedTypeMetaIsUndefined) {
|
||||
caffe2::TypeMeta meta;
|
||||
|
||||
EXPECT_TRUE(meta.isScalarType());
|
||||
EXPECT_TRUE(meta.isScalarType(c10::ScalarType::Undefined));
|
||||
EXPECT_EQ(meta.itemsize(), 0U);
|
||||
EXPECT_EQ(meta.name(), "Undefined");
|
||||
EXPECT_EQ(meta.id(),
|
||||
caffe2::TypeIdentifier::Get<caffe2::detail::_Uninitialized>());
|
||||
EXPECT_EQ(meta.toScalarType(), c10::ScalarType::Undefined);
|
||||
}
|
||||
|
||||
TEST(TypeMetaCompatTest, AssignFromScalarTypeAndHelpersWork) {
|
||||
caffe2::TypeMeta meta;
|
||||
meta = c10::kLong;
|
||||
|
||||
EXPECT_TRUE(meta.isScalarType(c10::kLong));
|
||||
EXPECT_TRUE(meta.Match<int64_t>());
|
||||
EXPECT_EQ(meta.itemsize(), sizeof(int64_t));
|
||||
EXPECT_EQ(caffe2::TypeMeta::Id<int64_t>(), meta.id());
|
||||
EXPECT_EQ(caffe2::TypeMeta::ItemSize<int64_t>(), sizeof(int64_t));
|
||||
EXPECT_FALSE(caffe2::TypeMeta::TypeName<int64_t>().empty());
|
||||
|
||||
std::ostringstream stream;
|
||||
stream << meta;
|
||||
EXPECT_FALSE(stream.str().empty());
|
||||
}
|
||||
|
||||
TEST(TypeMetaCompatTest, FundamentalTypesSkipPlacementAndCopyHooks) {
|
||||
const auto int_meta = caffe2::TypeMeta::Make<int>();
|
||||
const auto float_ptr_meta = caffe2::TypeMeta::Make<float*>();
|
||||
|
||||
EXPECT_NE(int_meta.newFn(), nullptr);
|
||||
EXPECT_EQ(int_meta.placementNew(), nullptr);
|
||||
EXPECT_EQ(int_meta.copy(), nullptr);
|
||||
EXPECT_EQ(int_meta.placementDelete(), nullptr);
|
||||
EXPECT_NE(int_meta.deleteFn(), nullptr);
|
||||
|
||||
EXPECT_NE(float_ptr_meta.newFn(), nullptr);
|
||||
EXPECT_EQ(float_ptr_meta.placementNew(), nullptr);
|
||||
EXPECT_EQ(float_ptr_meta.copy(), nullptr);
|
||||
EXPECT_EQ(float_ptr_meta.placementDelete(), nullptr);
|
||||
EXPECT_NE(float_ptr_meta.deleteFn(), nullptr);
|
||||
}
|
||||
|
||||
TEST(TypeMetaCompatTest, RegisteredCustomTypeLifecycleHooksWork) {
|
||||
const auto meta = caffe2::TypeMeta::Make<LifecycleTrackedType>();
|
||||
|
||||
EXPECT_TRUE(meta.Match<LifecycleTrackedType>());
|
||||
EXPECT_EQ(meta.itemsize(), sizeof(LifecycleTrackedType));
|
||||
EXPECT_NE(meta.newFn(), nullptr);
|
||||
EXPECT_NE(meta.placementNew(), nullptr);
|
||||
EXPECT_NE(meta.copy(), nullptr);
|
||||
EXPECT_NE(meta.placementDelete(), nullptr);
|
||||
EXPECT_NE(meta.deleteFn(), nullptr);
|
||||
EXPECT_FALSE(meta.name().empty());
|
||||
|
||||
auto* heap_object = static_cast<LifecycleTrackedType*>(meta.newFn()());
|
||||
EXPECT_EQ(heap_object->value, 7);
|
||||
heap_object->value = 19;
|
||||
meta.deleteFn()(heap_object);
|
||||
|
||||
alignas(LifecycleTrackedType) std::byte storage[sizeof(LifecycleTrackedType)];
|
||||
meta.placementNew()(storage, 1);
|
||||
auto* placed = reinterpret_cast<LifecycleTrackedType*>(storage);
|
||||
EXPECT_EQ(placed->value, 7);
|
||||
placed->value = 23;
|
||||
|
||||
alignas(LifecycleTrackedType)
|
||||
std::byte copy_storage[sizeof(LifecycleTrackedType)];
|
||||
meta.placementNew()(copy_storage, 1);
|
||||
meta.copy()(storage, copy_storage, 1);
|
||||
auto* copied = reinterpret_cast<LifecycleTrackedType*>(copy_storage);
|
||||
EXPECT_EQ(copied->value, 23);
|
||||
|
||||
meta.placementDelete()(copy_storage, 1);
|
||||
meta.placementDelete()(storage, 1);
|
||||
}
|
||||
|
||||
TEST(TypeMetaCompatTest, NonDefaultConstructibleHooksThrow) {
|
||||
const auto meta = caffe2::TypeMeta::Make<NonDefaultConstructibleType>();
|
||||
|
||||
EXPECT_NE(meta.newFn(), nullptr);
|
||||
EXPECT_NE(meta.placementNew(), nullptr);
|
||||
EXPECT_THROW(
|
||||
{
|
||||
auto* ptr = static_cast<NonDefaultConstructibleType*>(meta.newFn()());
|
||||
(void)ptr;
|
||||
},
|
||||
std::exception);
|
||||
|
||||
alignas(NonDefaultConstructibleType)
|
||||
std::byte storage[sizeof(NonDefaultConstructibleType)];
|
||||
EXPECT_THROW(meta.placementNew()(storage, 1), std::exception);
|
||||
}
|
||||
|
||||
TEST(TypeMetaCompatTest, NonCopyAssignableCopyHookThrows) {
|
||||
const auto meta = caffe2::TypeMeta::Make<NonCopyAssignableType>();
|
||||
|
||||
EXPECT_NE(meta.copy(), nullptr);
|
||||
|
||||
alignas(NonCopyAssignableType)
|
||||
std::byte src_storage[sizeof(NonCopyAssignableType)];
|
||||
alignas(NonCopyAssignableType)
|
||||
std::byte dst_storage[sizeof(NonCopyAssignableType)];
|
||||
meta.placementNew()(src_storage, 1);
|
||||
meta.placementNew()(dst_storage, 1);
|
||||
|
||||
auto* src = reinterpret_cast<NonCopyAssignableType*>(src_storage);
|
||||
src->value = 31;
|
||||
|
||||
EXPECT_THROW(meta.copy()(src_storage, dst_storage, 1), std::exception);
|
||||
|
||||
meta.placementDelete()(dst_storage, 1);
|
||||
meta.placementDelete()(src_storage, 1);
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
|
||||
#include <ATen/cuda/CUDAGeneratorImpl.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/common/exception.h"
|
||||
|
||||
// ============================================================================
|
||||
// Tests for at::get_generator_or_default<at::CUDAGeneratorImpl>
|
||||
// ============================================================================
|
||||
|
||||
// Verify that getDefaultCUDAGenerator returns a valid, defined Generator whose
|
||||
// underlying impl is a CUDAGeneratorImpl on device 0.
|
||||
TEST(CUDAGeneratorTest, DefaultGeneratorIsDefined) {
|
||||
const at::Generator& default_gen =
|
||||
at::cuda::detail::getDefaultCUDAGenerator(0);
|
||||
ASSERT_TRUE(default_gen.defined());
|
||||
ASSERT_EQ(default_gen.device().type(), at::kCUDA);
|
||||
ASSERT_EQ(default_gen.device().index(), 0);
|
||||
}
|
||||
|
||||
// get_generator_or_default should return the default generator when the
|
||||
// optional is empty (nullopt).
|
||||
TEST(CUDAGeneratorTest, GetGeneratorOrDefaultWithNullopt) {
|
||||
const at::Generator& default_gen =
|
||||
at::cuda::detail::getDefaultCUDAGenerator(0);
|
||||
|
||||
std::optional<at::Generator> empty_gen = std::nullopt;
|
||||
at::CUDAGeneratorImpl* impl =
|
||||
at::get_generator_or_default<at::CUDAGeneratorImpl>(empty_gen,
|
||||
default_gen);
|
||||
|
||||
ASSERT_NE(impl, nullptr);
|
||||
ASSERT_EQ(impl->device().type(), at::kCUDA);
|
||||
}
|
||||
|
||||
// get_generator_or_default should return the default generator when the
|
||||
// optional holds a default-constructed (undefined) Generator.
|
||||
TEST(CUDAGeneratorTest, GetGeneratorOrDefaultWithUndefined) {
|
||||
const at::Generator& default_gen =
|
||||
at::cuda::detail::getDefaultCUDAGenerator(0);
|
||||
|
||||
std::optional<at::Generator> undef_gen = at::Generator(); // undefined
|
||||
at::CUDAGeneratorImpl* impl =
|
||||
at::get_generator_or_default<at::CUDAGeneratorImpl>(undef_gen,
|
||||
default_gen);
|
||||
|
||||
ASSERT_NE(impl, nullptr);
|
||||
ASSERT_EQ(impl->device().type(), at::kCUDA);
|
||||
}
|
||||
|
||||
// get_generator_or_default should return the user-supplied generator when the
|
||||
// optional contains a valid (defined) Generator.
|
||||
TEST(CUDAGeneratorTest, GetGeneratorOrDefaultWithUserGenerator) {
|
||||
const at::Generator& default_gen =
|
||||
at::cuda::detail::getDefaultCUDAGenerator(0);
|
||||
|
||||
// Create a distinct user generator.
|
||||
at::Generator user_gen = at::cuda::detail::createCUDAGenerator(0);
|
||||
user_gen.set_current_seed(42);
|
||||
|
||||
std::optional<at::Generator> opt_gen = user_gen;
|
||||
at::CUDAGeneratorImpl* impl =
|
||||
at::get_generator_or_default<at::CUDAGeneratorImpl>(opt_gen, default_gen);
|
||||
|
||||
ASSERT_NE(impl, nullptr);
|
||||
ASSERT_EQ(impl->current_seed(), 42u);
|
||||
}
|
||||
|
||||
// Verify that check_generator works for a valid optional<Generator>.
|
||||
TEST(CUDAGeneratorTest, CheckGenerator) {
|
||||
at::Generator gen = at::cuda::detail::createCUDAGenerator(0);
|
||||
gen.set_current_seed(123);
|
||||
|
||||
std::optional<at::Generator> opt = gen;
|
||||
at::CUDAGeneratorImpl* impl = at::check_generator<at::CUDAGeneratorImpl>(opt);
|
||||
|
||||
ASSERT_NE(impl, nullptr);
|
||||
ASSERT_EQ(impl->current_seed(), 123u);
|
||||
}
|
||||
|
||||
// check_generator should throw when given nullopt.
|
||||
TEST(CUDAGeneratorTest, CheckGeneratorThrowsOnNullopt) {
|
||||
std::optional<at::Generator> empty;
|
||||
EXPECT_THROW(at::check_generator<at::CUDAGeneratorImpl>(empty),
|
||||
::common::PD_Exception);
|
||||
}
|
||||
|
||||
// check_generator should throw when the optional holds a default-constructed
|
||||
// (undefined) Generator — exercises the gen->defined() TORCH_CHECK branch.
|
||||
TEST(CUDAGeneratorTest, CheckGeneratorThrowsOnUndefined) {
|
||||
std::optional<at::Generator> undef_gen = at::Generator(); // undefined impl
|
||||
EXPECT_THROW(at::check_generator<at::CUDAGeneratorImpl>(undef_gen),
|
||||
::common::PD_Exception);
|
||||
}
|
||||
|
||||
// Verify Philox state management via the CUDAGeneratorImpl pointer returned
|
||||
// from get_generator_or_default.
|
||||
TEST(CUDAGeneratorTest, PhiloxStateThroughGetGeneratorOrDefault) {
|
||||
at::Generator gen = at::cuda::detail::createCUDAGenerator(0);
|
||||
gen.set_current_seed(999);
|
||||
|
||||
std::optional<at::Generator> opt = gen;
|
||||
const at::Generator& default_gen =
|
||||
at::cuda::detail::getDefaultCUDAGenerator(0);
|
||||
|
||||
at::CUDAGeneratorImpl* impl =
|
||||
at::get_generator_or_default<at::CUDAGeneratorImpl>(opt, default_gen);
|
||||
|
||||
// Initial Philox offset should be 0.
|
||||
ASSERT_EQ(impl->philox_offset_per_thread(), 0u);
|
||||
|
||||
// Advance via philox_engine_inputs.
|
||||
auto [seed, offset] = impl->philox_engine_inputs(4);
|
||||
ASSERT_EQ(seed, 999u);
|
||||
ASSERT_EQ(offset, 0u);
|
||||
ASSERT_EQ(impl->philox_offset_per_thread(), 4u);
|
||||
|
||||
// Further advance via philox_cuda_state.
|
||||
at::PhiloxCudaState state = impl->philox_cuda_state(8);
|
||||
(void)state; // Silence unused variable warning - state is used for its side
|
||||
// effect
|
||||
ASSERT_EQ(impl->philox_offset_per_thread(), 12u);
|
||||
}
|
||||
|
||||
// Seed / offset round-trip through get_generator_or_default.
|
||||
TEST(CUDAGeneratorTest, SeedOffsetRoundTrip) {
|
||||
at::Generator gen = at::cuda::detail::createCUDAGenerator(0);
|
||||
|
||||
std::optional<at::Generator> opt = gen;
|
||||
const at::Generator& default_gen =
|
||||
at::cuda::detail::getDefaultCUDAGenerator(0);
|
||||
|
||||
at::CUDAGeneratorImpl* impl =
|
||||
at::get_generator_or_default<at::CUDAGeneratorImpl>(opt, default_gen);
|
||||
|
||||
impl->set_current_seed(12345);
|
||||
ASSERT_EQ(impl->current_seed(), 12345u);
|
||||
|
||||
impl->set_offset(100);
|
||||
ASSERT_EQ(impl->get_offset(), 100u);
|
||||
|
||||
// seed() should reset the offset.
|
||||
uint64_t new_seed = impl->seed();
|
||||
ASSERT_EQ(impl->get_offset(), 0u);
|
||||
ASSERT_EQ(impl->current_seed(), new_seed);
|
||||
}
|
||||
|
||||
// Clone via the Generator wrapper preserves state.
|
||||
TEST(CUDAGeneratorTest, ClonePreservesState) {
|
||||
at::Generator gen = at::cuda::detail::createCUDAGenerator(0);
|
||||
gen.set_current_seed(777);
|
||||
|
||||
at::CUDAGeneratorImpl* impl = gen.get<at::CUDAGeneratorImpl>();
|
||||
impl->set_philox_offset_per_thread(50);
|
||||
|
||||
at::Generator cloned = gen.clone();
|
||||
at::CUDAGeneratorImpl* cloned_impl = cloned.get<at::CUDAGeneratorImpl>();
|
||||
|
||||
ASSERT_EQ(cloned_impl->current_seed(), 777u);
|
||||
ASSERT_EQ(cloned_impl->philox_offset_per_thread(), 50u);
|
||||
|
||||
// Modifying clone should not affect original.
|
||||
cloned_impl->set_current_seed(888);
|
||||
ASSERT_EQ(impl->current_seed(), 777u);
|
||||
ASSERT_EQ(cloned_impl->current_seed(), 888u);
|
||||
}
|
||||
|
||||
// Verify that CUDAGeneratorImpl::device_type() returns kCUDA.
|
||||
TEST(CUDAGeneratorTest, DeviceTypeStaticMethod) {
|
||||
ASSERT_EQ(at::CUDAGeneratorImpl::device_type(), at::kCUDA);
|
||||
}
|
||||
|
||||
// Verify that constructing CUDAGeneratorImpl with default device_index (-1)
|
||||
// uses the current GPU device.
|
||||
TEST(CUDAGeneratorTest, DefaultDeviceIndex) {
|
||||
at::Generator gen = at::cuda::detail::createCUDAGenerator(-1);
|
||||
ASSERT_TRUE(gen.defined());
|
||||
ASSERT_EQ(gen.device().type(), at::kCUDA);
|
||||
// device index should be the current device (>= 0).
|
||||
ASSERT_GE(gen.device().index(), 0);
|
||||
}
|
||||
|
||||
// Verify that getDefaultCUDAGenerator with default device (-1) resolves to
|
||||
// the current GPU device.
|
||||
TEST(CUDAGeneratorTest, GetDefaultCUDAGeneratorWithDefaultDevice) {
|
||||
const at::Generator& gen = at::cuda::detail::getDefaultCUDAGenerator(-1);
|
||||
ASSERT_TRUE(gen.defined());
|
||||
ASSERT_EQ(gen.device().type(), at::kCUDA);
|
||||
ASSERT_GE(gen.device().index(), 0);
|
||||
}
|
||||
|
||||
// graphsafe_set_state / graphsafe_get_state round-trip.
|
||||
TEST(CUDAGeneratorTest, GraphsafeStateTransfer) {
|
||||
at::Generator gen_a = at::cuda::detail::createCUDAGenerator(0);
|
||||
gen_a.set_current_seed(111);
|
||||
// Clone to get a generator with independent state.
|
||||
at::Generator gen_b = gen_a.clone();
|
||||
gen_b.set_current_seed(222);
|
||||
|
||||
ASSERT_NE(gen_a.current_seed(), gen_b.current_seed());
|
||||
|
||||
// Copy state from gen_a to gen_b.
|
||||
gen_b.graphsafe_set_state(gen_a);
|
||||
ASSERT_EQ(gen_b.current_seed(), 111u);
|
||||
|
||||
// graphsafe_get_state returns a snapshot.
|
||||
at::Generator snapshot = gen_a.graphsafe_get_state();
|
||||
gen_a.set_current_seed(333);
|
||||
ASSERT_EQ(snapshot.current_seed(), 111u);
|
||||
ASSERT_EQ(gen_a.current_seed(), 333u);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Test for createCUDAGenerator independence (AC-2 verification)
|
||||
// ============================================================================
|
||||
|
||||
// Verify that createCUDAGenerator creates a generator with independent state
|
||||
// that does not share RNG state with the default generator.
|
||||
TEST(CUDAGeneratorTest, CreateGeneratorDoesNotShareDefaultState) {
|
||||
// Get the default generator and set its seed.
|
||||
at::Generator default_gen = at::cuda::detail::getDefaultCUDAGenerator(0);
|
||||
default_gen.set_current_seed(1000);
|
||||
ASSERT_EQ(default_gen.current_seed(), 1000u);
|
||||
|
||||
// Create a user generator and set a different seed.
|
||||
at::Generator user_gen = at::cuda::detail::createCUDAGenerator(0);
|
||||
user_gen.set_current_seed(2000);
|
||||
|
||||
// Verify the user generator has the new seed.
|
||||
ASSERT_EQ(user_gen.current_seed(), 2000u);
|
||||
|
||||
// Verify the default generator's seed is unchanged (independence).
|
||||
ASSERT_EQ(default_gen.current_seed(), 1000u);
|
||||
|
||||
// Now change the default generator's seed and verify user is unaffected.
|
||||
default_gen.set_current_seed(3000);
|
||||
ASSERT_EQ(default_gen.current_seed(), 3000u);
|
||||
ASSERT_EQ(user_gen.current_seed(), 2000u); // Still 2000, not affected
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tests for unsafeReleaseGeneratorImpl (AC-3 verification)
|
||||
// ============================================================================
|
||||
|
||||
// Verify that unsafeReleaseGeneratorImpl transfers ownership and makes
|
||||
// the generator undefined.
|
||||
TEST(CUDAGeneratorTest, UnsafeReleaseMakesGeneratorUndefined) {
|
||||
at::Generator gen = at::cuda::detail::createCUDAGenerator(0);
|
||||
gen.set_current_seed(42);
|
||||
ASSERT_TRUE(gen.defined());
|
||||
|
||||
// Release the implementation - this transfers ownership to us.
|
||||
c10::GeneratorImpl* raw_impl = gen.unsafeReleaseGeneratorImpl();
|
||||
ASSERT_NE(raw_impl, nullptr);
|
||||
|
||||
// After release, generator should be undefined.
|
||||
ASSERT_FALSE(gen.defined());
|
||||
|
||||
// We can still access the released impl via the raw pointer.
|
||||
ASSERT_EQ(raw_impl->current_seed(), 42u);
|
||||
|
||||
// Clean up: properly delete the released implementation.
|
||||
delete raw_impl;
|
||||
}
|
||||
|
||||
// Verify that the released pointer can be reclaimed into a new intrusive_ptr
|
||||
// without double-free.
|
||||
TEST(CUDAGeneratorTest, UnsafeReleaseAndReclaim) {
|
||||
at::Generator gen = at::cuda::detail::createCUDAGenerator(0);
|
||||
gen.set_current_seed(123);
|
||||
ASSERT_TRUE(gen.defined());
|
||||
|
||||
// Release the implementation.
|
||||
c10::GeneratorImpl* raw_impl = gen.unsafeReleaseGeneratorImpl();
|
||||
ASSERT_NE(raw_impl, nullptr);
|
||||
ASSERT_FALSE(gen.defined());
|
||||
|
||||
// Reclaim the raw pointer into a new intrusive_ptr.
|
||||
// This should not cause double-free or crashes.
|
||||
c10::intrusive_ptr<c10::GeneratorImpl> reclaimed(
|
||||
c10::intrusive_ptr<c10::GeneratorImpl>::reclaim(raw_impl));
|
||||
ASSERT_TRUE(reclaimed.defined());
|
||||
ASSERT_EQ(reclaimed->current_seed(), 123u);
|
||||
|
||||
// reclaimed will be properly destroyed when it goes out of scope.
|
||||
}
|
||||
|
||||
// Verify that the generator is undefined after release and can be properly
|
||||
// reclaimed.
|
||||
TEST(CUDAGeneratorTest, UnsafeReleaseAndReclaimRoundTrip) {
|
||||
at::Generator gen = at::cuda::detail::createCUDAGenerator(0);
|
||||
gen.set_current_seed(789);
|
||||
ASSERT_TRUE(gen.defined());
|
||||
|
||||
// Release ownership.
|
||||
c10::GeneratorImpl* raw_impl = gen.unsafeReleaseGeneratorImpl();
|
||||
ASSERT_NE(raw_impl, nullptr);
|
||||
ASSERT_FALSE(gen.defined());
|
||||
|
||||
// Verify we can access the impl via raw pointer.
|
||||
ASSERT_EQ(raw_impl->current_seed(), 789u);
|
||||
|
||||
// Reclaim into a new intrusive_ptr.
|
||||
c10::intrusive_ptr<c10::GeneratorImpl> reclaimed(
|
||||
c10::intrusive_ptr<c10::GeneratorImpl>::reclaim(raw_impl));
|
||||
ASSERT_TRUE(reclaimed.defined());
|
||||
|
||||
// Create a new Generator from the reclaimed impl.
|
||||
at::Generator new_gen(reclaimed);
|
||||
ASSERT_TRUE(new_gen.defined());
|
||||
ASSERT_EQ(new_gen.current_seed(), 789u);
|
||||
|
||||
// Modifying new_gen should not affect the old (already undefined) gen.
|
||||
new_gen.set_current_seed(999);
|
||||
ASSERT_EQ(new_gen.current_seed(), 999u);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tests for check_generator device_type validation
|
||||
// ============================================================================
|
||||
|
||||
// check_generator should throw when the generator's device type does not match
|
||||
// the requested implementation type (CPU generator passed where CUDA expected).
|
||||
TEST(CUDAGeneratorTest, CheckGeneratorThrowsOnDeviceTypeMismatch) {
|
||||
// Create a CPU generator (device_type = kCPU).
|
||||
auto cpu_gen =
|
||||
c10::make_intrusive<c10::GeneratorImpl>(c10::Device(c10::kCPU));
|
||||
at::Generator cpu_wrapper(cpu_gen);
|
||||
std::optional<at::Generator> opt = cpu_wrapper;
|
||||
|
||||
// Requesting CUDAGeneratorImpl from a CPU generator should throw.
|
||||
EXPECT_THROW(at::check_generator<at::CUDAGeneratorImpl>(opt),
|
||||
::common::PD_Exception);
|
||||
}
|
||||
|
||||
// check_generator with matching device type should succeed.
|
||||
TEST(CUDAGeneratorTest, CheckGeneratorSucceedsWithMatchingDeviceType) {
|
||||
at::Generator cuda_gen = at::cuda::detail::createCUDAGenerator(0);
|
||||
cuda_gen.set_current_seed(555);
|
||||
std::optional<at::Generator> opt = cuda_gen;
|
||||
|
||||
at::CUDAGeneratorImpl* impl = at::check_generator<at::CUDAGeneratorImpl>(opt);
|
||||
ASSERT_NE(impl, nullptr);
|
||||
ASSERT_EQ(impl->current_seed(), 555u);
|
||||
}
|
||||
|
||||
#endif // PADDLE_WITH_CUDA || PADDLE_WITH_HIP
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <c10/core/GeneratorImpl.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/core/generator.h"
|
||||
|
||||
// ============================================================================
|
||||
// Tests for c10::GeneratorImpl (base class)
|
||||
// ============================================================================
|
||||
|
||||
// ---------- Construction ----------------------------------------------------
|
||||
|
||||
TEST(GeneratorImplTest, ConstructWithNullptrCreatesDefaultGen) {
|
||||
c10::GeneratorImpl impl{c10::Device(c10::kCPU)};
|
||||
ASSERT_NE(impl.paddle_generator(), nullptr);
|
||||
}
|
||||
|
||||
TEST(GeneratorImplTest, ConstructWithExistingGen) {
|
||||
auto gen = std::make_shared<phi::Generator>(42u);
|
||||
c10::GeneratorImpl impl{c10::Device(c10::kCPU), gen};
|
||||
ASSERT_EQ(impl.paddle_generator(), gen);
|
||||
ASSERT_EQ(impl.current_seed(), 42u);
|
||||
}
|
||||
|
||||
// ---------- Seed / offset API (base-class versions) -------------------------
|
||||
|
||||
TEST(GeneratorImplTest, SetAndGetCurrentSeed) {
|
||||
c10::GeneratorImpl impl{c10::Device(c10::kCPU)};
|
||||
impl.set_current_seed(12345);
|
||||
ASSERT_EQ(impl.current_seed(), 12345u);
|
||||
}
|
||||
|
||||
TEST(GeneratorImplTest, SeedGeneratesNewSeed) {
|
||||
c10::GeneratorImpl impl{c10::Device(c10::kCPU)};
|
||||
impl.set_current_seed(1);
|
||||
uint64_t new_seed = impl.seed();
|
||||
// seed() should return a new random seed (very unlikely to be 1 again).
|
||||
// We just verify it returns *something* and updates current_seed.
|
||||
ASSERT_EQ(impl.current_seed(), new_seed);
|
||||
}
|
||||
|
||||
TEST(GeneratorImplTest, GetOffsetInitiallyZero) {
|
||||
c10::GeneratorImpl impl{c10::Device(c10::kCPU)};
|
||||
ASSERT_EQ(impl.get_offset(), 0u);
|
||||
}
|
||||
|
||||
TEST(GeneratorImplTest, SetOffsetForward) {
|
||||
auto gen = std::make_shared<phi::Generator>(100u);
|
||||
c10::GeneratorImpl impl{c10::Device(c10::kCUDA, 0), gen};
|
||||
|
||||
impl.set_offset(10);
|
||||
ASSERT_EQ(impl.get_offset(), 10u);
|
||||
}
|
||||
|
||||
TEST(GeneratorImplTest, SetOffsetBackward) {
|
||||
auto gen = std::make_shared<phi::Generator>(100u);
|
||||
c10::GeneratorImpl impl{c10::Device(c10::kCUDA, 0), gen};
|
||||
|
||||
impl.set_offset(20);
|
||||
ASSERT_EQ(impl.get_offset(), 20u);
|
||||
|
||||
impl.set_offset(5);
|
||||
ASSERT_EQ(impl.get_offset(), 5u);
|
||||
}
|
||||
|
||||
TEST(GeneratorImplTest, SetOffsetSameValue) {
|
||||
auto gen = std::make_shared<phi::Generator>(100u);
|
||||
c10::GeneratorImpl impl{c10::Device(c10::kCUDA, 0), gen};
|
||||
|
||||
impl.set_offset(10);
|
||||
impl.set_offset(10);
|
||||
ASSERT_EQ(impl.get_offset(), 10u);
|
||||
}
|
||||
|
||||
// ---------- Device / DispatchKeySet -----------------------------------------
|
||||
|
||||
TEST(GeneratorImplTest, DeviceReturnsCorrectDevice) {
|
||||
c10::Device cpu_dev{c10::kCPU};
|
||||
c10::GeneratorImpl impl{cpu_dev};
|
||||
ASSERT_EQ(impl.device(), cpu_dev);
|
||||
}
|
||||
|
||||
TEST(GeneratorImplTest, KeySetCPU) {
|
||||
c10::GeneratorImpl impl{c10::Device(c10::kCPU)};
|
||||
c10::DispatchKeySet ks = impl.key_set();
|
||||
ASSERT_TRUE(ks.has(c10::DispatchKey::CPU));
|
||||
}
|
||||
|
||||
TEST(GeneratorImplTest, KeySetCUDA) {
|
||||
c10::GeneratorImpl impl{c10::Device(c10::kCUDA, 0)};
|
||||
c10::DispatchKeySet ks = impl.key_set();
|
||||
ASSERT_TRUE(ks.has(c10::DispatchKey::CUDA));
|
||||
}
|
||||
|
||||
TEST(GeneratorImplTest, KeySetOtherDevice) {
|
||||
// Use kCUSTOM which is neither CPU nor CUDA to exercise the fallback
|
||||
// branch that returns an empty DispatchKeySet.
|
||||
c10::GeneratorImpl impl{c10::Device(c10::kCUSTOM, 0)};
|
||||
c10::DispatchKeySet ks = impl.key_set();
|
||||
ASSERT_FALSE(ks.has(c10::DispatchKey::CPU));
|
||||
ASSERT_FALSE(ks.has(c10::DispatchKey::CUDA));
|
||||
}
|
||||
|
||||
// ---------- Clone -----------------------------------------------------------
|
||||
|
||||
TEST(GeneratorImplTest, ClonePreservesState) {
|
||||
auto gen = std::make_shared<phi::Generator>(42u);
|
||||
c10::GeneratorImpl impl{c10::Device(c10::kCPU), gen};
|
||||
impl.set_current_seed(777);
|
||||
|
||||
auto cloned = impl.clone();
|
||||
ASSERT_NE(cloned.get(), nullptr);
|
||||
ASSERT_EQ(cloned->current_seed(), 777u);
|
||||
ASSERT_EQ(cloned->device(), c10::Device(c10::kCPU));
|
||||
|
||||
cloned->set_current_seed(888);
|
||||
ASSERT_EQ(impl.current_seed(), 777u);
|
||||
ASSERT_EQ(cloned->current_seed(), 888u);
|
||||
}
|
||||
|
||||
// ---------- PyObject binding ------------------------------------------------
|
||||
|
||||
TEST(GeneratorImplTest, PyObjDefaultNull) {
|
||||
c10::GeneratorImpl impl{c10::Device(c10::kCPU)};
|
||||
ASSERT_EQ(impl.pyobj(), nullptr);
|
||||
}
|
||||
|
||||
TEST(GeneratorImplTest, SetAndGetPyObj) {
|
||||
c10::GeneratorImpl impl{c10::Device(c10::kCPU)};
|
||||
|
||||
// Use a dummy pointer (we never dereference it).
|
||||
int dummy = 0;
|
||||
auto* fake_pyobj = reinterpret_cast<PyObject*>(&dummy);
|
||||
|
||||
impl.set_pyobj(fake_pyobj);
|
||||
ASSERT_EQ(impl.pyobj(), fake_pyobj);
|
||||
}
|
||||
|
||||
// ---------- intrusive_ptr refcount semantics --------------------------------
|
||||
|
||||
TEST(GeneratorImplTest, MakeIntrusiveInitialRefcountIsOne) {
|
||||
auto ptr = c10::make_intrusive<c10::GeneratorImpl>(c10::Device(c10::kCPU));
|
||||
ASSERT_EQ(ptr.use_count(), 1u);
|
||||
}
|
||||
|
||||
TEST(GeneratorImplTest, CopyIntrusivePtrIncrementsRefcount) {
|
||||
auto ptr = c10::make_intrusive<c10::GeneratorImpl>(c10::Device(c10::kCPU));
|
||||
ASSERT_EQ(ptr.use_count(), 1u);
|
||||
{
|
||||
auto copy = ptr;
|
||||
ASSERT_EQ(ptr.use_count(), 2u);
|
||||
ASSERT_EQ(copy.use_count(), 2u);
|
||||
}
|
||||
ASSERT_EQ(ptr.use_count(), 1u);
|
||||
}
|
||||
|
||||
TEST(GeneratorImplTest, MoveIntrusivePtrKeepsRefcount) {
|
||||
auto ptr = c10::make_intrusive<c10::GeneratorImpl>(c10::Device(c10::kCPU));
|
||||
c10::GeneratorImpl* raw = ptr.get();
|
||||
auto moved = std::move(ptr);
|
||||
ASSERT_FALSE(ptr.defined());
|
||||
ASSERT_EQ(moved.use_count(), 1u);
|
||||
ASSERT_EQ(moved.get(), raw);
|
||||
}
|
||||
|
||||
// ---------- Internal accessor -----------------------------------------------
|
||||
|
||||
TEST(GeneratorImplTest, PaddleGeneratorAccessor) {
|
||||
auto gen = std::make_shared<phi::Generator>(99u);
|
||||
c10::GeneratorImpl impl{c10::Device(c10::kCPU), gen};
|
||||
ASSERT_EQ(impl.paddle_generator(), gen);
|
||||
ASSERT_EQ(impl.paddle_generator()->GetCurrentSeed(), 99u);
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <c10/util/intrusive_ptr.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
// ============================================================================
|
||||
// Helper: a minimal intrusive_ptr_target subclass that tracks destruction.
|
||||
// ============================================================================
|
||||
|
||||
namespace {
|
||||
|
||||
class TestTarget : public c10::intrusive_ptr_target {
|
||||
public:
|
||||
explicit TestTarget(std::atomic<int>* destroy_count)
|
||||
: destroy_count_(destroy_count) {}
|
||||
|
||||
~TestTarget() override {
|
||||
destroy_count_->fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<int>* destroy_count_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// ============================================================================
|
||||
// Weak reference lifecycle tests
|
||||
// ============================================================================
|
||||
|
||||
// When a weak_intrusive_ptr outlives an intrusive_ptr, the object must NOT be
|
||||
// deleted until the weak_intrusive_ptr is also destroyed.
|
||||
TEST(IntrusivePtrLifecycleTest, WeakPtrKeepsObjectAliveAfterStrongReset) {
|
||||
std::atomic<int> destroy_count{0};
|
||||
|
||||
c10::intrusive_ptr<TestTarget> strong =
|
||||
c10::make_intrusive<TestTarget>(&destroy_count);
|
||||
c10::weak_intrusive_ptr<TestTarget> weak(strong);
|
||||
|
||||
// Object should be alive with both strong and weak references.
|
||||
ASSERT_EQ(destroy_count.load(), 0);
|
||||
ASSERT_EQ(strong.use_count(), 1u);
|
||||
|
||||
// Destroy the strong reference.
|
||||
strong.reset();
|
||||
|
||||
// The object must NOT have been deleted yet: the weak reference keeps it.
|
||||
ASSERT_EQ(destroy_count.load(), 0);
|
||||
|
||||
// lock() should return an empty intrusive_ptr (strong count is 0).
|
||||
c10::intrusive_ptr<TestTarget> locked = weak.lock();
|
||||
ASSERT_FALSE(locked.defined());
|
||||
|
||||
// expired() should be true.
|
||||
ASSERT_TRUE(weak.expired());
|
||||
|
||||
// Destroying the weak reference should trigger deletion.
|
||||
weak.reset();
|
||||
ASSERT_EQ(destroy_count.load(), 1);
|
||||
}
|
||||
|
||||
// When all strong references are released and no weak references exist,
|
||||
// the object is deleted immediately.
|
||||
TEST(IntrusivePtrLifecycleTest, ObjectDeletedImmediatelyWithNoWeakRefs) {
|
||||
std::atomic<int> destroy_count{0};
|
||||
|
||||
{
|
||||
c10::intrusive_ptr<TestTarget> strong =
|
||||
c10::make_intrusive<TestTarget>(&destroy_count);
|
||||
// No weak references created.
|
||||
ASSERT_EQ(destroy_count.load(), 0);
|
||||
} // strong goes out of scope here.
|
||||
|
||||
// Without any weak references, the object should be deleted immediately.
|
||||
ASSERT_EQ(destroy_count.load(), 1);
|
||||
}
|
||||
|
||||
// lock() must return empty when the strong reference count is zero, and
|
||||
// must not resurrect the object.
|
||||
TEST(IntrusivePtrLifecycleTest, LockReturnsEmptyAfterStrongGone) {
|
||||
std::atomic<int> destroy_count{0};
|
||||
|
||||
c10::intrusive_ptr<TestTarget> strong =
|
||||
c10::make_intrusive<TestTarget>(&destroy_count);
|
||||
c10::weak_intrusive_ptr<TestTarget> weak(strong);
|
||||
|
||||
strong.reset(); // Kill the strong reference.
|
||||
|
||||
// lock() must return an empty (undefined) intrusive_ptr.
|
||||
c10::intrusive_ptr<TestTarget> result = weak.lock();
|
||||
ASSERT_FALSE(result.defined());
|
||||
ASSERT_EQ(result.get(), nullptr);
|
||||
|
||||
// Object is still alive (weak reference holds it).
|
||||
ASSERT_EQ(destroy_count.load(), 0);
|
||||
}
|
||||
|
||||
// Multiple weak references: object survives until the LAST weak ref is gone.
|
||||
TEST(IntrusivePtrLifecycleTest, ObjectSurvivesUntilLastWeakRefGone) {
|
||||
std::atomic<int> destroy_count{0};
|
||||
|
||||
c10::intrusive_ptr<TestTarget> strong =
|
||||
c10::make_intrusive<TestTarget>(&destroy_count);
|
||||
c10::weak_intrusive_ptr<TestTarget> weak1(strong);
|
||||
c10::weak_intrusive_ptr<TestTarget> weak2(strong);
|
||||
|
||||
strong.reset(); // Strong gone; two weak refs remain.
|
||||
ASSERT_EQ(destroy_count.load(), 0);
|
||||
|
||||
weak1.reset(); // First weak gone; one weak ref remains.
|
||||
ASSERT_EQ(destroy_count.load(), 0);
|
||||
|
||||
weak2.reset(); // Last weak gone; object should be deleted now.
|
||||
ASSERT_EQ(destroy_count.load(), 1);
|
||||
}
|
||||
|
||||
// Resetting multiple copies of a strong intrusive_ptr should not double-delete.
|
||||
TEST(IntrusivePtrLifecycleTest, MultipleCopiesNoDoubleFree) {
|
||||
std::atomic<int> destroy_count{0};
|
||||
|
||||
c10::intrusive_ptr<TestTarget> a =
|
||||
c10::make_intrusive<TestTarget>(&destroy_count);
|
||||
c10::intrusive_ptr<TestTarget> b = a; // copy: refcount = 2
|
||||
c10::intrusive_ptr<TestTarget> c = a; // copy: refcount = 3
|
||||
c10::weak_intrusive_ptr<TestTarget> weak(a);
|
||||
|
||||
ASSERT_EQ(a.use_count(), 3u);
|
||||
|
||||
a.reset();
|
||||
ASSERT_EQ(destroy_count.load(), 0);
|
||||
|
||||
b.reset();
|
||||
ASSERT_EQ(destroy_count.load(), 0);
|
||||
|
||||
c.reset(); // Strong count drops to zero; weak ref still alive.
|
||||
ASSERT_EQ(destroy_count.load(), 0); // Not deleted yet.
|
||||
|
||||
weak.reset(); // Last reference; object deleted.
|
||||
ASSERT_EQ(destroy_count.load(), 1);
|
||||
}
|
||||
|
||||
// raw::intrusive_ptr::incref/decref should follow the same two-phase lifecycle.
|
||||
TEST(IntrusivePtrLifecycleTest, RawIncrefDecrefTwoPhaseLifecycle) {
|
||||
std::atomic<int> destroy_count{0};
|
||||
|
||||
// Create via make_intrusive (strong=1, weak=1).
|
||||
c10::intrusive_ptr<TestTarget> strong =
|
||||
c10::make_intrusive<TestTarget>(&destroy_count);
|
||||
TestTarget* raw = strong.get();
|
||||
|
||||
// Create a weak reference.
|
||||
c10::weak_intrusive_ptr<TestTarget> weak(strong);
|
||||
|
||||
// Manually add a strong reference via raw API, then decref via raw API.
|
||||
c10::raw::intrusive_ptr::incref(raw); // strong = 2
|
||||
strong.reset(); // strong = 1 via reset_()
|
||||
|
||||
ASSERT_EQ(destroy_count.load(), 0); // Still alive.
|
||||
|
||||
// Decref the raw strong reference to zero.
|
||||
c10::raw::intrusive_ptr::decref(raw); // strong = 0; implicit weak released.
|
||||
|
||||
// Object is not yet deleted because weak reference still exists.
|
||||
ASSERT_EQ(destroy_count.load(), 0);
|
||||
|
||||
weak.reset(); // Weak reference gone; object deleted.
|
||||
ASSERT_EQ(destroy_count.load(), 1);
|
||||
}
|
||||
@@ -0,0 +1,620 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/Layout.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#include <vector>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
// ============== at::zeros sparse tests ==============
|
||||
|
||||
TEST(SparseZerosTest, SparseCOO) {
|
||||
// Dense tensor should return false
|
||||
at::TensorBase tensor = at::zeros({2, 3}, at::kFloat);
|
||||
ASSERT_FALSE(tensor.is_sparse());
|
||||
ASSERT_EQ(tensor.layout(), c10::kStrided);
|
||||
|
||||
// Create sparse COO tensor
|
||||
auto options = c10::TensorOptions().dtype(at::kFloat).layout(at::kSparse);
|
||||
at::TensorBase sparse_tensor = at::zeros({2, 3}, options);
|
||||
ASSERT_TRUE(sparse_tensor.is_sparse());
|
||||
ASSERT_EQ(sparse_tensor.layout(), c10::kSparse);
|
||||
}
|
||||
|
||||
TEST(SparseZerosTest, SparseCsr) {
|
||||
// Create sparse CSR tensor
|
||||
auto options = c10::TensorOptions().dtype(at::kFloat).layout(at::kSparseCsr);
|
||||
at::TensorBase sparse_csr_tensor = at::zeros({2, 3}, options);
|
||||
ASSERT_TRUE(sparse_csr_tensor.is_sparse_csr());
|
||||
ASSERT_TRUE(sparse_csr_tensor.is_sparse());
|
||||
ASSERT_EQ(sparse_csr_tensor.layout(), c10::kSparseCsr);
|
||||
}
|
||||
|
||||
TEST(SparseZerosTest, WithOptionalParams) {
|
||||
// Test zeros with optional parameters
|
||||
at::Tensor sparse_tensor = at::zeros(
|
||||
{2, 3}, at::kFloat, at::kSparse, at::kCPU, /*pin_memory=*/false);
|
||||
ASSERT_TRUE(sparse_tensor.is_sparse());
|
||||
ASSERT_EQ(sparse_tensor.layout(), c10::kSparse);
|
||||
|
||||
at::Tensor sparse_csr_tensor = at::zeros(
|
||||
{2, 3}, at::kFloat, at::kSparseCsr, at::kCPU, /*pin_memory=*/false);
|
||||
ASSERT_TRUE(sparse_csr_tensor.is_sparse_csr());
|
||||
ASSERT_EQ(sparse_csr_tensor.layout(), c10::kSparseCsr);
|
||||
}
|
||||
|
||||
// ============== at::empty sparse tests ==============
|
||||
|
||||
TEST(SparseEmptyTest, SparseCOO) {
|
||||
// Dense tensor should return false
|
||||
at::TensorBase tensor = at::empty({2, 3}, at::kFloat);
|
||||
ASSERT_FALSE(tensor.is_sparse());
|
||||
ASSERT_EQ(tensor.layout(), c10::kStrided);
|
||||
|
||||
// Create sparse COO tensor
|
||||
auto options = c10::TensorOptions().dtype(at::kFloat).layout(at::kSparse);
|
||||
at::TensorBase sparse_tensor = at::empty({2, 3}, options);
|
||||
ASSERT_TRUE(sparse_tensor.is_sparse());
|
||||
ASSERT_EQ(sparse_tensor.layout(), c10::kSparse);
|
||||
}
|
||||
|
||||
TEST(SparseEmptyTest, SparseCsr) {
|
||||
// Create sparse CSR tensor
|
||||
auto options = c10::TensorOptions().dtype(at::kFloat).layout(at::kSparseCsr);
|
||||
at::TensorBase sparse_csr_tensor = at::empty({2, 3}, options);
|
||||
ASSERT_TRUE(sparse_csr_tensor.is_sparse_csr());
|
||||
ASSERT_TRUE(sparse_csr_tensor.is_sparse());
|
||||
ASSERT_EQ(sparse_csr_tensor.layout(), c10::kSparseCsr);
|
||||
}
|
||||
|
||||
TEST(SparseEmptyTest, WithOptionalParams) {
|
||||
// Test empty with optional parameters
|
||||
at::Tensor sparse_tensor = at::empty({2, 3},
|
||||
at::kFloat,
|
||||
at::kSparse,
|
||||
at::kCPU,
|
||||
/*pin_memory=*/false,
|
||||
/*memory_format=*/std::nullopt);
|
||||
ASSERT_TRUE(sparse_tensor.is_sparse());
|
||||
ASSERT_EQ(sparse_tensor.layout(), c10::kSparse);
|
||||
|
||||
at::Tensor sparse_csr_tensor = at::empty({2, 3},
|
||||
at::kFloat,
|
||||
at::kSparseCsr,
|
||||
at::kCPU,
|
||||
/*pin_memory=*/false,
|
||||
/*memory_format=*/std::nullopt);
|
||||
ASSERT_TRUE(sparse_csr_tensor.is_sparse_csr());
|
||||
ASSERT_EQ(sparse_csr_tensor.layout(), c10::kSparseCsr);
|
||||
}
|
||||
|
||||
// ============== at::empty_like sparse tests ==============
|
||||
|
||||
TEST(SparseEmptyLikeTest, SparseCOO) {
|
||||
at::Tensor base_tensor = at::ones({2, 3}, at::kFloat);
|
||||
|
||||
// Dense empty_like should return false
|
||||
at::TensorBase tensor = at::empty_like(base_tensor);
|
||||
ASSERT_FALSE(tensor.is_sparse());
|
||||
ASSERT_EQ(tensor.layout(), c10::kStrided);
|
||||
|
||||
// Create sparse COO tensor using empty_like
|
||||
auto options = c10::TensorOptions().dtype(at::kFloat).layout(at::kSparse);
|
||||
at::TensorBase sparse_tensor = at::empty_like(base_tensor, options);
|
||||
ASSERT_TRUE(sparse_tensor.is_sparse());
|
||||
ASSERT_EQ(sparse_tensor.layout(), c10::kSparse);
|
||||
}
|
||||
|
||||
TEST(SparseEmptyLikeTest, SparseCsr) {
|
||||
at::Tensor base_tensor = at::ones({2, 3}, at::kFloat);
|
||||
|
||||
// Create sparse CSR tensor using empty_like
|
||||
auto options = c10::TensorOptions().dtype(at::kFloat).layout(at::kSparseCsr);
|
||||
at::TensorBase sparse_csr_tensor = at::empty_like(base_tensor, options);
|
||||
ASSERT_TRUE(sparse_csr_tensor.is_sparse_csr());
|
||||
ASSERT_TRUE(sparse_csr_tensor.is_sparse());
|
||||
ASSERT_EQ(sparse_csr_tensor.layout(), c10::kSparseCsr);
|
||||
}
|
||||
|
||||
TEST(SparseEmptyLikeTest, WithOptionalParams) {
|
||||
at::Tensor base_tensor = at::ones({2, 3}, at::kFloat);
|
||||
|
||||
// Test empty_like with optional parameters
|
||||
at::Tensor sparse_tensor = at::empty_like(base_tensor,
|
||||
at::kFloat,
|
||||
at::kSparse,
|
||||
at::kCPU,
|
||||
/*pin_memory=*/false,
|
||||
/*memory_format=*/std::nullopt);
|
||||
ASSERT_TRUE(sparse_tensor.is_sparse());
|
||||
ASSERT_EQ(sparse_tensor.layout(), c10::kSparse);
|
||||
|
||||
at::Tensor sparse_csr_tensor = at::empty_like(base_tensor,
|
||||
at::kFloat,
|
||||
at::kSparseCsr,
|
||||
at::kCPU,
|
||||
/*pin_memory=*/false,
|
||||
/*memory_format=*/std::nullopt);
|
||||
ASSERT_TRUE(sparse_csr_tensor.is_sparse_csr());
|
||||
ASSERT_EQ(sparse_csr_tensor.layout(), c10::kSparseCsr);
|
||||
}
|
||||
|
||||
// ============== at::zeros_like sparse tests ==============
|
||||
|
||||
TEST(SparseZerosLikeTest, SparseCOO) {
|
||||
at::Tensor base_tensor = at::ones({2, 3}, at::kFloat);
|
||||
|
||||
// Dense zeros_like should return false
|
||||
at::TensorBase tensor = at::zeros_like(base_tensor);
|
||||
ASSERT_FALSE(tensor.is_sparse());
|
||||
ASSERT_EQ(tensor.layout(), c10::kStrided);
|
||||
|
||||
// Create sparse COO tensor using zeros_like
|
||||
auto options = c10::TensorOptions().dtype(at::kFloat).layout(at::kSparse);
|
||||
at::TensorBase sparse_tensor = at::zeros_like(base_tensor, options);
|
||||
ASSERT_TRUE(sparse_tensor.is_sparse());
|
||||
ASSERT_EQ(sparse_tensor.layout(), c10::kSparse);
|
||||
}
|
||||
|
||||
TEST(SparseZerosLikeTest, SparseCsr) {
|
||||
at::Tensor base_tensor = at::ones({2, 3}, at::kFloat);
|
||||
|
||||
// Create sparse CSR tensor using zeros_like
|
||||
auto options = c10::TensorOptions().dtype(at::kFloat).layout(at::kSparseCsr);
|
||||
at::TensorBase sparse_csr_tensor = at::zeros_like(base_tensor, options);
|
||||
ASSERT_TRUE(sparse_csr_tensor.is_sparse_csr());
|
||||
ASSERT_TRUE(sparse_csr_tensor.is_sparse());
|
||||
ASSERT_EQ(sparse_csr_tensor.layout(), c10::kSparseCsr);
|
||||
}
|
||||
|
||||
TEST(SparseZerosLikeTest, WithOptionalParams) {
|
||||
at::Tensor base_tensor = at::ones({2, 3}, at::kFloat);
|
||||
|
||||
// Test zeros_like with optional parameters
|
||||
at::Tensor sparse_tensor = at::zeros_like(base_tensor,
|
||||
at::kFloat,
|
||||
at::kSparse,
|
||||
at::kCPU,
|
||||
/*pin_memory=*/false,
|
||||
/*memory_format=*/std::nullopt);
|
||||
ASSERT_TRUE(sparse_tensor.is_sparse());
|
||||
ASSERT_EQ(sparse_tensor.layout(), c10::kSparse);
|
||||
|
||||
at::Tensor sparse_csr_tensor = at::zeros_like(base_tensor,
|
||||
at::kFloat,
|
||||
at::kSparseCsr,
|
||||
at::kCPU,
|
||||
/*pin_memory=*/false,
|
||||
/*memory_format=*/std::nullopt);
|
||||
ASSERT_TRUE(sparse_csr_tensor.is_sparse_csr());
|
||||
ASSERT_EQ(sparse_csr_tensor.layout(), c10::kSparseCsr);
|
||||
}
|
||||
|
||||
// ============== at::sparse_coo_tensor tests ==============
|
||||
|
||||
TEST(SparseConstructorTest, SparseCooTensorBasic) {
|
||||
// Create indices: 2D tensor of shape [sparse_dim, nnz]
|
||||
// For a 3x4 sparse tensor with 2 non-zero elements at (0,1) and (2,3)
|
||||
at::Tensor indices = at::empty({2, 2}, c10::TensorOptions().dtype(at::kLong));
|
||||
int64_t* indices_ptr = indices.data_ptr<int64_t>();
|
||||
indices_ptr[0] = 0; // row index of first non-zero
|
||||
indices_ptr[1] = 2; // row index of second non-zero
|
||||
indices_ptr[2] = 1; // col index of first non-zero
|
||||
indices_ptr[3] = 3; // col index of second non-zero
|
||||
|
||||
// Create values: tensor of shape [nnz]
|
||||
at::Tensor values = at::empty({2}, c10::TensorOptions().dtype(at::kFloat));
|
||||
float* values_ptr = values.data_ptr<float>();
|
||||
values_ptr[0] = 1.0f;
|
||||
values_ptr[1] = 2.0f;
|
||||
|
||||
// Create sparse COO tensor
|
||||
at::Tensor sparse = at::sparse_coo_tensor(indices, values, {3, 4});
|
||||
|
||||
ASSERT_TRUE(sparse.is_sparse());
|
||||
ASSERT_EQ(sparse.layout(), c10::kSparse);
|
||||
}
|
||||
|
||||
TEST(SparseConstructorTest, SparseCooTensorWithOptions) {
|
||||
at::Tensor indices = at::empty({2, 2}, c10::TensorOptions().dtype(at::kLong));
|
||||
int64_t* indices_ptr = indices.data_ptr<int64_t>();
|
||||
indices_ptr[0] = 0;
|
||||
indices_ptr[1] = 1;
|
||||
indices_ptr[2] = 0;
|
||||
indices_ptr[3] = 1;
|
||||
|
||||
at::Tensor values = at::empty({2}, c10::TensorOptions().dtype(at::kFloat));
|
||||
float* values_ptr = values.data_ptr<float>();
|
||||
values_ptr[0] = 3.0f;
|
||||
values_ptr[1] = 4.0f;
|
||||
|
||||
// Create with optional parameters
|
||||
at::Tensor sparse = at::sparse_coo_tensor(indices,
|
||||
values,
|
||||
{2, 2},
|
||||
at::kFloat,
|
||||
at::kSparse,
|
||||
at::kCPU,
|
||||
/*pin_memory=*/false,
|
||||
std::nullopt);
|
||||
|
||||
ASSERT_TRUE(sparse.is_sparse());
|
||||
ASSERT_EQ(sparse.layout(), c10::kSparse);
|
||||
}
|
||||
|
||||
TEST(SparseConstructorTest, SparseCooTensorWithCoalescedOptionTrue) {
|
||||
at::Tensor indices = at::empty({2, 2}, c10::TensorOptions().dtype(at::kLong));
|
||||
int64_t* indices_ptr = indices.data_ptr<int64_t>();
|
||||
indices_ptr[0] = 0;
|
||||
indices_ptr[1] = 1;
|
||||
indices_ptr[2] = 0;
|
||||
indices_ptr[3] = 1;
|
||||
|
||||
at::Tensor values = at::empty({2}, c10::TensorOptions().dtype(at::kFloat));
|
||||
values.data_ptr<float>()[0] = 3.0f;
|
||||
values.data_ptr<float>()[1] = 4.0f;
|
||||
|
||||
at::Tensor sparse =
|
||||
at::sparse_coo_tensor(indices, values, {2, 2}, at::TensorOptions(), true);
|
||||
|
||||
ASSERT_TRUE(sparse.is_sparse());
|
||||
ASSERT_EQ(sparse.layout(), c10::kSparse);
|
||||
ASSERT_TRUE(sparse.is_coalesced());
|
||||
}
|
||||
|
||||
TEST(SparseConstructorTest, SparseCooTensorWithCoalescedOptionFalse) {
|
||||
at::Tensor indices = at::empty({2, 2}, c10::TensorOptions().dtype(at::kLong));
|
||||
int64_t* indices_ptr = indices.data_ptr<int64_t>();
|
||||
indices_ptr[0] = 0;
|
||||
indices_ptr[1] = 1;
|
||||
indices_ptr[2] = 0;
|
||||
indices_ptr[3] = 1;
|
||||
|
||||
at::Tensor values = at::empty({2}, c10::TensorOptions().dtype(at::kFloat));
|
||||
values.data_ptr<float>()[0] = 3.0f;
|
||||
values.data_ptr<float>()[1] = 4.0f;
|
||||
|
||||
at::Tensor sparse = at::sparse_coo_tensor(
|
||||
indices, values, {2, 2}, at::TensorOptions(), false);
|
||||
|
||||
ASSERT_TRUE(sparse.is_sparse());
|
||||
ASSERT_EQ(sparse.layout(), c10::kSparse);
|
||||
ASSERT_FALSE(sparse.is_coalesced());
|
||||
}
|
||||
|
||||
// ============== at::sparse_csr_tensor tests ==============
|
||||
|
||||
TEST(SparseConstructorTest, SparseCsrTensorBasic) {
|
||||
// Create a 3x4 sparse CSR tensor with 4 non-zero elements:
|
||||
// Row 0: values at columns 0, 2
|
||||
// Row 1: value at column 1
|
||||
// Row 2: value at column 3
|
||||
|
||||
// crow_indices: [0, 2, 3, 4] - compressed row pointers
|
||||
at::Tensor crow_indices =
|
||||
at::empty({4}, c10::TensorOptions().dtype(at::kLong));
|
||||
int64_t* crow_ptr = crow_indices.data_ptr<int64_t>();
|
||||
crow_ptr[0] = 0;
|
||||
crow_ptr[1] = 2;
|
||||
crow_ptr[2] = 3;
|
||||
crow_ptr[3] = 4;
|
||||
|
||||
// col_indices: [0, 2, 1, 3] - column indices
|
||||
at::Tensor col_indices =
|
||||
at::empty({4}, c10::TensorOptions().dtype(at::kLong));
|
||||
int64_t* col_ptr = col_indices.data_ptr<int64_t>();
|
||||
col_ptr[0] = 0;
|
||||
col_ptr[1] = 2;
|
||||
col_ptr[2] = 1;
|
||||
col_ptr[3] = 3;
|
||||
|
||||
// values: [1.0, 2.0, 3.0, 4.0]
|
||||
at::Tensor values = at::empty({4}, c10::TensorOptions().dtype(at::kFloat));
|
||||
float* values_ptr = values.data_ptr<float>();
|
||||
values_ptr[0] = 1.0f;
|
||||
values_ptr[1] = 2.0f;
|
||||
values_ptr[2] = 3.0f;
|
||||
values_ptr[3] = 4.0f;
|
||||
|
||||
// Create sparse CSR tensor
|
||||
at::Tensor sparse = at::sparse_csr_tensor(
|
||||
crow_indices, col_indices, values, {3, 4}, at::TensorOptions());
|
||||
|
||||
ASSERT_TRUE(sparse.is_sparse_csr());
|
||||
ASSERT_TRUE(sparse.is_sparse());
|
||||
ASSERT_EQ(sparse.layout(), c10::kSparseCsr);
|
||||
}
|
||||
|
||||
TEST(SparseConstructorTest, SparseCsrTensorWithOptions) {
|
||||
// Create a simple 2x2 sparse CSR tensor
|
||||
at::Tensor crow_indices =
|
||||
at::empty({3}, c10::TensorOptions().dtype(at::kLong));
|
||||
int64_t* crow_ptr = crow_indices.data_ptr<int64_t>();
|
||||
crow_ptr[0] = 0;
|
||||
crow_ptr[1] = 1;
|
||||
crow_ptr[2] = 2;
|
||||
|
||||
at::Tensor col_indices =
|
||||
at::empty({2}, c10::TensorOptions().dtype(at::kLong));
|
||||
int64_t* col_ptr = col_indices.data_ptr<int64_t>();
|
||||
col_ptr[0] = 0;
|
||||
col_ptr[1] = 1;
|
||||
|
||||
at::Tensor values = at::empty({2}, c10::TensorOptions().dtype(at::kFloat));
|
||||
float* values_ptr = values.data_ptr<float>();
|
||||
values_ptr[0] = 5.0f;
|
||||
values_ptr[1] = 6.0f;
|
||||
|
||||
// Create with optional parameters
|
||||
at::Tensor sparse = at::sparse_csr_tensor(crow_indices,
|
||||
col_indices,
|
||||
values,
|
||||
{2, 2},
|
||||
at::kFloat,
|
||||
at::kSparseCsr,
|
||||
at::kCPU,
|
||||
/*pin_memory=*/false);
|
||||
|
||||
ASSERT_TRUE(sparse.is_sparse_csr());
|
||||
ASSERT_TRUE(sparse.is_sparse());
|
||||
ASSERT_EQ(sparse.layout(), c10::kSparseCsr);
|
||||
}
|
||||
|
||||
TEST(SparseConstructorTest, SparseCsrTensorMismatchedOptionsDtypeIgnored) {
|
||||
// PyTorch ignores dtype mismatch in sparse_csr_tensor;
|
||||
// the resulting tensor uses values' original dtype.
|
||||
at::Tensor crow_indices =
|
||||
at::empty({3}, c10::TensorOptions().dtype(at::kLong));
|
||||
int64_t* crow_ptr = crow_indices.data_ptr<int64_t>();
|
||||
crow_ptr[0] = 0;
|
||||
crow_ptr[1] = 1;
|
||||
crow_ptr[2] = 2;
|
||||
|
||||
at::Tensor col_indices =
|
||||
at::empty({2}, c10::TensorOptions().dtype(at::kLong));
|
||||
int64_t* col_ptr = col_indices.data_ptr<int64_t>();
|
||||
col_ptr[0] = 0;
|
||||
col_ptr[1] = 1;
|
||||
|
||||
at::Tensor values = at::empty({2}, c10::TensorOptions().dtype(at::kFloat));
|
||||
float* values_ptr = values.data_ptr<float>();
|
||||
values_ptr[0] = 5.0f;
|
||||
values_ptr[1] = 6.0f;
|
||||
|
||||
std::vector<int64_t> size = {2, 2};
|
||||
auto options = c10::TensorOptions().dtype(at::kDouble);
|
||||
|
||||
at::Tensor sparse =
|
||||
at::sparse_csr_tensor(crow_indices, col_indices, values, size, options);
|
||||
|
||||
// Result should use values' dtype (float), not options' dtype (double).
|
||||
ASSERT_EQ(sparse.dtype(), at::kFloat);
|
||||
}
|
||||
|
||||
// ============== Additional sparse_coo_tensor tests ==============
|
||||
|
||||
TEST(SparseConstructorTest, SparseCooTensorInferSize) {
|
||||
// Test sparse_coo_tensor with inferred size (no explicit size parameter)
|
||||
at::Tensor indices = at::empty({2, 3}, c10::TensorOptions().dtype(at::kLong));
|
||||
int64_t* indices_ptr = indices.data_ptr<int64_t>();
|
||||
// 3 non-zero elements at (0,0), (1,1), (2,2)
|
||||
indices_ptr[0] = 0;
|
||||
indices_ptr[1] = 1;
|
||||
indices_ptr[2] = 2;
|
||||
indices_ptr[3] = 0;
|
||||
indices_ptr[4] = 1;
|
||||
indices_ptr[5] = 2;
|
||||
|
||||
at::Tensor values = at::empty({3}, c10::TensorOptions().dtype(at::kFloat));
|
||||
float* values_ptr = values.data_ptr<float>();
|
||||
values_ptr[0] = 1.0f;
|
||||
values_ptr[1] = 2.0f;
|
||||
values_ptr[2] = 3.0f;
|
||||
|
||||
// Create sparse COO tensor with inferred size
|
||||
at::Tensor sparse = at::sparse_coo_tensor(indices, values);
|
||||
|
||||
ASSERT_TRUE(sparse.is_sparse());
|
||||
ASSERT_EQ(sparse.layout(), c10::kSparse);
|
||||
ASSERT_EQ(sparse.dim(), 2);
|
||||
ASSERT_EQ(sparse.size(0), 3);
|
||||
ASSERT_EQ(sparse.size(1), 3);
|
||||
}
|
||||
|
||||
TEST(SparseConstructorTest, SparseCooTensorInferSizeWithCoalescedOption) {
|
||||
at::Tensor indices = at::empty({2, 2}, c10::TensorOptions().dtype(at::kLong));
|
||||
int64_t* indices_ptr = indices.data_ptr<int64_t>();
|
||||
indices_ptr[0] = 0;
|
||||
indices_ptr[1] = 2;
|
||||
indices_ptr[2] = 1;
|
||||
indices_ptr[3] = 3;
|
||||
|
||||
at::Tensor values = at::empty({2}, c10::TensorOptions().dtype(at::kFloat));
|
||||
values.data_ptr<float>()[0] = 1.0f;
|
||||
values.data_ptr<float>()[1] = 2.0f;
|
||||
|
||||
at::Tensor sparse =
|
||||
at::sparse_coo_tensor(indices, values, at::TensorOptions(), true);
|
||||
|
||||
ASSERT_TRUE(sparse.is_sparse());
|
||||
ASSERT_EQ(sparse.layout(), c10::kSparse);
|
||||
ASSERT_EQ(sparse.size(0), 3);
|
||||
ASSERT_EQ(sparse.size(1), 4);
|
||||
ASSERT_TRUE(sparse.is_coalesced());
|
||||
}
|
||||
|
||||
TEST(SparseConstructorTest, SparseCooTensorDouble) {
|
||||
// Test sparse_coo_tensor with double dtype
|
||||
at::Tensor indices = at::empty({2, 2}, c10::TensorOptions().dtype(at::kLong));
|
||||
int64_t* indices_ptr = indices.data_ptr<int64_t>();
|
||||
indices_ptr[0] = 0;
|
||||
indices_ptr[1] = 1;
|
||||
indices_ptr[2] = 0;
|
||||
indices_ptr[3] = 1;
|
||||
|
||||
at::Tensor values = at::empty({2}, c10::TensorOptions().dtype(at::kDouble));
|
||||
double* values_ptr = values.data_ptr<double>();
|
||||
values_ptr[0] = 1.5;
|
||||
values_ptr[1] = 2.5;
|
||||
|
||||
at::Tensor sparse = at::sparse_coo_tensor(indices, values, {2, 2});
|
||||
|
||||
ASSERT_TRUE(sparse.is_sparse());
|
||||
ASSERT_EQ(sparse.layout(), c10::kSparse);
|
||||
}
|
||||
|
||||
TEST(SparseConstructorTest, SparseCooTensor3D) {
|
||||
// Test 3D sparse COO tensor
|
||||
at::Tensor indices = at::empty({3, 2}, c10::TensorOptions().dtype(at::kLong));
|
||||
int64_t* indices_ptr = indices.data_ptr<int64_t>();
|
||||
// 2 non-zero elements at (0,1,2) and (1,0,1)
|
||||
indices_ptr[0] = 0;
|
||||
indices_ptr[1] = 1;
|
||||
indices_ptr[2] = 1;
|
||||
indices_ptr[3] = 0;
|
||||
indices_ptr[4] = 2;
|
||||
indices_ptr[5] = 1;
|
||||
|
||||
at::Tensor values = at::empty({2}, c10::TensorOptions().dtype(at::kFloat));
|
||||
float* values_ptr = values.data_ptr<float>();
|
||||
values_ptr[0] = 5.0f;
|
||||
values_ptr[1] = 6.0f;
|
||||
|
||||
at::Tensor sparse = at::sparse_coo_tensor(indices, values, {2, 2, 3});
|
||||
|
||||
ASSERT_TRUE(sparse.is_sparse());
|
||||
ASSERT_EQ(sparse.layout(), c10::kSparse);
|
||||
}
|
||||
|
||||
// ============== Additional sparse_csr_tensor tests ==============
|
||||
|
||||
TEST(SparseConstructorTest, SparseCsrTensorDouble) {
|
||||
// Test sparse_csr_tensor with double dtype
|
||||
at::Tensor crow_indices =
|
||||
at::empty({3}, c10::TensorOptions().dtype(at::kLong));
|
||||
int64_t* crow_ptr = crow_indices.data_ptr<int64_t>();
|
||||
crow_ptr[0] = 0;
|
||||
crow_ptr[1] = 1;
|
||||
crow_ptr[2] = 2;
|
||||
|
||||
at::Tensor col_indices =
|
||||
at::empty({2}, c10::TensorOptions().dtype(at::kLong));
|
||||
int64_t* col_ptr = col_indices.data_ptr<int64_t>();
|
||||
col_ptr[0] = 0;
|
||||
col_ptr[1] = 1;
|
||||
|
||||
at::Tensor values = at::empty({2}, c10::TensorOptions().dtype(at::kDouble));
|
||||
double* values_ptr = values.data_ptr<double>();
|
||||
values_ptr[0] = 1.5;
|
||||
values_ptr[1] = 2.5;
|
||||
|
||||
at::Tensor sparse = at::sparse_csr_tensor(
|
||||
crow_indices, col_indices, values, {2, 2}, at::TensorOptions());
|
||||
|
||||
ASSERT_TRUE(sparse.is_sparse_csr());
|
||||
ASSERT_TRUE(sparse.is_sparse());
|
||||
ASSERT_EQ(sparse.layout(), c10::kSparseCsr);
|
||||
}
|
||||
|
||||
TEST(SparseConstructorTest, SparseCsrTensorLarger) {
|
||||
// Test larger sparse CSR tensor (4x5 with 6 non-zero elements)
|
||||
// Row 0: values at columns 1, 3
|
||||
// Row 1: value at column 2
|
||||
// Row 2: values at columns 0, 4
|
||||
// Row 3: value at column 2
|
||||
|
||||
at::Tensor crow_indices =
|
||||
at::empty({5}, c10::TensorOptions().dtype(at::kLong));
|
||||
int64_t* crow_ptr = crow_indices.data_ptr<int64_t>();
|
||||
crow_ptr[0] = 0;
|
||||
crow_ptr[1] = 2;
|
||||
crow_ptr[2] = 3;
|
||||
crow_ptr[3] = 5;
|
||||
crow_ptr[4] = 6;
|
||||
|
||||
at::Tensor col_indices =
|
||||
at::empty({6}, c10::TensorOptions().dtype(at::kLong));
|
||||
int64_t* col_ptr = col_indices.data_ptr<int64_t>();
|
||||
col_ptr[0] = 1;
|
||||
col_ptr[1] = 3;
|
||||
col_ptr[2] = 2;
|
||||
col_ptr[3] = 0;
|
||||
col_ptr[4] = 4;
|
||||
col_ptr[5] = 2;
|
||||
|
||||
at::Tensor values = at::empty({6}, c10::TensorOptions().dtype(at::kFloat));
|
||||
float* values_ptr = values.data_ptr<float>();
|
||||
values_ptr[0] = 1.0f;
|
||||
values_ptr[1] = 2.0f;
|
||||
values_ptr[2] = 3.0f;
|
||||
values_ptr[3] = 4.0f;
|
||||
values_ptr[4] = 5.0f;
|
||||
values_ptr[5] = 6.0f;
|
||||
|
||||
at::Tensor sparse = at::sparse_csr_tensor(
|
||||
crow_indices, col_indices, values, {4, 5}, at::TensorOptions());
|
||||
|
||||
ASSERT_TRUE(sparse.is_sparse_csr());
|
||||
ASSERT_TRUE(sparse.is_sparse());
|
||||
ASSERT_EQ(sparse.layout(), c10::kSparseCsr);
|
||||
}
|
||||
|
||||
TEST(SparseConstructorTest, SparseCsrTensorEmpty) {
|
||||
// Test sparse CSR tensor with no non-zero elements
|
||||
at::Tensor crow_indices =
|
||||
at::empty({4}, c10::TensorOptions().dtype(at::kLong));
|
||||
int64_t* crow_ptr = crow_indices.data_ptr<int64_t>();
|
||||
crow_ptr[0] = 0;
|
||||
crow_ptr[1] = 0;
|
||||
crow_ptr[2] = 0;
|
||||
crow_ptr[3] = 0;
|
||||
|
||||
at::Tensor col_indices =
|
||||
at::empty({0}, c10::TensorOptions().dtype(at::kLong));
|
||||
|
||||
at::Tensor values = at::empty({0}, c10::TensorOptions().dtype(at::kFloat));
|
||||
|
||||
at::Tensor sparse = at::sparse_csr_tensor(
|
||||
crow_indices, col_indices, values, {3, 3}, at::TensorOptions());
|
||||
|
||||
ASSERT_TRUE(sparse.is_sparse_csr());
|
||||
ASSERT_TRUE(sparse.is_sparse());
|
||||
ASSERT_EQ(sparse.layout(), c10::kSparseCsr);
|
||||
}
|
||||
|
||||
// ============== Sparse tensor interoperability tests ==============
|
||||
|
||||
TEST(SparseInteropTest, SparseCsrFromZeros) {
|
||||
// Create sparse CSR tensor from zeros
|
||||
auto options = c10::TensorOptions().dtype(at::kFloat).layout(at::kSparseCsr);
|
||||
at::Tensor sparse = at::zeros({4, 4}, options);
|
||||
|
||||
ASSERT_TRUE(sparse.is_sparse_csr());
|
||||
ASSERT_TRUE(sparse.is_sparse());
|
||||
ASSERT_EQ(sparse.layout(), c10::kSparseCsr);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <ATen/Functions.h>
|
||||
#include <ATen/core/TensorBody.h>
|
||||
#include <ATen/cuda/EmptyTensor.h>
|
||||
#include <ATen/native/cuda/Resize.h>
|
||||
#include <ATen/ops/tensor.h>
|
||||
#include <c10/core/Layout.h>
|
||||
#include <c10/core/ScalarType.h>
|
||||
#include <c10/core/SymInt.h>
|
||||
#include <c10/core/TensorOptions.h>
|
||||
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
|
||||
#include <c10/cuda/CUDAFunctions.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#endif
|
||||
#include "ATen/ATen.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "paddle/phi/common/float16.h"
|
||||
#include "torch/all.h"
|
||||
|
||||
TEST(TensorBaseTest, IsSameAPI) {
|
||||
// Test is_same() API - checks if two tensors share the same underlying data
|
||||
at::Tensor tensor1 = at::ones({2, 3}, at::kFloat);
|
||||
at::TensorBase tensor2 = tensor1; // Same tensor
|
||||
at::TensorBase tensor3 = at::ones({2, 3}, at::kFloat); // Different tensor
|
||||
|
||||
// tensor1 and tensor2 should point to the same underlying tensor
|
||||
ASSERT_TRUE(tensor1.is_same(tensor2));
|
||||
ASSERT_TRUE(tensor2.is_same(tensor1));
|
||||
|
||||
// tensor1 and tensor3 should be different tensors
|
||||
ASSERT_FALSE(tensor1.is_same(tensor3));
|
||||
ASSERT_FALSE(tensor3.is_same(tensor1));
|
||||
|
||||
// A tensor should be the same as itself
|
||||
ASSERT_TRUE(tensor1.is_same(tensor1));
|
||||
|
||||
// Test with view - in Paddle, view creates a new tensor implementation
|
||||
// even though they may share underlying data storage
|
||||
at::TensorBase view_tensor = tensor1.view({6});
|
||||
// View tensor has different impl pointer in Paddle
|
||||
ASSERT_FALSE(tensor1.is_same(view_tensor));
|
||||
|
||||
// Test with undefined tensors
|
||||
at::TensorBase undefined1;
|
||||
at::TensorBase undefined2;
|
||||
ASSERT_TRUE(undefined1.is_same(undefined2)); // Both undefined
|
||||
}
|
||||
|
||||
TEST(TensorBaseTest, UseCountAPI) {
|
||||
// Test use_count() API - returns reference count of underlying tensor
|
||||
at::Tensor tensor1 = at::ones({2, 3}, at::kFloat);
|
||||
|
||||
// Initial reference count should be 1
|
||||
ASSERT_EQ(tensor1.use_count(), 1);
|
||||
|
||||
// Create a copy - reference count should increase
|
||||
at::TensorBase tensor2 = tensor1;
|
||||
ASSERT_EQ(tensor1.use_count(), 2);
|
||||
ASSERT_EQ(tensor2.use_count(), 2);
|
||||
|
||||
// Create another copy
|
||||
at::TensorBase tensor3 = tensor1;
|
||||
ASSERT_EQ(tensor1.use_count(), 3);
|
||||
ASSERT_EQ(tensor2.use_count(), 3);
|
||||
ASSERT_EQ(tensor3.use_count(), 3);
|
||||
|
||||
// Reset one copy - reference count should decrease
|
||||
tensor2.reset();
|
||||
ASSERT_EQ(tensor1.use_count(), 2);
|
||||
ASSERT_EQ(tensor3.use_count(), 2);
|
||||
|
||||
// Reset another copy
|
||||
tensor3.reset();
|
||||
ASSERT_EQ(tensor1.use_count(), 1);
|
||||
|
||||
// Test with view - in Paddle, view creates a new tensor with separate impl
|
||||
// So the use_count remains 1 for each
|
||||
{
|
||||
at::TensorBase view_tensor = tensor1.view({6});
|
||||
// Each tensor has its own impl, so use_count is 1 for each
|
||||
ASSERT_EQ(tensor1.use_count(), 1);
|
||||
ASSERT_EQ(view_tensor.use_count(), 1);
|
||||
}
|
||||
// After view goes out of scope
|
||||
ASSERT_EQ(tensor1.use_count(), 1);
|
||||
}
|
||||
|
||||
TEST(TensorBaseTest, WeakUseCountAPI) {
|
||||
// Test weak_use_count() API
|
||||
// Compat exposes PyTorch-visible semantics: live TensorImpl wrappers report
|
||||
// the self weak-reference count as 1.
|
||||
at::TensorBase tensor1 = at::ones({2, 3}, at::kFloat);
|
||||
|
||||
ASSERT_EQ(tensor1.weak_use_count(), 1);
|
||||
|
||||
at::TensorBase tensor2 = tensor1;
|
||||
ASSERT_EQ(tensor1.weak_use_count(), 1);
|
||||
ASSERT_EQ(tensor2.weak_use_count(), 1);
|
||||
|
||||
// Test with undefined tensor
|
||||
at::TensorBase undefined_tensor;
|
||||
ASSERT_EQ(undefined_tensor.weak_use_count(), 0);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,828 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <torch/library.h>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/cpp/utils/exception_test_utils.h"
|
||||
#include "torch/csrc/jit/schema_type_parser.h"
|
||||
|
||||
c10::FunctionSchema ParseAsSchema(const std::string& schema_text) {
|
||||
auto parsed = torch::jit::parseSchemaOrName(schema_text);
|
||||
EXPECT_TRUE(std::holds_alternative<c10::FunctionSchema>(parsed))
|
||||
<< "schema: " << schema_text;
|
||||
return std::get<c10::FunctionSchema>(std::move(parsed));
|
||||
}
|
||||
|
||||
c10::Argument MakeSchemaTestArg(const std::string& name,
|
||||
int32_t n,
|
||||
int64_t default_v,
|
||||
bool kwarg_only,
|
||||
std::optional<c10::AliasInfo> alias_info) {
|
||||
return c10::Argument(name,
|
||||
c10::IntType::get(),
|
||||
c10::FloatType::get(),
|
||||
std::optional<int32_t>(n),
|
||||
std::optional<torch::IValue>(torch::IValue(default_v)),
|
||||
kwarg_only,
|
||||
std::move(alias_info));
|
||||
}
|
||||
|
||||
void ExpectAssignedArgFields(const c10::Argument& arg,
|
||||
const std::string& expected_name,
|
||||
int32_t expected_n,
|
||||
int64_t expected_default_v,
|
||||
bool expected_kwarg_only,
|
||||
bool expected_is_out) {
|
||||
EXPECT_EQ(arg.name(), expected_name);
|
||||
EXPECT_EQ(arg.type()->kind(), c10::TypeKind::IntType);
|
||||
EXPECT_EQ(arg.real_type()->kind(), c10::TypeKind::FloatType);
|
||||
EXPECT_TRUE(arg.N().has_value());
|
||||
EXPECT_TRUE(arg.default_value().has_value());
|
||||
if (!arg.N().has_value() || !arg.default_value().has_value()) {
|
||||
return;
|
||||
}
|
||||
EXPECT_EQ(*arg.N(), expected_n);
|
||||
EXPECT_EQ(arg.default_value()->to_int(), expected_default_v);
|
||||
EXPECT_EQ(arg.kwarg_only(), expected_kwarg_only);
|
||||
EXPECT_EQ(arg.is_out(), expected_is_out);
|
||||
}
|
||||
|
||||
c10::TypePtr MakeSchemaTuple(std::vector<c10::TypePtr> types) {
|
||||
return c10::makeSchemaTupleType(std::move(types));
|
||||
}
|
||||
|
||||
void ExpectTypeText(const c10::Type& type,
|
||||
const std::string& expected_str,
|
||||
const std::string& expected_annotation) {
|
||||
EXPECT_EQ(type.str(), expected_str);
|
||||
EXPECT_EQ(type.annotation_str(), expected_annotation);
|
||||
}
|
||||
|
||||
TEST(schema_parser_type_test, ArgumentCopyAssignmentCoversAliasBranches) {
|
||||
// Reason: cover Argument::operator=(const Argument&) alias branches, plus
|
||||
// AliasInfo stream/equality branches in one place to reduce test split.
|
||||
c10::AliasInfo alias(/*is_write=*/true, std::set<std::string>{"a"}, {"b"});
|
||||
c10::Argument assigned_arg;
|
||||
c10::Argument with_alias =
|
||||
MakeSchemaTestArg("out",
|
||||
3,
|
||||
5,
|
||||
/*kwarg_only=*/true,
|
||||
std::optional<c10::AliasInfo>(alias));
|
||||
assigned_arg = with_alias;
|
||||
ExpectAssignedArgFields(assigned_arg,
|
||||
"out",
|
||||
3,
|
||||
5,
|
||||
/*kwarg_only=*/true,
|
||||
/*is_out=*/true);
|
||||
ASSERT_NE(assigned_arg.alias_info(), nullptr);
|
||||
ASSERT_NE(with_alias.alias_info(), nullptr);
|
||||
EXPECT_NE(assigned_arg.alias_info(), with_alias.alias_info());
|
||||
EXPECT_EQ(*assigned_arg.alias_info(), *with_alias.alias_info());
|
||||
|
||||
// Explicitly hit copy-assignment branch where rhs.alias_info_ is nullptr.
|
||||
c10::Argument no_alias_src =
|
||||
MakeSchemaTestArg("plain", 2, 9, /*kwarg_only=*/false, std::nullopt);
|
||||
assigned_arg = no_alias_src;
|
||||
ExpectAssignedArgFields(assigned_arg,
|
||||
"plain",
|
||||
2,
|
||||
9,
|
||||
/*kwarg_only=*/false,
|
||||
/*is_out=*/false);
|
||||
EXPECT_EQ(assigned_arg.alias_info(), nullptr);
|
||||
|
||||
// self-assignment should be a no-op.
|
||||
assigned_arg = assigned_arg;
|
||||
ExpectAssignedArgFields(assigned_arg,
|
||||
"plain",
|
||||
2,
|
||||
9,
|
||||
/*kwarg_only=*/false,
|
||||
/*is_out=*/false);
|
||||
|
||||
c10::AliasInfo child(
|
||||
/*is_write=*/false, std::set<std::string>{"inner"}, {"inner"});
|
||||
c10::AliasInfo rich_alias(
|
||||
/*is_write=*/true, std::set<std::string>{"a", "b"}, {"c", "d"});
|
||||
rich_alias.addContainedType(child);
|
||||
|
||||
ASSERT_EQ(rich_alias.containedTypes().size(), 1UL);
|
||||
EXPECT_FALSE(rich_alias.containedTypes()[0].isWrite());
|
||||
EXPECT_EQ(rich_alias.containedTypes()[0].beforeSets().count("inner"), 1UL);
|
||||
EXPECT_EQ(rich_alias.containedTypes()[0].afterSets().count("inner"), 1UL);
|
||||
|
||||
c10::AliasInfo same_alias(
|
||||
/*is_write=*/true, std::set<std::string>{"a", "b"}, {"c", "d"});
|
||||
same_alias.addContainedType(child);
|
||||
EXPECT_TRUE(rich_alias == same_alias);
|
||||
|
||||
c10::AliasInfo different_write(
|
||||
/*is_write=*/false, std::set<std::string>{"a", "b"}, {"c", "d"});
|
||||
EXPECT_FALSE(rich_alias == different_write);
|
||||
|
||||
c10::AliasInfo different_sets(
|
||||
/*is_write=*/true, std::set<std::string>{"a"}, {"c", "d"});
|
||||
EXPECT_FALSE(rich_alias == different_sets);
|
||||
|
||||
std::ostringstream with_arrow;
|
||||
with_arrow << rich_alias;
|
||||
const std::string with_arrow_str = with_arrow.str();
|
||||
ASSERT_FALSE(with_arrow_str.empty());
|
||||
EXPECT_EQ(with_arrow_str.front(), '(');
|
||||
EXPECT_EQ(with_arrow_str.back(), ')');
|
||||
EXPECT_NE(with_arrow_str.find('!'), std::string::npos);
|
||||
EXPECT_NE(with_arrow_str.find(" -> "), std::string::npos);
|
||||
EXPECT_NE(with_arrow_str.find("a"), std::string::npos);
|
||||
EXPECT_NE(with_arrow_str.find("b"), std::string::npos);
|
||||
EXPECT_NE(with_arrow_str.find("c"), std::string::npos);
|
||||
EXPECT_NE(with_arrow_str.find("d"), std::string::npos);
|
||||
size_t pipe_count = 0;
|
||||
for (char ch : with_arrow_str) {
|
||||
if (ch == '|') {
|
||||
++pipe_count;
|
||||
}
|
||||
}
|
||||
EXPECT_GE(pipe_count, 2UL);
|
||||
|
||||
c10::AliasInfo no_arrow(
|
||||
/*is_write=*/false, std::set<std::string>{"z"}, {"z"});
|
||||
std::ostringstream no_arrow_stream;
|
||||
no_arrow_stream << no_arrow;
|
||||
EXPECT_EQ(no_arrow_stream.str(), "(z)");
|
||||
}
|
||||
|
||||
TEST(schema_parser_type_test, AliasInfoHashWorksInUnorderedContainers) {
|
||||
EXPECT_NE(::hash_combine(1U, 2U), 1U);
|
||||
|
||||
c10::AliasInfo child(
|
||||
/*is_write=*/false, std::set<std::string>{"inner"}, {"inner"});
|
||||
c10::AliasInfo alias(
|
||||
/*is_write=*/true, std::set<std::string>{"a", "b"}, {"c", "d"});
|
||||
alias.addContainedType(child);
|
||||
|
||||
c10::AliasInfo same_alias(
|
||||
/*is_write=*/true, std::set<std::string>{"b", "a"}, {"d", "c"});
|
||||
same_alias.addContainedType(c10::AliasInfo(
|
||||
/*is_write=*/false, std::set<std::string>{"inner"}, {"inner"}));
|
||||
|
||||
c10::AliasInfo different_alias(
|
||||
/*is_write=*/true, std::set<std::string>{"a", "b"}, {"c", "d"});
|
||||
different_alias.addContainedType(c10::AliasInfo(
|
||||
/*is_write=*/true, std::set<std::string>{"inner"}, {"inner"}));
|
||||
|
||||
std::hash<c10::AliasInfo> hasher;
|
||||
const auto hash_alias = hasher(alias);
|
||||
const auto hash_same_alias = hasher(same_alias);
|
||||
const auto hash_different_alias = hasher(different_alias);
|
||||
EXPECT_EQ(hash_alias, hash_same_alias);
|
||||
EXPECT_NE(hash_alias, hash_different_alias);
|
||||
|
||||
// Also hit zero-sized before/after/contained loops in std::hash<AliasInfo>.
|
||||
const auto hash_empty = hasher(c10::AliasInfo());
|
||||
EXPECT_NE(hash_empty, hash_alias);
|
||||
|
||||
std::unordered_set<c10::AliasInfo> alias_set;
|
||||
alias_set.insert(alias);
|
||||
alias_set.insert(same_alias);
|
||||
alias_set.insert(different_alias);
|
||||
EXPECT_EQ(alias_set.size(), 2UL);
|
||||
EXPECT_EQ(alias_set.count(alias), 1UL);
|
||||
EXPECT_EQ(alias_set.count(same_alias), 1UL);
|
||||
EXPECT_EQ(alias_set.count(different_alias), 1UL);
|
||||
|
||||
std::unordered_map<c10::AliasInfo, int> alias_map;
|
||||
alias_map.emplace(alias, 1);
|
||||
++alias_map[same_alias];
|
||||
++alias_map[different_alias];
|
||||
EXPECT_EQ(alias_map.size(), 2UL);
|
||||
EXPECT_EQ(alias_map.at(alias), 2);
|
||||
EXPECT_EQ(alias_map.at(different_alias), 1);
|
||||
}
|
||||
|
||||
TEST(schema_parser_type_test, TorchCodecSchemasSmoke) {
|
||||
struct SchemaCase {
|
||||
const char* reason;
|
||||
const char* schema;
|
||||
};
|
||||
|
||||
// Reason: keep a compact smoke set that exercises torchcodec-like grammar
|
||||
// shapes without duplicating all type-structure assertions in one test.
|
||||
const std::vector<SchemaCase> schemas = {
|
||||
{"optional string arg + default None",
|
||||
"create_from_file(str filename, str? seek_mode=None) -> Tensor"},
|
||||
{"multiple optional numeric args + void return",
|
||||
"encode_audio_to_file(Tensor samples, int sample_rate, str filename, "
|
||||
"int? bit_rate=None, int? num_channels=None, int? "
|
||||
"desired_sample_rate=None) -> ()"},
|
||||
{"alias annotation + kw-only section + defaults",
|
||||
"add_audio_stream(Tensor(a!) decoder, *, int? stream_index=None, int? "
|
||||
"sample_rate=None, int? num_channels=None) -> ()"},
|
||||
{"multi-value return list",
|
||||
"get_next_frame(Tensor(a!) decoder) -> (Tensor, Tensor, Tensor)"},
|
||||
{"kw-only arg in call-site-sensitive API",
|
||||
"get_frame_at_index(Tensor(a!) decoder, *, int frame_index) -> (Tensor, "
|
||||
"Tensor, Tensor)"},
|
||||
{"bool return + kw-only args",
|
||||
"_test_frame_pts_equality(Tensor(a!) decoder, *, int frame_index, float "
|
||||
"pts_seconds_to_test) -> bool"},
|
||||
{"no-arg function + string return",
|
||||
"_get_json_ffmpeg_library_versions() -> str"}};
|
||||
|
||||
for (const auto& test_case : schemas) {
|
||||
EXPECT_NO_THROW({
|
||||
auto parsed = torch::jit::parseSchemaOrName(test_case.schema);
|
||||
EXPECT_TRUE(std::holds_alternative<c10::FunctionSchema>(parsed))
|
||||
<< "reason: " << test_case.reason << ", schema: " << test_case.schema;
|
||||
}) << "reason: "
|
||||
<< test_case.reason << ", schema: " << test_case.schema;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(schema_parser_type_test, OptionalTypeShapes) {
|
||||
// Reason: cover optional scalar arg, optional tuple arg, and optional return.
|
||||
{
|
||||
const auto schema = ParseAsSchema(
|
||||
"get_frames_by_pts_in_range_audio(Tensor(a!) decoder, *, float "
|
||||
"start_seconds, float? stop_seconds) -> (Tensor, Tensor)");
|
||||
ASSERT_EQ(schema.arguments().size(), 3UL);
|
||||
|
||||
const auto& stop_seconds = schema.arguments()[2];
|
||||
ASSERT_EQ(stop_seconds.name(), "stop_seconds");
|
||||
ASSERT_NE(stop_seconds.type(), nullptr);
|
||||
EXPECT_EQ(stop_seconds.type()->kind(), c10::TypeKind::OptionalType);
|
||||
const auto optional_inner = stop_seconds.type()->containedTypes();
|
||||
ASSERT_EQ(optional_inner.size(), 1UL);
|
||||
EXPECT_EQ(optional_inner[0]->kind(), c10::TypeKind::FloatType);
|
||||
}
|
||||
|
||||
{
|
||||
const auto schema = ParseAsSchema(
|
||||
"_add_video_stream(Tensor(a!) decoder, *, (Tensor, Tensor, Tensor)? "
|
||||
"custom_frame_mappings=None) -> ()");
|
||||
ASSERT_EQ(schema.arguments().size(), 2UL);
|
||||
|
||||
const auto& mappings = schema.arguments()[1];
|
||||
ASSERT_EQ(mappings.name(), "custom_frame_mappings");
|
||||
ASSERT_NE(mappings.type(), nullptr);
|
||||
EXPECT_EQ(mappings.type()->kind(), c10::TypeKind::OptionalType);
|
||||
const auto optional_inner = mappings.type()->containedTypes();
|
||||
ASSERT_EQ(optional_inner.size(), 1UL);
|
||||
EXPECT_EQ(optional_inner[0]->kind(), c10::TypeKind::TupleType);
|
||||
|
||||
const auto tuple_elements = optional_inner[0]->containedTypes();
|
||||
ASSERT_EQ(tuple_elements.size(), 3UL);
|
||||
EXPECT_EQ(tuple_elements[0]->kind(), c10::TypeKind::TensorType);
|
||||
EXPECT_EQ(tuple_elements[1]->kind(), c10::TypeKind::TensorType);
|
||||
EXPECT_EQ(tuple_elements[2]->kind(), c10::TypeKind::TensorType);
|
||||
}
|
||||
|
||||
{
|
||||
const auto schema =
|
||||
ParseAsSchema("maybe_decode(Tensor decoder) -> Tensor?");
|
||||
ASSERT_EQ(schema.returns().size(), 1UL);
|
||||
ASSERT_NE(schema.returns()[0].type(), nullptr);
|
||||
EXPECT_EQ(schema.returns()[0].type()->kind(), c10::TypeKind::OptionalType);
|
||||
const auto optional_inner = schema.returns()[0].type()->containedTypes();
|
||||
ASSERT_EQ(optional_inner.size(), 1UL);
|
||||
EXPECT_EQ(optional_inner[0]->kind(), c10::TypeKind::TensorType);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(schema_parser_type_test, KwOnlyDefaultAndAliasMetadata) {
|
||||
// Reason: parser should preserve kw-only/default/alias metadata for callers.
|
||||
const auto schema = ParseAsSchema(
|
||||
"alias_and_kwonly(Tensor(a! -> b) x, *, int? idx=None, str "
|
||||
"mode=\"nearest\") -> ()");
|
||||
ASSERT_EQ(schema.arguments().size(), 3UL);
|
||||
|
||||
const auto& x = schema.arguments()[0];
|
||||
EXPECT_FALSE(x.kwarg_only());
|
||||
ASSERT_NE(x.alias_info(), nullptr);
|
||||
EXPECT_TRUE(x.alias_info()->isWrite());
|
||||
EXPECT_EQ(x.alias_info()->beforeSets().count("a"), 1UL);
|
||||
EXPECT_EQ(x.alias_info()->afterSets().count("b"), 1UL);
|
||||
|
||||
const auto& idx = schema.arguments()[1];
|
||||
EXPECT_TRUE(idx.kwarg_only());
|
||||
ASSERT_TRUE(idx.default_value().has_value());
|
||||
EXPECT_TRUE(idx.default_value()->is_none());
|
||||
ASSERT_NE(idx.type(), nullptr);
|
||||
EXPECT_EQ(idx.type()->kind(), c10::TypeKind::OptionalType);
|
||||
ASSERT_EQ(idx.type()->containedTypes().size(), 1UL);
|
||||
EXPECT_EQ(idx.type()->containedTypes()[0]->kind(), c10::TypeKind::IntType);
|
||||
|
||||
const auto& mode = schema.arguments()[2];
|
||||
EXPECT_TRUE(mode.kwarg_only());
|
||||
ASSERT_TRUE(mode.default_value().has_value());
|
||||
EXPECT_EQ(mode.default_value()->to_string(), "nearest");
|
||||
}
|
||||
|
||||
TEST(schema_parser_type_test, MultiReturnVersusTupleReturn) {
|
||||
// Reason: "(T1, T2)" is two return slots, not one Tuple return.
|
||||
const auto multi_ret = ParseAsSchema("f(Tensor x) -> (Tensor, Tensor)");
|
||||
ASSERT_EQ(multi_ret.returns().size(), 2UL);
|
||||
EXPECT_EQ(multi_ret.returns()[0].type()->kind(), c10::TypeKind::TensorType);
|
||||
EXPECT_EQ(multi_ret.returns()[1].type()->kind(), c10::TypeKind::TensorType);
|
||||
|
||||
// Reason: a single Tuple return needs an extra layer of parentheses.
|
||||
const auto tuple_ret =
|
||||
ParseAsSchema("f_tuple(Tensor x) -> ((Tensor, Tensor))");
|
||||
ASSERT_EQ(tuple_ret.returns().size(), 1UL);
|
||||
ASSERT_NE(tuple_ret.returns()[0].type(), nullptr);
|
||||
EXPECT_EQ(tuple_ret.returns()[0].type()->kind(), c10::TypeKind::TupleType);
|
||||
const auto tuple_elems = tuple_ret.returns()[0].type()->containedTypes();
|
||||
ASSERT_EQ(tuple_elems.size(), 2UL);
|
||||
EXPECT_EQ(tuple_elems[0]->kind(), c10::TypeKind::TensorType);
|
||||
EXPECT_EQ(tuple_elems[1]->kind(), c10::TypeKind::TensorType);
|
||||
}
|
||||
|
||||
TEST(schema_parser_type_test, ParseEntryBoundariesAndVariadicValidation) {
|
||||
// Reason: keep parse entrypoint boundary checks and variadic behavior in one
|
||||
// place because both target parseName/parseSchema contract edges.
|
||||
EXPECT_EQ(torch::jit::parseName("just_name"), "just_name");
|
||||
|
||||
const auto named_schema = torch::jit::parseSchema("named(int x) -> int");
|
||||
ASSERT_EQ(named_schema.arguments().size(), 1UL);
|
||||
EXPECT_EQ(named_schema.arguments()[0].name(), "x");
|
||||
|
||||
EXPECT_ANY_THROW(torch::jit::parseSchema("name_only"));
|
||||
EXPECT_ANY_THROW(torch::jit::parseName("has_schema(int x) -> int"));
|
||||
|
||||
// Variadic arg/ret markers should map to FunctionSchema flags.
|
||||
const auto variadic = ParseAsSchema("variadic(Tensor x, ...) -> ...");
|
||||
EXPECT_TRUE(variadic.is_vararg());
|
||||
EXPECT_TRUE(variadic.is_varret());
|
||||
ASSERT_EQ(variadic.arguments().size(), 1UL);
|
||||
EXPECT_EQ(variadic.arguments()[0].name(), "x");
|
||||
EXPECT_TRUE(variadic.returns().empty());
|
||||
|
||||
// Parser currently forbids defaults when vararg is present.
|
||||
EXPECT_ANY_THROW(torch::jit::parseSchema("broken(int x=1, ...) -> int"));
|
||||
}
|
||||
|
||||
TEST(schema_parser_type_test, SchemaTypeTreeDebugStringCoversNestedTypes) {
|
||||
// Reason: appendTypeTree() is only used by debug-tree rendering and should
|
||||
// recursively print nested Optional/Tuple structures.
|
||||
const auto schema = ParseAsSchema(
|
||||
"tree_dbg((Tensor, float?)? payload=None) -> (Tensor, Tensor?)");
|
||||
const auto debug_str = torch::jit::schemaTypeTreeToDebugString(schema);
|
||||
|
||||
EXPECT_NE(debug_str.find("schema_type_tree"), std::string::npos);
|
||||
EXPECT_NE(debug_str.find("arg[0] `payload`"), std::string::npos);
|
||||
EXPECT_NE(debug_str.find("kind=OptionalType"), std::string::npos);
|
||||
EXPECT_NE(debug_str.find("kind=TupleType"), std::string::npos);
|
||||
EXPECT_NE(debug_str.find("kind=TensorType"), std::string::npos);
|
||||
EXPECT_NE(debug_str.find("kind=FloatType"), std::string::npos);
|
||||
EXPECT_NE(debug_str.find("ret[1] ``"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST(schema_parser_type_test, DefaultsFixedListAndNamedReturns) {
|
||||
// Reason: cover default-literal parsing branches and fixed-size list suffix.
|
||||
const auto schema = ParseAsSchema(
|
||||
R"schema(defaults(int[3]? xs=None, bool bt=true, bool bf=false, str ident=truevalue, float exp=1.25e-1, int plus=+42, str esc="a\n\t\r\\\'\"\q") -> (Tensor out, int idx))schema");
|
||||
|
||||
ASSERT_EQ(schema.arguments().size(), 7UL);
|
||||
const auto& xs = schema.arguments()[0];
|
||||
ASSERT_TRUE(xs.N().has_value());
|
||||
EXPECT_EQ(*xs.N(), 3);
|
||||
ASSERT_NE(xs.type(), nullptr);
|
||||
EXPECT_EQ(xs.type()->kind(), c10::TypeKind::OptionalType);
|
||||
ASSERT_TRUE(xs.default_value().has_value());
|
||||
EXPECT_TRUE(xs.default_value()->is_none());
|
||||
|
||||
const auto& bt = schema.arguments()[1];
|
||||
ASSERT_TRUE(bt.default_value().has_value());
|
||||
EXPECT_TRUE(bt.default_value()->is_bool());
|
||||
EXPECT_TRUE(bt.default_value()->to_bool());
|
||||
|
||||
const auto& bf = schema.arguments()[2];
|
||||
ASSERT_TRUE(bf.default_value().has_value());
|
||||
EXPECT_TRUE(bf.default_value()->is_bool());
|
||||
EXPECT_FALSE(bf.default_value()->to_bool());
|
||||
|
||||
const auto& ident = schema.arguments()[3];
|
||||
ASSERT_TRUE(ident.default_value().has_value());
|
||||
EXPECT_TRUE(ident.default_value()->is_string());
|
||||
EXPECT_EQ(ident.default_value()->to_string(), "truevalue");
|
||||
|
||||
const auto& exp = schema.arguments()[4];
|
||||
ASSERT_TRUE(exp.default_value().has_value());
|
||||
EXPECT_TRUE(exp.default_value()->is_double());
|
||||
EXPECT_DOUBLE_EQ(exp.default_value()->to_double(), 0.125);
|
||||
|
||||
const auto& plus = schema.arguments()[5];
|
||||
ASSERT_TRUE(plus.default_value().has_value());
|
||||
EXPECT_TRUE(plus.default_value()->is_int());
|
||||
EXPECT_EQ(plus.default_value()->to_int(), 42);
|
||||
|
||||
const auto& esc = schema.arguments()[6];
|
||||
ASSERT_TRUE(esc.default_value().has_value());
|
||||
EXPECT_TRUE(esc.default_value()->is_string());
|
||||
const std::string esc_value = esc.default_value()->to_string();
|
||||
EXPECT_NE(esc_value.find('\n'), std::string::npos);
|
||||
EXPECT_NE(esc_value.find('\t'), std::string::npos);
|
||||
EXPECT_NE(esc_value.find('\r'), std::string::npos);
|
||||
EXPECT_NE(esc_value.find('\\'), std::string::npos);
|
||||
EXPECT_NE(esc_value.find('\''), std::string::npos);
|
||||
EXPECT_NE(esc_value.find('"'), std::string::npos);
|
||||
EXPECT_EQ(esc_value.back(), 'q');
|
||||
|
||||
ASSERT_EQ(schema.returns().size(), 2UL);
|
||||
EXPECT_EQ(schema.returns()[0].name(), "out");
|
||||
EXPECT_EQ(schema.returns()[0].type()->kind(), c10::TypeKind::TensorType);
|
||||
EXPECT_EQ(schema.returns()[1].name(), "idx");
|
||||
EXPECT_EQ(schema.returns()[1].type()->kind(), c10::TypeKind::IntType);
|
||||
}
|
||||
|
||||
TEST(schema_parser_type_test, ParserErrorMatrixAndDiagnostics) {
|
||||
// Reason: combine parser branch errors and message-specific diagnostics in
|
||||
// one table-driven matrix to reduce duplication with the same parse path.
|
||||
const auto empty = torch::jit::parseSchemaOrName(" ");
|
||||
ASSERT_TRUE(std::holds_alternative<std::string>(empty));
|
||||
EXPECT_EQ(std::get<std::string>(empty), "");
|
||||
|
||||
struct ParserErrorCase {
|
||||
const char* schema;
|
||||
const char* expected_substr; // null -> only require throw
|
||||
};
|
||||
const std::vector<ParserErrorCase> cases = {
|
||||
{"op(int x) -> int trailing extra", nullptr},
|
||||
{"dup_vararg(..., ...) -> int", nullptr},
|
||||
{"vararg_not_last(..., int x) -> int", nullptr},
|
||||
{"dup_varret() -> (..., ...)", nullptr},
|
||||
{"varret_not_last() -> (..., int)", nullptr},
|
||||
{"missing_default(int x=) -> int", nullptr},
|
||||
{"unsupported_default(int x=@) -> int", nullptr},
|
||||
{"identifier_for_non_string(int x=cpu) -> int", nullptr},
|
||||
{"malformed_number(float x=1e) -> int", nullptr},
|
||||
{"overflow_number(int x=999999999999999999999999999999999999999999)",
|
||||
nullptr},
|
||||
{"(int x) -> int", nullptr},
|
||||
{"missing_arg_name(int ) -> int", nullptr},
|
||||
{"missing_unsigned(int[] x) -> int", nullptr},
|
||||
{"missing_arrow(int x) int", nullptr},
|
||||
{"missing_rparen(int x -> int", nullptr},
|
||||
{"invalid_fixed_size(int[999999999999999999999999999999999999] x) -> int",
|
||||
nullptr},
|
||||
{"unterminated(str s=\"abc", nullptr},
|
||||
{R"schema(unterminated_escape(str s="abc\)schema", nullptr},
|
||||
{"bad(double x) -> int", "Use `float` instead of `double`"},
|
||||
{"bad(int64_t x) -> int", "Use `int` instead of `int64_t`"},
|
||||
{"bad(foo.bar x) -> int", "Unsupported type specifier `foo.bar`"},
|
||||
{"alias_missing_rparen(Tensor(a! x) -> ()", "Expected `)`"},
|
||||
{"alias_bad_set(Tensor(!) x) -> ()", "Expected alias set"}};
|
||||
|
||||
for (const auto& test_case : cases) {
|
||||
if (test_case.expected_substr == nullptr) {
|
||||
EXPECT_ANY_THROW(torch::jit::parseSchemaOrName(test_case.schema))
|
||||
<< "schema: " << test_case.schema;
|
||||
continue;
|
||||
}
|
||||
test::utils::ExpectThrowContains<std::exception>(
|
||||
[&]() { (void)torch::jit::parseSchemaOrName(test_case.schema); },
|
||||
test_case.expected_substr,
|
||||
std::string("schema: ") + test_case.schema);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(schema_parser_type_test, SchemaTypeParserAliasAndTupleForms) {
|
||||
// Reason: cover alias shapes and tuple parsing in a single table-like test.
|
||||
{
|
||||
const auto schema =
|
||||
ParseAsSchema("fresh_alias(Tensor! a, Tensor! b) -> ()");
|
||||
ASSERT_EQ(schema.arguments().size(), 2UL);
|
||||
|
||||
const auto* alias_a = schema.arguments()[0].alias_info();
|
||||
ASSERT_NE(alias_a, nullptr);
|
||||
EXPECT_TRUE(alias_a->isWrite());
|
||||
EXPECT_EQ(alias_a->beforeSets().count("$0"), 1UL);
|
||||
EXPECT_EQ(alias_a->afterSets().count("$0"), 1UL);
|
||||
|
||||
const auto* alias_b = schema.arguments()[1].alias_info();
|
||||
ASSERT_NE(alias_b, nullptr);
|
||||
EXPECT_TRUE(alias_b->isWrite());
|
||||
EXPECT_EQ(alias_b->beforeSets().count("$1"), 1UL);
|
||||
EXPECT_EQ(alias_b->afterSets().count("$1"), 1UL);
|
||||
}
|
||||
|
||||
{
|
||||
const auto schema = ParseAsSchema("wild_alias(Tensor(*! -> a|*) x) -> ()");
|
||||
ASSERT_EQ(schema.arguments().size(), 1UL);
|
||||
const auto* alias = schema.arguments()[0].alias_info();
|
||||
ASSERT_NE(alias, nullptr);
|
||||
EXPECT_TRUE(alias->isWrite());
|
||||
EXPECT_EQ(alias->beforeSets().count("*"), 1UL);
|
||||
EXPECT_EQ(alias->afterSets().count("a"), 1UL);
|
||||
EXPECT_EQ(alias->afterSets().count("*"), 1UL);
|
||||
}
|
||||
|
||||
{
|
||||
const auto schema =
|
||||
ParseAsSchema("tuple_alias((Tensor(a), Tensor(b!)) x) -> ()");
|
||||
ASSERT_EQ(schema.arguments().size(), 1UL);
|
||||
const auto* alias = schema.arguments()[0].alias_info();
|
||||
ASSERT_NE(alias, nullptr);
|
||||
EXPECT_TRUE(alias->beforeSets().empty());
|
||||
EXPECT_TRUE(alias->afterSets().empty());
|
||||
ASSERT_EQ(alias->containedTypes().size(), 2UL);
|
||||
EXPECT_EQ(alias->containedTypes()[0].beforeSets().count("a"), 1UL);
|
||||
EXPECT_FALSE(alias->containedTypes()[0].isWrite());
|
||||
EXPECT_EQ(alias->containedTypes()[1].beforeSets().count("b"), 1UL);
|
||||
EXPECT_TRUE(alias->containedTypes()[1].isWrite());
|
||||
}
|
||||
|
||||
{
|
||||
const auto schema = ParseAsSchema("empty_tuple_arg(() x) -> ()");
|
||||
ASSERT_EQ(schema.arguments().size(), 1UL);
|
||||
ASSERT_NE(schema.arguments()[0].type(), nullptr);
|
||||
EXPECT_EQ(schema.arguments()[0].type()->kind(), c10::TypeKind::TupleType);
|
||||
EXPECT_TRUE(schema.arguments()[0].type()->containedTypes().empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(schema_parser_type_test, SchemaTypeParserCtorRejectsNullPointers) {
|
||||
// Reason: cover refFromPtr null-check branch.
|
||||
size_t pos = 0;
|
||||
size_t fresh_id = 0;
|
||||
EXPECT_ANY_THROW(
|
||||
(void)torch::jit::SchemaTypeParser("Tensor", nullptr, &fresh_id));
|
||||
EXPECT_ANY_THROW((void)torch::jit::SchemaTypeParser("Tensor", &pos, nullptr));
|
||||
}
|
||||
|
||||
TEST(schema_parser_type_test, JitTypeAtomicTypeOperatorsAndAnnotation) {
|
||||
// Reason: cover jit_type.h atomic helpers used by schema parser internals.
|
||||
auto tensor_type_a =
|
||||
c10::makeSchemaAtomicType(c10::TypeKind::TensorType, "Tensor");
|
||||
auto tensor_type_b =
|
||||
c10::makeSchemaAtomicType(c10::TypeKind::TensorType, "Tensor");
|
||||
auto int_type = c10::makeSchemaAtomicType(c10::TypeKind::IntType, "int");
|
||||
|
||||
ASSERT_NE(tensor_type_a, nullptr);
|
||||
ASSERT_NE(tensor_type_b, nullptr);
|
||||
ASSERT_NE(int_type, nullptr);
|
||||
|
||||
// Hits SchemaAtomicType::equals and global Type operator!=.
|
||||
EXPECT_TRUE(*tensor_type_a == *tensor_type_b);
|
||||
EXPECT_FALSE(*tensor_type_a != *tensor_type_b);
|
||||
EXPECT_TRUE(*tensor_type_a != *int_type);
|
||||
|
||||
// Hits SchemaAtomicType::annotation_str_impl.
|
||||
EXPECT_EQ(tensor_type_a->annotation_str(), "Tensor");
|
||||
}
|
||||
|
||||
TEST(schema_parser_type_test, JitTypeBaseWrapperAndAnnotationBranches) {
|
||||
// Reason: cover jit_type_base.h wrapper constructors/assignments and
|
||||
// Type::annotation_str/repr_str/createWithContained branches.
|
||||
c10::SingletonOrSharedTypePtr<c10::Type> from_singleton(c10::IntType::get());
|
||||
c10::SingletonOrSharedTypePtr<c10::IntType> from_singleton_exact(
|
||||
c10::IntType::get());
|
||||
c10::SingletonOrSharedTypePtr<c10::Type> from_nullptr(nullptr);
|
||||
EXPECT_EQ(from_nullptr, nullptr);
|
||||
EXPECT_FALSE(static_cast<bool>(from_nullptr));
|
||||
|
||||
c10::SingletonOrSharedTypePtr<c10::Type> assigned;
|
||||
assigned = from_singleton;
|
||||
ASSERT_NE(assigned, nullptr);
|
||||
ASSERT_NE(assigned.get(), nullptr);
|
||||
ASSERT_NE(from_singleton_exact.get(), nullptr);
|
||||
|
||||
auto shared_atomic =
|
||||
c10::makeSchemaAtomicType(c10::TypeKind::DynamicType, "shared");
|
||||
ASSERT_NE(shared_atomic, nullptr);
|
||||
c10::Type& shared_as_base = *shared_atomic;
|
||||
auto shared_roundtrip = shared_as_base.cast<c10::detail::SchemaAtomicType>();
|
||||
ASSERT_NE(shared_roundtrip, nullptr);
|
||||
EXPECT_EQ(shared_roundtrip->str(), "shared");
|
||||
|
||||
const c10::Type& shared_as_const = *shared_atomic;
|
||||
auto shared_const_roundtrip =
|
||||
shared_as_const.cast<c10::detail::SchemaAtomicType>();
|
||||
ASSERT_NE(shared_const_roundtrip, nullptr);
|
||||
EXPECT_EQ(shared_const_roundtrip->str(), "shared");
|
||||
|
||||
c10::SingletonOrSharedTypePtr<c10::detail::SchemaAtomicType> shared_exact_ptr(
|
||||
shared_roundtrip);
|
||||
c10::SingletonOrSharedTypePtr<c10::Type> shared_base_ptr(shared_roundtrip);
|
||||
ASSERT_NE(shared_exact_ptr.get(), nullptr);
|
||||
ASSERT_NE(shared_base_ptr.get(), nullptr);
|
||||
|
||||
EXPECT_EQ(shared_as_base.cast<c10::detail::SchemaTupleType>(), nullptr);
|
||||
EXPECT_EQ(shared_as_const.cast<c10::detail::SchemaTupleType>(), nullptr);
|
||||
|
||||
c10::Type& int_as_base = *c10::IntType::get();
|
||||
ASSERT_TRUE(static_cast<bool>(int_as_base.cast<c10::IntType>()));
|
||||
EXPECT_FALSE(static_cast<bool>(int_as_base.cast<c10::TensorType>()));
|
||||
|
||||
const c10::Type& int_as_const = *c10::IntType::get();
|
||||
ASSERT_TRUE(static_cast<bool>(int_as_const.cast<c10::IntType>()));
|
||||
EXPECT_FALSE(static_cast<bool>(int_as_const.cast<c10::TensorType>()));
|
||||
|
||||
EXPECT_EQ(from_singleton, c10::IntType::get());
|
||||
EXPECT_EQ(c10::IntType::get(), from_singleton);
|
||||
EXPECT_NE(shared_base_ptr, c10::IntType::get());
|
||||
EXPECT_NE(from_singleton, shared_base_ptr);
|
||||
|
||||
const c10::Type& plain = *c10::TensorType::get();
|
||||
const c10::TypePrinter rename_printer = [](const c10::Type&) {
|
||||
return std::optional<std::string>("renamed");
|
||||
};
|
||||
const c10::TypePrinter passthrough_printer = [](const c10::Type&) {
|
||||
return std::optional<std::string>();
|
||||
};
|
||||
EXPECT_EQ(plain.annotation_str(), "Tensor");
|
||||
EXPECT_EQ(plain.annotation_str(rename_printer), "renamed");
|
||||
EXPECT_EQ(plain.annotation_str(passthrough_printer), "Tensor");
|
||||
EXPECT_EQ(plain.repr_str(), "Tensor");
|
||||
|
||||
test::utils::ExpectThrowContains<std::exception>(
|
||||
[&]() {
|
||||
(void)plain.createWithContained(
|
||||
std::vector<c10::TypePtr>{c10::IntType::get()});
|
||||
},
|
||||
"type with contained types did not overload createWithContained");
|
||||
}
|
||||
|
||||
TEST(schema_parser_type_test, JitTypeBaseEqualityAndSubtypeBranches) {
|
||||
// Reason: cover operator== symmetric/asymmetric branches and
|
||||
// Type::isSubtypeOfExt optional-inner matching.
|
||||
class AsymmetricRhsType final : public c10::Type {
|
||||
public:
|
||||
AsymmetricRhsType() : Type(c10::TypeKind::DynamicType) {}
|
||||
|
||||
bool equals(const c10::Type& rhs) const override {
|
||||
(void)rhs;
|
||||
++equals_calls_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool symmetric() const override { return false; }
|
||||
|
||||
std::string str() const override { return "asymmetric_rhs"; }
|
||||
|
||||
int equals_calls() const { return equals_calls_; }
|
||||
|
||||
private:
|
||||
mutable int equals_calls_{0};
|
||||
};
|
||||
|
||||
const c10::Type& lhs = *c10::TensorType::get();
|
||||
AsymmetricRhsType rhs;
|
||||
EXPECT_TRUE(lhs == rhs);
|
||||
EXPECT_EQ(rhs.equals_calls(), 1);
|
||||
|
||||
EXPECT_TRUE(*c10::IntType::get() == *c10::IntType::get());
|
||||
EXPECT_FALSE(*c10::IntType::get() == *c10::FloatType::get());
|
||||
|
||||
auto optional_dynamic =
|
||||
c10::makeSchemaOptionalType(c10::TypePtr(c10::IntType::get()));
|
||||
EXPECT_TRUE(c10::IntType::get()->isSubtypeOfExt(*optional_dynamic, nullptr));
|
||||
EXPECT_FALSE(
|
||||
c10::TensorType::get()->isSubtypeOfExt(*optional_dynamic, nullptr));
|
||||
}
|
||||
|
||||
TEST(schema_parser_type_test, JitTypeSchemaContainerBranchMatrix) {
|
||||
// Reason: cover uncovered Optional/Tuple branches in jit_type.h.
|
||||
auto optional_int = c10::makeSchemaOptionalType(c10::IntType::get());
|
||||
auto optional_float = c10::makeSchemaOptionalType(c10::FloatType::get());
|
||||
auto optional_int_same = c10::makeSchemaOptionalType(c10::IntType::get());
|
||||
ASSERT_NE(optional_int, nullptr);
|
||||
ASSERT_NE(optional_float, nullptr);
|
||||
ASSERT_NE(optional_int_same, nullptr);
|
||||
|
||||
EXPECT_FALSE(*optional_int == *c10::IntType::get());
|
||||
EXPECT_TRUE(*optional_int == *optional_int_same);
|
||||
EXPECT_FALSE(*optional_int == *optional_float);
|
||||
|
||||
auto optional_rebound =
|
||||
optional_int->createWithContained({c10::FloatType::get()});
|
||||
ASSERT_NE(optional_rebound, nullptr);
|
||||
EXPECT_EQ(optional_rebound->kind(), c10::TypeKind::OptionalType);
|
||||
ASSERT_EQ(optional_rebound->containedTypes().size(), 1UL);
|
||||
EXPECT_EQ(optional_rebound->containedTypes()[0]->kind(),
|
||||
c10::TypeKind::FloatType);
|
||||
EXPECT_EQ(optional_rebound->annotation_str(), "Optional[float]");
|
||||
|
||||
test::utils::ExpectThrowContains<std::exception>(
|
||||
[&]() {
|
||||
(void)optional_int->createWithContained(
|
||||
{c10::IntType::get(), c10::FloatType::get()});
|
||||
},
|
||||
"Optional type expects exactly one contained type");
|
||||
|
||||
auto tuple_int_float =
|
||||
MakeSchemaTuple({c10::IntType::get(), c10::FloatType::get()});
|
||||
auto tuple_int = MakeSchemaTuple({c10::IntType::get()});
|
||||
auto tuple_int_bool =
|
||||
MakeSchemaTuple({c10::IntType::get(), c10::BoolType::get()});
|
||||
auto tuple_int_float_same =
|
||||
MakeSchemaTuple({c10::IntType::get(), c10::FloatType::get()});
|
||||
ASSERT_NE(tuple_int_float, nullptr);
|
||||
ASSERT_NE(tuple_int, nullptr);
|
||||
ASSERT_NE(tuple_int_bool, nullptr);
|
||||
ASSERT_NE(tuple_int_float_same, nullptr);
|
||||
|
||||
EXPECT_FALSE(*tuple_int_float == *c10::FloatType::get());
|
||||
EXPECT_FALSE(*tuple_int_float == *tuple_int);
|
||||
EXPECT_FALSE(*tuple_int_float == *tuple_int_bool);
|
||||
EXPECT_TRUE(*tuple_int_float == *tuple_int_float_same);
|
||||
|
||||
auto tuple_rebound = tuple_int->createWithContained(
|
||||
{c10::StringType::get(), c10::DeviceObjType::get()});
|
||||
ASSERT_NE(tuple_rebound, nullptr);
|
||||
EXPECT_EQ(tuple_rebound->kind(), c10::TypeKind::TupleType);
|
||||
ASSERT_EQ(tuple_rebound->containedTypes().size(), 2UL);
|
||||
EXPECT_EQ(tuple_rebound->containedTypes()[0]->kind(),
|
||||
c10::TypeKind::StringType);
|
||||
EXPECT_EQ(tuple_rebound->containedTypes()[1]->kind(),
|
||||
c10::TypeKind::DeviceObjType);
|
||||
|
||||
EXPECT_EQ(tuple_int_float->annotation_str(), "Tuple[int, float]");
|
||||
const c10::TypePrinter float_printer = [](const c10::Type& type) {
|
||||
if (type.kind() == c10::TypeKind::FloatType) {
|
||||
return std::optional<std::string>("F");
|
||||
}
|
||||
return std::optional<std::string>();
|
||||
};
|
||||
EXPECT_EQ(tuple_int_float->annotation_str(float_printer), "Tuple[int, F]");
|
||||
}
|
||||
|
||||
TEST(schema_parser_type_test, JitTypeBuiltinKindsBranchMatrix) {
|
||||
// Reason: cover uncovered scalar/string/none/device branches in jit_type.h.
|
||||
auto number_type = c10::NumberType::get();
|
||||
auto float_type = c10::FloatType::get();
|
||||
auto int_type = c10::IntType::get();
|
||||
auto bool_type = c10::BoolType::get();
|
||||
auto string_type = c10::StringType::get();
|
||||
auto none_type = c10::NoneType::get();
|
||||
auto device_type = c10::DeviceObjType::get();
|
||||
auto tensor_type = c10::TensorType::get();
|
||||
|
||||
EXPECT_TRUE(*number_type == *number_type);
|
||||
ExpectTypeText(*number_type, "Scalar", "number");
|
||||
EXPECT_TRUE(*float_type == *float_type);
|
||||
ExpectTypeText(*float_type, "float", "float");
|
||||
EXPECT_TRUE(*int_type == *int_type);
|
||||
ExpectTypeText(*int_type, "int", "int");
|
||||
EXPECT_TRUE(*bool_type == *bool_type);
|
||||
ExpectTypeText(*bool_type, "bool", "bool");
|
||||
EXPECT_TRUE(*string_type == *string_type);
|
||||
ExpectTypeText(*string_type, "str", "str");
|
||||
EXPECT_TRUE(*none_type == *none_type);
|
||||
ExpectTypeText(*none_type, "NoneType", "NoneType");
|
||||
EXPECT_TRUE(*device_type == *device_type);
|
||||
ExpectTypeText(*device_type, "Device", "Device");
|
||||
|
||||
EXPECT_FALSE(*int_type != *int_type);
|
||||
EXPECT_TRUE(*int_type != *float_type);
|
||||
|
||||
auto optional_int = c10::makeSchemaOptionalType(int_type);
|
||||
EXPECT_EQ(number_type->isSubtypeOfExt(*number_type, nullptr), true);
|
||||
EXPECT_EQ(number_type->isSubtypeOfExt(*tensor_type, nullptr), false);
|
||||
EXPECT_EQ(float_type->isSubtypeOfExt(*number_type, nullptr), true);
|
||||
EXPECT_EQ(float_type->isSubtypeOfExt(*tensor_type, nullptr), false);
|
||||
EXPECT_EQ(none_type->isSubtypeOfExt(*optional_int, nullptr), true);
|
||||
EXPECT_EQ(none_type->isSubtypeOfExt(*int_type, nullptr), false);
|
||||
|
||||
std::ostringstream os;
|
||||
os << *device_type;
|
||||
EXPECT_EQ(os.str(), "Device");
|
||||
}
|
||||
|
||||
TEST(schema_parser_type_test, TypePtrSingletonTypePtrCtorAndGet) {
|
||||
// Reason: cover type_ptr.h raw-pointer constructor and get().
|
||||
int value = 7;
|
||||
c10::SingletonTypePtr<int> ptr(&value);
|
||||
EXPECT_EQ(ptr.get(), &value);
|
||||
EXPECT_TRUE(static_cast<bool>(ptr));
|
||||
EXPECT_EQ(*ptr, 7);
|
||||
EXPECT_EQ(ptr.operator->(), &value);
|
||||
|
||||
*ptr = 9;
|
||||
EXPECT_EQ(value, 9);
|
||||
|
||||
c10::SingletonTypePtr<int> same_ptr(&value);
|
||||
EXPECT_TRUE(ptr == same_ptr);
|
||||
EXPECT_FALSE(ptr != same_ptr);
|
||||
|
||||
int other = 3;
|
||||
c10::SingletonTypePtr<int> other_ptr(&other);
|
||||
EXPECT_FALSE(ptr == other_ptr);
|
||||
EXPECT_TRUE(ptr != other_ptr);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
cc_test(all_schema_test SRCS all_schema_test.cc)
|
||||
@@ -0,0 +1,199 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// The file has been adapted from pytorch project
|
||||
// Licensed under BSD-style license -
|
||||
// https://github.com/pytorch/pytorch/blob/main/LICENSE
|
||||
|
||||
#include <torch/all.h>
|
||||
#include <torch/library.h>
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/cpp/utils/exception_test_utils.h"
|
||||
|
||||
// ----- test/cpp/jit/test_misc.cpp start -----
|
||||
|
||||
namespace torch {
|
||||
namespace jit {
|
||||
|
||||
TEST(SchemaParserTest, OutVariant) {
|
||||
auto schema_with_out = parseSchema(
|
||||
"at::foo(Tensor self, *, Tensor(a!) f, Tensor(b!) l) -> (Tensor(a!) f, "
|
||||
"Tensor(b!) l)");
|
||||
ASSERT_TRUE(schema_with_out.arguments().at(1).is_out());
|
||||
ASSERT_TRUE(schema_with_out.arguments().at(2).is_out());
|
||||
|
||||
auto schema_without_out =
|
||||
parseSchema("at::foo(Tensor self, *, int scalar) -> (int)");
|
||||
|
||||
for (const auto& arg : schema_without_out.arguments()) {
|
||||
ASSERT_TRUE(!arg.is_out());
|
||||
}
|
||||
|
||||
auto schema_with_is_write = parseSchema(
|
||||
"aten::ne_.Scalar(Tensor(a!) self, Scalar other) -> (Tensor(a!))");
|
||||
|
||||
for (const auto& arg : schema_with_is_write.arguments()) {
|
||||
ASSERT_TRUE(!arg.is_out());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SchemaParserTest, NamedReturns) {
|
||||
// named returns
|
||||
parseSchema("at::what(Tensor! i_will_be_written_to) -> ()");
|
||||
auto s3 =
|
||||
parseSchema("at::what() -> (Tensor the_return, Tensor the_return2)");
|
||||
ASSERT_EQ(s3.returns().at(0).name(), "the_return");
|
||||
ASSERT_EQ(s3.returns().at(1).name(), "the_return2");
|
||||
}
|
||||
|
||||
TEST(SchemaParserTest, AnnotatedAliasSets) {
|
||||
// test tensor with annotated alias sets
|
||||
parseSchema("at::what(Tensor(a) foo) -> (Tensor(a))");
|
||||
}
|
||||
|
||||
TEST(SchemaParserTest, AnnotatedAliasWithoutBeforeSet) {
|
||||
const std::string schema = "at::foo(Tensor(!) self) -> Tensor";
|
||||
test::utils::ExpectThrowContains<std::exception>(
|
||||
[&]() { (void)parseSchema(schema); },
|
||||
"Expected alias set",
|
||||
std::string("schema: ") + schema);
|
||||
}
|
||||
|
||||
} // namespace jit
|
||||
} // namespace torch
|
||||
|
||||
// ----- test/cpp/jit/test_misc.cpp end -----
|
||||
|
||||
// ----- test/cpp/jit/test_schema_info.cpp start -----
|
||||
|
||||
TEST(FunctionSchemaIsAliasingTest, Basic) {
|
||||
c10::FunctionSchema schema = torch::jit::parseSchema(
|
||||
"aten::test.Tensor(Tensor(a) self, Tensor(b!) other, Tensor more_other) "
|
||||
"-> (Tensor(a), Tensor(b!))");
|
||||
ASSERT_TRUE(schema.is_aliasing({c10::SchemaArgType::output, 0}));
|
||||
ASSERT_TRUE(schema.is_aliasing({c10::SchemaArgType::output, 1}));
|
||||
ASSERT_TRUE(schema.is_aliasing({c10::SchemaArgType::input, 0}));
|
||||
ASSERT_TRUE(schema.is_aliasing({c10::SchemaArgType::input, 1}));
|
||||
ASSERT_FALSE(schema.is_aliasing({c10::SchemaArgType::input, 2}));
|
||||
}
|
||||
|
||||
TEST(FunctionSchemaIsAliasingTest, InvalidArgument) {
|
||||
c10::FunctionSchema schema = torch::jit::parseSchema(
|
||||
"aten::sub_.Tensor(Tensor(a!) self, Tensor other, *, Scalar alpha=1) -> "
|
||||
"(Tensor(a!))");
|
||||
test::utils::ExpectThrowContains<std::exception>(
|
||||
[&]() {
|
||||
(void)schema.is_aliasing({c10::SchemaArgType::input, 4});
|
||||
},
|
||||
"Schema input index 4 is out of bounds",
|
||||
"input index out of bounds");
|
||||
test::utils::ExpectThrowContains<std::exception>(
|
||||
[&]() {
|
||||
(void)schema.is_aliasing({c10::SchemaArgType::output, 4});
|
||||
},
|
||||
"Schema output index 4 is out of bounds",
|
||||
"output index out of bounds");
|
||||
}
|
||||
|
||||
TEST(FunctionSchemaIsMutableTest, Basic) {
|
||||
c10::FunctionSchema schema = torch::jit::parseSchema(
|
||||
"aten::sub_.Tensor(Tensor(a!) self, Tensor other, *, Scalar alpha=1) -> "
|
||||
"(Tensor(a!))");
|
||||
ASSERT_TRUE(schema.is_mutable({c10::SchemaArgType::output, 0}));
|
||||
ASSERT_TRUE(schema.is_mutable({c10::SchemaArgType::input, 0}));
|
||||
ASSERT_TRUE(schema.is_mutable("self"));
|
||||
ASSERT_FALSE(schema.is_mutable({c10::SchemaArgType::input, 1}));
|
||||
ASSERT_FALSE(schema.is_mutable("other"));
|
||||
ASSERT_FALSE(schema.is_mutable({c10::SchemaArgType::input, 2}));
|
||||
ASSERT_FALSE(schema.is_mutable("alpha"));
|
||||
}
|
||||
|
||||
TEST(FunctionSchemaIsMutableTest, InvalidArgument) {
|
||||
c10::FunctionSchema schema = torch::jit::parseSchema(
|
||||
"aten::sub_.Tensor(Tensor(a!) self, Tensor other, *, Scalar alpha=1) -> "
|
||||
"(Tensor(a!))");
|
||||
test::utils::ExpectThrowContains<std::exception>(
|
||||
[&]() {
|
||||
(void)schema.is_mutable({c10::SchemaArgType::input, 4});
|
||||
},
|
||||
"Schema input index 4 is out of bounds",
|
||||
"mutable input index out of bounds");
|
||||
test::utils::ExpectThrowContains<std::exception>(
|
||||
[&]() {
|
||||
(void)schema.is_mutable({c10::SchemaArgType::output, 4});
|
||||
},
|
||||
"Schema output index 4 is out of bounds",
|
||||
"mutable output index out of bounds");
|
||||
test::utils::ExpectThrowContains<std::exception>(
|
||||
[&]() { (void)schema.is_mutable("named_argument"); },
|
||||
"Tried to test mutability of nonexistent name named_argument",
|
||||
"mutable name not found");
|
||||
}
|
||||
|
||||
TEST(FunctionSchemaMayAliasTest, Basic) {
|
||||
c10::FunctionSchema schema = torch::jit::parseSchema(
|
||||
"aten::sub_.Tensor(Tensor(a!) self, Tensor other, *, Scalar alpha=1) -> "
|
||||
"(Tensor(a!))");
|
||||
ASSERT_TRUE(schema.may_alias({c10::SchemaArgType::input, 0},
|
||||
{c10::SchemaArgType::output, 0}));
|
||||
ASSERT_FALSE(schema.may_alias({c10::SchemaArgType::input, 1},
|
||||
{c10::SchemaArgType::output, 0}));
|
||||
ASSERT_FALSE(schema.may_alias({c10::SchemaArgType::input, 1},
|
||||
{c10::SchemaArgType::input, 0}));
|
||||
}
|
||||
|
||||
TEST(FunctionSchemaMayAliasTest, InvalidArgument) {
|
||||
c10::FunctionSchema schema = torch::jit::parseSchema(
|
||||
"aten::sub_.Tensor(Tensor(a!) self, Tensor other, *, Scalar alpha=1) -> "
|
||||
"(Tensor(a!))");
|
||||
test::utils::ExpectThrowContains<std::exception>(
|
||||
[&]() {
|
||||
(void)schema.may_alias({c10::SchemaArgType::input, 15},
|
||||
{c10::SchemaArgType::output, 0});
|
||||
},
|
||||
"Schema input index 15 is out of bounds",
|
||||
"may_alias input index out of bounds");
|
||||
test::utils::ExpectThrowContains<std::exception>(
|
||||
[&]() {
|
||||
(void)schema.may_alias({c10::SchemaArgType::input, 0},
|
||||
{c10::SchemaArgType::output, 15});
|
||||
},
|
||||
"Schema output index 15 is out of bounds",
|
||||
"may_alias output index out of bounds");
|
||||
}
|
||||
|
||||
TEST(FunctionSchemaMayAliasTest, Wildcard) {
|
||||
c10::FunctionSchema schema = torch::jit::parseSchema(
|
||||
"aten::test.Tensor(Tensor(*) self) -> (Tensor(*), Tensor)");
|
||||
ASSERT_TRUE(schema.may_alias({c10::SchemaArgType::output, 0},
|
||||
{c10::SchemaArgType::input, 0}));
|
||||
ASSERT_FALSE(schema.may_alias({c10::SchemaArgType::output, 1},
|
||||
{c10::SchemaArgType::input, 0}));
|
||||
}
|
||||
|
||||
TEST(FunctionSchemaMayContainAliasTest, Basic) {
|
||||
c10::FunctionSchema schema = torch::jit::parseSchema(
|
||||
"aten::sub_.Tensor(Tensor(a!) self, Tensor other, *, Scalar alpha=1) -> "
|
||||
"(Tensor(a!))");
|
||||
ASSERT_TRUE(schema.may_contain_alias({c10::SchemaArgType::input, 0},
|
||||
{c10::SchemaArgType::output, 0}));
|
||||
ASSERT_FALSE(schema.may_contain_alias({c10::SchemaArgType::input, 1},
|
||||
{c10::SchemaArgType::output, 0}));
|
||||
ASSERT_FALSE(schema.may_contain_alias({c10::SchemaArgType::input, 1},
|
||||
{c10::SchemaArgType::input, 0}));
|
||||
}
|
||||
|
||||
// ----- test/cpp/jit/test_schema_info.cpp end -----
|
||||
@@ -0,0 +1,200 @@
|
||||
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// The file has been adapted from pytorch project
|
||||
// Licensed under BSD-style license -
|
||||
// https://github.com/pytorch/pytorch/blob/main/LICENSE
|
||||
|
||||
// Tests for the compat-layer dispatch key priority selection logic introduced
|
||||
// in OperationInvoker::get_op_with_args (torch_compat.h).
|
||||
//
|
||||
// The lookup order is: CPU → BackendSelect → CatchAll.
|
||||
// If none of those keys exist and exactly one implementation is registered it
|
||||
// is used directly (deterministic). If multiple unrecognised keys exist the
|
||||
// invoker raises an Ambiguous error rather than picking arbitrarily from an
|
||||
// unordered_map (which has no stable iteration order).
|
||||
// These tests exercise scenarios where the registrant uses BackendSelect
|
||||
// (e.g. TORCH_LIBRARY_IMPL(..., BackendSelect, m)) so that the Python-facing
|
||||
// invoker can reach it even when no CPU implementation exists.
|
||||
|
||||
#include <torch/library.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Operator implementations used by the tests below
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
|
||||
int backend_select_probe(int x) { return x + 10; }
|
||||
|
||||
int backend_select_and_cpu_cpu_fn(int x) { return x + 1; }
|
||||
int backend_select_and_cpu_bs_fn(int x) { return x + 2; }
|
||||
|
||||
} // namespace
|
||||
|
||||
int unique_non_preferred_fn(int x) { return x + 7; }
|
||||
int ambiguous_cuda_fn(int x) { return x + 100; }
|
||||
int ambiguous_xpu_fn(int x) { return x + 200; }
|
||||
|
||||
TORCH_LIBRARY(compat_dispatch_test_lib, m) {
|
||||
m.def("backend_select_only(int x) -> int");
|
||||
m.def("backend_select_and_cpu(int x) -> int");
|
||||
m.def("unique_non_preferred(int x) -> int");
|
||||
m.def("ambiguous_multi_key(int x) -> int");
|
||||
}
|
||||
|
||||
TORCH_LIBRARY_IMPL(compat_dispatch_test_lib, BackendSelect, m) {
|
||||
m.impl("backend_select_only", &backend_select_probe);
|
||||
m.impl("backend_select_and_cpu", &backend_select_and_cpu_bs_fn);
|
||||
}
|
||||
|
||||
TORCH_LIBRARY_IMPL(compat_dispatch_test_lib, CPU, m) {
|
||||
m.impl("backend_select_and_cpu", &backend_select_and_cpu_cpu_fn);
|
||||
}
|
||||
|
||||
TORCH_LIBRARY_IMPL(compat_dispatch_test_lib, CUDA, m) {
|
||||
m.impl("unique_non_preferred", &unique_non_preferred_fn);
|
||||
m.impl("ambiguous_multi_key", &ambiguous_cuda_fn);
|
||||
}
|
||||
|
||||
TORCH_LIBRARY_IMPL(compat_dispatch_test_lib, XPU, m) {
|
||||
m.impl("ambiguous_multi_key", &ambiguous_xpu_fn);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: simulate the priority-fallback lookup used by get_op_with_args
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static decltype(torch::OperatorRegistry::instance()
|
||||
.find_operator("")
|
||||
->implementations.end())
|
||||
pick_impl(torch::OperatorRegistration* op) {
|
||||
using DK = c10::DispatchKey;
|
||||
const std::vector<DK> preferred_keys = {
|
||||
DK::CPU, DK::BackendSelect, DK::CatchAll};
|
||||
auto chosen = op->implementations.end();
|
||||
for (const auto& key : preferred_keys) {
|
||||
chosen = op->implementations.find(key);
|
||||
if (chosen != op->implementations.end()) break;
|
||||
}
|
||||
// Mirror the production rule: allow exactly-one-impl, reject ambiguous.
|
||||
if (chosen == op->implementations.end() && op->implementations.size() == 1) {
|
||||
chosen = op->implementations.begin();
|
||||
}
|
||||
return chosen;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// An operator registered only under BackendSelect must be queryable under
|
||||
// that key and must NOT appear under CPU.
|
||||
TEST(CompatTorchDispatchTest, BackendSelectOnlyRegistration) {
|
||||
const auto qname = "compat_dispatch_test_lib::backend_select_only";
|
||||
auto* op = torch::OperatorRegistry::instance().find_operator(qname);
|
||||
ASSERT_NE(op, nullptr);
|
||||
|
||||
EXPECT_EQ(op->implementations.find(c10::DispatchKey::CPU),
|
||||
op->implementations.end());
|
||||
|
||||
auto bs_it = op->implementations.find(c10::DispatchKey::BackendSelect);
|
||||
ASSERT_NE(bs_it, op->implementations.end());
|
||||
|
||||
torch::FunctionArgs args;
|
||||
args.add_arg(torch::IValue(int64_t(32)));
|
||||
auto result = bs_it->second.call_with_args(args);
|
||||
ASSERT_TRUE(result.get_value().is_int());
|
||||
EXPECT_EQ(result.get_value().to_int(), 42); // 32 + 10
|
||||
}
|
||||
|
||||
// When CPU and BackendSelect are both registered, the priority lookup must
|
||||
// pick CPU (higher priority in get_op_with_args).
|
||||
TEST(CompatTorchDispatchTest, CpuPreferredOverBackendSelect) {
|
||||
const auto qname = "compat_dispatch_test_lib::backend_select_and_cpu";
|
||||
auto* op = torch::OperatorRegistry::instance().find_operator(qname);
|
||||
ASSERT_NE(op, nullptr);
|
||||
|
||||
ASSERT_NE(op->implementations.find(c10::DispatchKey::CPU),
|
||||
op->implementations.end());
|
||||
ASSERT_NE(op->implementations.find(c10::DispatchKey::BackendSelect),
|
||||
op->implementations.end());
|
||||
|
||||
auto chosen = pick_impl(op);
|
||||
ASSERT_NE(chosen, op->implementations.end());
|
||||
EXPECT_EQ(chosen->first, c10::DispatchKey::CPU);
|
||||
|
||||
torch::FunctionArgs args;
|
||||
args.add_arg(torch::IValue(int64_t(41)));
|
||||
auto result = chosen->second.call_with_args(args);
|
||||
ASSERT_TRUE(result.get_value().is_int());
|
||||
EXPECT_EQ(result.get_value().to_int(), 42); // CPU impl: x + 1
|
||||
}
|
||||
|
||||
// When CPU is absent, the priority lookup must fall through to BackendSelect.
|
||||
TEST(CompatTorchDispatchTest, BackendSelectPickedWhenCpuAbsent) {
|
||||
const auto qname = "compat_dispatch_test_lib::backend_select_only";
|
||||
auto* op = torch::OperatorRegistry::instance().find_operator(qname);
|
||||
ASSERT_NE(op, nullptr);
|
||||
|
||||
auto chosen = pick_impl(op);
|
||||
ASSERT_NE(chosen, op->implementations.end());
|
||||
EXPECT_EQ(chosen->first, c10::DispatchKey::BackendSelect);
|
||||
|
||||
torch::FunctionArgs args;
|
||||
args.add_arg(torch::IValue(int64_t(32)));
|
||||
auto result = chosen->second.call_with_args(args);
|
||||
ASSERT_TRUE(result.get_value().is_int());
|
||||
EXPECT_EQ(result.get_value().to_int(), 42); // BackendSelect impl: x + 10
|
||||
}
|
||||
|
||||
// An operator registered only under one non-preferred key (e.g. CUDA) must
|
||||
// still be reachable when it's the sole implementation (deterministic).
|
||||
TEST(CompatTorchDispatchTest, UniqueNonPreferredKeyIsCallable) {
|
||||
const auto qname = "compat_dispatch_test_lib::unique_non_preferred";
|
||||
auto* op = torch::OperatorRegistry::instance().find_operator(qname);
|
||||
ASSERT_NE(op, nullptr);
|
||||
ASSERT_EQ(op->implementations.size(), 1UL);
|
||||
|
||||
auto chosen = pick_impl(op);
|
||||
ASSERT_NE(chosen, op->implementations.end());
|
||||
|
||||
torch::FunctionArgs args;
|
||||
args.add_arg(torch::IValue(int64_t(35)));
|
||||
auto result = chosen->second.call_with_args(args);
|
||||
ASSERT_TRUE(result.get_value().is_int());
|
||||
EXPECT_EQ(result.get_value().to_int(), 42); // unique impl: x + 7
|
||||
}
|
||||
|
||||
// An operator with multiple non-preferred keys (CUDA + XPU) must produce
|
||||
// end() from pick_impl (the production code would raise an Ambiguous error).
|
||||
TEST(CompatTorchDispatchTest, AmbiguousMultiKeyProducesEnd) {
|
||||
const auto qname = "compat_dispatch_test_lib::ambiguous_multi_key";
|
||||
auto* op = torch::OperatorRegistry::instance().find_operator(qname);
|
||||
ASSERT_NE(op, nullptr);
|
||||
// Registered under CUDA and XPU – neither is in the preferred list.
|
||||
ASSERT_GE(op->implementations.size(), 2UL);
|
||||
EXPECT_EQ(op->implementations.find(c10::DispatchKey::CPU),
|
||||
op->implementations.end());
|
||||
EXPECT_EQ(op->implementations.find(c10::DispatchKey::BackendSelect),
|
||||
op->implementations.end());
|
||||
|
||||
auto chosen = pick_impl(op);
|
||||
// Must not resolve to any implementation – ambiguous.
|
||||
EXPECT_EQ(chosen, op->implementations.end());
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user