Add PushConstantToSubgraph transformation (#15250)

* Add PushConstantToSubgraph transformation

Transformation detects constfoldable inputs to MultiSubGraphOp,
constantfold them and then pushes them to inner subgraphs.

Ticket: 98155

* cast to int

* comments, split to functions

* remove op::util
This commit is contained in:
Mateusz Tabaka 2023-01-25 15:46:09 +01:00 committed by GitHub
parent 2c64c3a8a9
commit 8512fc1655
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 464 additions and 0 deletions

View File

@ -0,0 +1,25 @@
// Copyright (C) 2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include <openvino/pass/pass.hpp>
#include <transformations_visibility.hpp>
namespace ov {
namespace pass {
/**
* @ingroup ie_transformation_common_api
* @brief PushConstantToSubgraph transformation detects MultiSubGraphOp inputs
* that can be constfoldable pushes that inputs to subgraphs.
*/
class TRANSFORMATIONS_API PushConstantToSubgraph : public ov::pass::ModelPass {
public:
OPENVINO_RTTI("PushConstantToSubgraph", "0");
bool run_on_model(const std::shared_ptr<Model>& model) override;
};
} // namespace pass
} // namespace ov

View File

@ -45,6 +45,7 @@
#include <transformations/common_optimizations/prelu_fusion.hpp>
#include <transformations/common_optimizations/pull_through_reduce.hpp>
#include <transformations/common_optimizations/pull_transpose_through_fq.hpp>
#include <transformations/common_optimizations/push_constant_to_subgraph.hpp>
#include <transformations/common_optimizations/random_uniform_fusion.hpp>
#include <transformations/common_optimizations/reduce_reshape_fusion.hpp>
#include <transformations/common_optimizations/relu_fake_quantize_fusion.hpp>
@ -121,6 +122,7 @@ bool ov::pass::MOCTransformations::run_on_model(const std::shared_ptr<ngraph::Fu
REGISTER_PASS(manager, RemoveMultiSubGraphOpDanglingParams)
REGISTER_PASS(manager, FoldSubgraphEmptyInputs)
REGISTER_PASS(manager, DisableRandomUniformConstantFolding)
REGISTER_PASS(manager, PushConstantToSubgraph)
REGISTER_PASS(manager, ConstantFolding)
REGISTER_PASS(manager, Validate)

View File

@ -0,0 +1,122 @@
// Copyright (C) 2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "transformations/common_optimizations/push_constant_to_subgraph.hpp"
#include <openvino/core/validation_util.hpp>
#include <openvino/op/util/multi_subgraph_base.hpp>
#include "itt.hpp"
using MultiSubGraphOp = ov::op::util::MultiSubGraphOp;
static std::shared_ptr<ov::op::v0::Constant> try_constantfold_input(
const std::shared_ptr<MultiSubGraphOp>& op,
const MultiSubGraphOp::InputDescription::Ptr& input_desc,
std::unordered_map<size_t, std::shared_ptr<ov::op::v0::Constant>>& cache) {
if (!std::dynamic_pointer_cast<MultiSubGraphOp::InvariantInputDescription>(input_desc)) {
return nullptr;
}
const auto input_index = input_desc->m_input_index;
auto it = cache.find(input_index);
if (it == cache.end()) {
auto constant = constantfold_subgraph(op->input_value(input_index));
if (constant) {
cache.insert({input_index, constant});
}
return constant;
}
return it->second;
}
static void replace_body_parameter(const std::shared_ptr<ov::Model>& body,
const std::shared_ptr<ov::op::v0::Parameter>& body_param,
size_t body_parameter_index,
const std::shared_ptr<ov::op::v0::Constant>& constant,
MultiSubGraphOp::MultiSubgraphInputDescriptionVector& descriptions) {
body_param->output(0).replace(constant);
body->remove_parameter(body_param);
// update all input descriptions to reflect that body parameter was removed
for (auto& desc : descriptions) {
if (desc->m_body_parameter_index > body_parameter_index) {
desc->m_body_parameter_index--;
}
}
}
static void update_multi_sub_graph_op_inputs(const std::shared_ptr<MultiSubGraphOp>& multi_sub_graph_op,
int remove_inputs_mask) {
int num_subgraphs = static_cast<int>(multi_sub_graph_op->get_internal_subgraphs_size());
auto inputs = multi_sub_graph_op->input_values();
for (size_t i = multi_sub_graph_op->get_input_size(); i > 0; i--) {
const auto input_index = i - 1;
if ((remove_inputs_mask & (1 << input_index)) != 0) {
// remove MultiSubGraphOp's input if it was marked to be removed
// (meaning it was constfolded and pushed to inner subgraph)
inputs.erase(inputs.begin() + input_index);
// update input descriptions to reflect that the input was removed
for (int body_idx = 0; body_idx < num_subgraphs; body_idx++) {
auto& descriptions = multi_sub_graph_op->get_input_descriptions(body_idx);
for (auto& desc : descriptions) {
if (desc->m_input_index > input_index) {
desc->m_input_index--;
}
}
}
}
}
multi_sub_graph_op->set_arguments(inputs);
}
bool ov::pass::PushConstantToSubgraph::run_on_model(const std::shared_ptr<Model>& model) {
RUN_ON_FUNCTION_SCOPE(PushConstantToSubgraph);
bool result = false;
for (const auto& op : model->get_ordered_ops()) {
const auto multi_sub_graph_op = as_type_ptr<op::util::MultiSubGraphOp>(op);
if (!multi_sub_graph_op) {
continue;
}
// cache for already constant folded inputs
std::unordered_map<size_t, std::shared_ptr<op::v0::Constant>> cache;
// bitmask describing which MultiSubGraphOp's input to remove
int remove_inputs_mask = 0;
int num_subgraphs = static_cast<int>(multi_sub_graph_op->get_internal_subgraphs_size());
for (int body_idx = 0; body_idx < num_subgraphs; body_idx++) {
const auto& body = multi_sub_graph_op->get_function(body_idx);
auto& body_params = body->get_parameters();
auto& descriptions = multi_sub_graph_op->get_input_descriptions(body_idx);
for (auto desc_it = descriptions.begin(); desc_it < descriptions.end();) {
const auto& desc = *desc_it;
const auto input_index = desc->m_input_index;
const auto constant = try_constantfold_input(multi_sub_graph_op, desc, cache);
if (!constant) {
remove_inputs_mask &= ~(1 << input_index);
desc_it++;
continue;
}
const auto body_parameter_index = desc->m_body_parameter_index;
desc_it = descriptions.erase(desc_it);
auto& body_param = body_params[body_parameter_index];
replace_body_parameter(body, body_param, body_parameter_index, constant, descriptions);
remove_inputs_mask |= 1 << input_index;
result = true;
}
}
if (remove_inputs_mask > 0) {
update_multi_sub_graph_op_inputs(multi_sub_graph_op, remove_inputs_mask);
}
for (int body_idx = 0; body_idx < num_subgraphs; body_idx++) {
bool model_changed = run_on_model(multi_sub_graph_op->get_function(body_idx));
result = result || model_changed;
}
}
return result;
}

View File

@ -0,0 +1,181 @@
// Copyright (C) 2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include <openvino/core/model.hpp>
#include <openvino/opsets/opset10.hpp>
#include <transformations/common_optimizations/push_constant_to_subgraph.hpp>
#include "common_test_utils/ngraph_test_utils.hpp"
using namespace testing;
using namespace ov;
TEST_F(TransformationTestsF, PushConstantToSubgraphLoop) {
{
auto trip_count = opset10::Constant::create(element::i32, Shape{}, {2});
auto term_cond = opset10::Constant::create(element::boolean, Shape{}, {true});
std::shared_ptr<Model> loop_body;
{
auto X = std::make_shared<opset10::Parameter>(element::f32, Shape{1, 2});
auto Y = std::make_shared<opset10::Parameter>(element::f32, Shape{1, 2});
auto Z = std::make_shared<opset10::Parameter>(element::f32, Shape{1, 2});
auto mul = std::make_shared<opset10::Multiply>(X, Y);
auto add = std::make_shared<opset10::Add>(mul, Z);
auto cond = opset10::Constant::create(element::boolean, Shape{}, {true});
loop_body = std::make_shared<Model>(NodeVector{add, cond}, ParameterVector{X, Y, Z});
}
auto loop = std::make_shared<opset10::Loop>(trip_count, term_cond);
loop->set_function(loop_body);
auto X = std::make_shared<opset10::Parameter>(element::f32, Shape{2, 2});
auto constant_1 = opset10::Constant::create(element::i32, Shape{2, 2}, {11});
auto convert_1 = std::make_shared<opset10::Convert>(constant_1, element::f32);
auto constant_2 = opset10::Constant::create(element::i32, Shape{1, 2}, {22});
auto convert_2 = std::make_shared<opset10::Convert>(constant_2, element::f32);
const auto& loop_params = loop_body->get_parameters();
loop->set_special_body_ports({-1, 1});
loop->set_sliced_input(loop_params[0], X, 0, 1, 1, -1, 0);
loop->set_sliced_input(loop_params[1], convert_1, 0, 1, 1, -1, 0);
loop->set_invariant_input(loop_params[2], convert_2);
auto out = loop->get_concatenated_slices(loop_body->get_results()[0], 0, 1, 1, -1, 0);
function = std::make_shared<Model>(OutputVector{out}, ParameterVector{X});
manager.register_pass<pass::PushConstantToSubgraph>();
}
{
auto trip_count = opset10::Constant::create(element::i32, Shape{}, {2});
auto term_cond = opset10::Constant::create(element::boolean, Shape{}, {true});
std::shared_ptr<Model> loop_body;
{
auto constant = opset10::Constant::create(element::f32, Shape{1, 2}, {22});
auto X = std::make_shared<opset10::Parameter>(element::f32, Shape{1, 2});
auto Y = std::make_shared<opset10::Parameter>(element::f32, Shape{1, 2});
auto mul = std::make_shared<opset10::Multiply>(X, Y);
auto add = std::make_shared<opset10::Add>(mul, constant);
auto cond = opset10::Constant::create(element::boolean, Shape{}, {true});
loop_body = std::make_shared<Model>(NodeVector{add, cond}, ParameterVector{X, Y});
}
auto loop = std::make_shared<opset10::Loop>(trip_count, term_cond);
loop->set_function(loop_body);
auto X = std::make_shared<opset10::Parameter>(element::f32, Shape{2, 2});
auto constant_1 = opset10::Constant::create(element::i32, Shape{2, 2}, {11});
auto convert_1 = std::make_shared<opset10::Convert>(constant_1, element::f32);
const auto& loop_params = loop_body->get_parameters();
loop->set_special_body_ports({-1, 1});
loop->set_sliced_input(loop_params[0], X, 0, 1, 1, -1, 0);
loop->set_sliced_input(loop_params[1], convert_1, 0, 1, 1, -1, 0);
auto out = loop->get_concatenated_slices(loop_body->get_results()[0], 0, 1, 1, -1, 0);
function_ref = std::make_shared<Model>(OutputVector{out}, ParameterVector{X});
}
comparator.enable(FunctionsComparator::CmpValues::ATTRIBUTES);
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
comparator.enable(FunctionsComparator::CmpValues::ACCURACY);
}
TEST_F(TransformationTestsF, PushConstantToSubgraphIf) {
{
auto cond = opset10::Constant::create(element::boolean, Shape{}, {false});
auto if_op = std::make_shared<ov::opset10::If>(cond);
std::shared_ptr<ov::Model> then_body;
{
auto A = std::make_shared<ov::opset10::Parameter>(ov::element::f32, ov::Shape{3});
auto B = std::make_shared<ov::opset10::Parameter>(ov::element::f32, ov::Shape{3});
auto C = std::make_shared<ov::opset10::Parameter>(ov::element::f32, ov::Shape{3});
auto D = std::make_shared<ov::opset10::Parameter>(ov::element::f32, ov::Shape{3});
auto add = std::make_shared<ov::opset10::Add>(A, B);
auto mul = std::make_shared<ov::opset10::Multiply>(add, C);
auto sub = std::make_shared<ov::opset10::Subtract>(mul, D);
then_body = std::make_shared<ov::Model>(add, ov::ParameterVector{A, B, C, D});
}
std::shared_ptr<ov::Model> else_body;
{
auto A = std::make_shared<ov::opset10::Parameter>(ov::element::f32, ov::Shape{3});
auto B = std::make_shared<ov::opset10::Parameter>(ov::element::f32, ov::Shape{3});
auto C = std::make_shared<ov::opset10::Parameter>(ov::element::f32, ov::Shape{3});
auto D = std::make_shared<ov::opset10::Parameter>(ov::element::f32, ov::Shape{3});
auto mul = std::make_shared<ov::opset10::Multiply>(A, B);
auto add = std::make_shared<ov::opset10::Add>(mul, C);
auto div = std::make_shared<ov::opset10::Divide>(add, D);
else_body = std::make_shared<ov::Model>(div, ov::ParameterVector{A, B, C, D});
}
if_op->set_then_body(then_body);
if_op->set_else_body(else_body);
const auto& then_params = then_body->get_parameters();
const auto& else_params = else_body->get_parameters();
auto A = std::make_shared<ov::opset10::Parameter>(ov::element::f32, ov::Shape{3});
auto B = std::make_shared<ov::opset10::Parameter>(ov::element::f32, ov::Shape{3});
auto C = std::make_shared<ov::opset10::Parameter>(ov::element::f32, ov::Shape{3});
auto const_1 = ov::opset10::Constant::create(ov::element::i32, ov::Shape{3}, {1});
auto convert_1 = std::make_shared<ov::opset10::Convert>(const_1, ov::element::f32);
auto const_2 = ov::opset10::Constant::create(ov::element::i32, ov::Shape{3}, {2});
auto convert_2 = std::make_shared<ov::opset10::Convert>(const_2, ov::element::f32);
auto const_3 = ov::opset10::Constant::create(ov::element::i32, ov::Shape{3}, {3});
auto convert_3 = std::make_shared<ov::opset10::Convert>(const_3, ov::element::f32);
if_op->set_input(A, then_params[0], nullptr);
if_op->set_input(convert_1, then_params[1], nullptr);
if_op->set_input(B, then_params[2], else_params[0]);
if_op->set_input(convert_2, then_params[3], else_params[1]);
if_op->set_input(C, nullptr, else_params[2]);
if_op->set_input(convert_3, nullptr, else_params[3]);
if_op->set_output(then_body->get_results()[0], else_body->get_results()[0]);
function = std::make_shared<ov::Model>(if_op, ov::ParameterVector{A, B, C});
manager.register_pass<pass::PushConstantToSubgraph>();
}
{
auto cond = opset10::Constant::create(element::boolean, Shape{}, {false});
auto const_1 = ov::opset10::Constant::create(ov::element::f32, ov::Shape{3}, {1});
auto const_2 = ov::opset10::Constant::create(ov::element::f32, ov::Shape{3}, {2});
auto const_3 = ov::opset10::Constant::create(ov::element::f32, ov::Shape{3}, {3});
auto if_op = std::make_shared<ov::opset10::If>(cond);
std::shared_ptr<ov::Model> then_body;
{
auto A = std::make_shared<ov::opset10::Parameter>(ov::element::f32, ov::Shape{3});
auto B = std::make_shared<ov::opset10::Parameter>(ov::element::f32, ov::Shape{3});
auto add = std::make_shared<ov::opset10::Add>(A, const_1);
auto mul = std::make_shared<ov::opset10::Multiply>(add, B);
auto sub = std::make_shared<ov::opset10::Subtract>(mul, const_2);
then_body = std::make_shared<ov::Model>(add, ov::ParameterVector{A, B});
}
std::shared_ptr<ov::Model> else_body;
{
auto A = std::make_shared<ov::opset10::Parameter>(ov::element::f32, ov::Shape{3});
auto B = std::make_shared<ov::opset10::Parameter>(ov::element::f32, ov::Shape{3});
auto mul = std::make_shared<ov::opset10::Multiply>(A, const_2);
auto add = std::make_shared<ov::opset10::Add>(mul, B);
auto div = std::make_shared<ov::opset10::Divide>(add, const_3);
else_body = std::make_shared<ov::Model>(div, ov::ParameterVector{A, B});
}
if_op->set_then_body(then_body);
if_op->set_else_body(else_body);
const auto& then_params = then_body->get_parameters();
const auto& else_params = else_body->get_parameters();
auto A = std::make_shared<ov::opset10::Parameter>(ov::element::f32, ov::Shape{3});
auto B = std::make_shared<ov::opset10::Parameter>(ov::element::f32, ov::Shape{3});
auto C = std::make_shared<ov::opset10::Parameter>(ov::element::f32, ov::Shape{3});
if_op->set_input(A, then_params[0], nullptr);
if_op->set_input(B, then_params[1], else_params[0]);
if_op->set_input(C, nullptr, else_params[1]);
if_op->set_output(then_body->get_results()[0], else_body->get_results()[0]);
function_ref = std::make_shared<ov::Model>(if_op, ov::ParameterVector{A, B, C});
}
comparator.enable(FunctionsComparator::CmpValues::ATTRIBUTES);
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
comparator.enable(FunctionsComparator::CmpValues::ACCURACY);
}

View File

@ -6,7 +6,9 @@
#include <memory>
#include <openvino/opsets/opset9.hpp>
#include <openvino/pass/constant_folding.hpp>
#include <openvino/pass/manager.hpp>
#include <transformations/common_optimizations/push_constant_to_subgraph.hpp>
#include <transformations/control_flow/unroll_if.hpp>
#include <transformations/init_node_info.hpp>
@ -221,3 +223,85 @@ TEST(TransformationTests, UnrollIfCondIsTrueMultiOutput) {
auto res = compare_functions(f, f_ref);
ASSERT_TRUE(res.first) << res.second;
}
TEST(TransformationTests, UnrollIfInsideIf) {
std::shared_ptr<ov::Model> f(nullptr), f_ref(nullptr);
{
auto cond = std::make_shared<ov::opset9::Constant>(ov::element::boolean, ov::Shape{1}, true);
auto not_cond = std::make_shared<ov::opset9::LogicalNot>(cond);
auto if_op = std::make_shared<ov::opset8::If>(cond);
std::shared_ptr<ov::Model> then_body;
{
auto cond_inside = std::make_shared<ov::opset9::Parameter>(ov::element::boolean, ov::Shape{1});
auto if_inside = std::make_shared<ov::opset8::If>(cond_inside);
std::shared_ptr<ov::Model> then_body_inside;
{
auto X = std::make_shared<ov::opset9::Parameter>(ov::element::f32, ov::Shape{3});
auto Y = std::make_shared<ov::opset9::Parameter>(ov::element::f32, ov::Shape{3});
auto add = std::make_shared<ov::opset9::Add>(X, Y);
then_body_inside = std::make_shared<ov::Model>(add, ov::ParameterVector{X, Y});
}
std::shared_ptr<ov::Model> else_body_inside;
{
auto X = std::make_shared<ov::opset9::Parameter>(ov::element::f32, ov::Shape{3});
auto Y = std::make_shared<ov::opset9::Parameter>(ov::element::f32, ov::Shape{3});
auto mul = std::make_shared<ov::opset9::Multiply>(X, Y);
else_body_inside = std::make_shared<ov::Model>(mul, ov::ParameterVector{X, Y});
}
auto X = std::make_shared<ov::opset9::Parameter>(ov::element::f32, ov::Shape{3});
auto Y = std::make_shared<ov::opset9::Parameter>(ov::element::f32, ov::Shape{3});
if_inside->set_then_body(then_body_inside);
if_inside->set_else_body(else_body_inside);
auto then_p = then_body_inside->get_parameters();
auto else_p = else_body_inside->get_parameters();
if_inside->set_input(X, then_p[0], else_p[0]);
if_inside->set_input(Y, then_p[1], else_p[1]);
if_inside->set_output(then_body_inside->get_results()[0], else_body_inside->get_results()[0]);
auto if_result = std::make_shared<ov::opset9::Result>(if_inside);
then_body = std::make_shared<ov::Model>(if_result, ov::ParameterVector{cond_inside, X, Y});
}
std::shared_ptr<ov::Model> else_body;
{
auto X = std::make_shared<ov::opset9::Parameter>(ov::element::f32, ov::Shape{3});
auto Y = std::make_shared<ov::opset9::Parameter>(ov::element::f32, ov::Shape{3});
auto sub = std::make_shared<ov::opset9::Subtract>(X, Y);
else_body = std::make_shared<ov::Model>(sub, ov::ParameterVector{X, Y});
}
auto X = std::make_shared<ov::opset9::Parameter>(ov::element::f32, ov::Shape{3});
auto Y = std::make_shared<ov::opset9::Parameter>(ov::element::f32, ov::Shape{3});
if_op->set_then_body(then_body);
if_op->set_else_body(else_body);
auto then_p = then_body->get_parameters();
auto else_p = else_body->get_parameters();
if_op->set_input(not_cond, then_p[0], nullptr);
if_op->set_input(X, then_p[1], else_p[0]);
if_op->set_input(Y, then_p[2], else_p[1]);
if_op->set_output(then_body->get_results()[0], else_body->get_results()[0]);
auto if_result = std::make_shared<ov::opset9::Result>(if_op);
f = std::make_shared<ov::Model>(ov::NodeVector{if_result}, ov::ParameterVector{X, Y});
ov::pass::Manager manager;
manager.register_pass<ngraph::pass::InitNodeInfo>();
manager.register_pass<ov::pass::PushConstantToSubgraph>();
manager.register_pass<ov::pass::ConstantFolding>();
manager.register_pass<ngraph::pass::UnrollIf>();
manager.run_passes(f);
ASSERT_NO_THROW(check_rt_info(f));
}
{
auto X = std::make_shared<ov::opset9::Parameter>(ov::element::f32, ov::Shape{3});
auto Y = std::make_shared<ov::opset9::Parameter>(ov::element::f32, ov::Shape{3});
auto mul = std::make_shared<ov::opset9::Multiply>(X, Y);
f_ref = std::make_shared<ov::Model>(mul, ov::ParameterVector{X, Y});
}
auto res = compare_functions(f, f_ref);
ASSERT_TRUE(res.first) << res.second;
}

View File

@ -198,4 +198,11 @@ OPENVINO_API bool are_unique(const std::vector<int64_t>& data);
/// \return Value if between min, max otherwise min or max.
OPENVINO_API
int64_t clip(const int64_t& value, const int64_t& min, const int64_t& max);
/// \brief Constant folds a subgraph to a constant node
///
/// \param subgraph sink
///
/// \return Constant node or nullptr if unable to constantfold the subgraph
OPENVINO_API std::shared_ptr<op::v0::Constant> constantfold_subgraph(const Output<Node>& subgraph_sink);
} // namespace ov

View File

@ -1690,3 +1690,27 @@ bool ov::are_unique(const std::vector<int64_t>& data) {
int64_t ov::clip(const int64_t& value, const int64_t& min, const int64_t& max) {
return std::min(std::max(value, min), max);
};
std::shared_ptr<op::v0::Constant> ov::constantfold_subgraph(const Output<Node>& subgraph_sink) {
if (const auto& c = ov::as_type_ptr<op::v0::Constant>(subgraph_sink.get_node_shared_ptr()))
return c;
const auto node = subgraph_sink.get_node();
const auto num_inputs = node->get_input_size();
if (num_inputs == 0)
return nullptr;
OutputVector inputs;
inputs.reserve(num_inputs);
for (size_t i = 0; i < num_inputs; i++) {
auto constant = constantfold_subgraph(node->input_value(i));
if (constant == nullptr)
return nullptr;
inputs.push_back(constant);
}
OutputVector outputs(node->get_output_size());
if (!node->constant_fold(outputs, inputs))
return nullptr;
return ov::as_type_ptr<op::v0::Constant>(outputs[subgraph_sink.get_index()].get_node_shared_ptr());
}

View File

@ -47,3 +47,22 @@ TEST(get_constant_from_source, extract_static_dim_from_dynamic_shape_check) {
ASSERT_TRUE(extract_static_dimension->get_output_tensor(0).get_lower_value());
ASSERT_TRUE(extract_static_dimension->get_output_tensor(0).get_upper_value());
}
TEST(constantfold_subgraph, split) {
std::vector<float> input{0, 1, 2, 3, 4, 5, 6, 7, 8};
auto constant = ov::opset8::Constant::create(ov::element::f32, ov::Shape{input.size()}, input);
auto mul = std::make_shared<ov::opset8::Multiply>(constant,
ov::opset8::Constant::create(ov::element::f32, ov::Shape{}, {1}));
auto shape = std::make_shared<ov::opset8::ShapeOf>(mul);
auto len_0 =
std::make_shared<ov::opset8::Divide>(shape, ov::opset8::Constant::create(ov::element::i64, ov::Shape{}, {2}));
auto len_1 = std::make_shared<ov::opset8::Subtract>(shape, len_0);
auto lenghts = std::make_shared<ov::opset8::Concat>(ov::OutputVector{len_0, len_1}, 0);
auto axis = ov::opset8::Constant::create(ov::element::i64, ov::Shape{}, {0});
auto split = std::make_shared<ov::opset8::VariadicSplit>(mul, axis, lenghts);
std::vector<float> expected(std::next(input.begin(), input.size() / 2), input.end());
auto ret = ov::constantfold_subgraph(split->output(1));
ASSERT_NE(ret, nullptr);
auto actual = ret->cast_vector<float>();
ASSERT_EQ(expected, actual);
}