/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ #include // Declares the operator #include #include #include #include #include #include #include #include using namespace ::testing; using exec_aten::ArrayRef; using exec_aten::optional; using exec_aten::ScalarType; using exec_aten::Tensor; using torch::executor::testing::TensorFactory; using OptTensorArrayRef = ArrayRef>; class OpIndexTensorOutTest : public OperatorTest { protected: Tensor& op_index_tensor_out( const Tensor& input, OptTensorArrayRef indices, Tensor& out) { #ifdef USE_ATEN_LIB c10::List> indices_list(indices); return torch::executor::aten::index_outf( context_, input, indices_list, out); #else return torch::executor::aten::index_outf(context_, input, indices, out); #endif } template < exec_aten::ScalarType INPUT_DTYPE, exec_aten::ScalarType INDEX_DTYPE, exec_aten::ScalarType OUTPUT_DTYPE> void test_dtype() { TensorFactory tf; TensorFactory tfl; TensorFactory tfo; TensorFactory tfb; // clang-format off Tensor x = tf.make( {3, 2, 4}, { // all ones below are from x, // and all zeros are from y. // [0, :, :] 1, 1, 1, 1, // [0, 0, :] 0, 0, 0, 0, // [0, 1, :] // [1, :, :] 1, 1, 1, 1, // [1, 0, :] 0, 0, 0, 0, // [1, 1, :] // [2, :, :] 1, 1, 1, 1, // [2, 0, :] 0, 0, 0, 0, // [2, 1, :] }); // clang-format on // indices [0, 1, 2], [1, 0, 3], expressed two different ways optional indices[] = { optional(tfl.make({2}, {0, 1})), optional(tfl.make({2}, {1, 0})), optional(tfl.make({2}, {2, 3}))}; optional indices_mixed[] = { optional(tfl.make({2}, {0, 1})), optional(tfb.make({2}, {false, true})), optional(tfl.make({2}, {2, 3}))}; std::vector out_size{2}; Tensor out_0 = tfo.zeros(out_size); Tensor ret_0 = op_index_tensor_out(x, /*indices=*/indices, out_0); EXPECT_TENSOR_EQ(ret_0, out_0); EXPECT_TENSOR_EQ(ret_0, tfo.make(out_size, {0, 1})); // Repeat the test with alternative indices representation Tensor out_0_with_mixed = tfo.zeros(out_size); Tensor ret_0_with_mixed = op_index_tensor_out(x, /*indices=*/indices, out_0_with_mixed); EXPECT_TENSOR_EQ(ret_0_with_mixed, out_0_with_mixed); EXPECT_TENSOR_EQ(ret_0_with_mixed, tfo.make(out_size, {0, 1})); } /** * Generic test for integral index lists */ void test_dtype_enumerate_in_types() { #define TEST_ENTRY(ctype, dtype) \ test_dtype(); ET_FORALL_REALHBF16_TYPES(TEST_ENTRY); #undef TEST_ENTRY } // Run the test by selecting elements in input void run_test_cases( const Tensor& x, OptTensorArrayRef indices, const Tensor& expected) { // Generated out tensor sharing same size and dtype with expected tensor TensorFactory tf; const std::vector out_size( expected.sizes().begin(), expected.sizes().end()); Tensor out = tf.ones(out_size); Tensor ret = op_index_tensor_out(x, indices, out); EXPECT_TENSOR_EQ(out, ret); EXPECT_TENSOR_EQ(ret, expected); } }; // // Correctness Tests // TEST_F(OpIndexTensorOutTest, IndexMask) { TensorFactory tf; TensorFactory tfb; // clang-format off Tensor x = tf.make( {2, 3, 4}, { // [0, :, :] 1., 2., 3., 4., // [0, 0, :] 5., 6., 7., 8., // [0, 1, :] 9., 10., 11., 12., // [0, 2, :] // [1, :, :] -1., -2., -3., -4., // [1, 0, :] -5., -6., -7., -8., // [1, 1, :] -9., -10., -11., -12., // [1, 2, :] }); // clang-format on // clang-format off Tensor indices = tfb.make( {2, 3, 4}, { // [0, :, :] true, false, false, false, // [0, 0, :] false, false, true, false, // [0, 1, :] false, false, false, false, // [0, 2, :] // [1, :, :] false, true, false, false, // [1, 0, :] false, false, false, false, // [1, 1, :] false, false, true, false, // [1, 2, :] }); // clang-format on // clang-format off Tensor expected = tf.make( {4}, {1., 7., -2., -11.} ); // clang-format on run_test_cases(x, {indices}, expected); } TEST_F(OpIndexTensorOutTest, SelectFrontDimAllIndexes) { TensorFactory tf; TensorFactory tfi; TensorFactory tfl; TensorFactory tfb; // clang-format off Tensor x = tf.make( {2, 3, 4}, { // [0, :, :] 1., 2., 3., 4., // [0, 0, :] 5., 6., 7., 8., // [0, 1, :] 9., 10., 11., 12., // [0, 2, :] // [1, :, :] -1., -2., -3., -4., // [1, 0, :] -5., -6., -7., -8., // [1, 1, :] -9., -10., -11., -12., // [1, 2, :] }); // clang-format on // Try to select the input value at indices // [1, 0, 1], [1, 0, 2]. This is expressed in various ways to test different // indexing expressions. optional indices[] = { optional(tfl.make({1}, {1})), optional(tfl.make({1}, {0})), optional(tfl.make({2}, {1, 2}))}; optional indices_int[] = { optional(tfi.make({1}, {1})), optional(tfi.make({1}, {0})), optional(tfi.make({2}, {1, 2}))}; optional indices_negative[] = { optional(tfl.make({1}, {-1})), optional(tfl.make({1}, {0})), optional(tfl.make({2}, {-3, -2}))}; optional indices_bool[] = { optional(tfb.make({2}, {false, true})), optional(tfb.make({3}, {true, false, false})), optional(tfl.make({2}, {-3, -2}))}; optional indices_mixed[] = { optional(tfb.make({2}, {false, true})), optional(tfl.make({1}, {0})), optional(tfl.make({2}, {-3, -2}))}; std::vector out_size{2}; // clang-format off Tensor expected = tf.make( out_size, {-2., -3.,} ); // clang-format on run_test_cases(x, /*indices=*/indices, expected); run_test_cases(x, /*indices=*/indices_int, expected); run_test_cases(x, /*indices=*/indices_negative, expected); run_test_cases(x, /*indices=*/indices_bool, expected); run_test_cases(x, /*indices=*/indices_mixed, expected); } TEST_F(OpIndexTensorOutTest, SelectTwoValuesAtSameIndex) { TensorFactory tf; TensorFactory tfl; // clang-format off Tensor x = tf.make( {2, 3, 4}, { // [0, :, :] 1., 2., 3., 4., // [0, 0, :] 5., 6., 7., 8., // [0, 1, :] 9., 10., 11., 12., // [0, 2, :] // [1, :, :] -1., -2., -3., -4., // [1, 0, :] -5., -6., -7., -8., // [1, 1, :] -9., -10., -11., -12., // [1, 2, :] }); // clang-format on // Try to select the value at the same index optional indices[] = { optional(tfl.make({1, 2}, {0, 0})), optional(tfl.make({1, 2}, {1, 1})), optional(tfl.make({1, 2}, {2, 2}))}; std::vector out_size{1, 2}; // In ATen the size is (1, 2) // clang-format off Tensor expected = tf.make( out_size, {7., 7.,} ); // clang-format on run_test_cases(x, /*indices=*/indices, expected); } TEST_F(OpIndexTensorOutTest, IndicesFewerThanInputDimSupported) { TensorFactory tf; TensorFactory tfi; TensorFactory tfl; TensorFactory tfb; // clang-format off Tensor x = tf.make( {2, 3, 4}, { // [0, :, :] 1., 2., 3., 4., // [0, 0, :] 5., 6., 7., 8., // [0, 1, :] 9., 10., 11., 12., // [0, 2, :] // [1, :, :] -1., -2., -3., -4., // [1, 0, :] -5., -6., -7., -8., // [1, 1, :] -9., -10., -11., -12., // [1, 2, :] }); // clang-format on // Try to select the input value at indices // [1, 0, :], [1, 1, :]. This is expressed in various ways to test different // indexing expressions. optional indices[] = { optional(tfl.make({1}, {1})), optional(tfl.make({2}, {0, 1}))}; optional indices_mixed[] = { optional(tfi.make({1}, {-1})), optional(tfb.make({3}, {true, true, false}))}; std::vector out_size{2, 4}; // clang-format off Tensor expected = tf.make( out_size, { -1., -2., -3., -4., -5., -6., -7., -8., } ); // clang-format on run_test_cases(x, /*indices=*/indices, expected); run_test_cases(x, /*indices=*/indices_mixed, expected); } TEST_F(OpIndexTensorOutTest, IndicesWithNullTensorsSupported) { TensorFactory tf; TensorFactory tfl; // clang-format off Tensor x = tf.make( {2, 3, 4}, { // [0, :, :] 1., 2., 3., 4., // [0, 0, :] 5., 6., 7., 8., // [0, 1, :] 9., 10., 11., 12., // [0, 2, :] // [1, :, :] -1., -2., -3., -4., // [1, 0, :] -5., -6., -7., -8., // [1, 1, :] -9., -10., -11., -12., // [1, 2, :] }); // clang-format on optional indices0[] = { optional(), optional(tfl.make({1}, {1})), optional(tfl.make({2}, {0, 1}))}; // clang-format off Tensor expected0 = tf.make( {2, 2}, { 5., 6., -5., -6., } ); // clang-format on run_test_cases(x, /*indices=*/indices0, expected0); optional indices1[] = { optional(tfl.make({1}, {1})), optional(), optional(tfl.make({2}, {0, 1}))}; // clang-format off Tensor expected1 = tf.make( {2, 3}, { -1., -5., -9., -2., -6., -10., } ); // clang-format on run_test_cases(x, /*indices=*/indices1, expected1); optional indices2[] = { optional(tfl.make({1}, {1})), optional(tfl.make({2}, {0, 1})), optional()}; // clang-format off Tensor expected2 = tf.make( {2, 4}, { -1., -2., -3., -4., -5., -6., -7., -8., } ); // clang-format on run_test_cases(x, /*indices=*/indices2, expected2); } TEST_F(OpIndexTensorOutTest, IndicesWithOnlyNullTensorsSupported) { if (torch::executor::testing::SupportedFeatures::get()->is_aten) { GTEST_SKIP() << "ATen kernel test fails"; } TensorFactory tf; Tensor x = tf.make({2, 3}, {1., 2., 3., 4., 5., 6.}); optional indices0[] = {optional()}; run_test_cases(x, indices0, x); optional indices1[] = {optional(), optional()}; run_test_cases(x, indices1, x); optional indices2[] = { optional(), optional(), optional()}; Tensor out = tf.ones({2, 3}); ET_EXPECT_KERNEL_FAILURE_WITH_MSG( context_, op_index_tensor_out(x, indices2, out), ""); } TEST_F(OpIndexTensorOutTest, EmptyIndicesSupported) { if (torch::executor::testing::SupportedFeatures::get()->is_aten) { GTEST_SKIP() << "ATen kernel test fails"; } TensorFactory tf; // Using empty tensors as input. Tensor x = tf.make({2}, {1., 2.}); Tensor out = tf.zeros({2}); op_index_tensor_out(x, /*indices=*/{}, out); EXPECT_TENSOR_EQ(out, x); // Success if it doesn't assert on the weird-shaped empty input and the // ret is still a empty array } // // Test that all dtypes are supported // TEST_F(OpIndexTensorOutTest, AllDtypesSupportedForInput) { test_dtype_enumerate_in_types(); } TEST_F(OpIndexTensorOutTest, AllDtypesSupportedForIndex) { test_dtype(); test_dtype(); } // // Death Tests // TEST_F(OpIndexTensorOutTest, IndexOutOfBoundDies) { TensorFactory tf; TensorFactory tfl; Tensor x = tf.ones({1, 1, 1}); Tensor out = tf.zeros({1, 1, 1}); Tensor index = tfl.make({1}, {5}); ET_EXPECT_KERNEL_FAILURE_WITH_MSG( context_, op_index_tensor_out(x, /*indices=*/{index}, out), ""); } TEST_F(OpIndexTensorOutTest, NegativeIndexOutOfBoundDies) { TensorFactory tf; TensorFactory tfl; Tensor x = tf.ones({1, 1, 1}); Tensor out = tf.zeros({1, 1, 1}); Tensor index = tfl.make({1}, {-5}); ET_EXPECT_KERNEL_FAILURE_WITH_MSG( context_, op_index_tensor_out(x, /*indices=*/{index}, out), ""); } TEST_F(OpIndexTensorOutTest, TooManyBooleanIndexCountDies) { TensorFactory tf; TensorFactory tfb; Tensor x = tf.ones({1, 1, 1}); Tensor out = tf.zeros({1, 1, 1}); Tensor index = tfb.make({3}, {true, false, false}); ET_EXPECT_KERNEL_FAILURE_WITH_MSG( context_, op_index_tensor_out(x, /*indices=*/{index}, out), ""); } TEST_F(OpIndexTensorOutTest, TooFewBooleanIndexCountDies) { TensorFactory tf; TensorFactory tfb; Tensor x = tf.ones({4}); Tensor out = tf.zeros({1}); Tensor index = tfb.make({1}, {true}); // ATen kernel will throw exception instead of death ET_EXPECT_KERNEL_FAILURE_WITH_MSG( context_, op_index_tensor_out(x, /*indices=*/{index}, out), ""); } TEST_F(OpIndexTensorOutTest, MismatchedIndexMaskDies) { TensorFactory tf; TensorFactory tfb; Tensor x = tf.ones({4, 4}); Tensor out = tf.zeros({9}); Tensor index = tfb.ones({3, 3}); // ATen kernel will throw exception instead of death ET_EXPECT_KERNEL_FAILURE_WITH_MSG( context_, op_index_tensor_out(x, /*indices=*/{index}, out), ""); } TEST_F(OpIndexTensorOutTest, MismatchedOutputDimDies) { TensorFactory tf; TensorFactory tfl; Tensor x = tf.zeros({2, 4, 7, 5}); Tensor index = tfl.make({1}, {3}); // Should be {1, 4, 7, 5} Tensor out = tf.zeros({2, 4}); ET_EXPECT_KERNEL_FAILURE_WITH_MSG( context_, op_index_tensor_out(x, /*indices=*/{index}, out), ""); } TEST_F(OpIndexTensorOutTest, InvalidIndicesDtypeDies) { TensorFactory tf; TensorFactory tff; Tensor x = tf.zeros({2, 4, 7, 5}); Tensor index = tff.make({1}, {3}); Tensor out = tf.zeros({1, 4, 7, 5}); ET_EXPECT_KERNEL_FAILURE_WITH_MSG( context_, op_index_tensor_out(x, /*indices=*/{index}, out), ""); } TEST_F(OpIndexTensorOutTest, InvalidIndicesShapesDies) { TensorFactory tf; TensorFactory tfl; Tensor x = tf.zeros({2, 4, 7, 5}); // clang-format off optional indices[] = { optional(tfl.make({3}, {1, 1, 1,})), optional(tfl.make({2}, {1, 2}))}; Tensor out = tf.ones({3, 7, 5}); // clang-format on ET_EXPECT_KERNEL_FAILURE_WITH_MSG( context_, op_index_tensor_out(x, indices, out), ""); } TEST_F(OpIndexTensorOutTest, InvalidIndicesShapeDies2) { if (torch::executor::testing::SupportedFeatures::get()->is_aten) { GTEST_SKIP() << ""; } TensorFactory tf; TensorFactory tfl; Tensor x = tf.zeros({4, 4}); // clang-format off optional indices[] = { optional(tfl.make({2, 2}, {1, 1, 1, 1,})), optional(tfl.make({1, 2}, {3, 0,}))}; Tensor out = tf.ones({4}); // clang-format on ET_EXPECT_KERNEL_FAILURE_WITH_MSG( context_, op_index_tensor_out(x, indices, out), ""); } // // Dynamic Shape Tests // // Test whether resize works when out is having larger size TEST_F(OpIndexTensorOutTest, UpperBoundOutTensor) { TensorFactory tf; TensorFactory tfl; // clang-format off Tensor x = tf.make( {2, 3, 4}, { // [0, :, :] 1., 2., 3., 4., // [0, 0, :] 5., 6., 7., 8., // [0, 1, :] 9., 10., 11., 12., // [0, 2, :] // [1, :, :] -1., -2., -3., -4., // [1, 0, :] -5., -6., -7., -8., // [1, 1, :] -9., -10., -11., -12., // [1, 2, :] }); // clang-format on // Try to select the tensor from the input // indices [0, 2, 2], [1, 1, 2] optional indices[] = { optional(tfl.make({1, 2}, {0, 1})), optional(tfl.make({1, 2}, {2, 1})), optional(tfl.make({1, 2}, {2, 2}))}; Tensor out = tf.zeros({5, 5}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND); // clang-format off Tensor expected = tf.make( {1, 2}, { 11., -7. } ); // clang-format on Tensor ret = op_index_tensor_out(x, indices, out); EXPECT_TENSOR_EQ(out, ret); EXPECT_TENSOR_EQ(ret, expected); }