Files
2026-07-13 12:40:42 +08:00

395 lines
13 KiB
C++

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