diff --git a/inference-engine/src/transformations/include/transformations/op_conversions/mvn6_decomposition.hpp b/inference-engine/src/transformations/include/transformations/op_conversions/mvn6_decomposition.hpp index 2604830755f..f1b5eb7abe6 100644 --- a/inference-engine/src/transformations/include/transformations/op_conversions/mvn6_decomposition.hpp +++ b/inference-engine/src/transformations/include/transformations/op_conversions/mvn6_decomposition.hpp @@ -18,7 +18,7 @@ namespace pass { /** * @ingroup ie_transformation_common_api * @brief MVN6Decomposition transformation into sub-graph x - ReduceMean(x, axes) if normalize_variance is false and - * into sub-graph (x - ReduceMean(x, axes)) / Sqrt(ReduceSum((x - ReduceMean(x, axes)) ^ 2)) if normalize_variance is true. + * into sub-graph (x - ReduceMean(x, axes)) / Sqrt(ReduceMean((x - ReduceMean(x, axes)) ^ 2)) if normalize_variance is true. */ class ngraph::pass::MVN6Decomposition : public ngraph::pass::MatcherPass { public: diff --git a/inference-engine/src/transformations/src/transformations/common_optimizations/common_optimizations.cpp b/inference-engine/src/transformations/src/transformations/common_optimizations/common_optimizations.cpp index 8004599a89b..3adefade0bd 100644 --- a/inference-engine/src/transformations/src/transformations/common_optimizations/common_optimizations.cpp +++ b/inference-engine/src/transformations/src/transformations/common_optimizations/common_optimizations.cpp @@ -51,6 +51,7 @@ #include "transformations/op_conversions/convert_previous_nms_to_nms_5.hpp" #include "transformations/op_conversions/hsigmoid_decomposition.hpp" #include "transformations/op_conversions/log_softmax_decomposition.hpp" +#include "transformations/op_conversions/mvn6_decomposition.hpp" #include #include @@ -115,6 +116,7 @@ bool ngraph::pass::CommonOptimizations::run_on_function(std::shared_ptradd_matcher(); decomp->add_matcher(); decomp->add_matcher(); + decomp->add_matcher(); decomp->set_name("ngraph::pass::CommonDecompositions"); // CF is required after all decompositions diff --git a/inference-engine/src/transformations/src/transformations/op_conversions/mvn6_decomposition.cpp b/inference-engine/src/transformations/src/transformations/op_conversions/mvn6_decomposition.cpp index bc5b0974f7c..2876dca7d4a 100644 --- a/inference-engine/src/transformations/src/transformations/op_conversions/mvn6_decomposition.cpp +++ b/inference-engine/src/transformations/src/transformations/op_conversions/mvn6_decomposition.cpp @@ -17,7 +17,7 @@ ngraph::pass::MVN6Decomposition::MVN6Decomposition() { MATCHER_SCOPE(MVN6Decomposition); // Decomposes MVN(x, axes) op if normalize_variance is false into sub-graph // x - ReduceMean(x, axes), if normalize_variance is true into sub-graph - // (x - ReduceMean(x, axes)) / Sqrt(ReduceSum((x - ReduceMean(x, axes)) ^ 2)) + // (x - ReduceMean(x, axes)) / Sqrt(ReduceMean((x - ReduceMean(x, axes)) ^ 2)) auto mvn = ngraph::pattern::wrap_type(); ngraph::matcher_pass_callback callback = [=](ngraph::pattern::Matcher& m) { @@ -40,7 +40,7 @@ ngraph::pass::MVN6Decomposition::MVN6Decomposition() { ngraph::replace_node(mvn_node, mean_normalization); } else { auto mul = std::make_shared(mean_normalization, mean_normalization); - auto sum = std::make_shared(mul, axes, true); + auto mean2 = std::make_shared(mul, axes, true); auto eps = mvn_node->get_eps(); auto eps_node = ngraph::opset6::Constant::create(data.get_element_type(), ngraph::Shape{ 1 }, { eps }); @@ -51,19 +51,19 @@ ngraph::pass::MVN6Decomposition::MVN6Decomposition() { std::shared_ptr div; if (eps_mode == op::MVNEpsMode::INSIDE_SQRT) { - eps_add = std::make_shared(sum, eps_node); + eps_add = std::make_shared(mean2, eps_node); sqrt = std::make_shared(eps_add); div = std::make_shared(mean_normalization, sqrt); } else if (eps_mode == op::MVNEpsMode::OUTSIDE_SQRT) { - sqrt = std::make_shared(sum); + sqrt = std::make_shared(mean2); eps_add = std::make_shared(sqrt, eps_node); - div = std::make_shared(mean_normalization, sqrt); + div = std::make_shared(mean_normalization, eps_add); } else { return false; } div->set_friendly_name(mvn_node->get_friendly_name()); - ngraph::copy_runtime_info(mvn_node, { mean, mean_normalization, mul, sum, eps_node, eps_add, sqrt, div }); + ngraph::copy_runtime_info(mvn_node, { mean, mean_normalization, mul, mean2, eps_node, eps_add, sqrt, div }); ngraph::replace_node(mvn_node, div); } return true; diff --git a/inference-engine/tests/functional/inference_engine/ngraph_reader/mvn_tests.cpp b/inference-engine/tests/functional/inference_engine/ngraph_reader/mvn_tests.cpp index 0453d719038..7a11642b564 100644 --- a/inference-engine/tests/functional/inference_engine/ngraph_reader/mvn_tests.cpp +++ b/inference-engine/tests/functional/inference_engine/ngraph_reader/mvn_tests.cpp @@ -96,3 +96,174 @@ TEST_F(NGraphReaderTests, ReadMVNNetwork) { compareIRs(model, modelV5, 0); } + +TEST_F(NGraphReaderTests, ReadMVN6Network) { + std::string model = R"V0G0N( + + + + + + + 1 + 3 + 22 + 22 + + + + + + + + 3 + + + + + + + + 1 + 3 + 22 + 22 + + + 3 + + + + + 1 + 3 + 22 + 22 + + + + + + + 1 + 3 + 22 + 22 + + + + + + + + + + +)V0G0N"; + std::string modelV5 = R"V0G0N( + + + + + + 1 + 3 + 22 + 22 + + + + + + + 3 + + + + + + + + + + + 1 + 3 + 22 + 22 + + + 3 + + + + + 1 + 3 + 1 + 1 + + + + + + + + 1 + 3 + 1 + 1 + + + + + 1 + 3 + 1 + 1 + + + + + + + + 1 + 3 + 22 + 22 + + + 1 + 3 + 1 + 1 + + + + + 1 + 3 + 22 + 22 + + + + + + + + + + + + +)V0G0N"; + + compareIRs(model, modelV5, 24, [](Blob::Ptr& weights) { + auto* buffer = weights->buffer().as(); + buffer[0] = 0; + buffer[1] = 2; + buffer[2] = 3; + }); +} diff --git a/inference-engine/tests/functional/inference_engine/transformations/mvn6_decomposition_test.cpp b/inference-engine/tests/functional/inference_engine/transformations/mvn6_decomposition_test.cpp index 8fa9f92c9a5..a98f14d5ed2 100644 --- a/inference-engine/tests/functional/inference_engine/transformations/mvn6_decomposition_test.cpp +++ b/inference-engine/tests/functional/inference_engine/transformations/mvn6_decomposition_test.cpp @@ -70,11 +70,11 @@ TEST(TransformationTests, MVN6Decomposition_Inside_Sqrt) { auto mean_normalization = std::make_shared(input0, mean); auto mul = std::make_shared(mean_normalization, mean_normalization); - auto sum = std::make_shared(mul, axes_const, true); + auto mean2 = std::make_shared(mul, axes_const, true); auto eps_node = ngraph::opset6::Constant::create(ngraph::element::f32, ngraph::Shape{ 1 }, { 1e-5 }); - auto eps_add = std::make_shared(sum, eps_node); + auto eps_add = std::make_shared(mean2, eps_node); auto sqrt = std::make_shared(eps_add); auto div = std::make_shared(mean_normalization, sqrt); @@ -108,13 +108,13 @@ TEST(TransformationTests, MVN6Decomposition_Outside_Sqrt) { auto mean_normalization = std::make_shared(input0, mean); auto mul = std::make_shared(mean_normalization, mean_normalization); - auto sum = std::make_shared(mul, axes_const, true); + auto mean2 = std::make_shared(mul, axes_const, true); auto eps_node = ngraph::opset6::Constant::create(ngraph::element::f32, ngraph::Shape{ 1 }, { 1e-5 }); - auto sqrt = std::make_shared(sum); + auto sqrt = std::make_shared(mean2); auto eps_add = std::make_shared(sqrt, eps_node); - auto div = std::make_shared(mean_normalization, sqrt); + auto div = std::make_shared(mean_normalization, eps_add); f_ref = std::make_shared(ngraph::NodeVector{ div }, ngraph::ParameterVector{ input0 }); } diff --git a/model-optimizer/automation/package_BOM.txt b/model-optimizer/automation/package_BOM.txt index d781682f3a9..6b9577e8efe 100644 --- a/model-optimizer/automation/package_BOM.txt +++ b/model-optimizer/automation/package_BOM.txt @@ -95,6 +95,7 @@ extensions/front/caffe/input_ext.py extensions/front/caffe/interp_ext.py extensions/front/caffe/lrn_ext.py extensions/front/caffe/mvn_ext.py +extensions/front/caffe/MVNNormalizer.py extensions/front/caffe/normalize_ext.py extensions/front/caffe/permute_ext.py extensions/front/caffe/pooling_ext.py diff --git a/model-optimizer/extensions/front/LayerNorm.py b/model-optimizer/extensions/front/LayerNorm.py index 7ced6688cc1..80f3779b521 100644 --- a/model-optimizer/extensions/front/LayerNorm.py +++ b/model-optimizer/extensions/front/LayerNorm.py @@ -1,5 +1,5 @@ """ - Copyright (C) 2020 Intel Corporation + Copyright (C) 2017-2021 Intel Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ import logging as log from mo.front.common.replacement import FrontReplacementSubgraph +from mo.front.tf.graph_utils import create_op_with_const_inputs from mo.graph.graph import Graph, rename_nodes from extensions.ops.mvn import MVN @@ -65,9 +66,9 @@ class LayerNorm(FrontReplacementSubgraph): if add_param.value.size == 1 and pow_param.value.size == 1 and add_param.value.item() <= 1e-05 \ and pow_param.value.item() == 0.5 and match['pool0_param'].value == match['pool1_param'].value: log.debug('Found LayerNorm pattern after {} with name {}'.format(inp_port.node.op, inp_port.node.name)) - mvn = MVN(graph, {'eps': add_param.value.item(), - 'axes': match['pool1_param'].value, - 'normalize_variance': 1}).create_node() + mvn = create_op_with_const_inputs(graph, MVN, {1: match['pool1_param'].value}, + {'eps': add_param.value.item(), 'normalize_variance': 1, + 'eps_mode': 'inside_sqrt'}) div_name = match['div'].soft_get('name', match['div'].id) rename_nodes([(match['div'], div_name + '/to_be_removed'), (mvn, div_name)]) diff --git a/model-optimizer/extensions/front/LayerNorm_test.py b/model-optimizer/extensions/front/LayerNorm_test.py index 5b35acbb857..59a6079d4ce 100644 --- a/model-optimizer/extensions/front/LayerNorm_test.py +++ b/model-optimizer/extensions/front/LayerNorm_test.py @@ -1,5 +1,5 @@ """ - Copyright (C) 2020 Intel Corporation + Copyright (C) 2017-2021 Intel Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ import unittest import numpy as np -from math import sqrt from extensions.front.LayerNorm import LayerNorm from mo.utils.ir_engine.compare_graphs import compare_graphs @@ -41,7 +40,8 @@ nodes_attributes_mvn = { nodes_attributes_ref = { 'inp': {'kind': 'op', 'op': 'AnyOp'}, - 'mvn': {'kind': 'op', 'op': 'MVN'}, + 'mvn': {'kind': 'op', 'op': 'MVN', 'eps': 1e-6, 'normalize_variance': 1, 'eps_mode': 'inside_sqrt'}, + 'mvn_param': {'kind': 'op', 'op': 'Const'}, 'out': {'kind': 'op', 'op': 'AnyOp'}, } @@ -72,6 +72,7 @@ class TestMVNPatternReplacement(unittest.TestCase): nodes_with_edges_only=True) graph_ref = build_graph(nodes_attributes_ref, [('inp', 'mvn'), + ('mvn_param', 'mvn'), ('mvn', 'out')], {}, nodes_with_edges_only=True) graph.stage = 'front' diff --git a/model-optimizer/extensions/front/caffe/MVNNormalize_test.py b/model-optimizer/extensions/front/caffe/MVNNormalize_test.py new file mode 100644 index 00000000000..b16f55235ad --- /dev/null +++ b/model-optimizer/extensions/front/caffe/MVNNormalize_test.py @@ -0,0 +1,81 @@ +""" + Copyright (C) 2017-2021 Intel Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" + +import unittest + +import numpy as np + +from extensions.front.caffe.MVNNormalizer import MVNCaffeToMVN +from mo.utils.ir_engine.compare_graphs import compare_graphs +from mo.utils.unittest.graph import build_graph, regular_op_with_empty_data, result, const, connect_front + +nodes = { + **regular_op_with_empty_data('input', {'type': 'Parameter'}), + **regular_op_with_empty_data('mvn_caffe', {'op': 'MVNCaffe'}), + **result(), + + # nodes after replacement + **const('start_1', np.array(1)), + **const('start_2', np.array(2)), + **const('step', np.array(1)), + **regular_op_with_empty_data('rank', {'op': 'Rank', 'type': None}), + **regular_op_with_empty_data('range', {'op': 'Range', 'type': None}), + **regular_op_with_empty_data('mvn', {'op': 'MVN', 'type': None}), +} + + +class MVNNormalizerTest(unittest.TestCase): + def test_mvn_normalizer(self): + graph = build_graph(nodes, [('input', 'mvn_caffe'), + ('mvn_caffe', 'output')], + {'mvn_caffe': {'across_channels': 0}}, + nodes_with_edges_only=True) + graph.stage = 'front' + + MVNCaffeToMVN().find_and_replace_pattern(graph) + + graph_ref = build_graph(nodes, [('input', 'mvn', {'out': 0}), + ('input', 'rank', {'out': 0}), + *connect_front('start_2', '0:range'), + *connect_front('rank', '1:range'), + *connect_front('step', '2:range'), + *connect_front('range', '1:mvn'), + ('mvn', 'output')], + nodes_with_edges_only=True) + + (flag, resp) = compare_graphs(graph, graph_ref, 'output', check_op_attrs=True) + self.assertTrue(flag, resp) + + def test_mvn_normalizer_across_channels(self): + graph = build_graph(nodes, [('input', 'mvn_caffe'), + ('mvn_caffe', 'output')], + {'mvn_caffe': {'across_channels': 1}}, + nodes_with_edges_only=True) + graph.stage = 'front' + + MVNCaffeToMVN().find_and_replace_pattern(graph) + + graph_ref = build_graph(nodes, [('input', 'mvn', {'out': 0}), + ('input', 'rank', {'out': 0}), + *connect_front('start_1', '0:range'), + *connect_front('rank', '1:range'), + *connect_front('step', '2:range'), + *connect_front('range', '1:mvn'), + ('mvn', 'output')], + nodes_with_edges_only=True) + + (flag, resp) = compare_graphs(graph, graph_ref, 'output', check_op_attrs=True) + self.assertTrue(flag, resp) diff --git a/model-optimizer/extensions/front/caffe/MVNNormalizer.py b/model-optimizer/extensions/front/caffe/MVNNormalizer.py new file mode 100644 index 00000000000..bd9ebff5c83 --- /dev/null +++ b/model-optimizer/extensions/front/caffe/MVNNormalizer.py @@ -0,0 +1,55 @@ +""" + Copyright (C) 2017-2021 Intel Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" +from extensions.ops.mvn import MVN +from extensions.ops.range import Range +from extensions.ops.rank import Rank +from mo.front.common.partial_infer.utils import int64_array +from mo.front.common.replacement import FrontReplacementPattern +from mo.front.tf.graph_utils import create_op_with_const_inputs +from mo.graph.graph import Graph, rename_nodes + +import numpy as np + + +class MVNCaffeToMVN(FrontReplacementPattern): + """ + Replace MVNCaffe operation with MVN + """ + enabled = True + + def find_and_replace_pattern(self, graph: Graph): + for node in graph.get_op_nodes(op='MVNCaffe'): + node_name = node.soft_get('name', node.id) + + start_axis = 2 + if node['across_channels'] == 1: + start_axis = 1 + + rank = Rank(graph, {'name': node_name + '/Rank'}).create_node() + + # create range of axes based on `start_axis` and rank of input + rng = create_op_with_const_inputs(graph, Range, {0: int64_array(start_axis), 2: int64_array(1)}, + {'name': node_name + '/Range', 'output_type': np.int64}) + rng.in_port(1).connect(rank.out_port(0)) + + new_mvn = MVN(graph, {'eps': node.soft_get('eps', 1e-9), 'eps_mode': 'inside_sqrt', + 'normalize_variance': node.soft_get('normalize_variance', 1)}).create_node( + [node.in_port(0).get_source().node, rng]) + new_mvn.in_port(0).get_connection().add_destination(rank.in_port(0)) + node.out_port(0).get_connection().set_source(new_mvn.out_port(0)) + rename_nodes([(node, node_name + '/tbd'), (new_mvn, node_name)]) + + graph.remove_node(node.id) diff --git a/model-optimizer/extensions/front/caffe/mvn_ext.py b/model-optimizer/extensions/front/caffe/mvn_ext.py index f9445fb6cca..6b50a786edc 100644 --- a/model-optimizer/extensions/front/caffe/mvn_ext.py +++ b/model-optimizer/extensions/front/caffe/mvn_ext.py @@ -1,5 +1,5 @@ """ - Copyright (C) 2018-2020 Intel Corporation + Copyright (C) 2018-2021 Intel Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. """ -from extensions.ops.mvn import MVN +from extensions.ops.mvn import MVNCaffe from mo.front.caffe.collect_attributes import collect_attributes from mo.front.extractor import FrontExtractorOp @@ -30,5 +30,5 @@ class MVNFrontExtractor(FrontExtractorOp): attrs = collect_attributes(param) # update the attributes of the node - MVN.update_node_stat(node, attrs) + MVNCaffe.update_node_stat(node, attrs) return cls.enabled diff --git a/model-optimizer/extensions/front/instance_normalization.py b/model-optimizer/extensions/front/instance_normalization.py index 8a22b7b1903..d4f9917e832 100644 --- a/model-optimizer/extensions/front/instance_normalization.py +++ b/model-optimizer/extensions/front/instance_normalization.py @@ -1,5 +1,5 @@ """ - Copyright (C) 2018-2020 Intel Corporation + Copyright (C) 2018-2021 Intel Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,8 +16,14 @@ from extensions.ops.elementwise import Add, Mul from extensions.ops.mvn import MVN +from extensions.ops.range import Range +from extensions.ops.rank import Rank +from mo.front.common.partial_infer.utils import int64_array from mo.front.common.replacement import FrontReplacementOp -from mo.graph.graph import Node, Graph +from mo.front.tf.graph_utils import create_op_with_const_inputs +from mo.graph.graph import Node, Graph, rename_nodes + +import numpy as np class InstanceNormalization(FrontReplacementOp): @@ -29,18 +35,26 @@ class InstanceNormalization(FrontReplacementOp): enabled = True def replace_op(self, graph: Graph, node: Node): + name = node.soft_get('name', node.id) - # Add new nodes - mvn = MVN(graph, {'eps': node.epsilon, 'name': node.name + '/Ins_Norm/MVN_', }).create_node() - mul = Mul(graph, {'axis': 1, 'name': node.name + '/Ins_Norm/mul_'}).create_node() - add = Add(graph, {'axis': 1, 'name': node.name + '/Ins_Norm/add_'}).create_node() - - # Connect nodes + # create range of axes for MVN based on `start_axis` and rank of input + rank = Rank(graph, {'name': name + '/Rank'}).create_node() + rng = create_op_with_const_inputs(graph, Range, {0: int64_array(2), 2: int64_array(1)}, + {'name': name + '/Range', 'output_type': np.int64}) + mvn = MVN(graph, {'eps': node.epsilon, 'eps_mode': 'inside_sqrt', 'normalize_variance': 1, + 'name': name + '/Ins_Norm/MVN_', }).create_node() node.in_port(0).get_connection().set_destination(mvn.in_port(0)) + rng.out_port(0).connect(mvn.in_port(1)) + mul = Mul(graph, {'axis': 1, 'name': name + '/Ins_Norm/mul_'}).create_node() + mvn.out_port(0).connect(mul.in_port(0)) node.in_port(1).get_connection().set_destination(mul.in_port(1)) + add = Add(graph, {'axis': 1, 'name': name + '/Ins_Norm/add_'}).create_node() + mul.out_port(0).connect(add.in_port(0)) node.in_port(2).get_connection().set_destination(add.in_port(1)) - mvn.out_port(0).connect(mul.in_port(0)) - mul.out_port(0).connect(add.in_port(0)) + mvn.in_port(0).get_connection().add_destination(rank.in_port(0)) + rng.in_port(1).connect(rank.out_port(0)) + + rename_nodes([(node, name + '/TBD'), (add, name)]) return [add.id] diff --git a/model-optimizer/extensions/front/instance_normalization_test.py b/model-optimizer/extensions/front/instance_normalization_test.py index 26814a287a9..91ad4a5646b 100644 --- a/model-optimizer/extensions/front/instance_normalization_test.py +++ b/model-optimizer/extensions/front/instance_normalization_test.py @@ -1,5 +1,5 @@ """ - Copyright (C) 2018-2020 Intel Corporation + Copyright (C) 2018-2021 Intel Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,10 +16,8 @@ import unittest -import networkx as nx - from extensions.front.instance_normalization import InstanceNormalization -from mo.middle.pattern_match import node_match +from mo.utils.ir_engine.compare_graphs import compare_graphs from mo.utils.unittest.graph import build_graph nodes_attributes = { @@ -27,15 +25,21 @@ nodes_attributes = { 'scale': {'kind': 'op', 'op': 'AnyOp'}, 'B': {'kind': 'op', 'op': 'AnyOp'}, 'node': {'kind': 'op', 'op': 'InstanceNormalization', 'epsilon': None}, + 'out': {'kind': 'op', 'op': 'AnyOp'}, } nodes_ref_attributes = { - 'input': {'op': 'AnyOp'}, - 'scale': {'op': 'AnyOp'}, - 'B': {'op': 'AnyOp'}, + 'input': {'kind': 'op', 'op': 'AnyOp'}, + 'scale': {'kind': 'op', 'op': 'AnyOp'}, + 'B': {'kind': 'op', 'op': 'AnyOp'}, + 'start': {'kind': 'op', 'op': 'Const'}, + 'step': {'kind': 'op', 'op': 'Const'}, + 'rank': {'kind': 'op', 'op': 'Rank'}, + 'mvn_axes': {'kind': 'op', 'op': 'Range'}, 'mvn': {'kind': 'op', 'op': 'MVN', 'name': 'node/Ins_Norm/MVN_', 'eps': None}, 'mul': {'kind': 'op', 'op': 'Mul', 'name': 'node/Ins_Norm/mul_'}, 'add': {'kind': 'op', 'op': 'Add', 'name': 'node/Ins_Norm/add_'}, + 'out': {'kind': 'op', 'op': 'AnyOp'}, } @@ -45,18 +49,25 @@ class TestInstanceNormalization(unittest.TestCase): [('input', 'node'), ('scale', 'node'), ('B', 'node'), + ('node', 'out') ], {'node': {'epsilon': 0.123}, }, nodes_with_edges_only=True) - ref_graph = build_graph(nodes_ref_attributes, - [('input', 'mvn'), + graph_ref = build_graph(nodes_ref_attributes, + [('input', 'mvn', {'out': 0}), + ('input', 'rank', {'out': 0}), + ('start', 'mvn_axes'), + ('rank', 'mvn_axes'), + ('step', 'mvn_axes'), + ('mvn_axes', 'mvn'), ('mvn', 'mul'), ('scale', 'mul'), ('mul', 'add'), ('B', 'add'), + ('add', 'out') ], - {'mvn': {'eps': 0.123}, + {'mvn': {'eps': 0.123, 'eps_mode': 'inside_sqrt', 'normalize_variance': 1}, }, nodes_with_edges_only=True) graph.stage = 'front' @@ -64,4 +75,41 @@ class TestInstanceNormalization(unittest.TestCase): tested_class = InstanceNormalization() tested_class.find_and_replace_pattern(graph) - self.assertTrue(nx.is_isomorphic(graph, ref_graph, node_match)) + (flag, resp) = compare_graphs(graph, graph_ref, 'out', check_op_attrs=False) + self.assertTrue(flag, resp) + + def test_instance_normalization_test_2(self): + graph = build_graph(nodes_attributes, + [('input', 'out', {'out': 0, 'in': 0}), + ('input', 'node', {'out': 1}), + ('scale', 'node'), + ('B', 'node'), + ('node', 'out', {'in': 1}) + ], + {'node': {'epsilon': 0.123}, + }, nodes_with_edges_only=True) + + graph_ref = build_graph(nodes_ref_attributes, + [('input', 'out', {'out': 0, 'in': 0}), + ('input', 'mvn', {'out': 1}), + ('input', 'rank', {'out': 1}), + ('start', 'mvn_axes'), + ('rank', 'mvn_axes'), + ('step', 'mvn_axes'), + ('mvn_axes', 'mvn'), + ('mvn', 'mul'), + ('scale', 'mul'), + ('mul', 'add'), + ('B', 'add'), + ('add', 'out', {'in': 1}) + ], + {'mvn': {'eps': 0.123, 'eps_mode': 'inside_sqrt', 'normalize_variance': 1}, + }, nodes_with_edges_only=True) + + graph.stage = 'front' + + tested_class = InstanceNormalization() + tested_class.find_and_replace_pattern(graph) + + (flag, resp) = compare_graphs(graph, graph_ref, 'out', check_op_attrs=False) + self.assertTrue(flag, resp) diff --git a/model-optimizer/extensions/front/onnx/mean_variance_normalization_ext.py b/model-optimizer/extensions/front/onnx/mean_variance_normalization_ext.py index 5a0c53f8d87..a4d5f286490 100644 --- a/model-optimizer/extensions/front/onnx/mean_variance_normalization_ext.py +++ b/model-optimizer/extensions/front/onnx/mean_variance_normalization_ext.py @@ -1,5 +1,5 @@ """ - Copyright (C) 2018-2020 Intel Corporation + Copyright (C) 2018-2021 Intel Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import numpy as np from extensions.ops.mvn import MVN from mo.front.extractor import FrontExtractorOp from mo.front.onnx.extractors.utils import onnx_attr -from mo.utils.error import Error +from mo.ops.const import Const class MeanVarianceNormalizationExtractor(FrontExtractorOp): @@ -33,18 +33,15 @@ class MeanVarianceNormalizationExtractor(FrontExtractorOp): default=np.array([0, 2, 3], dtype=np.int64), dst_type=lambda x: np.array(x, dtype=np.int64)) - if 0 in axes: - raise Error('Reduction over the batch dimension in node "{}" is not supported by the backend.'.format(name)) - # Dimension 4 (if it's present in the input tensor) should also be in the list of axes for reduction. - # This case will be handled at the MVN Op side, because input shape is not available at that stage. - for i in (2, 3): - if i not in axes: - raise Error('Reduction over spatial dimensions in node "{}" is obligatory for a backend.'.format(name)) + axes = Const(node.graph, {'value': axes, 'name': name + '/Axes'}).create_node() + node.add_input_port(1, skip_if_exist=True) + node.in_port(1).connect(axes.out_port(0)) attrs = { 'eps': 1e-9, 'normalize_variance': 1, - 'axes': axes + 'eps_mode': 'outside_sqrt' } + MVN.update_node_stat(node, attrs) return cls.enabled diff --git a/model-optimizer/extensions/front/tf/mvn.py b/model-optimizer/extensions/front/tf/mvn.py index edd401fe702..003d23939d4 100644 --- a/model-optimizer/extensions/front/tf/mvn.py +++ b/model-optimizer/extensions/front/tf/mvn.py @@ -1,5 +1,5 @@ """ - Copyright (C) 2017-2020 Intel Corporation + Copyright (C) 2017-2021 Intel Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -59,7 +59,8 @@ class MVNReplacer(FrontReplacementSubgraph): mvn = MVN(graph, dict( name=fbn.name + '/MVN_', eps=fbn.eps, - required_reduction_indices=[1, 2] if fbn.data_format == b'NHWC' else [2, 3] + eps_mode='outside_sqrt', + normalize_variance=1 )) mvn.attrs['old_infer'] = mvn.attrs['infer'] mvn.attrs['infer'] = __class__.infer @@ -84,19 +85,20 @@ class MVNReplacer(FrontReplacementSubgraph): @staticmethod def infer(node: Node): - if not (node.in_node(1).has_valid('value') and node.in_node(2).has_valid('value')): + axes_1_value = node.in_port(1).data.get_value() + axes_2_value = node.in_port(2).data.get_value() + if axes_1_value is None or axes_2_value is None: log.warning('Reduction indices for mean and variance for MVN node {} are not constants'.format(node.name)) return - if not (all(node.in_node(1).value == node.required_reduction_indices) and - all(node.in_node(2).value == node.required_reduction_indices)): - log.warning('Reduction indices for mean {} and variance {} do not match required ones {}'.format( - node.in_node(1).value, - node.in_node(2).value, - node.required_reduction_indices + if not (all(axes_1_value == axes_2_value)): + log.warning('Reduction indices for mean {} and variance {} do not match'.format( + axes_1_value, + axes_2_value )) return - node.graph.remove_edge(node.in_node(2).id, node.id) - node.graph.remove_edge(node.in_node(1).id, node.id) + node.in_port(2).disconnect() node.old_infer(node) + node.infer = node.old_infer + del node['old_infer'] diff --git a/model-optimizer/extensions/front/tf/mvn_unrolled.py b/model-optimizer/extensions/front/tf/mvn_unrolled.py index 49b403a542e..78229f991d8 100644 --- a/model-optimizer/extensions/front/tf/mvn_unrolled.py +++ b/model-optimizer/extensions/front/tf/mvn_unrolled.py @@ -1,5 +1,5 @@ """ - Copyright (C) 2017-2020 Intel Corporation + Copyright (C) 2017-2021 Intel Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -56,7 +56,8 @@ class MVNUnrolled(FrontReplacementSubgraph): def replace_sub_graph(graph: Graph, match: dict): mvn = MVN(graph, dict( name=match['truediv'].name + '/MVN_', - required_reduction_indices=[1, 2] if graph.graph['layout'] == 'NHWC' else [2, 3] + eps_mode='outside_sqrt', + normalize_variance=1 )) mvn.attrs['old_infer'] = mvn.attrs['infer'] mvn.attrs['infer'] = __class__.infer @@ -72,29 +73,33 @@ class MVNUnrolled(FrontReplacementSubgraph): @staticmethod def infer(node: Node): - if not (node.in_node(1).has_valid('value') and node.in_node(2).has_valid('value')): + axes_1_value = node.in_port(1).data.get_value() + axes_2_value = node.in_port(2).data.get_value() + if axes_1_value is None or axes_2_value is None: log.warning('Reduction indices for mean and variance for MVN node {} are not constants'.format(node.name)) return - if not (all(node.in_node(1).value == node.required_reduction_indices) and - all(node.in_node(2).value == node.required_reduction_indices)): - log.warning('Reduction indices for mean {} and variance {} do not match required ones {}'.format( - node.in_node(1).value, - node.in_node(2).value, - node.required_reduction_indices + if not (all(axes_1_value == axes_2_value)): + log.warning('Reduction indices for mean {} and variance {} do not match'.format( + axes_1_value, + axes_2_value )) return - - if not (node.in_node(3).has_valid('value') and node.in_node(4).has_valid('value')): + + power_value = node.in_port(3).data.get_value() + eps_value = node.in_port(4).data.get_value() + if power_value is None or eps_value is None: log.warning('Power or/and epsilon values for MVN node {} are not constants'.format(node.name)) return - if node.in_node(3).value != 0.5: - log.warning('Power for MVN node {} ({}) is not equal to 0.5'.format(node.name, node.in_node(3).value)) + if power_value != 0.5: + log.warning('Power for MVN node {} ({}) is not equal to 0.5'.format(node.name, power_value)) return - node['eps'] = node.in_node(4).value + node['eps'] = eps_value - for i in range(1, 5): - node.graph.remove_edge(node.in_node(i).id, node.id) + for i in range(2, 5): + node.in_port(i).disconnect() node.old_infer(node) + node.infer = node.old_infer + del node['old_infer'] diff --git a/model-optimizer/extensions/front/tf/transposed_mvn_unrolled.py b/model-optimizer/extensions/front/tf/transposed_mvn_unrolled.py index 5567335da1b..6f7c895169f 100644 --- a/model-optimizer/extensions/front/tf/transposed_mvn_unrolled.py +++ b/model-optimizer/extensions/front/tf/transposed_mvn_unrolled.py @@ -1,5 +1,5 @@ """ - Copyright (C) 2018-2020 Intel Corporation + Copyright (C) 2018-2021 Intel Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ from extensions.ops.mvn import MVN from extensions.ops.transpose import Transpose from mo.front.common.partial_infer.utils import int64_array from mo.front.common.replacement import FrontReplacementSubgraph -from mo.front.tf.graph_utils import create_op_node_with_second_input +from mo.front.tf.graph_utils import create_op_node_with_second_input, create_op_with_const_inputs from mo.graph.graph import Graph from mo.ops.reshape import Reshape from mo.ops.shape import Shape @@ -173,8 +173,10 @@ class TransposedMVNUnrolled(FrontReplacementSubgraph): variance = match['variance'] eps_port_num = 0 if add.in_port(0).get_connection().get_source().node.id != variance.id else 1 eps = add.in_port(eps_port_num).get_connection().get_source().node - mvn_node = MVN(graph, dict(name=div_name + '/MVN/MVN_T_', required_reduction_indices=[1, 2, 3], - eps=eps.value)).create_node() + mvn_node = create_op_with_const_inputs(graph, MVN, {1: int64_array([1, 2, 3])}, + dict(name=div_name + '/MVN/MVN_T_', + eps=eps.value, normalize_variance=1, + eps_mode='inside_sqrt')) first_permute.out_port(0).connect(mvn_node.in_port(0)) second_permute = create_op_node_with_second_input(graph, Transpose, permute_order, diff --git a/model-optimizer/extensions/middle/FusedBatchNormTraining.py b/model-optimizer/extensions/middle/FusedBatchNormTraining.py index 3640a59c8e3..586c38a3892 100644 --- a/model-optimizer/extensions/middle/FusedBatchNormTraining.py +++ b/model-optimizer/extensions/middle/FusedBatchNormTraining.py @@ -1,5 +1,5 @@ """ - Copyright (C) 2018-2020 Intel Corporation + Copyright (C) 2018-2021 Intel Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,8 +17,9 @@ import numpy as np from extensions.ops.mvn import MVN +from extensions.ops.range import Range from mo.front.common.partial_infer.utils import int64_array -from mo.front.tf.graph_utils import create_op_node_with_second_input +from mo.front.tf.graph_utils import create_op_node_with_second_input, create_op_with_const_inputs from mo.graph.graph import Graph from mo.middle.replacement import MiddleReplacementPattern from mo.ops.const import Const @@ -45,20 +46,21 @@ class FusedBatchNormTraining(MiddleReplacementPattern): return dict( nodes=[ ('op', dict(kind='op', op=lambda op: op in ['FusedBatchNorm', 'FusedBatchNormV2', 'FusedBatchNormV3'], - is_training=True))], + is_training=True))], edges=[] ) def replace_pattern(self, graph: Graph, match: dict): node = match['op'] + node_name = node.soft_get('name', node.id) node.is_training = False shape = node.in_port(1).data.get_shape() assert shape is not None, 'The shape of scale input of the BatchNorm node {} is not defined'.format(node.name) - bn_mean = Const(graph, {'name': node.name + '/mean', 'value': np.zeros(shape, dtype=np.float32), + bn_mean = Const(graph, {'name': node_name + '/mean', 'value': np.zeros(shape, dtype=np.float32), 'override_output_shape': True}).create_node() - bn_std = Const(graph, {'name': node.name + '/std', 'value': np.ones(shape, dtype=np.float32), + bn_std = Const(graph, {'name': node_name + '/std', 'value': np.ones(shape, dtype=np.float32), 'override_output_shape': True}).create_node() node.in_port(3).get_connection().set_source(bn_mean.out_port(0)) node.in_port(4).get_connection().set_source(bn_std.out_port(0)) @@ -67,17 +69,22 @@ class FusedBatchNormTraining(MiddleReplacementPattern): original_shape = Shape(graph, {'name': node.in_port(0).get_source().node.soft_get('name')}).create_node() original_shape.in_port(0).connect(node.in_port(0).get_source()) - mvn = MVN(graph, {'name': node.name + '/mvn_', 'eps': node.soft_get('eps', 1e-6), - 'override_output_shape': True}).create_node() + input_rank = len(node.in_port(0).data.get_shape()) + rng = create_op_with_const_inputs(graph, Range, + {0: int64_array(2), 1: int64_array(input_rank), 2: int64_array(1)}, + {'name': node_name + '/Range', 'output_type': np.int64}) + mvn = MVN(graph, {'name': node_name + '/mvn_', 'eps': node.soft_get('eps', 1e-6), 'eps_mode': 'outside_sqrt', + 'normalize_variance': 1, 'override_output_shape': True}).create_node() node.in_port(0).get_connection().insert_node(mvn) + mvn.in_port(1).connect(rng.out_port(0)) reshape_4d = create_op_node_with_second_input(graph, Reshape, int64_array([1, -1, 0, 0]), {'override_output_shape': True, - 'name': node.soft_get('name') + '/fused_batch_and_channels'}) + 'name': node_name + '/fused_batch_and_channels'}) mvn.in_port(0).get_connection().insert_node(reshape_4d) # restore original shape - reshape_back = Reshape(graph, {'name': mvn.soft_get('name') + '/restore_shape', + reshape_back = Reshape(graph, {'name': node_name + '/restore_shape', 'override_output_shape': True}).create_node() reshape_back.in_port(1).connect(original_shape.out_port(0)) mvn.out_port(0).get_connection().insert_node(reshape_back) diff --git a/model-optimizer/extensions/middle/FusedBatchNormTraining_test.py b/model-optimizer/extensions/middle/FusedBatchNormTraining_test.py index 6f8e3990679..9f5c52bbca3 100644 --- a/model-optimizer/extensions/middle/FusedBatchNormTraining_test.py +++ b/model-optimizer/extensions/middle/FusedBatchNormTraining_test.py @@ -1,5 +1,5 @@ """ - Copyright (C) 2018-2020 Intel Corporation + Copyright (C) 2018-2021 Intel Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -65,6 +65,15 @@ nodes_attributes = { 'reshape_to_orig': {'type': 'Reshape', 'value': None, 'kind': 'op', 'op': 'Reshape'}, 'reshape_to_orig_data': {'value': None, 'shape': None, 'kind': 'data'}, + 'start': {'kind': 'op', 'op': 'Const'}, + 'start_data': {'value': None, 'shape': None, 'kind': 'data'}, + 'stop': {'kind': 'op', 'op': 'Const'}, + 'stop_data': {'value': None, 'shape': None, 'kind': 'data'}, + 'step': {'kind': 'op', 'op': 'Const'}, + 'step_data': {'value': None, 'shape': None, 'kind': 'data'}, + 'mvn_axes': {'kind': 'op', 'op': 'Range'}, + 'mvn_axes_data': {'value': None, 'shape': None, 'kind': 'data'}, + 'mvn': {'type': 'MVN', 'value': None, 'kind': 'op', 'op': 'MVN', 'eps': 1e-3}, 'mvn_data': {'value': None, 'shape': None, 'kind': 'data'}, @@ -115,6 +124,14 @@ class FusedBatchNormTrainingTest(unittest.TestCase): ('reshape_1_data', 'mvn', {'in': 0}), ('mvn', 'mvn_data'), ('mvn_data', 'reshape_to_orig', {'in': 0}), + ('start', 'start_data'), + ('start_data', 'mvn_axes'), + ('stop', 'stop_data'), + ('stop_data', 'mvn_axes'), + ('step', 'step_data'), + ('step_data', 'mvn_axes'), + ('mvn_axes', 'mvn_axes_data'), + ('mvn_axes_data', 'mvn'), ('placeholder_data', 'shapeof', {'in': 0}), ('shapeof', 'shapeof_data'), ('shapeof_data', 'reshape_to_orig', {'in': 1}), diff --git a/model-optimizer/extensions/middle/GroupNorm.py b/model-optimizer/extensions/middle/GroupNorm.py index 166fcd55859..7cf7c9f130d 100644 --- a/model-optimizer/extensions/middle/GroupNorm.py +++ b/model-optimizer/extensions/middle/GroupNorm.py @@ -1,5 +1,5 @@ """ - Copyright (C) 2018-2020 Intel Corporation + Copyright (C) 2018-2021 Intel Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,7 +21,9 @@ import numpy as np from extensions.ops.Cast import Cast from extensions.ops.elementwise import Mul, Add from extensions.ops.mvn import MVN +from extensions.ops.range import Range from mo.front.common.partial_infer.utils import int64_array +from mo.front.tf.graph_utils import create_op_with_const_inputs from mo.graph.graph import Graph, Node from mo.middle.passes.convert_data_type import data_type_str_to_np from mo.middle.replacement import MiddleReplacementPattern @@ -29,7 +31,7 @@ from mo.ops.const import Const from mo.ops.reshape import Reshape from mo.ops.shape import Shape from mo.utils.shape import node_to_get_spatial_dimensions_value, node_to_get_features_dimension_value, \ - node_to_get_batch_value, new_shape_node_from_shape_nodes + node_to_get_batch_value, new_shape_node_from_shape_nodes, get_shape_and_rank_nodes_by_port class GroupNormToMVN(MiddleReplacementPattern): @@ -115,11 +117,19 @@ class GroupNormToMVN(MiddleReplacementPattern): # MVN mvn_node = MVN(graph, {'name': group_norm_node.name + '/MVN', - 'across_channels': 1, 'normalize_variance': 1, - 'eps': group_norm_node.eps}).create_node() + 'eps': group_norm_node.eps, + 'eps_mode': 'inside_sqrt'}).create_node() mvn_node.in_port(0).connect(reshape_for_mvn_node.out_port(0)) + # MVN axes + _, rank = get_shape_and_rank_nodes_by_port(mvn_node.in_port(0), return_as_a_scalar=True) + rng = create_op_with_const_inputs(graph, Range, {0: int64_array(1), 2: int64_array(1)}, + {'name': group_norm_node.name + '/Range', 'output_type': np.int64}) + mvn_node.in_port(1).connect(rng.out_port(0)) + rng.in_port(1).connect(rank.out_port(0)) + mvn_node.in_port(0).get_connection().add_destination(rank.in_port(0)) + # reshape to the initial shape before multiplying with gamma and adding beta reshape_to_initial_shape_node = Reshape(graph, {}).create_node() reshape_to_initial_shape_node.in_port(0).connect(mvn_node.out_port(0)) diff --git a/model-optimizer/extensions/ops/mvn.py b/model-optimizer/extensions/ops/mvn.py index d085cc4472a..bc58a44ec2c 100644 --- a/model-optimizer/extensions/ops/mvn.py +++ b/model-optimizer/extensions/ops/mvn.py @@ -18,6 +18,7 @@ from mo.front.common.layout import get_features_dim from mo.front.common.partial_infer.elemental import copy_shape_infer from mo.front.extractor import bool_to_str from mo.graph.graph import Graph +from mo.graph.perm_inputs import PermuteInputs from mo.ops.op import Op from mo.utils.error import Error @@ -29,55 +30,58 @@ class MVN(Op): def __init__(self, graph: Graph, attrs: dict): super().__init__(graph, { 'kind': 'op', - 'type': __class__.op, - 'op': __class__.op, - 'version': 'opset2', + 'type': self.op, + 'op': self.op, + 'version': 'opset6', 'eps': None, - 'across_channels': None, - 'normalize_variance': 1, - 'axes': None, - 'in_ports_count': 1, + 'normalize_variance': None, + 'eps_mode': None, + 'in_ports_count': 2, 'out_ports_count': 1, - 'infer': __class__.infer + 'infer': self.infer }, attrs) def supported_attrs(self): - return ['eps', 'across_channels', 'normalize_variance', 'axes'] + return ['eps', 'eps_mode', 'normalize_variance'] def backend_attrs(self): - return ['eps', - ('across_channels', lambda node: bool_to_str(node, 'across_channels')), - ('normalize_variance', lambda node: bool_to_str(node, 'normalize_variance'))] + version = self.get_opset() + if version == 'opset2': + return ['eps', + ('across_channels', lambda node: bool_to_str(node, 'across_channels')), + ('normalize_variance', lambda node: bool_to_str(node, 'normalize_variance'))] + elif version == 'opset6': + return ['eps', 'eps_mode', ('normalize_variance', lambda node: bool_to_str(node, 'normalize_variance'))] + else: + raise Error('Unsupported MVN opset version "{}"'.format(version)) @staticmethod def infer(node: None): - input_shape = node.in_node(0).shape name = node.soft_get('name', node.id) - if node.axes is not None and node.across_channels is not None: - raise Error('Either axes or across_channels can be set for the MVN in node "{}".'.format(name)) - - if node.across_channels is None: - if node.axes is not None: - # normalizing (replacing -1 with actual index) - axes_data_value = node.axes - axes = [axes_data_value.item()] if axes_data_value.size == 1 else axes_data_value - axes = [get_canonical_axis_index(input_shape, a) for a in axes] - # deduce across_channels from the axes, e.g. if the first axis is included (assuming batch is zero axis) - feature_dim = get_features_dim(node.graph.graph['layout'], len(input_shape)) \ - if (4 <= len(input_shape) <= 5) \ - else 1 - node.across_channels = int(feature_dim in axes) - - if 0 in axes: - raise Error('Reduction over the batch dimension in node "{}" ' - 'is not supported by the backend.'.format(name)) - for i in range(2, len(input_shape)): - if i not in axes: - raise Error( - 'Reduction over spatial dimensions in node "{}" ' - 'is obligatory for the backend.'.format(name)) - else: - node.across_channels = 0 # default + assert node.eps is not None, 'MVN required attribute `eps` unspecified for node {}'.format(name) + assert node.eps_mode is not None, 'MVN required attribute `eps_mode` unspecified for node {}'.format(name) + assert node.normalize_variance is not None, \ + 'MVN required attribute `normalize_variance` unspecified for node {}'.format(name) + PermuteInputs().set_input_permutation(node.in_node(1), node, 'input:0', 'axis') copy_shape_infer(node) + + +class MVNCaffe(Op): + op = 'MVNCaffe' + enabled = False + + def __init__(self, graph: Graph, attrs: dict): + super().__init__(graph, { + 'kind': 'op', + 'type': None, + 'op': self.op, + 'version': None, + 'eps': 1e-9, + 'normalize_variance': 1, + 'across_channels': 0, + 'in_ports_count': 1, + 'out_ports_count': 1, + 'infer': None + }, attrs) diff --git a/ngraph/python/tests/test_onnx/test_backend.py b/ngraph/python/tests/test_onnx/test_backend.py index 6f56f0fac3e..2111d2d2709 100644 --- a/ngraph/python/tests/test_onnx/test_backend.py +++ b/ngraph/python/tests/test_onnx/test_backend.py @@ -235,8 +235,7 @@ tests_expected_to_fail = [ "OnnxBackendNodeModelTest.test_argmin_negative_axis_keepdims_random_select_last_index_cpu", "OnnxBackendNodeModelTest.test_argmin_no_keepdims_random_select_last_index_cpu"), (xfail_issue_38091, - "OnnxBackendNodeModelTest.test_gather_negative_indices_cpu", - "OnnxBackendNodeModelTest.test_mvn_cpu",), + "OnnxBackendNodeModelTest.test_gather_negative_indices_cpu"), (xfail_issue_47317, "OnnxBackendPyTorchOperatorModelTest.test_operator_add_size1_broadcast_cpu", "OnnxBackendPyTorchOperatorModelTest.test_operator_add_size1_singleton_broadcast_cpu",),