// 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 "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()[i] = static_cast(i * 10); } // Index with [0, 2, 4] at::Tensor idx = at::empty({3}, at::kLong); int64_t* idx_data = idx.data_ptr(); idx_data[0] = 0; idx_data[1] = 2; idx_data[2] = 4; c10::List<::std::optional> indices; indices.push_back(idx); at::Tensor result = t.index(indices); ASSERT_EQ(result.numel(), 3); float* result_data = result.data_ptr(); 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{}), 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(); 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(); 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 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()[0] = 0; idx.data_ptr()[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(); // Create index tensor [1, 3] at::Tensor idx = at::empty({2}, at::kLong); int64_t* idx_data = idx.data_ptr(); 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> indices; indices.push_back(idx); t.index_put_(indices, values); // Verify data pointer unchanged (inplace) ASSERT_EQ(t.data_ptr(), original_data_ptr); float* data = t.data_ptr(); 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(); at::Tensor idx = at::empty({2}, at::kLong); int64_t* idx_data = idx.data_ptr(); 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(), original_data_ptr); float* data = t.data_ptr(); 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(); idx_data[0] = 1; idx_data[1] = 3; at::Tensor values = at::full({2}, 42.0f, at::kFloat); c10::List<::std::optional> indices; indices.push_back(idx); at::Tensor result = t.index_put(indices, values); // Original should be unchanged ASSERT_FLOAT_EQ(t.data_ptr()[1], 0.0f); // Result should have the values float* rdata = result.data_ptr(); 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> 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(); idx0_data[0] = 0; idx0_data[1] = 1; at::Tensor idx1 = at::empty({2}, at::kLong); int64_t* idx1_data = idx1.data_ptr(); idx1_data[0] = 0; idx1_data[1] = 2; c10::List<::std::optional> 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()[0] = 0; idx.data_ptr()[1] = 2; c10::List<::std::optional> 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> 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()[0], 0.0f); ASSERT_FLOAT_EQ(result.data_ptr()[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()[0] = 2; idx.data_ptr()[1] = 0; c10::List<::std::optional> 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()[0], 6.0f); ASSERT_FLOAT_EQ(result.data_ptr()[1], 7.0f); ASSERT_FLOAT_EQ(result.data_ptr()[2], 8.0f); ASSERT_FLOAT_EQ(result.data_ptr()[3], 0.0f); ASSERT_FLOAT_EQ(result.data_ptr()[4], 1.0f); ASSERT_FLOAT_EQ(result.data_ptr()[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()[0] = 2; idx.data_ptr()[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()[0], 8.0f); ASSERT_FLOAT_EQ(result.data_ptr()[1], 9.0f); ASSERT_FLOAT_EQ(result.data_ptr()[2], 10.0f); ASSERT_FLOAT_EQ(result.data_ptr()[3], 11.0f); ASSERT_FLOAT_EQ(result.data_ptr()[4], 0.0f); ASSERT_FLOAT_EQ(result.data_ptr()[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()[0] = 0; idx0.data_ptr()[1] = 1; at::Tensor idx1 = at::empty({2}, at::kLong); idx1.data_ptr()[0] = 0; idx1.data_ptr()[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(); at::Tensor idx = at::empty({2}, at::kLong); idx.data_ptr()[0] = 1; idx.data_ptr()[1] = 1; at::Tensor values = at::full({2}, 5.0f, at::kFloat); c10::List<::std::optional> indices; indices.push_back(idx); t.index_put_(indices, values, true); // accumulate=true // Verify data pointer unchanged (inplace) ASSERT_EQ(t.data_ptr(), original_data_ptr); float* data = t.data_ptr(); 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(); at::Tensor idx0 = at::arange(2, at::kLong); idx0.data_ptr()[0] = 0; idx0.data_ptr()[1] = 1; at::Tensor idx1 = at::arange(2, at::kLong); idx1.data_ptr()[0] = 0; idx1.data_ptr()[1] = 1; c10::List<::std::optional> 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(), original_data_ptr); float* data = t.data_ptr(); 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()[0] = 1; idx.data_ptr()[1] = 1; at::Tensor values = at::full({2}, 3.0f, at::kFloat); c10::List<::std::optional> indices; indices.push_back(idx); at::Tensor result = t.index_put(indices, values, true); // Original unchanged ASSERT_FLOAT_EQ(t.data_ptr()[1], 0.0f); // Result has accumulated ASSERT_FLOAT_EQ(result.data_ptr()[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()[0] = 1; idx.data_ptr()[1] = 4; at::Tensor values = at::full({2}, 13.0f, at::kFloat); std::vector tensor_indices = {idx}; t.index_put_(at::ArrayRef(tensor_indices), values); ASSERT_FLOAT_EQ(t.data_ptr()[0], 0.0f); ASSERT_FLOAT_EQ(t.data_ptr()[1], 13.0f); ASSERT_FLOAT_EQ(t.data_ptr()[2], 0.0f); ASSERT_FLOAT_EQ(t.data_ptr()[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()[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()[0] = 2; idx.data_ptr()[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()[i], 8.0f); ASSERT_FLOAT_EQ(t.data_ptr()[8 + i], 8.0f); } for (int i = 4; i < 8; ++i) { ASSERT_FLOAT_EQ(t.data_ptr()[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()[i], 4.0f); } }