blob: 03a91005e83159acb7424f69e73f9807c5bdb49a [file] [log] [blame]
/*
* 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 <executorch/kernels/test/FunctionHeaderWrapper.h> // Declares the operator
#include <executorch/kernels/test/TestUtil.h>
#include <executorch/kernels/test/supported_features.h>
#include <executorch/runtime/core/exec_aten/exec_aten.h>
#include <executorch/runtime/core/exec_aten/testing_util/tensor_factory.h>
#include <executorch/runtime/core/exec_aten/testing_util/tensor_util.h>
#include <executorch/runtime/core/exec_aten/util/scalar_type_util.h>
#include <gtest/gtest.h>
#include <sys/types.h>
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<optional<Tensor>>;
class OpIndexTensorOutTest : public OperatorTest {
protected:
Tensor& op_index_tensor_out(
const Tensor& input,
OptTensorArrayRef indices,
Tensor& out) {
#ifdef USE_ATEN_LIB
c10::List<c10::optional<at::Tensor>> 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<INPUT_DTYPE> tf;
TensorFactory<INDEX_DTYPE> tfl;
TensorFactory<OUTPUT_DTYPE> tfo;
TensorFactory<ScalarType::Bool> 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<Tensor> indices[] = {
optional<Tensor>(tfl.make({2}, {0, 1})),
optional<Tensor>(tfl.make({2}, {1, 0})),
optional<Tensor>(tfl.make({2}, {2, 3}))};
optional<Tensor> indices_mixed[] = {
optional<Tensor>(tfl.make({2}, {0, 1})),
optional<Tensor>(tfb.make({2}, {false, true})),
optional<Tensor>(tfl.make({2}, {2, 3}))};
std::vector<int32_t> 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<ScalarType::dtype, ScalarType::Long, ScalarType::dtype>();
ET_FORALL_REAL_TYPES_AND(Bool, 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<ScalarType::Double> tf;
const std::vector<int32_t> 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<ScalarType::Double> tf;
TensorFactory<ScalarType::Bool> 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<ScalarType::Double> tf;
TensorFactory<ScalarType::Int> tfi;
TensorFactory<ScalarType::Long> tfl;
TensorFactory<ScalarType::Bool> 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<Tensor> indices[] = {
optional<Tensor>(tfl.make({1}, {1})),
optional<Tensor>(tfl.make({1}, {0})),
optional<Tensor>(tfl.make({2}, {1, 2}))};
optional<Tensor> indices_int[] = {
optional<Tensor>(tfi.make({1}, {1})),
optional<Tensor>(tfi.make({1}, {0})),
optional<Tensor>(tfi.make({2}, {1, 2}))};
optional<Tensor> indices_negative[] = {
optional<Tensor>(tfl.make({1}, {-1})),
optional<Tensor>(tfl.make({1}, {0})),
optional<Tensor>(tfl.make({2}, {-3, -2}))};
optional<Tensor> indices_bool[] = {
optional<Tensor>(tfb.make({2}, {false, true})),
optional<Tensor>(tfb.make({3}, {true, false, false})),
optional<Tensor>(tfl.make({2}, {-3, -2}))};
optional<Tensor> indices_mixed[] = {
optional<Tensor>(tfb.make({2}, {false, true})),
optional<Tensor>(tfl.make({1}, {0})),
optional<Tensor>(tfl.make({2}, {-3, -2}))};
std::vector<int32_t> 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<ScalarType::Double> tf;
TensorFactory<ScalarType::Long> 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<Tensor> indices[] = {
optional<Tensor>(tfl.make({1, 2}, {0, 0})),
optional<Tensor>(tfl.make({1, 2}, {1, 1})),
optional<Tensor>(tfl.make({1, 2}, {2, 2}))};
std::vector<int32_t> 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<ScalarType::Double> tf;
TensorFactory<ScalarType::Int> tfi;
TensorFactory<ScalarType::Long> tfl;
TensorFactory<ScalarType::Bool> 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<Tensor> indices[] = {
optional<Tensor>(tfl.make({1}, {1})),
optional<Tensor>(tfl.make({2}, {0, 1}))};
optional<Tensor> indices_mixed[] = {
optional<Tensor>(tfi.make({1}, {-1})),
optional<Tensor>(tfb.make({3}, {true, true, false}))};
std::vector<int32_t> 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<ScalarType::Double> tf;
TensorFactory<ScalarType::Long> 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<Tensor> indices0[] = {
optional<Tensor>(),
optional<Tensor>(tfl.make({1}, {1})),
optional<Tensor>(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<Tensor> indices1[] = {
optional<Tensor>(tfl.make({1}, {1})),
optional<Tensor>(),
optional<Tensor>(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<Tensor> indices2[] = {
optional<Tensor>(tfl.make({1}, {1})),
optional<Tensor>(tfl.make({2}, {0, 1})),
optional<Tensor>()};
// 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<ScalarType::Double> tf;
Tensor x = tf.make({2, 3}, {1., 2., 3., 4., 5., 6.});
optional<Tensor> indices0[] = {optional<Tensor>()};
run_test_cases(x, indices0, x);
optional<Tensor> indices1[] = {optional<Tensor>(), optional<Tensor>()};
run_test_cases(x, indices1, x);
optional<Tensor> indices2[] = {
optional<Tensor>(), optional<Tensor>(), optional<Tensor>()};
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<ScalarType::Float> 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<ScalarType::Double, ScalarType::Long, ScalarType::Double>();
test_dtype<ScalarType::Double, ScalarType::Int, ScalarType::Double>();
}
//
// Death Tests
//
TEST_F(OpIndexTensorOutTest, IndexOutOfBoundDies) {
TensorFactory<ScalarType::Int> tf;
TensorFactory<ScalarType::Long> 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<ScalarType::Int> tf;
TensorFactory<ScalarType::Long> 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<ScalarType::Float> tf;
TensorFactory<ScalarType::Bool> 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<ScalarType::Float> tf;
TensorFactory<ScalarType::Bool> 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<ScalarType::Float> tf;
TensorFactory<ScalarType::Bool> 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<ScalarType::Int> tf;
TensorFactory<ScalarType::Long> 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<ScalarType::Int> tf;
TensorFactory<ScalarType::Float> 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<ScalarType::Float> tf;
TensorFactory<ScalarType::Long> tfl;
Tensor x = tf.zeros({2, 4, 7, 5});
// clang-format off
optional<Tensor> indices[] = {
optional<Tensor>(tfl.make({3}, {1, 1, 1,})),
optional<Tensor>(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<ScalarType::Float> tf;
TensorFactory<ScalarType::Long> tfl;
Tensor x = tf.zeros({4, 4});
// clang-format off
optional<Tensor> indices[] = {
optional<Tensor>(tfl.make({2, 2}, {1, 1, 1, 1,})),
optional<Tensor>(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<ScalarType::Double> tf;
TensorFactory<ScalarType::Long> 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<Tensor> indices[] = {
optional<Tensor>(tfl.make({1, 2}, {0, 1})),
optional<Tensor>(tfl.make({1, 2}, {2, 1})),
optional<Tensor>(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);
}