/* * 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 using namespace ::testing; using exec_aten::ArrayRef; using exec_aten::IntArrayRef; using exec_aten::ScalarType; using exec_aten::Tensor; using torch::executor::testing::TensorFactory; class OpPermuteCopyTest : public OperatorTest { protected: Tensor& op_permute_copy_out(const Tensor& self, IntArrayRef dims, Tensor& out) { return torch::executor::aten::permute_copy_outf(context_, self, dims, out); } }; TEST_F(OpPermuteCopyTest, OneDPermute) { TensorFactory tf; const std::vector new_dim = {0}; const std::vector sizes = {2}; Tensor t_int = tf.make(sizes, {1, 2}); Tensor out = tf.zeros(sizes); op_permute_copy_out( t_int, ArrayRef(new_dim.data(), new_dim.size()), out); EXPECT_TENSOR_EQ(out, tf.make(sizes, {1, 2})); } TEST_F(OpPermuteCopyTest, PermuteWithNoDataReorder) { TensorFactory tf; const std::vector new_dim = {1, 0, 2}; // clang-format off Tensor t_int = tf.make({1,4,5}, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,18, 19}); // clang-format on const std::vector new_sizes = {4, 1, 5}; Tensor out = tf.zeros(new_sizes); op_permute_copy_out( t_int, ArrayRef(new_dim.data(), new_dim.size()), out); // clang-format off EXPECT_TENSOR_EQ(out, tf.make(new_sizes, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19})); // clang-format on } TEST_F(OpPermuteCopyTest, TwoDPermute) { TensorFactory tf; const std::vector new_dim = {1, 0}; // clang-format off Tensor t_int = tf.make({2, 3}, { // 2x3 data block 0, 1, 2, 3, 4, 5 }); // clang-format on const std::vector new_sizes = {3, 2}; Tensor out = tf.zeros(new_sizes); op_permute_copy_out( t_int, ArrayRef(new_dim.data(), new_dim.size()), out); // clang-format off EXPECT_TENSOR_EQ(out, tf.make(new_sizes, { // 3x2 data block 0, 3, 1, 4, 2, 5 })); // clang-format on } TEST_F(OpPermuteCopyTest, ThreeDPermute) { TensorFactory tf; const std::vector new_dim = {2, 0, 1}; // clang-format off Tensor t_int = tf.make({2, 1, 3}, { // 2 1x3 data blocks 0, 1, 2, 3, 4, 5 }); // clang-format on const std::vector new_sizes = {3, 2, 1}; Tensor out = tf.zeros(new_sizes); op_permute_copy_out( t_int, ArrayRef(new_dim.data(), new_dim.size()), out); // clang-format off EXPECT_TENSOR_EQ(out, tf.make(new_sizes, { // 3 2x1 data blocks 0, 3, 1, 4, 2, 5 })); // clang-format on } TEST_F(OpPermuteCopyTest, FourDPermute) { TensorFactory tf; const std::vector new_dim = {0, 3, 2, 1}; // clang-format off Tensor t_int = tf.make( {2, 3, 3, 4}, // 2 groupings of 3 3x4 data blocks {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}); // clang-format on const std::vector new_sizes = {2, 4, 3, 3}; Tensor out = tf.zeros(new_sizes); // Long results like this are gotten by running torch.permute in a notebook // and copy pasting the result. Ex: // import torch // x = torch.arange(0, 72, 1).view(2, 3, 3, 4).contiguous() // print(x.flatten().contiguous()) // z = torch.permute(x, (0, 3, 2, 1)) // print(z.flatten().contiguous()) op_permute_copy_out( t_int, ArrayRef(new_dim.data(), new_dim.size()), out); EXPECT_TENSOR_EQ( out, // clang-format off tf.make(new_sizes, { // 2 groupings of 4 3x3 data blocks 0, 12, 24, 4, 16, 28, 8, 20, 32, 1, 13, 25, 5, 17, 29, 9, 21, 33, 2, 14, 26, 6, 18, 30, 10, 22, 34, 3, 15, 27, 7, 19, 31, 11, 23, 35, 36, 48, 60, 40, 52, 64, 44, 56, 68, 37, 49, 61, 41, 53, 65, 45, 57, 69, 38, 50, 62, 42, 54, 66, 46, 58, 70, 39, 51, 63, 43, 55, 67, 47, 59, 71})); // clang-format on } TEST_F(OpPermuteCopyTest, FiveDPermute) { TensorFactory tf; const std::vector new_dim = {4, 3, 2, 1, 0}; const std::vector sizes = {2, 2, 2, 2, 2}; // clang-format off Tensor t_int = tf.make( sizes, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}); // clang-format on Tensor out = tf.zeros(sizes); op_permute_copy_out( t_int, ArrayRef(new_dim.data(), new_dim.size()), out); // Long results like this are gotten by running torch.permute in a notebook // and copy pasting the result. Ex: // import torch // x = torch.arange(0, 32, 1).view(2, 2, 2, 2, 2).contiguous() // print(x.flatten().contiguous()) // z = torch.permute(x, (4, 3, 2, 1, 0)) // print(z.flatten().contiguous()) // clang-format off EXPECT_TENSOR_EQ( out, tf.make(sizes, { 0, 16, 8, 24, 4, 20, 12, 28, 2, 18, 10, 26, 6, 22, 14, 30, 1, 17, 9, 25, 5, 21, 13, 29, 3, 19, 11, 27, 7, 23, 15, 31})); // clang-format on } TEST_F(OpPermuteCopyTest, AllDimensionsSizeOne) { TensorFactory tf; const std::vector new_dim = {4, 3, 2, 1, 0}; const std::vector sizes = {1, 1, 1, 1, 1}; Tensor t_int = tf.make(sizes, {1}); Tensor out = tf.zeros(sizes); op_permute_copy_out( t_int, ArrayRef(new_dim.data(), new_dim.size()), out); EXPECT_TENSOR_EQ(out, tf.make(sizes, {1})); } TEST_F(OpPermuteCopyTest, DupeDimensionPos) { TensorFactory tf; const std::vector new_dim = {0, 1, 1}; const std::vector sizes = {1, 1, 1}; Tensor t_int = tf.make(sizes, {1}); Tensor out = tf.zeros(sizes); ET_EXPECT_KERNEL_FAILURE( context_, op_permute_copy_out( t_int, ArrayRef(new_dim.data(), new_dim.size()), out)); } TEST_F(OpPermuteCopyTest, DupeDimensionPos2) { TensorFactory tf; const std::vector new_dim = {1, 1, 1}; const std::vector sizes = {1, 1, 1}; Tensor t_int = tf.make(sizes, {1}); Tensor out = tf.zeros(sizes); ET_EXPECT_KERNEL_FAILURE( context_, op_permute_copy_out( t_int, ArrayRef(new_dim.data(), new_dim.size()), out)); } TEST_F(OpPermuteCopyTest, DupeDimensionNeg) { TensorFactory tf; const std::vector new_dim = {0, 1, -2}; const std::vector sizes = {1, 1, 1}; Tensor t_int = tf.make(sizes, {1}); Tensor out = tf.zeros(sizes); ET_EXPECT_KERNEL_FAILURE( context_, op_permute_copy_out( t_int, ArrayRef(new_dim.data(), new_dim.size()), out)); } TEST_F(OpPermuteCopyTest, DupeDimensionNeg2) { TensorFactory tf; const std::vector new_dim = {0, 1, -5}; const std::vector sizes = {1, 1, 1}; Tensor t_int = tf.make(sizes, {1}); Tensor out = tf.zeros(sizes); ET_EXPECT_KERNEL_FAILURE( context_, op_permute_copy_out( t_int, ArrayRef(new_dim.data(), new_dim.size()), out)); } TEST_F(OpPermuteCopyTest, MismatchDim) { TensorFactory tf; const std::vector new_dim = {0, 1, 2}; const std::vector sizes = {1, 1}; Tensor t_int = tf.make(sizes, {1}); Tensor out = tf.zeros(sizes); ET_EXPECT_KERNEL_FAILURE( context_, op_permute_copy_out( t_int, ArrayRef(new_dim.data(), new_dim.size()), out)); } /* %python import torch torch.manual_seed(0) x = torch.randint(10, (2, 3, 4)) res = torch.permute(x, (2, 0, 1)) op = "op_permute_copy_out" opt_setup_params = f""" {declare_array_ref([2, 0, 1], "int64_t", "perm_aref")} """ opt_extra_params = "perm_aref," dtype = "ScalarType::Int" check = "EXPECT_TENSOR_EQ" */ TEST_F(OpPermuteCopyTest, DynamicShapeUpperBoundSameAsExpected) { /* %python out_args = "{4, 2, 3}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND" %rewrite(unary_op) */ TensorFactory tf; Tensor x = tf.make({2, 3, 4}, {4, 9, 3, 0, 3, 9, 7, 3, 7, 3, 1, 6, 6, 9, 8, 6, 6, 8, 4, 3, 6, 9, 1, 4}); Tensor expected = tf.make({4, 2, 3}, {4, 3, 7, 6, 6, 6, 9, 9, 3, 9, 8, 9, 3, 7, 1, 8, 4, 1, 0, 3, 6, 6, 3, 4}); std::vector perm_arefv = {2, 0, 1}; ArrayRef perm_aref(perm_arefv.data(), perm_arefv.size()); Tensor out = tf.zeros({4, 2, 3}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND); op_permute_copy_out(x, perm_aref, out); EXPECT_TENSOR_EQ(out, expected); } TEST_F(OpPermuteCopyTest, DynamicShapeUpperBoundLargerThanExpected) { /* %python out_args = "{5, 5, 5}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND" %rewrite(unary_op) */ TensorFactory tf; Tensor x = tf.make({2, 3, 4}, {4, 9, 3, 0, 3, 9, 7, 3, 7, 3, 1, 6, 6, 9, 8, 6, 6, 8, 4, 3, 6, 9, 1, 4}); Tensor expected = tf.make({4, 2, 3}, {4, 3, 7, 6, 6, 6, 9, 9, 3, 9, 8, 9, 3, 7, 1, 8, 4, 1, 0, 3, 6, 6, 3, 4}); std::vector perm_arefv = {2, 0, 1}; ArrayRef perm_aref(perm_arefv.data(), perm_arefv.size()); Tensor out = tf.zeros({5, 5, 5}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND); op_permute_copy_out(x, perm_aref, out); EXPECT_TENSOR_EQ(out, expected); } TEST_F(OpPermuteCopyTest, DynamicShapeUnbound) { if (!torch::executor::testing::SupportedFeatures::get()->output_resize) { GTEST_SKIP() << "Dynamic shape unbound not supported"; } /* %python out_args = "{1, 1, 1}, torch::executor::TensorShapeDynamism::DYNAMIC_UNBOUND" %rewrite(unary_op) */ TensorFactory tf; Tensor x = tf.make({2, 3, 4}, {4, 9, 3, 0, 3, 9, 7, 3, 7, 3, 1, 6, 6, 9, 8, 6, 6, 8, 4, 3, 6, 9, 1, 4}); Tensor expected = tf.make({4, 2, 3}, {4, 3, 7, 6, 6, 6, 9, 9, 3, 9, 8, 9, 3, 7, 1, 8, 4, 1, 0, 3, 6, 6, 3, 4}); std::vector perm_arefv = {2, 0, 1}; ArrayRef perm_aref(perm_arefv.data(), perm_arefv.size()); Tensor out = tf.zeros( {1, 1, 1}, torch::executor::TensorShapeDynamism::DYNAMIC_UNBOUND); op_permute_copy_out(x, perm_aref, out); EXPECT_TENSOR_EQ(out, expected); }