// 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 #include #include #include #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 #elif defined(PADDLE_WITH_HIP) #include #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(), 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(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(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(), 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