GN decomposition/tokenization test and snippets func test (#23903)

### Details:
 - *GroupNormalization decomposition test*
 - *GroupNormalization tokenization test*
 - *GroupNormalization snippets func test*

### Tickets:
 - *[CVS-136159](https://jira.devtools.intel.com/browse/CVS-136159)*
 - *[CVS-137359](https://jira.devtools.intel.com/browse/CVS-137359)*
This commit is contained in:
Chenhu Wang 2024-05-29 18:45:07 +08:00 committed by GitHub
parent 4dfca61bd2
commit feb0c7eff0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 543 additions and 18 deletions

View File

@ -38,6 +38,7 @@ GNDecomposition::GNDecomposition() {
// reshape [N, C, spatial] to [N, group, 1, (C / group) * spatial]
const auto orig_shape = group_norm_node->get_input_partial_shape(0).to_shape();
size_t orig_rank = orig_shape.size();
OPENVINO_ASSERT(orig_rank >= 2, "First input rank for group normalization op should be greater than 1");
size_t group_rank = 4;
size_t c_in_group = orig_shape[1] / num_groups;
size_t spatial_dim = 1;

View File

@ -20,7 +20,8 @@ ov::snippets::pass::TokenizeGNSnippets::TokenizeGNSnippets() {
ov::matcher_pass_callback callback = [=](ov::pass::pattern::Matcher& m) {
OV_ITT_SCOPED_TASK(ov::pass::itt::domains::SnippetsTransform, "Snippets::pass::TokenizeGNSnippets")
auto group_norm_node = ov::as_type_ptr<ov::op::v12::GroupNormalization>(m.get_match_root());
if (group_norm_node->is_dynamic() || group_norm_node->get_element_type() != element::f32)
if (group_norm_node->is_dynamic() || group_norm_node->get_element_type() != element::f32 ||
GetSnippetsNodeType(group_norm_node) == SnippetsNodeType::SkippedByPlugin)
return false;
auto subgraph = op::Subgraph::wrap_node_as_subgraph(group_norm_node);

View File

@ -0,0 +1,33 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "lowering_utils.hpp"
#include "subgraph_group_normalization.hpp"
/* The main purpose is to test that GNDecomposition properly decomposes groupNormalization operation
*/
namespace ov {
namespace test {
namespace snippets {
typedef std::tuple<
PartialShape, // Input 0 Shape
size_t, // numGroup
float // epsilon
> GroupNormalizationParams;
class GNDecompositionTest : public LoweringTests, public testing::WithParamInterface<GroupNormalizationParams> {
public:
static std::string getTestCaseName(testing::TestParamInfo<GroupNormalizationParams> obj);
protected:
void SetUp() override;
std::shared_ptr<SnippetsFunctionBase> snippets_model;
};
} // namespace snippets
} // namespace test
} // namespace ov

View File

@ -0,0 +1,32 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include <common_test_utils/ov_test_utils.hpp>
#include "snippets/pass/tokenization.hpp"
#include "subgraph_group_normalization.hpp"
namespace ov {
namespace test {
namespace snippets {
typedef std::tuple<
PartialShape, // Input 0 Shape
size_t, // numGroup
float // epsilon
> GroupNormalizationParams;
class TokenizeGNSnippetsTests : public TransformationTestsF, public testing::WithParamInterface<GroupNormalizationParams> {
public:
static std::string getTestCaseName(testing::TestParamInfo<GroupNormalizationParams> obj);
protected:
void SetUp() override;
std::shared_ptr<GroupNormalizationFunction> snippets_model;
};
} // namespace snippets
} // namespace test
} // namespace ov

View File

@ -7,6 +7,7 @@
#include "utils.hpp"
#include "snippets/pass/tokenization.hpp"
#include "snippets/pass/collapse_subgraph.hpp"
#include "snippets/pass/gn_tokenization.hpp"
#include "snippets/lowered/expression.hpp"
@ -30,6 +31,7 @@ DummyTargetMachine::DummyTargetMachine(const std::vector<ov::Node::type_info_t>&
jitters[op::v1::Divide::get_type_info_static()] = dummy_functor;
jitters[op::v1::Maximum::get_type_info_static()] = dummy_functor;
jitters[op::v0::Exp::get_type_info_static()] = dummy_functor;
jitters[op::v0::Sqrt::get_type_info_static()] = dummy_functor;
jitters[ov::snippets::op::PowerStatic::get_type_info_static()] = dummy_functor;
jitters[ov::snippets::op::HorizonMax::get_type_info_static()] = dummy_functor;
jitters[ov::snippets::op::HorizonSum::get_type_info_static()] = dummy_functor;
@ -57,6 +59,7 @@ DummyTargetMachine::DummyTargetMachine(const std::vector<ov::Node::type_info_t>&
jitters[ov::snippets::op::Fill::get_type_info_static()] = dummy_functor;
jitters[ov::snippets::op::ReduceMax::get_type_info_static()] = dummy_functor;
jitters[ov::snippets::op::ReduceSum::get_type_info_static()] = dummy_functor;
jitters[ov::snippets::op::Reshape::get_type_info_static()] = dummy_functor;
for (const auto& elem : custom_opset) {
jitters[elem] = dummy_functor;
@ -133,6 +136,7 @@ std::shared_ptr<ov::snippets::op::Subgraph> LoweringTests::getTokenizedSubgraph(
ov::pass::Manager m;
ov::snippets::pass::SnippetsTokenization::Config config = get_default_tokenization_config();
m.register_pass<ov::snippets::pass::EnumerateNodes>();
m.register_pass<ov::snippets::pass::TokenizeGNSnippets>();
m.register_pass<ov::snippets::pass::TokenizeSnippets>(config);
m.run_passes(f);
// Perform lowering

View File

@ -0,0 +1,65 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include <gtest/gtest.h>
#include "pass/gn_decomposition.hpp"
#include "common_test_utils/common_utils.hpp"
#include "subgraph_group_normalization.hpp"
#include "subgraph_lowered.hpp"
namespace ov {
namespace test {
namespace snippets {
std::string GNDecompositionTest::getTestCaseName(testing::TestParamInfo<GroupNormalizationParams> obj) {
PartialShape input_shape;
size_t num_group;
float eps;
std::tie(input_shape, num_group, eps) = obj.param;
std::ostringstream result;
result << "IS=" << ov::test::utils::partialShape2str({input_shape}) << "_";
result << "num_group=" << num_group << "_";
result << "eps=" << eps;
return result.str();
}
void GNDecompositionTest::SetUp() {
LoweringTests::SetUp();
PartialShape data_shape;
size_t num_group;
float eps;
std::tie(data_shape, num_group, eps) = this->GetParam();
OPENVINO_ASSERT(data_shape.size() >= 2, "First input rank for group normalization op should be greater than 1");
PartialShape scaleShiftShape = PartialShape{data_shape[1]};
std::vector<PartialShape> input_shapes = { data_shape, scaleShiftShape, scaleShiftShape};
snippets_model = std::make_shared<GroupNormalizationFunction>(input_shapes, num_group, eps);
}
TEST_P(GNDecompositionTest, GNDecomposition) {
auto subgraph = getLoweredSubgraph(snippets_model->getOriginal());
model = subgraph->body_ptr();
model_ref = snippets_model->getLowered();
}
namespace {
const std::vector<ov::PartialShape> input_shapes{
{1, 8},
{1, 8, 18},
{1, 16, 8, 5},
{3, 8, 2, 2, 3},
{3, 8, 2, 2, 3, 3}
};
INSTANTIATE_TEST_SUITE_P(smoke_Snippets_GNDecomposition,
GNDecompositionTest,
::testing::Combine(::testing::ValuesIn(input_shapes),
::testing::Values(4),
::testing::Values(0.0001)),
GNDecompositionTest::getTestCaseName);
} // namespace
} // namespace snippets
} // namespace test
} // namespace ov

View File

@ -0,0 +1,64 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include <gtest/gtest.h>
#include <pass/gn_tokenization.hpp>
#include "snippets/pass/gn_tokenization.hpp"
#include "common_test_utils/common_utils.hpp"
namespace ov {
namespace test {
namespace snippets {
std::string TokenizeGNSnippetsTests::getTestCaseName(testing::TestParamInfo<GroupNormalizationParams> obj) {
PartialShape input_shape;
size_t num_group;
float eps;
std::tie(input_shape, num_group, eps) = obj.param;
std::ostringstream result;
result << "IS=" << ov::test::utils::partialShape2str({input_shape}) << "_";
result << "num_group=" << num_group << "_";
result << "eps=" << eps;
return result.str();
}
void TokenizeGNSnippetsTests::SetUp() {
TransformationTestsF::SetUp();
PartialShape data_shape;
size_t num_group;
float eps;
std::tie(data_shape, num_group, eps) = this->GetParam();
OPENVINO_ASSERT(data_shape.size() >= 2, "First input rank for group normalization op should be greater than 1");
PartialShape scaleShiftShape = PartialShape{data_shape[1]};
std::vector<PartialShape> input_shapes = { data_shape, scaleShiftShape, scaleShiftShape};
snippets_model = std::make_shared<GroupNormalizationFunction>(input_shapes, num_group, eps);
manager.register_pass<ov::snippets::pass::TokenizeGNSnippets>();
}
TEST_P(TokenizeGNSnippetsTests, smoke_TokenizeGNSnippets) {
model = snippets_model->getOriginal();
model_ref = snippets_model->getReference();
}
namespace {
const std::vector<ov::PartialShape> input_shapes{
{3, 10},
{3, 10, 1},
{3, 10, 2, 2},
{1, 20, 2, 2, 3},
{1, 20, 2, 2, 3, 3}
};
INSTANTIATE_TEST_SUITE_P(smoke_Snippets_GNTokenize,
TokenizeGNSnippetsTests,
::testing::Combine(::testing::ValuesIn(input_shapes),
::testing::Values(5),
::testing::Values(0.0001)),
TokenizeGNSnippetsTests::getTestCaseName);
} // namespace
} // namespace snippets
} // namespace test
} // namespace ov

View File

@ -521,24 +521,26 @@ void Transformations::PreLpt(const std::vector<ov::element::Type>& defaultPrecis
// 2. GroupNormalizationDecomposition produce MVN, and MVN have a conditional pass MVN6Decomposition. If call MVN6Decomposition again after
// snippets pipeline as well, where MVN is decomposed to simple ops, these simple ops will not tokenized into subgraph again.
// CVS-134277 to fully enable GN as snippets to disable this GroupNormalizationDecomposition entirly.
if (node->is_dynamic() || !one_of(inferencePrecision, element::f32, element::undefined))
return false;
const auto group_norm = ov::as_type_ptr<const ov::op::v12::GroupNormalization>(node);
if (!group_norm || !implication(inferencePrecision == element::undefined, group_norm->get_element_type() == element::f32))
return false;
const auto num_groups = static_cast<size_t>(group_norm->get_num_groups());
const auto shape = group_norm->get_input_partial_shape(0).to_shape();
size_t snippets_work_amount = shape[0] * num_groups;
size_t concurrency = parallel_get_max_threads();
if (concurrency > snippets_work_amount)
return false;
size_t spatial_dim = 1;
for (size_t i = 2; i < shape.size(); ++i)
spatial_dim = spatial_dim * shape[i];
size_t snippets_tensor_size = spatial_dim * shape[1] / num_groups * node->get_element_type().size();
size_t cache_size_l1 = dnnl::utils::get_cache_size(1, true);
if (snippets_tensor_size > cache_size_l1) {
if (node->is_dynamic() || !one_of(inferencePrecision, element::f32, element::undefined) || snippetsMode == Config::SnippetsMode::Disable)
return false;
if (snippetsMode != Config::SnippetsMode::IgnoreCallback) {
const auto group_norm = ov::as_type_ptr<const ov::op::v12::GroupNormalization>(node);
if (!group_norm || !implication(inferencePrecision == element::undefined, group_norm->get_element_type() == element::f32))
return false;
const auto num_groups = static_cast<size_t>(group_norm->get_num_groups());
const auto shape = group_norm->get_input_partial_shape(0).to_shape();
size_t snippets_work_amount = shape[0] * num_groups;
size_t concurrency = parallel_get_max_threads();
if (concurrency > snippets_work_amount)
return false;
size_t spatial_dim = 1;
for (size_t i = 2; i < shape.size(); ++i)
spatial_dim = spatial_dim * shape[i];
size_t snippets_tensor_size = spatial_dim * shape[1] / num_groups * node->get_element_type().size();
size_t cache_size_l1 = dnnl::utils::get_cache_size(1, true);
if (snippets_tensor_size > cache_size_l1) {
return false;
}
}
return true;

View File

@ -0,0 +1,47 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "snippets/group_normalization.hpp"
#include "common_test_utils/test_constants.hpp"
namespace ov {
namespace test {
namespace snippets {
namespace {
// snippets ignore_callback is set in setup, so these tests will always run as snippets
const std::vector<ov::Shape> inputShape = {
{3, 8},
{3, 8, 1},
{3, 8, 7},
{3, 8, 16},
{3, 8, 21},
{1, 4, 8, 8},
{1, 8, 1, 22},
{1, 16, 1, 33},
{1, 4, 1, 1, 34},
{1, 8, 1, 8, 2, 2},
{1, 8, 1, 8, 2, 2, 2}
};
const std::vector<size_t> numGroups = {
2, 4,
};
INSTANTIATE_TEST_SUITE_P(smoke_Snippets_GroupNormalization, GroupNormalization,
::testing::Combine(
::testing::ValuesIn(ov::test::static_shapes_to_test_representation(inputShape)),
::testing::ValuesIn(numGroups), // num_group
::testing::Values(0.0001), // eps
::testing::Values(1), // expected node number
::testing::Values(1), // expected subgraph number
::testing::Values(ov::test::utils::DEVICE_CPU)),
GroupNormalization::getTestCaseName);
} // namespace
} // namespace snippets
} // namespace test
} // namespace ov

View File

@ -0,0 +1,34 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "shared_test_classes/base/snippets_test_utils.hpp"
namespace ov {
namespace test {
namespace snippets {
typedef std::tuple<
InputShape, // Input 0 Shape
size_t, // numGroup
float, // epsilon
size_t, // Expected num nodes
size_t, // Expected num subgraphs
std::string // Target Device
> GroupNormalizationParams;
class GroupNormalization : public testing::WithParamInterface<ov::test::snippets::GroupNormalizationParams>,
virtual public ov::test::SnippetsTestsCommon {
public:
static std::string getTestCaseName(testing::TestParamInfo<ov::test::snippets::GroupNormalizationParams> obj);
protected:
void SetUp() override;
InputShape ExtractScaleShiftShape(const InputShape& shape);
};
} // namespace snippets
} // namespace test
} // namespace ov

View File

@ -0,0 +1,78 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "common_test_utils/common_utils.hpp"
#include "snippets/group_normalization.hpp"
#include "subgraph_group_normalization.hpp"
#include "functional_test_utils/skip_tests_config.hpp"
namespace ov {
namespace test {
namespace snippets {
std::string GroupNormalization::getTestCaseName(testing::TestParamInfo<ov::test::snippets::GroupNormalizationParams> obj) {
InputShape inputShapes;
size_t numGroup;
float eps;
std::string targetDevice;
size_t num_nodes, num_subgraphs;
std::tie(inputShapes, numGroup, eps, num_nodes, num_subgraphs, targetDevice) = obj.param;
std::ostringstream result;
result << "IS=" << ov::test::utils::partialShape2str({inputShapes.first}) << "_";
result << "TS=";
for (const auto& shape : inputShapes.second) {
result << "(" << ov::test::utils::vec2str(shape) << ")_";
}
result << "numGroup=" << numGroup << "_";
result << "epsilon=" << eps << "_";
result << "#N=" << num_nodes << "_";
result << "#S=" << num_subgraphs << "_";
result << "targetDevice=" << targetDevice;
return result.str();
}
void GroupNormalization::SetUp() {
InputShape inputShape;
size_t numGroup;
float eps;
std::tie(inputShape, numGroup, eps, ref_num_nodes, ref_num_subgraphs, targetDevice) = this->GetParam();
InputShape scaleShiftShape = ExtractScaleShiftShape(inputShape);
init_input_shapes({inputShape, scaleShiftShape, scaleShiftShape});
auto f = ov::test::snippets::GroupNormalizationFunction(inputDynamicShapes, numGroup, eps);
function = f.getOriginal();
if (!configuration.count("SNIPPETS_MODE")) {
configuration.insert({"SNIPPETS_MODE", "IGNORE_CALLBACK"});
}
abs_threshold = 1e-5;
}
InputShape GroupNormalization::ExtractScaleShiftShape(const InputShape& shape) {
std::vector<ov::Shape> biasShape;
std::transform(shape.second.cbegin(), shape.second.cend(), std::back_inserter(biasShape),
[](const ov::Shape& s)->ov::Shape {
OPENVINO_ASSERT(s.size() >= 2, "First input rank for group normalization op should be greater than 1");
return {s[1]};
});
InputShape biasInputShape {
shape.first.is_dynamic() ? ov::PartialShape{shape.first[1]} : shape.first,
std::move(biasShape)
};
return biasInputShape;
}
TEST_P(GroupNormalization, CompareWithRefImpl) {
SKIP_IF_CURRENT_TEST_IS_DISABLED()
run();
validateNumSubgraphs();
}
} // namespace snippets
} // namespace test
} // namespace ov

View File

@ -0,0 +1,65 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "snippets_helpers.hpp"
namespace ov {
namespace test {
namespace snippets {
/* Graph with example shape propogation:
* Graph between Reshape[1,2,1,8] and Reshape[1,2,2,4] is a MVN
* Parameter[1,4,2,2], group_num is 2
* |
* Reshape[1,2,1,8]
* | |
* | ReduceSum[1,2,1,1] Scalar
* | | /
* | Multiply[1,2,1,1]
* | /
* Substract[1,2,1,8]
* | |
* | PowerStatic[1,2,1,8]
* | |
* | ReduceSum[1,2,1,1]
* | |
* | FMA(Multiply+Add)[1,2,1,1]
* | |
* | sqrt[1,2,1,1]
* | |
* | PowerStatic[1,2,1,1]
* | /
* Multiply[1,2,1,8] Parameter[4] Parameter[4]
* | | |
* Reshape[1,2,2,4] Reshape[1,2,2,1] Reshape[1,2,2,1]
* \ | /
* \ | /
* FMA(Multiply+Add)[1,2,2,4]
* |
* Reshape[1,4,2,2]
* |
* Result[1,4,2,2]
*/
class GroupNormalizationFunction : public SnippetsFunctionBase {
public:
explicit GroupNormalizationFunction(const std::vector<PartialShape>& inputShapes, const size_t& numGroup, const float& eps)
: SnippetsFunctionBase(inputShapes), num_groups(numGroup), epsilon(eps) {
OPENVINO_ASSERT(input_shapes.size() == 3, "Got invalid number of input shapes");
}
protected:
std::shared_ptr<ov::Model> initOriginal() const override;
std::shared_ptr<ov::Model> initReference() const override;
std::shared_ptr<ov::Model> initLowered() const override;
private:
size_t num_groups;
float epsilon;
};
} // namespace snippets
} // namespace test
} // namespace ov

View File

@ -0,0 +1,99 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "subgraph_group_normalization.hpp"
#include <snippets/op/subgraph.hpp>
namespace ov {
namespace test {
namespace snippets {
std::shared_ptr<ov::Model> GroupNormalizationFunction::initOriginal() const {
auto data = std::make_shared<op::v0::Parameter>(precision, input_shapes[0]);
auto scale = std::make_shared<op::v0::Parameter>(precision, input_shapes[1]);
auto shift = std::make_shared<op::v0::Parameter>(precision, input_shapes[2]);
const auto groupNormalization = std::make_shared<ov::op::v12::GroupNormalization>(data, scale, shift, num_groups, epsilon);
return std::make_shared<ov::Model>(NodeVector{groupNormalization}, ParameterVector{data, scale, shift});
}
std::shared_ptr<ov::Model> GroupNormalizationFunction::initReference() const {
auto data = std::make_shared<op::v0::Parameter>(precision, input_shapes[0]);
auto scale = std::make_shared<op::v0::Parameter>(precision, input_shapes[1]);
auto shift = std::make_shared<op::v0::Parameter>(precision, input_shapes[2]);
auto data_ = std::make_shared<op::v0::Parameter>(precision, input_shapes[0]);
auto scale_ = std::make_shared<op::v0::Parameter>(precision, input_shapes[1]);
auto shift_ = std::make_shared<op::v0::Parameter>(precision, input_shapes[2]);
const auto groupNormalization = std::make_shared<ov::op::v12::GroupNormalization>(data_, scale_, shift_, num_groups, epsilon);
auto subgraph = std::make_shared<ov::snippets::op::Subgraph>(NodeVector{data, scale, shift},
std::make_shared<ov::Model>(NodeVector{groupNormalization}, ParameterVector{data_, scale_, shift_}));
return std::make_shared<ov::Model>(NodeVector{subgraph}, ParameterVector{data, scale, shift});
}
std::shared_ptr<ov::Model> GroupNormalizationFunction::initLowered() const {
auto data = std::make_shared<op::v0::Parameter>(precision, input_shapes[0]);
auto scale = std::make_shared<op::v0::Parameter>(precision, input_shapes[1]);
auto bias = std::make_shared<op::v0::Parameter>(precision, input_shapes[2]);
// reshape [N, C, spatial] to [N, group, 1, (C / group) * spatial]
const auto orig_shape = input_shapes[0].to_shape();
size_t orig_rank = orig_shape.size();
size_t group_rank = 4;
size_t c_in_group = orig_shape[1] / num_groups;
size_t spatial_dim = 1;
for (size_t i = 2; i < orig_rank; ++i) {
spatial_dim = spatial_dim * orig_shape[i];
}
ov::Shape group_shape = {orig_shape[0], num_groups, 1ul, c_in_group * spatial_dim};
std::shared_ptr<ov::Node> reshaped_node_orig = std::make_shared<ov::snippets::op::Reshape>(data, group_shape);
const auto reduce_sum = std::make_shared<ov::snippets::op::ReduceSum>(reshaped_node_orig, group_rank - 1);
// reduceMean
float group_size_inv = 1.0f / static_cast<float>(group_shape[3]);
// scalar const -> scalar in data_flow_optimization.
const auto group_size_inv_node = std::make_shared<ov::snippets::op::Scalar>(element::f32, Shape{1}, group_size_inv);
const auto reduce_mean = std::make_shared<ov::op::v1::Multiply>(reduce_sum, group_size_inv_node);
// x - mean
std::shared_ptr<ov::Node> reshaped_node2 = reshaped_node_orig;
auto sub_mean = std::make_shared<ov::op::v1::Subtract>(reshaped_node2, reduce_mean);
// (x - mean) ^ 2
// power -> poweStatic in data_flow_optimization
auto sqr = std::make_shared<ov::snippets::op::PowerStatic>(sub_mean, 2.0f);
// reduceSum((x - mean) ^ 2)
auto sqr_reduce_sum = std::make_shared<ov::snippets::op::ReduceSum>(sqr, group_rank - 1);
// reduceMean((x - mean) ^ 2)
const auto group_size_inv_node_aux = std::make_shared<ov::snippets::op::Scalar>(element::f32, Shape{1}, group_size_inv);
auto sqr_mean = std::make_shared<ov::op::v1::Multiply>(sqr_reduce_sum, group_size_inv_node_aux);
// reduceMean((x - mean) ^ 2) + eps
auto eps_node = std::make_shared<ov::snippets::op::Scalar>(element::f32, Shape{1}, epsilon);
auto eps_add = std::make_shared<ov::op::v1::Add>(sqr_mean, eps_node);
// variance = sqrt( reducemean( (x - mean) ^ 2 ) + eps )
auto variance = std::make_shared<ov::op::v0::Sqrt>(eps_add);
// divide variance
const auto variance_inv = std::make_shared<ov::snippets::op::PowerStatic>(variance, -1.f);
auto mvn = std::make_shared<ov::op::v1::Multiply>(sub_mean, variance_inv);
// reshape mvn from [N, group, 1, (C / group) * spatial] to [N, group, C / group, spatial]
ov::Shape group_channel_shape = {orig_shape[0], num_groups, c_in_group, spatial_dim};
const auto mvn_reshaped = std::make_shared<ov::snippets::op::Reshape>(mvn, group_channel_shape);
// reshape scale and bias to [1, group, C / group, 1]
ov::Shape scale_bias_shape = {1ul, num_groups, c_in_group, 1ul};
std::shared_ptr<ov::Node> reshape_scale = std::make_shared<ov::snippets::op::Reshape>(scale, scale_bias_shape);
std::shared_ptr<ov::Node> reshape_bias = std::make_shared<ov::snippets::op::Reshape>(bias, scale_bias_shape);
auto scaled_node = std::make_shared<ov::op::v1::Multiply>(mvn_reshaped, reshape_scale);
auto biased_node = std::make_shared<ov::op::v1::Add>(scaled_node, reshape_bias);
// reshape_back [N, group, C / group, spatial] to [N, C, spatial]
const auto reshape_back_node = std::make_shared<ov::snippets::op::Reshape>(biased_node, orig_shape);
return std::make_shared<ov::Model>(NodeVector{reshape_back_node}, ParameterVector{data, scale, bias});
}
} // namespace snippets
} // namespace test
} // namespace ov