// 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 #include #include #include #include #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(); 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(); for (int i = 0; i < 20; ++i) { ASSERT_FLOAT_EQ(data[i], static_cast(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(); for (int i = 0; i < 6; ++i) { ASSERT_FLOAT_EQ(data[i], static_cast(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(); for (int i = 0; i < 24; ++i) { ASSERT_FLOAT_EQ(data[i], static_cast(i)); } } TEST(TensorResizeTest, ResizeChannelsLastMemoryFormatDoesNotThrow) { at::Tensor t = at::arange(24, at::kFloat).reshape({1, 2, 3, 4}); EXPECT_NO_THROW({ t.resize_(std::vector{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{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{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{std::numeric_limits::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(), ta.data_ptr() + 1); // The original storage contents should remain unchanged. ASSERT_EQ(ta[0].item(), 1); ASSERT_EQ(ta[1].item(), 2); ASSERT_EQ(ta[2].item(), 3); ASSERT_EQ(ta[3].item(), 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(), 2); ASSERT_EQ(tb[1].item(), 3); ASSERT_EQ(tb[2].item(), 4); ASSERT_EQ(tb.numel(), 4); }