From 0afddfe02bc50d40e5604fa6fc62f78f494f305c Mon Sep 17 00:00:00 2001 From: Maxim Vafin Date: Wed, 13 Mar 2024 22:41:08 +0100 Subject: [PATCH] [PT FE] Support elu, celu, mish, shuffle ops (#23332) ### Details: - *Support `aten::elu_`, `aten::celu`, `aten::celu_`, `aten::mish`, `aten::mish_` TorchScript ops* - *Support `aten.celu.default`, `aten.channel_shuffle.default`, `aten.elu.default`, `aten.mish.default`, `aten.pixel_unshuffle.default`, `aten.pixel_shuffle.default` FX ops* ### Tickets: - *ticket-id* --------- Co-authored-by: Ilya Lavrenov --- src/frontends/pytorch/src/op/celu.cpp | 40 +++++++++++++++++ src/frontends/pytorch/src/op/elu.cpp | 14 ++++-- src/frontends/pytorch/src/op_table.cpp | 12 +++++ .../aten_stack_list_construct_replacer.cpp | 1 - src/frontends/pytorch/src/utils.cpp | 7 +-- .../py_frontend_tests/test_torch_frontend.py | 2 +- tests/layer_tests/pytorch_tests/test_celu.py | 45 +++++++++++++++++++ tests/layer_tests/pytorch_tests/test_elu.py | 45 +++++++++++++++++++ .../pytorch_tests/test_pixel_shuffle.py | 3 ++ .../pytorch_tests/test_unary_ops.py | 3 ++ 10 files changed, 163 insertions(+), 9 deletions(-) create mode 100644 src/frontends/pytorch/src/op/celu.cpp create mode 100644 tests/layer_tests/pytorch_tests/test_celu.py create mode 100644 tests/layer_tests/pytorch_tests/test_elu.py diff --git a/src/frontends/pytorch/src/op/celu.cpp b/src/frontends/pytorch/src/op/celu.cpp new file mode 100644 index 00000000000..5bec418d82c --- /dev/null +++ b/src/frontends/pytorch/src/op/celu.cpp @@ -0,0 +1,40 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "openvino/frontend/pytorch/node_context.hpp" +#include "openvino/op/divide.hpp" +#include "openvino/op/elu.hpp" +#include "openvino/op/multiply.hpp" +#include "utils.hpp" + +namespace ov { +namespace frontend { +namespace pytorch { +namespace op { + +using namespace ov::op; + +OutputVector translate_celu(const NodeContext& context) { + // aten::celu(%x_copy.1, %self.alpha) + num_inputs_check(context, 1, 2); + auto x = context.get_input(0); + Output alpha; + if (context.input_is_none(1)) { + alpha = context.mark_node(v0::Constant::create(element::f32, Shape{}, {1.})); + } else { + alpha = context.get_input(1); + } + + alpha = context.mark_node(std::make_shared(alpha, x)); + auto divide_node = context.mark_node(std::make_shared(x, alpha)); + auto elu_node = context.mark_node(std::make_shared(divide_node, 1.)); + + auto elu = context.mark_node(std::make_shared(alpha, elu_node)); + return {elu}; +}; + +} // namespace op +} // namespace pytorch +} // namespace frontend +} // namespace ov \ No newline at end of file diff --git a/src/frontends/pytorch/src/op/elu.cpp b/src/frontends/pytorch/src/op/elu.cpp index fee33345436..53904f9b1a8 100644 --- a/src/frontends/pytorch/src/op/elu.cpp +++ b/src/frontends/pytorch/src/op/elu.cpp @@ -5,6 +5,7 @@ #include "openvino/op/elu.hpp" #include "openvino/frontend/pytorch/node_context.hpp" +#include "openvino/op/multiply.hpp" #include "utils.hpp" namespace ov { @@ -12,17 +13,22 @@ namespace frontend { namespace pytorch { namespace op { +using namespace ov::op; + OutputVector translate_elu(const NodeContext& context) { // aten::elu(Tensor self, Scalar alpha=1, Scalar scale=1, Scalar input_scale=1) -> Tensor num_inputs_check(context, 2, 4); auto x = context.get_input(0); auto alpha = context.const_input(1); - // TODO: Figure out what scale and input_scale do - PYTORCH_OP_CONVERSION_CHECK(context.input_is_none(2) || context.const_input(2) == 1, - "Unexpected value of scale input for elu operation"); + auto elu = context.mark_node(std::make_shared(x, alpha)); + if (!context.input_is_none(2)) { + auto scale = context.get_input(2); + scale = context.mark_node(std::make_shared(scale, elu)); + elu = context.mark_node(std::make_shared(elu, scale)); + } PYTORCH_OP_CONVERSION_CHECK(context.input_is_none(3) || context.const_input(3) == 1, "Unexpected value of input_scale input for elu operation"); - return {context.mark_node(std::make_shared(x, alpha))}; + return {elu}; }; } // namespace op diff --git a/src/frontends/pytorch/src/op_table.cpp b/src/frontends/pytorch/src/op_table.cpp index ae62d4b30e7..d853e7a3f67 100644 --- a/src/frontends/pytorch/src/op_table.cpp +++ b/src/frontends/pytorch/src/op_table.cpp @@ -49,6 +49,7 @@ OP_CONVERTER(translate_bitwise_or); OP_CONVERTER(translate_bitwise_xor); OP_CONVERTER(translate_cat); OP_CONVERTER(translate_cdist); +OP_CONVERTER(translate_celu); OP_CONVERTER(translate_channel_shuffle); OP_CONVERTER(translate_clamp); OP_CONVERTER(translate_constant); @@ -361,6 +362,8 @@ const std::map get_supported_ops_ts() { {"aten::cdist", op::translate_cdist}, {"aten::ceil", op::optional_out, 1>}, {"aten::ceil_", op::inplace_op>}, + {"aten::celu", op::translate_celu}, + {"aten::celu_", op::inplace_op}, {"aten::channel_shuffle", op::translate_channel_shuffle}, // aten::chunk - Supported in limited set of patterns {"aten::clamp", op::translate_clamp}, @@ -399,6 +402,7 @@ const std::map get_supported_ops_ts() { {"aten::dropout_", op::skip_node}, // aten::einsum - Supported in limited set of patterns {"aten::elu", op::translate_elu}, + {"aten::elu_", op::inplace_op}, {"aten::embedding", op::translate_embedding}, {"aten::embedding_bag", op::translate_embedding_bag}, {"aten::empty", op::translate_empty}, @@ -512,6 +516,8 @@ const std::map get_supported_ops_ts() { {"aten::meshgrid", op::translate_meshgrid}, {"aten::min", op::translate_min}, {"aten::minimum", op::translate_minimum}, + {"aten::mish", op::translate_1to1_match_1_inputs_with_fp32_type_alignment}, + {"aten::mish_", op::inplace_op>}, {"aten::mm", op::translate_1to1_match_2_inputs}, {"aten::mul", op::translate_mul}, {"aten::mul_", op::translate_mul_}, @@ -739,6 +745,7 @@ const std::map get_supported_ops_fx() { {"aten.bmm.default", op::translate_1to1_match_2_inputs_align_types}, {"aten.cat.default", op::translate_cat_fx}, {"aten.ceil.default", op::translate_1to1_match_1_inputs}, + {"aten.celu.default", op::translate_celu}, {"aten.clamp.default", op::translate_clamp}, {"aten.clamp_max.default", op::translate_1to1_match_2_inputs_align_types}, {"aten.clamp_max.Tensor", op::translate_1to1_match_2_inputs_align_types}, @@ -752,10 +759,12 @@ const std::map get_supported_ops_fx() { {"aten.cos.default", op::translate_1to1_match_1_inputs_with_fp32_type_alignment}, {"aten.cosh.default", op::translate_1to1_match_1_inputs_with_fp32_type_alignment}, {"aten.cumsum.default", op::translate_cumsum_fx}, + {"aten.channel_shuffle.default", op::translate_channel_shuffle}, {"aten.detach.default", op::skip_node}, {"aten.div.Scalar", op::translate_div_fx}, {"aten.div.Tensor", op::translate_div_fx}, {"aten.div.Tensor_mode", op::translate_div_fx}, + {"aten.elu.default", op::translate_elu}, {"aten.embedding.default", op::translate_embedding}, {"aten.empty.memory_format", op::translate_empty}, {"aten.eq.Scalar", op::translate_1to1_match_2_inputs_align_types}, @@ -816,6 +825,7 @@ const std::map get_supported_ops_fx() { {"aten.min.default", op::translate_min}, {"aten.min.dim", op::translate_min_dim_fx}, {"aten.minimum.default", op::translate_minimum}, + {"aten.mish.default", op::translate_1to1_match_1_inputs_with_fp32_type_alignment}, {"aten.mm.default", op::translate_1to1_match_2_inputs}, {"aten.mul.Scalar", op::translate_mul}, {"aten.mul.Tensor", op::translate_mul}, @@ -832,6 +842,8 @@ const std::map get_supported_ops_fx() { {"aten.pow.Scalar", op::translate_pow}, {"aten.pow.Tensor_Scalar", op::translate_pow}, {"aten.pow.Tensor_Tensor", op::translate_pow}, + {"aten.pixel_shuffle.default", op::translate_pixel_shuffle}, + {"aten.pixel_unshuffle.default", op::translate_pixel_unshuffle}, {"aten.reciprocal.default", op::translate_reciprocal}, {"aten.relu.default", op::translate_1to1_match_1_inputs}, {"aten.relu_.default", op::inplace_op>}, diff --git a/src/frontends/pytorch/src/transforms/aten_stack_list_construct_replacer.cpp b/src/frontends/pytorch/src/transforms/aten_stack_list_construct_replacer.cpp index 67ea5f4f9e1..b96d14ca319 100644 --- a/src/frontends/pytorch/src/transforms/aten_stack_list_construct_replacer.cpp +++ b/src/frontends/pytorch/src/transforms/aten_stack_list_construct_replacer.cpp @@ -51,7 +51,6 @@ AtenStackListConstructReplacer::AtenStackListConstructReplacer() { node = compression; } else { OutputVector node_vector; - auto zero = v0::Constant::create(element::i32, Shape{}, {0}); // Iterate over values in ListConstruct for (const auto& list_input : list_inputs) { auto node = concat_list_construct(list_input); diff --git a/src/frontends/pytorch/src/utils.cpp b/src/frontends/pytorch/src/utils.cpp index 32f62eed603..f061efefccb 100644 --- a/src/frontends/pytorch/src/utils.cpp +++ b/src/frontends/pytorch/src/utils.cpp @@ -152,9 +152,10 @@ const std::unordered_map TORCH_TO_OV_TYPE{ {6, element::f32}, {7, element::f64}, {11, element::boolean}, - {12, element::i8}, // quantized i8 - {13, element::u8}, // quantized u8 - {14, element::i32} // quantized i32 + {12, element::i8}, // quantized i8 + {13, element::u8}, // quantized u8 + {14, element::i32}, // quantized i32 + {15, element::bf16}, }; const std::unordered_map TORCH_AUTO_PAD_TO_OV{{"valid", ov::op::PadType::VALID}, diff --git a/tests/layer_tests/py_frontend_tests/test_torch_frontend.py b/tests/layer_tests/py_frontend_tests/test_torch_frontend.py index 63fb7869af5..ebe5c4013d4 100644 --- a/tests/layer_tests/py_frontend_tests/test_torch_frontend.py +++ b/tests/layer_tests/py_frontend_tests/test_torch_frontend.py @@ -250,7 +250,7 @@ def test_so_extension(): converted_model = fe.convert(input_model) assert converted_model assert [n.get_type_name() for n in converted_model.get_ordered_ops()] == [ - "Parameter", "Elu", "Result"] + 'Parameter', 'Elu', 'Constant', 'ConvertLike', 'Multiply', 'Result'] fe.add_extension(get_builtin_extensions_path()) converted_model = fe.convert(input_model) diff --git a/tests/layer_tests/pytorch_tests/test_celu.py b/tests/layer_tests/pytorch_tests/test_celu.py new file mode 100644 index 00000000000..d08fceea789 --- /dev/null +++ b/tests/layer_tests/pytorch_tests/test_celu.py @@ -0,0 +1,45 @@ +# Copyright (C) 2018-2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import pytest +import torch +import torch.nn.functional as F + +from pytorch_layer_test_class import PytorchLayerTest, skip_if_export + + +class aten_celu(torch.nn.Module): + def __init__(self, alpha, dtype, inplace): + super(aten_celu, self).__init__() + self.alpha = alpha + self.dtype = dtype + self.inplace = inplace + if alpha is None: + self.forward = self.forward_no_alpha + + def forward(self, x): + x_copy = x.to(self.dtype) + return F.celu(x_copy, alpha=self.alpha, inplace=self.inplace), x_copy + + def forward_no_alpha(self, x): + x_copy = x.to(self.dtype) + return F.celu(x_copy, inplace=self.inplace), x_copy + + +class TestCelu(PytorchLayerTest): + def _prepare_input(self): + import numpy as np + return (np.random.randn(2, 4, 224, 224).astype(np.float32),) + + @pytest.mark.nightly + @pytest.mark.precommit + @pytest.mark.precommit_torch_export + @pytest.mark.parametrize("alpha", [None, 0.5, 2.]) + @pytest.mark.parametrize("dtype", [torch.float16, torch.float32, torch.float64]) + @pytest.mark.parametrize("inplace", [skip_if_export(True), False]) + def test_celu(self, alpha, dtype, inplace, ie_device, precision, ir_version): + kwargs = {} + if dtype == torch.float16: + kwargs["custom_eps"] = 1e-2 + self._test(aten_celu(alpha, dtype, inplace), None, + "aten::celu_" if inplace else "aten::celu", ie_device, precision, ir_version, **kwargs) diff --git a/tests/layer_tests/pytorch_tests/test_elu.py b/tests/layer_tests/pytorch_tests/test_elu.py new file mode 100644 index 00000000000..283875d7be9 --- /dev/null +++ b/tests/layer_tests/pytorch_tests/test_elu.py @@ -0,0 +1,45 @@ +# Copyright (C) 2018-2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import pytest +import torch +import torch.nn.functional as F + +from pytorch_layer_test_class import PytorchLayerTest, skip_if_export + + +class aten_elu(torch.nn.Module): + def __init__(self, alpha, dtype, inplace): + super(aten_elu, self).__init__() + self.alpha = alpha + self.dtype = dtype + self.inplace = inplace + if alpha is None: + self.forward = self.forward_no_alpha + + def forward(self, x): + x_copy = x.to(self.dtype) + return F.elu(x_copy, alpha=self.alpha, inplace=self.inplace), x_copy + + def forward_no_alpha(self, x): + x_copy = x.to(self.dtype) + return F.elu(x_copy, inplace=self.inplace), x_copy + + +class TestElu(PytorchLayerTest): + def _prepare_input(self): + import numpy as np + return (np.random.randn(2, 4, 224, 224).astype(np.float32),) + + @pytest.mark.nightly + @pytest.mark.precommit + @pytest.mark.precommit_torch_export + @pytest.mark.parametrize("alpha", [None, 0.5, 2.]) + @pytest.mark.parametrize("dtype", [torch.float16, torch.float32, torch.float64]) + @pytest.mark.parametrize("inplace", [skip_if_export(True), False]) + def test_elu(self, alpha, dtype, inplace, ie_device, precision, ir_version): + kwargs = {} + if dtype == torch.float16: + kwargs["custom_eps"] = 1e-2 + self._test(aten_elu(alpha, dtype, inplace), None, + "aten::elu_" if inplace else "aten::elu", ie_device, precision, ir_version, **kwargs) diff --git a/tests/layer_tests/pytorch_tests/test_pixel_shuffle.py b/tests/layer_tests/pytorch_tests/test_pixel_shuffle.py index 4422626ccba..511827eb7ce 100644 --- a/tests/layer_tests/pytorch_tests/test_pixel_shuffle.py +++ b/tests/layer_tests/pytorch_tests/test_pixel_shuffle.py @@ -29,6 +29,7 @@ class TestPixelShuffle(PytorchLayerTest): (2, [1, 2, 3, 8, 4, 4]),]) @pytest.mark.nightly @pytest.mark.precommit + @pytest.mark.precommit_torch_export def test_pixel_shuffle(self, upscale_factor, shape, ie_device, precision, ir_version): self.shape = shape self._test(*self.create_model(upscale_factor), @@ -57,6 +58,7 @@ class TestPixelUnshuffle(PytorchLayerTest): (2, [1, 2, 3, 2, 8, 8]),]) @pytest.mark.nightly @pytest.mark.precommit + @pytest.mark.precommit_torch_export def test_pixel_unshuffle(self, upscale_factor, shape, ie_device, precision, ir_version): self.shape = shape self._test(*self.create_model(upscale_factor), @@ -90,6 +92,7 @@ class TestChannelShuffle(PytorchLayerTest): ]) @pytest.mark.nightly @pytest.mark.precommit + @pytest.mark.precommit_torch_export def test_channel_shuffle(self, groups, shape, ie_device, precision, ir_version): self.shape = shape self._test(*self.create_model(groups), diff --git a/tests/layer_tests/pytorch_tests/test_unary_ops.py b/tests/layer_tests/pytorch_tests/test_unary_ops.py index 9981ba3b178..d93a25edcc8 100644 --- a/tests/layer_tests/pytorch_tests/test_unary_ops.py +++ b/tests/layer_tests/pytorch_tests/test_unary_ops.py @@ -42,6 +42,7 @@ OPS = { "aten::log1p": torch.log1p, "aten::log1p_": torch.log1p_, "aten::log_sigmoid": F.logsigmoid, + "aten::mish": F.mish, "aten::cos": torch.cos, "aten::cos_": torch.cos_, "aten::sin": torch.sin, @@ -178,6 +179,7 @@ class TestUnaryOp(PytorchLayerTest): "aten::log2_", "aten::log10_", "aten::log1p_", + "aten::mish", # trigonometry "aten::cos_", "aten::sin_", @@ -245,6 +247,7 @@ class TestUnaryOp(PytorchLayerTest): "aten::relu6", "aten::selu", "aten::silu", + "aten::mish", ]) def test_unary_func_op_inplace(self, op_type, dtype, ie_device, precision, ir_version): self.dtype = dtype