Files
paddlepaddle--paddle/test/cpp/compat/ATen_basic_test.cc
T
2026-07-13 12:40:42 +08:00

519 lines
16 KiB
C++

// 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);
}