diff --git a/.github/workflows/job_python_unit_tests.yml b/.github/workflows/job_python_unit_tests.yml index a2129b72672..86e111c5346 100644 --- a/.github/workflows/job_python_unit_tests.yml +++ b/.github/workflows/job_python_unit_tests.yml @@ -97,9 +97,9 @@ jobs: run: | # Install the core OV wheel python3 -m pip install ${INSTALL_DIR}/tools/openvino-*.whl - + extras_to_install="caffe,kaldi,onnx,tensorflow2,pytorch" - + if [[ "${{ runner.arch }}" != "ARM64" ]]; then extras_to_install="mxnet,$extras_to_install" fi @@ -144,14 +144,14 @@ jobs: # Skips under Ticket: 122666 skip_filter='--ignore-glob=**/mo/unit_tests/mo/front/mxnet/**' fi - + python3 -m pytest -s ${INSTALL_TEST_DIR}/mo/unit_tests \ --junitxml=${INSTALL_TEST_DIR}/TEST-ModelOptimizer.xml \ "$skip_filter" - name: Python ONNX operators tests if: (fromJSON(inputs.affected-components).Python_API.test || - fromJSON(inputs.affected-components).ONNX_FE.test) && + fromJSON(inputs.affected-components).ONNX_FE.test) && runner.arch != 'ARM64' # Ticket: 123325 run: | # Skip test_onnx/test_zoo_models and test_onnx/test_backend due to long execution time - ONNX Model Zoo tests are run separately @@ -174,13 +174,13 @@ jobs: # Import 'test_utils' installed in '/tests/python/openvino' export LD_LIBRARY_PATH=${PIP_INSTALL_PATH}/openvino/libs:$LD_LIBRARY_PATH export PYTHONPATH=${INSTALL_TEST_DIR}/python - + if [[ "${{ runner.os }}" == "Linux" ]] && [[ "${{ runner.arch }}" == "ARM64" ]]; then # Find gomp lib GOMP_LIB=$(find "${PIP_INSTALL_PATH}/torch/lib/../../torch.libs/" -name '*libgomp-*so*') export LD_PRELOAD=${GOMP_LIB} fi - + python3 -m pytest ${LAYER_TESTS_INSTALL_DIR}/mo_python_api_tests --junitxml=${INSTALL_TEST_DIR}/TEST-test_mo_convert.xml env: TEST_DEVICE: CPU @@ -192,13 +192,13 @@ jobs: # Import 'test_utils' installed in '/tests/python/openvino' export PYTHONPATH=${INSTALL_TEST_DIR}/python export LD_LIBRARY_PATH=${PIP_INSTALL_PATH}/openvino/libs:$LD_LIBRARY_PATH - + if [[ "${{ runner.os }}" == "Linux" ]] && [[ "${{ runner.arch }}" == "ARM64" ]]; then # Find gomp lib GOMP_LIB=$(find "${PIP_INSTALL_PATH}/torch/lib/../../torch.libs/" -name '*libgomp-*so*') export LD_PRELOAD=${GOMP_LIB} fi - + python3 -m pytest ${LAYER_TESTS_INSTALL_DIR}/ovc_python_api_tests --junitxml=${INSTALL_TEST_DIR}/TEST-test_ovc_convert.xml env: TEST_DEVICE: CPU @@ -229,7 +229,7 @@ jobs: PYTORCH_TRACING_MODE: EXPORT - name: PyTorch torch.compile TORCHFX Layer Tests - if: ${{ fromJSON(inputs.affected-components).PyTorch_FE.test && runner.os != 'macOS' }} + if: ${{ fromJSON(inputs.affected-components).PyTorch_FE.test && runner.os != 'macOS' && runner.arch != 'ARM64' }} # Ticket: 126287 run: | python3 -m pytest ${LAYER_TESTS_INSTALL_DIR}/pytorch_tests -m precommit_fx_backend --junitxml=${INSTALL_TEST_DIR}/TEST-pytorch.xml env: @@ -238,7 +238,7 @@ jobs: PYTORCH_TRACING_MODE: TORCHFX - name: PyTorch torch.compile TORCHSCRIPT Layer Tests - if: ${{ fromJSON(inputs.affected-components).PyTorch_FE.test && runner.os != 'macOS' }} + if: ${{ fromJSON(inputs.affected-components).PyTorch_FE.test && runner.os != 'macOS' && runner.arch != 'ARM64' }} # Ticket: 126287 run: | python3 -m pytest ${LAYER_TESTS_INSTALL_DIR}/pytorch_tests -m precommit_ts_backend --junitxml=${INSTALL_TEST_DIR}/TEST-pytorch.xml env: diff --git a/src/bindings/python/src/openvino/frontend/pytorch/torchdynamo/backend.py b/src/bindings/python/src/openvino/frontend/pytorch/torchdynamo/backend.py index fff781fa88f..dbea0f93c2a 100644 --- a/src/bindings/python/src/openvino/frontend/pytorch/torchdynamo/backend.py +++ b/src/bindings/python/src/openvino/frontend/pytorch/torchdynamo/backend.py @@ -10,18 +10,21 @@ from functools import partial from hashlib import sha256 import torch -from torch._dynamo.backends.common import fake_tensor_unsupported +from torch._dynamo.backends.common import fake_tensor_unsupported, aot_autograd from torch._dynamo.backends.registry import register_backend from torch._inductor.compile_fx import compile_fx from torch.fx.experimental.proxy_tensor import make_fx +from torch._decomp import decomposition_table, get_decompositions from openvino.frontend import FrontEndManager from openvino.runtime import Core, Type, PartialShape from openvino.frontend.pytorch.ts_decoder import TorchScriptPythonDecoder +from openvino.frontend.pytorch.torchdynamo import decompositions +from openvino.frontend.pytorch.torchdynamo.decompositions import get_aot_decomposition_list from openvino.frontend.pytorch.torchdynamo.partition import Partitioner from openvino.frontend.pytorch.torchdynamo.execute import execute, execute_cached from openvino.frontend.pytorch.torchdynamo.compile import cached_model_name, openvino_compile_cached_model -from openvino.frontend.pytorch.torchdynamo.backend_utils import _get_cache_dir, _get_device, _get_model_caching +from openvino.frontend.pytorch.torchdynamo.backend_utils import _get_cache_dir, _get_device, _get_model_caching, _get_decompositions, _get_aot_autograd from openvino.runtime import Core, Type, PartialShape @@ -42,10 +45,15 @@ log = logging.getLogger(__name__) 2) model = torch.compile(model, backend="openvino") """ +openvino_options = {} @register_backend @fake_tensor_unsupported def openvino(subgraph, example_inputs, options=None): + if (_get_aot_autograd(options)): + global openvino_options + openvino_options = options + return aot_autograd(fw_compiler=fx_openvino, bw_compiler=fx_openvino)(subgraph, example_inputs) return fx_openvino(subgraph, example_inputs, options) @register_backend @@ -111,9 +119,10 @@ def ts_openvino(subgraph, example_inputs): log.debug(f"Failed in compilation: {e}") return compile_fx(subgraph, example_inputs) - -def fx_openvino(subgraph, example_inputs, options): +def fx_openvino(subgraph, example_inputs, options=None): try: + if len(openvino_options) != 0: + options = openvino_options executor_parameters = None inputs_reversed = False openvino_model_caching = _get_model_caching(options) @@ -134,11 +143,18 @@ def fx_openvino(subgraph, example_inputs, options): return _call if inputs_reversed: example_inputs.reverse() - model = make_fx(subgraph)(*example_inputs) + + from torch._subclasses.fake_tensor import FakeTensorMode + decompositions = _get_decompositions(options) + if (_get_aot_autograd(options)): + decompositions = decompositions + get_aot_decomposition_list() + with FakeTensorMode(allow_non_fake_inputs=True): + model = make_fx(subgraph, decomposition_table=get_decompositions(decompositions))(*example_inputs) + with torch.no_grad(): model.eval() - partitioner = Partitioner() - compiled_model = partitioner.make_partitions(model) + partitioner = Partitioner(options) + compiled_model = partitioner.make_partitions(model, options) if executor_parameters is not None and 'model_hash_str' in executor_parameters: # Check if the model is fully supported. diff --git a/src/bindings/python/src/openvino/frontend/pytorch/torchdynamo/backend_utils.py b/src/bindings/python/src/openvino/frontend/pytorch/torchdynamo/backend_utils.py index 56be57e01aa..867a4a2e9fd 100644 --- a/src/bindings/python/src/openvino/frontend/pytorch/torchdynamo/backend_utils.py +++ b/src/bindings/python/src/openvino/frontend/pytorch/torchdynamo/backend_utils.py @@ -49,6 +49,15 @@ def _get_cache_dir(options) -> Optional[Any]: return cache_dir +def _get_aot_autograd(options) -> Optional[Any]: + if options is not None and "aot_autograd" in options: + aot_autograd = options["aot_autograd"] + if bool(aot_autograd) and str(aot_autograd).lower() not in ["false", "0"]: + return True + else: + return False + + def _get_model_caching(options) -> Optional[Any]: if options is not None and "model_caching" in options: caching = options["model_caching"] @@ -67,4 +76,24 @@ def _get_model_caching(options) -> Optional[Any]: def _get_config(options) -> Optional[Any]: if options is not None and "config" in options: return options["config"] - return {} \ No newline at end of file + return {} + +def _get_decompositions(options) -> Optional[Any]: + decompositions = [] + if options is not None and "decompositions" in options: + decompositions = options["decompositions"] + return decompositions + +def _get_disabled_ops(options) -> Optional[Any]: + disabled_ops = [] + if options is not None and "disabled_ops" in options: + disabled_ops = options["disabled_ops"] + return disabled_ops + +def _is_testing(options) -> Optional[Any]: + if options is not None and "testing" in options: + is_testing = options["testing"] + if bool(is_testing) and str(is_testing).lower not in ["false", "0"]: + return True + return False + diff --git a/src/bindings/python/src/openvino/frontend/pytorch/torchdynamo/decompositions.py b/src/bindings/python/src/openvino/frontend/pytorch/torchdynamo/decompositions.py new file mode 100644 index 00000000000..98821f4844e --- /dev/null +++ b/src/bindings/python/src/openvino/frontend/pytorch/torchdynamo/decompositions.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2018-2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +# flake8: noqa +# mypy: ignore-errors + +import torch +from torch._decomp.decompositions import aten, pw_cast_for_opmath +from torch._decomp import register_decomposition, get_decompositions + + +@register_decomposition(aten.convolution_backward) +@pw_cast_for_opmath +def convolution_backward( + grad_output, + inp, + weight, + bias, + stride, + padding, + dilation, + transposed, + output_padding, + groups, + output_mask, +): + if stride == [2, 2]: + output_padding = [1, 1] + + # Compute the gradient of the input tensor + grad_input = torch.nn.functional.conv_transpose2d( + grad_output, weight, stride=stride, padding=padding, dilation=dilation, groups=groups, output_padding=output_padding + ) + + # Compute the gradient of the weight tensor + grad_weight = torch.nn.functional.conv_transpose2d( + inp, weight.transpose(0, 1), stride=stride, padding=padding, dilation=dilation, groups=groups, output_padding=output_padding + ) + + # Compute the gradient of the bias tensor + if bias is not None: + grad_bias = grad_output.sum([0, 2, 3], keepdim=True) + else: + grad_bias = None + + return grad_input, grad_weight, grad_bias + +if len(get_decompositions([aten._scaled_dot_product_flash_attention.default])) == 0: + @register_decomposition(aten._scaled_dot_product_flash_attention.default) + def scaled_dot_product_flash_attention( + query, + key, + value, + dropout_p=0.0, + is_causal=False, + *, + return_debug_mask=False, + scale=None, + ): + batch_size, num_head, q_size, head_size = ( + query.shape[0], + query.shape[1], + query.shape[2], + query.shape[3], + ) + + logsumexp = torch.empty([batch_size, q_size, num_head, head_size], dtype=torch.float) + cum_seq_q, cum_seq_k = torch.empty([], dtype=torch.long), torch.empty( + [], dtype=torch.long + ) + max_q, max_k = 0, 0 + philox_seed, philox_offset = torch.empty([], dtype=torch.long), torch.empty( + [], dtype=torch.long + ) + debug_attn_mask = torch.empty( + [], + dtype=query.dtype, + device=query.device, + requires_grad=query.requires_grad, + ) + output, _ = aten._scaled_dot_product_attention_math.default( + query, key, value, None, dropout_p, is_causal, None, scale=scale + ) + + scores = torch.matmul(query, key.transpose(-2, -1)) / (key.size(-1) ** 0.5) + logsumexp = torch.logsumexp(scores, dim=-1) + + output = output.transpose(1, 2).contiguous(memory_format=torch.contiguous_format) + return ( + output.transpose(1, 2), + logsumexp, + cum_seq_q, + cum_seq_k, + max_q, + max_k, + philox_seed, + philox_offset, + debug_attn_mask, + ) + + +def get_aot_decomposition_list(): + return ([torch.ops.aten._scaled_dot_product_flash_attention.default, + torch.ops.aten._softmax.default, + torch.ops.aten._softmax_backward_data.default, + torch.ops.aten.convolution_backward.default, + torch.ops.aten.gelu_backward.default, + torch.ops.aten.native_group_norm.default, + torch.ops.aten.native_group_norm_backward.default, + torch.ops.aten.native_layer_norm.default, + torch.ops.aten.native_layer_norm_backward.default, + torch.ops.aten.slice_backward.default]) diff --git a/src/bindings/python/src/openvino/frontend/pytorch/torchdynamo/op_support.py b/src/bindings/python/src/openvino/frontend/pytorch/torchdynamo/op_support.py index d7cb590a6dc..5e8a1285134 100644 --- a/src/bindings/python/src/openvino/frontend/pytorch/torchdynamo/op_support.py +++ b/src/bindings/python/src/openvino/frontend/pytorch/torchdynamo/op_support.py @@ -13,9 +13,9 @@ from torch._ops import OpOverload from torch.fx.node import Node, _get_qualified_name from torch.fx.passes.operator_support import OperatorSupport from torch.fx.passes.tools_common import CALLABLE_NODE_OPS +from openvino.frontend.pytorch.torchdynamo.backend_utils import _get_disabled_ops import typing as t - import logging logger = logging.getLogger(__name__) @@ -26,17 +26,20 @@ class OperatorSupport(OperatorSupport): Operator support for OpenVINO backend. """ - def __init__(self): + def __init__(self, options): support_dict = { "_operator.getitem": None, "torch.ops.aten._adaptive_avg_pool2d.default": None, + "torch.ops.aten._log_softmax.default": None, "torch.ops.aten._softmax.default": None, "torch.ops.aten._to_copy.default": None, "torch.ops.aten._unsafe_view.default": None, "torch.ops.aten._unsafe_view.default": None, + "torch.ops.aten.add.Scalar": None, "torch.ops.aten.add.Tensor": None, "torch.ops.aten.add_.Tensor": None, "torch.ops.aten.addmm.default": None, + "torch.ops.aten.amax.default": None, "torch.ops.aten.arange.start": None, "torch.ops.aten.arange.default": None, "torch.ops.aten.argmax.default": None, @@ -56,10 +59,12 @@ class OperatorSupport(OperatorSupport): "torch.ops.aten.div.Tensor": None, "torch.ops.aten.embedding.default": None, "torch.ops.aten.empty.memory_format": None, + "torch.ops.aten.erf.default": None, "torch.ops.aten.eq.Scalar": None, "torch.ops.aten.eq.Tensor": None, "torch.ops.aten.exp.default": None, "torch.ops.aten.expand.default": None, + "torch.ops.aten.fill.Scalar": None, "torch.ops.aten.full.default": None, "torch.ops.aten.gather.default": None, "torch.ops.aten.gelu.default": None, @@ -73,9 +78,11 @@ class OperatorSupport(OperatorSupport): "torch.ops.aten.linalg_vector_norm.default": None, "torch.ops.aten.lt.Tensor": None, "torch.ops.aten.log.default": None, + "torch.ops.aten.log_sigmoid_forward.default": None, "torch.ops.aten.logsumexp.default": None, "torch.ops.aten.masked_fill_.Scalar": None, "torch.ops.aten.masked_fill.Tensor": None, + "torch.ops.aten.max.dim": None, "torch.ops.aten.max_pool2d_with_indices.default": None, "torch.ops.aten.mean.dim": None, "torch.ops.aten.mm.default": None, @@ -86,12 +93,14 @@ class OperatorSupport(OperatorSupport): "torch.ops.aten._native_batch_norm_legit_no_training.default": None, "torch.ops.aten.native_group_norm.default": None, "torch.ops.aten.native_layer_norm.default": None, + "torch.ops.aten.new_full.default": None, "torch.ops.aten.neg.default": None, "torch.ops.aten.new_ones.default": None, "torch.ops.aten.permute.default": None, "torch.ops.aten.pow.Tensor_Scalar": None, "torch.ops.aten.relu.default": None, "torch.ops.aten.relu_.default": None, + "torch.ops.aten.rsqrt.default": None, "torch.ops.aten.rsub.Scalar": None, "torch.ops.aten._scaled_dot_product_flash_attention.default": None, "torch.ops.aten.select.int": None, @@ -101,18 +110,26 @@ class OperatorSupport(OperatorSupport): "torch.ops.aten.sin.default": None, "torch.ops.aten.slice.Tensor": None, "torch.ops.aten.split.Tensor": None, + "torch.ops.aten.squeeze.dim": None, + "torch.ops.aten.squeeze.dims": None, "torch.ops.aten.sub.default": None, "torch.ops.aten.sub.Tensor": None, + "torch.ops.aten.sum.dim_IntList": None, "torch.ops.aten.t.default": None, "torch.ops.aten.tanh.default": None, "torch.ops.aten.transpose.int": None, + "torch.ops.aten.unbind.int": None, "torch.ops.aten.unsqueeze.default": None, "torch.ops.aten.upsample_nearest2d.default": None, + "torch.ops.aten.var_mean.correction": None, "torch.ops.aten.view.default": None, "torch.ops.aten.where.self": None, "torch.ops.aten.zeros_like.default": None, } + for op in _get_disabled_ops(options): + del support_dict[op] + super().__init__(support_dict) def is_node_supported(self, submodules: t.Mapping[str, Module], node: Node) -> bool: diff --git a/src/bindings/python/src/openvino/frontend/pytorch/torchdynamo/partition.py b/src/bindings/python/src/openvino/frontend/pytorch/torchdynamo/partition.py index 468eed28d91..62ceada5041 100644 --- a/src/bindings/python/src/openvino/frontend/pytorch/torchdynamo/partition.py +++ b/src/bindings/python/src/openvino/frontend/pytorch/torchdynamo/partition.py @@ -16,6 +16,7 @@ from torch._decomp import decomposition_table from torch.fx.experimental.proxy_tensor import make_fx from torch.utils._pytree import tree_flatten, tree_map, tree_unflatten from openvino.frontend.pytorch.torchdynamo.op_support import OperatorSupport +from openvino.frontend.pytorch.torchdynamo.backend_utils import _is_testing import typing as t import logging @@ -25,8 +26,8 @@ logger.setLevel(logging.WARNING) class Partitioner: - def __init__(self): - self.supported_ops = OperatorSupport() + def __init__(self, options): + self.supported_ops = OperatorSupport(options) def fx_serialize(self, graph_module: GraphModule, *args, **kwargs): fx_gm = make_fx(graph_module)(*args) @@ -55,9 +56,10 @@ class Partitioner: return True return False - def make_partitions(self, graph_module: GraphModule) -> GraphModule: + def make_partitions(self, graph_module: GraphModule, options) -> GraphModule: + allow_single_node_partition = _is_testing(options) partitioner = CapabilityBasedPartitioner( - graph_module, self.supported_ops, allows_single_node_partition=False) + graph_module, self.supported_ops, allows_single_node_partition=allow_single_node_partition) partitions = partitioner.propose_partitions() self.add_get_attr_inputs(partitions) fused_graph_module = partitioner.fuse_partitions(partitions) diff --git a/src/frontends/pytorch/src/op/cat.cpp b/src/frontends/pytorch/src/op/cat.cpp index 5b873193157..c1a571dc8d7 100644 --- a/src/frontends/pytorch/src/op/cat.cpp +++ b/src/frontends/pytorch/src/op/cat.cpp @@ -9,6 +9,7 @@ #include "openvino/op/scatter_elements_update.hpp" #include "openvino/op/shape_of.hpp" #include "openvino/op/slice.hpp" +#include "openvino/op/unsqueeze.hpp" #include "pt_framework_node.hpp" #include "utils.hpp" #include "utils_quantize.hpp" @@ -94,6 +95,24 @@ OutputVector translate_quantized_cat(const NodeContext& context) { list_elems.front())}; }; +OutputVector translate_stack_fx(const NodeContext& context) { + num_inputs_check(context, 2, context.get_input_size()); + auto dim = context.mark_node(v0::Constant::create(element::i32, Shape{}, {0})); + std::deque> list_elems; + auto num_elements = context.get_input_size(); + if (num_elements > 2) + num_elements = num_elements - 1; + for (size_t i = 0; i < num_elements; i++) { + auto stack_input = + context.mark_node(std::make_shared(context.get_input(static_cast(i)), dim)); + list_elems.push_back(stack_input); + } + int64_t axis = 0; + if (context.get_input_size() > 2) + axis = context.const_input(context.get_input_size() - 1); + return translate_cat_common(context, list_elems, axis, true); +} + } // namespace op } // namespace pytorch } // namespace frontend diff --git a/src/frontends/pytorch/src/op/log.cpp b/src/frontends/pytorch/src/op/log.cpp index a5eb3fb89ba..573033105d9 100644 --- a/src/frontends/pytorch/src/op/log.cpp +++ b/src/frontends/pytorch/src/op/log.cpp @@ -11,6 +11,7 @@ #include "openvino/op/divide.hpp" #include "openvino/op/exp.hpp" #include "openvino/op/reduce_sum.hpp" +#include "openvino/op/sigmoid.hpp" #include "utils.hpp" namespace ov { @@ -29,6 +30,15 @@ OutputVector translate_log(const NodeContext& context) { return {log}; }; +OutputVector translate_log_sigmoid(const NodeContext& context) { + num_inputs_check(context, 1, 1); + auto x = context.get_input(0); + x = context.mark_node(std::make_shared(x, element::f32)); + auto sigmoid = context.mark_node(std::make_shared(x)); + auto log = context.mark_node(std::make_shared(sigmoid)); + return {log}; +}; + OutputVector translate_log2(const NodeContext& context) { // torch.log2 returns a tensor with the logarithm to the base 2 of the elements of input. num_inputs_check(context, 1, 1); diff --git a/src/frontends/pytorch/src/op/log_softmax.cpp b/src/frontends/pytorch/src/op/log_softmax.cpp index 1baaf428981..1f7491d3b57 100644 --- a/src/frontends/pytorch/src/op/log_softmax.cpp +++ b/src/frontends/pytorch/src/op/log_softmax.cpp @@ -13,7 +13,7 @@ namespace op { using namespace ov::op; -OutputVector translate_log_softmax(const NodeContext& context) { +OutputVector translate_log_softmax_common(const NodeContext& context, bool is_fx) { /* aten::log_softmax( Tensor input, @@ -25,7 +25,7 @@ OutputVector translate_log_softmax(const NodeContext& context) { auto input = context.get_input(0); auto const dim = context.const_input(1); - if (!context.input_is_none(2)) { + if (!context.input_is_none(2) && !is_fx) { const auto elem_type = input.get_element_type(); const auto target_dtype_i64 = context.const_input(2); const auto target_dtype = convert_dtype(target_dtype_i64); @@ -38,6 +38,14 @@ OutputVector translate_log_softmax(const NodeContext& context) { return {log_softmax}; }; +OutputVector translate_log_softmax(const NodeContext& context) { + return translate_log_softmax_common(context, false); +} + +OutputVector translate_log_softmax_fx(const NodeContext& context) { + return translate_log_softmax_common(context, true); +} + } // namespace op } // namespace pytorch } // namespace frontend diff --git a/src/frontends/pytorch/src/op/min_max.cpp b/src/frontends/pytorch/src/op/min_max.cpp index 928b963f074..3523209e983 100644 --- a/src/frontends/pytorch/src/op/min_max.cpp +++ b/src/frontends/pytorch/src/op/min_max.cpp @@ -11,6 +11,7 @@ #include "openvino/op/reduce_min.hpp" #include "openvino/op/squeeze.hpp" #include "openvino/op/topk.hpp" +#include "openvino/op/util/framework_node.hpp" #include "utils.hpp" namespace ov { @@ -36,7 +37,7 @@ OutputVector translate_max(const NodeContext& context) { align_eltwise_input_types(context, x, y, true); return {context.mark_node(std::make_shared(x, y))}; } - // torch.max(input, dim, keepdim), returns values and indicies + // torch.max(input, dim, keepdim), returns values and indices auto axes_node = context.get_input(1); auto axis_const = context.const_input(1); auto keepdims = context.const_input(2); @@ -44,11 +45,38 @@ OutputVector translate_max(const NodeContext& context) { auto k = context.mark_node(std::make_shared(element::i32, Shape{}, 1)); auto topk = context.mark_node(std::make_shared(x, k, axis_const, v3::TopK::Mode::MAX, v3::TopK::SortType::NONE)); - auto indicies = context.mark_node(std::make_shared(topk->output(1), element::i64)); + auto indices = context.mark_node(std::make_shared(topk->output(1), element::i64)); if (!keepdims) { - indicies = context.mark_node(std::make_shared(indicies, axes_node)); + indices = context.mark_node(std::make_shared(indices, axes_node)); } - return {values, indicies}; + return {values, indices}; +}; + +OutputVector translate_max_dim(const NodeContext& context) { + // torch.max.dim(x, dim, keepdim) + num_inputs_check(context, 2, 3); + auto x = context.get_input(0); + auto axes_node = context.get_input(1); + auto axis_const = context.const_input(1); + + bool keepdims = false; + if (!context.input_is_none(2)) { + keepdims = context.const_input(2); + } + + auto values = context.mark_node(std::make_shared(x, axes_node, keepdims)); + auto k = context.mark_node(std::make_shared(element::i32, Shape{}, 1)); + auto topk = std::make_shared(x, k, axis_const, v3::TopK::Mode::MAX, v3::TopK::SortType::NONE); + auto indices = context.mark_node(std::make_shared(topk->output(1), element::i64)); + if (!keepdims) { + indices = std::make_shared(indices, axes_node); + } + return {values, indices}; +}; + +OutputVector translate_max_dim_fx(const NodeContext& context) { + ov::OutputVector out_vec = translate_max_dim(context); + return {context.mark_node(make_list_construct(out_vec))}; }; OutputVector translate_min(const NodeContext& context) { @@ -67,7 +95,7 @@ OutputVector translate_min(const NodeContext& context) { align_eltwise_input_types(context, x, y, true); return {context.mark_node(std::make_shared(x, y))}; } - // torch.min(input, dim, keepdim), returns values and indicies + // torch.min(input, dim, keepdim), returns values and indices auto axes_node = context.get_input(1); auto axis_const = context.const_input(1); auto keepdims = context.const_input(2); @@ -75,11 +103,11 @@ OutputVector translate_min(const NodeContext& context) { auto k = context.mark_node(std::make_shared(element::i32, Shape{}, 1)); auto topk = context.mark_node(std::make_shared(x, k, axis_const, v3::TopK::Mode::MIN, v3::TopK::SortType::NONE)); - auto indicies = context.mark_node(std::make_shared(topk->output(1), element::i64)); + auto indices = context.mark_node(std::make_shared(topk->output(1), element::i64)); if (!keepdims) { - indicies = context.mark_node(std::make_shared(indicies, axes_node)); + indices = context.mark_node(std::make_shared(indices, axes_node)); } - return {values, indicies}; + return {values, indices}; }; OutputVector translate_maximum(const NodeContext& context) { diff --git a/src/frontends/pytorch/src/op/quantize.cpp b/src/frontends/pytorch/src/op/quantize.cpp index 166839011c8..a4a4f248c1a 100644 --- a/src/frontends/pytorch/src/op/quantize.cpp +++ b/src/frontends/pytorch/src/op/quantize.cpp @@ -3,6 +3,7 @@ // #include "openvino/frontend/pytorch/node_context.hpp" +#include "openvino/op/unsqueeze.hpp" #include "utils_quantize.hpp" namespace ov { @@ -31,6 +32,22 @@ OutputVector translate_quantize_per_channel(const NodeContext& context) { return {quantize(context, input, scales, zero_points, axis, dtype, QuantizedPtNodeType::QUANTIZE_PER_CHANNEL)}; } +OutputVector translate_fake_quantize_per_tensor_affine_fx(const NodeContext& context) { + num_inputs_check(context, 6, 6); + auto out = translate_quantize_per_tensor(context); + auto axis_0 = context.mark_node(v0::Constant::create(element::i32, Shape{}, {0})); + + return {context.mark_node(std::make_shared(out[0], axis_0))}; +} + +OutputVector translate_fake_quantize_per_channel_affine_fx(const NodeContext& context) { + num_inputs_check(context, 6, 6); + auto out = translate_quantize_per_channel(context); + auto axis_0 = context.mark_node(v0::Constant::create(element::i32, Shape{}, {0})); + + return {context.mark_node(std::make_shared(out[0], axis_0))}; +} + } // namespace op } // namespace pytorch } // namespace frontend diff --git a/src/frontends/pytorch/src/op/split.cpp b/src/frontends/pytorch/src/op/split.cpp index 7b0241192cb..acc12ca3d33 100644 --- a/src/frontends/pytorch/src/op/split.cpp +++ b/src/frontends/pytorch/src/op/split.cpp @@ -36,6 +36,23 @@ OutputVector translate_chunk_fx(const NodeContext& context) { return {context.mark_node(make_list_construct(chunk->outputs()))}; } +OutputVector translate_unbind_int_fx(const NodeContext& context) { + num_inputs_check(context, 2, 3); + auto input = context.get_input(0); + auto dim = context.get_input(1); + auto dim_val = context.const_input(1); + auto shape = input.get_shape(); + + if (dim_val < 0) { + dim_val = static_cast(shape.size()) + dim_val; + } + + auto num_splits = static_cast(shape[dim_val]); + auto chunk = context.mark_node(std::make_shared(input, dim, num_splits)); + + return {context.mark_node(make_list_construct(chunk->outputs()))}; +} + OutputVector translate_split_with_sizes_fx(const NodeContext& context) { num_inputs_check(context, 3, 3); auto data = context.get_input(0); diff --git a/src/frontends/pytorch/src/op/var_mean.cpp b/src/frontends/pytorch/src/op/var_mean.cpp index f09eae42b5a..5d4de546a90 100644 --- a/src/frontends/pytorch/src/op/var_mean.cpp +++ b/src/frontends/pytorch/src/op/var_mean.cpp @@ -12,6 +12,7 @@ #include "openvino/op/shape_of.hpp" #include "openvino/op/sqrt.hpp" #include "openvino/op/subtract.hpp" +#include "openvino/op/util/framework_node.hpp" #include "utils.hpp" namespace ov { @@ -73,6 +74,27 @@ OutputVector translate_var_mean(const NodeContext& context) { return {var, mean}; }; +OutputVector translate_var_mean_fx(const NodeContext& context) { + num_inputs_check(context, 2, 2); + auto data = context.get_input(0); + auto num_elements = numel(context, data); + std::shared_ptr mean; + ov::Output axes; + + axes = context.get_input(1); + mean = context.mark_node(std::make_shared(data, axes, true)); + + auto sub_v = context.mark_node(std::make_shared(data, mean)); + auto sqr_sub = context.mark_node(std::make_shared(sub_v, sub_v)); + auto var = context.mark_node(std::make_shared(sqr_sub, axes, true)); + + ov::OutputVector out_vec; + + out_vec.push_back(var); + out_vec.push_back(mean); + return {context.mark_node(make_list_construct(out_vec))}; +}; + OutputVector translate_var(const NodeContext& context) { auto res = translate_var_mean(context); return {res[0]}; diff --git a/src/frontends/pytorch/src/op_table.cpp b/src/frontends/pytorch/src/op_table.cpp index 7eb8875787e..689102066d4 100644 --- a/src/frontends/pytorch/src/op_table.cpp +++ b/src/frontends/pytorch/src/op_table.cpp @@ -114,6 +114,7 @@ OP_CONVERTER(translate_list_construct); OP_CONVERTER(translate_list_unpack); OP_CONVERTER(translate_log); OP_CONVERTER(translate_log1p); +OP_CONVERTER(translate_log_sigmoid); OP_CONVERTER(translate_log_softmax); OP_CONVERTER(translate_log2); OP_CONVERTER(translate_log10); @@ -123,6 +124,7 @@ OP_CONVERTER(translate_lstm); OP_CONVERTER(translate_masked_fill); OP_CONVERTER(translate_masked_scatter); OP_CONVERTER(translate_max); +OP_CONVERTER(translate_max_dim); OP_CONVERTER(translate_maximum); OP_CONVERTER(translate_max_poolnd); OP_CONVERTER(translate_mean); @@ -247,23 +249,30 @@ OP_CONVERTER(translate_constant_pad_nd_fx); OP_CONVERTER(translate_chunk_fx); OP_CONVERTER(translate_div_fx); OP_CONVERTER(translate_expand_fx); +OP_CONVERTER(translate_fake_quantize_per_channel_affine_fx); +OP_CONVERTER(translate_fake_quantize_per_tensor_affine_fx); OP_CONVERTER(translate_full_fx); OP_CONVERTER(translate_gelu_fx); OP_CONVERTER(translate_group_norm_fx); OP_CONVERTER(translate_index_fx); OP_CONVERTER(translate_layer_norm_fx); OP_CONVERTER(translate_leaky_relu_fx); +OP_CONVERTER(translate_log_softmax_fx); +OP_CONVERTER(translate_max_dim_fx); OP_CONVERTER(translate_max_poolnd_fx); OP_CONVERTER(translate_mean_fx); -OP_CONVERTER(translate_split_with_sizes_fx); OP_CONVERTER(translate_scalar_tensor_fx); OP_CONVERTER(translate_scaled_dot_product_attention_fx); OP_CONVERTER(translate_slice_fx); OP_CONVERTER(translate_slice_scatter_fx); OP_CONVERTER(translate_softmax_fx); +OP_CONVERTER(translate_split_with_sizes_fx); +OP_CONVERTER(translate_stack_fx); OP_CONVERTER(translate_sub_fx); OP_CONVERTER(translate_to_fx); OP_CONVERTER(translate_transpose_fx); +OP_CONVERTER(translate_var_mean_fx); +OP_CONVERTER(translate_unbind_int_fx); } // namespace op @@ -660,15 +669,20 @@ const std::map get_supported_ops_fx() { {"aten.adaptive_max_pool1d.default", op::translate_adaptive_max_pool1d_fx}, {"aten.adaptive_max_pool2d.default", op::translate_adaptive_max_pool2d_fx}, {"aten.adaptive_max_pool3d.default", op::translate_adaptive_max_pool3d_fx}, + {"aten._fake_quantize_per_tensor_affine_cachemask_tensor_qparams.default", + op::translate_fake_quantize_per_tensor_affine_fx}, {"aten._local_scalar_dense.default", op::skip_node}, + {"aten._log_softmax.default", op::translate_log_softmax_fx}, {"aten._softmax.default", op::translate_softmax_fx}, {"aten._to_copy.default", op::translate_to_fx}, {"aten._unsafe_view.default", op::translate_reshape}, + {"aten.add.Scalar", op::translate_add}, {"aten.add.Tensor", op::translate_add}, {"aten.add_.Tensor", op::translate_add}, {"aten.addcmul.default", op::translate_addcmul_fx}, {"aten.addmm.default", op::translate_addmm_fx}, {"aten.alias.default", op::skip_node}, + {"aten.amax.default", op::translate_amax}, {"aten.arange.start", op::translate_arange_fx}, {"aten.arange.start_step", op::translate_arange_fx}, {"aten.arange.default", op::translate_arange_fx}, @@ -697,10 +711,13 @@ const std::map get_supported_ops_fx() { {"aten.div.Tensor_mode", op::translate_div_fx}, {"aten.embedding.default", op::translate_embedding}, {"aten.empty.memory_format", op::translate_empty}, + {"aten.erf.default", op::translate_erf}, {"aten.eq.Scalar", op::translate_1to1_match_2_inputs_align_types}, {"aten.eq.Tensor", op::translate_1to1_match_2_inputs_align_types}, {"aten.exp.default", op::translate_1to1_match_1_inputs}, {"aten.expand.default", op::translate_expand_fx}, + {"aten.fake_quantize_per_channel_affine_cachemask.default", op::translate_fake_quantize_per_channel_affine_fx}, + {"aten.fill.Scalar", op::translate_fill}, {"aten.floor.default", op::translate_1to1_match_1_inputs}, {"aten.floor_divide.default", op::translate_floor_divide}, {"aten.full.default", op::translate_full_fx}, @@ -721,11 +738,13 @@ const std::map get_supported_ops_fx() { {"aten.lift_fresh_copy.default", op::skip_node}, {"aten.linalg_vector_norm.default", op::translate_linalg_vector_norm}, {"aten.log.default", op::translate_log}, + {"aten.log_sigmoid_forward.default", op::translate_log_sigmoid}, {"aten.logsumexp.default", op::translate_logsumexp}, {"aten.lt.Scalar", op::translate_1to1_match_2_inputs_align_types}, {"aten.lt.Tensor", op::translate_1to1_match_2_inputs_align_types}, {"aten.masked_fill_.Scalar", op::inplace_op}, {"aten.masked_fill.Tensor", op::translate_masked_fill}, + {"aten.max.dim", op::translate_max_dim_fx}, {"aten.max_pool2d_with_indices.default", op::translate_max_poolnd_fx}, {"aten.max_pool3d_with_indices.default", op::translate_max_poolnd_fx}, {"aten.mean.dim", op::translate_mean_fx}, @@ -742,12 +761,14 @@ const std::map get_supported_ops_fx() { {"aten.native_layer_norm.default", op::translate_layer_norm_fx}, {"aten.ne.Scalar", op::translate_1to1_match_2_inputs_align_types}, {"aten.neg.default", op::translate_neg}, + {"aten.new_full.default", op::translate_new_full}, {"aten.new_ones.default", op::translate_new_ones}, {"aten.permute.default", op::translate_1to1_match_2_inputs}, {"aten.pow.Tensor_Scalar", op::translate_pow}, {"aten.relu.default", op::translate_1to1_match_1_inputs}, {"aten.relu_.default", op::inplace_op>}, {"aten.repeat.default", op::translate_1to1_match_2_inputs}, + {"aten.rsqrt.default", op::translate_rsqrt}, {"aten.rsub.Scalar", op::translate_rsub}, {"aten.roll.default", op::translate_roll}, {"aten._scaled_dot_product_flash_attention.default", op::translate_scaled_dot_product_attention_fx}, @@ -764,6 +785,7 @@ const std::map get_supported_ops_fx() { {"aten.split_with_sizes.default", op::translate_split_with_sizes_fx}, {"aten.squeeze.dim", op::translate_squeeze}, {"aten.squeeze.dims", op::translate_squeeze}, + {"aten.stack.default", op::translate_stack_fx}, {"aten.sub.default", op::translate_sub_fx}, {"aten.sub.Tensor", op::translate_sub_fx}, {"aten.sum.dim_IntList", op::translate_sum}, @@ -771,8 +793,10 @@ const std::map get_supported_ops_fx() { {"aten.tanh.default", op::translate_1to1_match_1_inputs}, {"aten.unfold.default", op::translate_unfold}, {"aten.transpose.int", op::translate_transpose}, + {"aten.unbind.int", op::translate_unbind_int_fx}, {"aten.unsqueeze.default", op::translate_1to1_match_2_inputs}, {"aten.upsample_nearest2d.default", op::translate_upsample_nearest2d}, + {"aten.var_mean.correction", op::translate_var_mean_fx}, {"aten.view.default", op::translate_reshape}, {"aten.where.self", op::translate_where}, {"aten.zeros_like.default", op::translate_zeros_like}, diff --git a/tests/layer_tests/pytorch_tests/pytorch_layer_test_class.py b/tests/layer_tests/pytorch_tests/pytorch_layer_test_class.py index 3d906447978..cbdf9fc4217 100644 --- a/tests/layer_tests/pytorch_tests/pytorch_layer_test_class.py +++ b/tests/layer_tests/pytorch_tests/pytorch_layer_test_class.py @@ -15,7 +15,7 @@ from openvino.frontend import FrontEndManager from openvino.runtime import Core, Type, PartialShape import torch from packaging import version -import openvino.frontend.pytorch.torchdynamo.backend +import openvino.torch class PytorchLayerTest: @@ -263,10 +263,13 @@ class PytorchLayerTest: torch._dynamo.reset() with torch.no_grad(): model.eval() - fw_model = torch.compile(model) - ov_model = torch.compile(model, backend="openvino") - ov_res = ov_model(*inputs) - fw_res = fw_model(*inputs) + fw_res = model(*inputs) + + torch._dynamo.reset() + with torch.no_grad(): + model.eval() + ov_model = torch.compile(model, backend="openvino", options={"testing" : 1}) + ov_res = ov_model(*inputs) if not isinstance(fw_res, (tuple)): fw_res = (fw_res,) diff --git a/tests/layer_tests/pytorch_tests/test_erf.py b/tests/layer_tests/pytorch_tests/test_erf.py index ab4d1b8d9d7..cced0b35d79 100644 --- a/tests/layer_tests/pytorch_tests/test_erf.py +++ b/tests/layer_tests/pytorch_tests/test_erf.py @@ -17,7 +17,7 @@ class TestErf(PytorchLayerTest): def create_model(self, mode="", input_dtype="float32"): import torch dtypes = { - "float32": torch.float32, + "float32": torch.float32, "float64": torch.float64, "int32": torch.int32 } @@ -48,10 +48,11 @@ class TestErf(PytorchLayerTest): @pytest.mark.nightly @pytest.mark.precommit + @pytest.mark.precommit_fx_backend @pytest.mark.parametrize("mode,input_dtype", [ ("", "float32"), ("", "float64"), ("", "int32"), ("out", "float32"), ("out", "float64"), ("inplace", "float32"), ("inplace", "float64")]) def test_erf(self, mode, input_dtype, ie_device, precision, ir_version): - self._test(*self.create_model(mode, input_dtype), ie_device, precision, ir_version, + self._test(*self.create_model(mode, input_dtype), ie_device, precision, ir_version, kwargs_to_prepare_input={"input_dtype": input_dtype, "out": mode == "out"} ) \ No newline at end of file diff --git a/tests/layer_tests/pytorch_tests/test_log_softmax.py b/tests/layer_tests/pytorch_tests/test_log_softmax.py index de9ce28fded..7e61ab7d160 100644 --- a/tests/layer_tests/pytorch_tests/test_log_softmax.py +++ b/tests/layer_tests/pytorch_tests/test_log_softmax.py @@ -34,12 +34,13 @@ class TestLogSoftmax(PytorchLayerTest): ]) @pytest.mark.parametrize("dim", [ 0, - 1, + 1, -1 ]) @pytest.mark.nightly @pytest.mark.precommit + @pytest.mark.precommit_fx_backend def test_log_softmax(self, input_dtype, convert_dtype, dim, ie_device, precision, ir_version): self.input_dtype = input_dtype - self._test(aten_log_softmax(dim, convert_dtype), None, "aten::log_softmax", + self._test(aten_log_softmax(dim, convert_dtype), None, "aten::log_softmax", ie_device, precision, ir_version) diff --git a/tests/layer_tests/pytorch_tests/test_min_max.py b/tests/layer_tests/pytorch_tests/test_min_max.py index 3a624d534fa..1610194d0a7 100644 --- a/tests/layer_tests/pytorch_tests/test_min_max.py +++ b/tests/layer_tests/pytorch_tests/test_min_max.py @@ -245,13 +245,13 @@ class TestMinimumMaximum(PytorchLayerTest): self.r_dtype = r_dtype if out: self.forward = self.forward_out - + def forward_out(self, x, y, z): return self.op(x.to(self.l_dtype), y.to(self.r_dtype), out=z), z def forward(self, x, y): return self.op(x.to(self.l_dtype), y.to(self.r_dtype)) - + l_dtype = dtypes_map[dtypes[0]] r_dtype = dtypes_map[dtypes[1]] model_cls = aten_minimum_maximum(op, l_dtype, r_dtype, out) @@ -281,7 +281,7 @@ class TestMinimumMaximum(PytorchLayerTest): ): self._test(*self.create_model(op_type, dtypes=(input_dtype, input_dtype), out=True), ie_device, precision, ir_version, kwargs_to_prepare_input= - {"input_dtype": input_dtype, "second_input_dtype": input_dtype, + {"input_dtype": input_dtype, "second_input_dtype": input_dtype, "out": True} ) @@ -315,13 +315,13 @@ class TestAminAmax(PytorchLayerTest): self.keep_dims = keep_dims if out: self.forward = self.forward_out - + def forward_out(self, x, y): - return self.op(x, self.axis, self.keep_dims, out=y), y + return self.op(x, self.axis, self.keep_dims, out=y), y def forward(self, x): return self.op(x, self.axis, self.keep_dims) - + model_cls = aten_amin_amax(op, axis, keep_dims, out) @@ -332,6 +332,7 @@ class TestAminAmax(PytorchLayerTest): @pytest.mark.parametrize("keep_dims", [True, False]) @pytest.mark.parametrize("out", [True, False]) @pytest.mark.parametrize("input_dtype", ['float32', 'int32', 'int64', 'float64']) + @pytest.mark.precommit_fx_backend def test_amin_amax(self, op_type, input_dtype, axis, keep_dims, out, ie_device, precision, ir_version): self._test(*self.create_model(op_type, axis, keep_dims, out), ie_device, precision, ir_version, kwargs_to_prepare_input= diff --git a/tests/layer_tests/pytorch_tests/test_softmax.py b/tests/layer_tests/pytorch_tests/test_softmax.py index 820e99fad17..d6c85d89c60 100644 --- a/tests/layer_tests/pytorch_tests/test_softmax.py +++ b/tests/layer_tests/pytorch_tests/test_softmax.py @@ -50,6 +50,7 @@ class TestSoftmax(PytorchLayerTest): @pytest.mark.nightly @pytest.mark.precommit @pytest.mark.precommit_torch_export + @pytest.mark.precommit_fx_backend def test_softmax(self, dim, ie_device, precision, ir_version): self._test(*self.create_model(dim), ie_device, precision, ir_version) @@ -59,6 +60,7 @@ class TestSoftmax(PytorchLayerTest): @pytest.mark.nightly @pytest.mark.precommit @pytest.mark.precommit_torch_export + @pytest.mark.precommit_fx_backend def test_softmax(self, dim, dtype, use_prim_dtype, ie_device, precision, ir_version): input_kwargs = {} if use_prim_dtype: diff --git a/tests/layer_tests/pytorch_tests/test_squeeze.py b/tests/layer_tests/pytorch_tests/test_squeeze.py index 5c72800360e..4a90a6946f6 100644 --- a/tests/layer_tests/pytorch_tests/test_squeeze.py +++ b/tests/layer_tests/pytorch_tests/test_squeeze.py @@ -32,6 +32,7 @@ class TestSqueeze(PytorchLayerTest): @pytest.mark.parametrize("dim,dynamic_shapes", [(-2, True), (0, True), (None, False)]) @pytest.mark.nightly @pytest.mark.precommit + @pytest.mark.precommit_fx_backend def test_squeeze(self, dim, dynamic_shapes, ie_device, precision, ir_version): self._test(*self.create_model(dim), ie_device, precision, ir_version, dynamic_shapes=dynamic_shapes) @@ -39,6 +40,7 @@ class TestSqueeze(PytorchLayerTest): @pytest.mark.parametrize("dim", [-1, 2]) @pytest.mark.nightly @pytest.mark.precommit + @pytest.mark.precommit_fx_backend def test_squeeze_non_1(self, dim, ie_device, precision, ir_version): # Dynamic shapes are introducing dynamic rank, with is not suppoerted by Squeeze operation. self._test(*self.create_model(dim), ie_device, precision, ir_version, dynamic_shapes=False) diff --git a/tests/layer_tests/pytorch_tests/test_sum.py b/tests/layer_tests/pytorch_tests/test_sum.py index d73e6913a18..f045ea466c3 100644 --- a/tests/layer_tests/pytorch_tests/test_sum.py +++ b/tests/layer_tests/pytorch_tests/test_sum.py @@ -57,7 +57,7 @@ class TestSum(PytorchLayerTest): else: return torch.sum(x, self.axes, dtype=self.dtype) - if self.dtype is not None: + if self.dtype is not None: return torch.sum(x, self.axes, self.keep_dims, dtype=self.dtype) else: return torch.sum(x, self.axes, self.keep_dims)