Skip to content

Commit

Permalink
Symbolic shape inference and graph optimizations (openvinotoolkit#19392)
Browse files Browse the repository at this point in the history
* Symbolic shape inference and graph optimizations
- Prepares a place in CommonOptimizations pipeline for symbolic optimizations
- Introduces symbolic propagation and symbolic optimizations for ChainedMaximum, NopBroadcast and shape sub-graph optimization
- Introduces utility runtime info for TableOfEquivalence passing and disabling of value invalidation during shape inference

* Executes NgramFusion in a symbolic environment. Relaxes Ngram fusion pattern utilizing symbolic knowledge

* Remove debug model visualization

* rt_info copying to new Add operation

* Fix visualization and place validation in nicer place in symbolic transformation

* Fix Slice operation not to propagate labels if input and output dimension is fully dynamic

* Covering Vladislav comments

* Replace value invalidation followed by validation to revalidation since it does the same thing

* Adding back invalidation of cached values to Symbolic Propagation pass

* Fix StridedSlice label propagation. Code style

* Update src/common/transformations/tests/symbolic_transformations/nop_broadcast.cpp
  • Loading branch information
jane-intel authored Sep 20, 2023
1 parent 8558476 commit c1a8380
Show file tree
Hide file tree
Showing 33 changed files with 1,359 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ class ov::pass::GroupedGatherElimination : public ov::pass::MatcherPass {
class ov::pass::SimplifyShapeOfSubGraph : public ov::pass::ModelPass {
public:
OPENVINO_RTTI("SimplifyShapeOfSubGraph", "0");
explicit SimplifyShapeOfSubGraph(bool use_shapes = true) : m_use_shapes(use_shapes){};
bool run_on_model(const std::shared_ptr<ov::Model>& m) override;

private:
bool m_use_shapes;
};

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#pragma once

#include <openvino/pass/graph_rewrite.hpp>
#include <transformations_visibility.hpp>

namespace ov {
namespace pass {
class TRANSFORMATIONS_API ChainedMaximumOptimization;
} // namespace pass
} // namespace ov

/**
* @ingroup ie_transformation_common_api
* @brief Optimizes graphs based on value labels / symbols
* Maximum(Maximum(A, B), B) -> Maximum(A, B)
* Maximum(Maximum(A, B), A) -> Maximum(A, B)
*/
class ov::pass::ChainedMaximumOptimization : public ov::pass::MatcherPass {
public:
OPENVINO_RTTI("ChainedMaximumOptimization", "0");
ChainedMaximumOptimization();
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#pragma once
#include <openvino/pass/graph_rewrite.hpp>
#include <openvino/pass/pass.hpp>
#include <transformations_visibility.hpp>

namespace ov {
namespace pass {
class TRANSFORMATIONS_API ApplyTableOfEquivalence;
class TRANSFORMATIONS_API OptimizeLabelsUsedAsValues;
} // namespace pass
} // namespace ov

/**
* @ingroup ie_transformation_common_api
* @brief Resets symbols / labels on output shapes and values according to table of symbol / label equivalence. It
* allows to reduce number of labels used in the model and to disambiguate label values.
*/
class ov::pass::ApplyTableOfEquivalence : public ov::pass::ModelPass {
public:
OPENVINO_RTTI("ApplyTableOfEquivalence", "0");
bool run_on_model(const std::shared_ptr<ov::Model>& m) override;
};

/**
* @ingroup ie_transformation_common_api
* @brief Collects sources where each symbol / label initially appeared (on shape or shape sub-graph) and attaches all
* value usages of this label to this initial source
*/
class ov::pass::OptimizeLabelsUsedAsValues : public ov::pass::ModelPass {
public:
OPENVINO_RTTI("OptimizeLabelsUsedAsValues", "0");
bool run_on_model(const std::shared_ptr<ov::Model>& m) override;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#pragma once

#include <openvino/pass/graph_rewrite.hpp>
#include <transformations_visibility.hpp>

namespace ov {
namespace pass {
class TRANSFORMATIONS_API NopBroadcast;
} // namespace pass
} // namespace ov

/**
* @ingroup ie_transformation_common_api
* @brief Optimizes out Broadcast(data, Maximum(shape, ones)) if labels on data and shape are equal
* Use case with data being empty should not be considered here since original graph has Maximum with ones
*/
class ov::pass::NopBroadcast : public ov::pass::MatcherPass {
public:
OPENVINO_RTTI("NopBroadcast", "0");
NopBroadcast();
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#pragma once

#include <openvino/pass/graph_rewrite.hpp>
#include <openvino/pass/manager.hpp>
#include <openvino/pass/pass.hpp>
#include <openvino/pass/pattern/matcher.hpp>
#include <transformations_visibility.hpp>

namespace ov {
namespace pass {
class TRANSFORMATIONS_API SymbolicOptimizations;
class TRANSFORMATIONS_API SymbolicPropagation;
} // namespace pass
} // namespace ov

/**
* @ingroup ie_transformation_common_api
* @brief Runs optimizations which are based on symbolic shape inference
*/
class ov::pass::SymbolicOptimizations : public ov::pass::ModelPass {
public:
OPENVINO_RTTI("SymbolicOptimizations", "0");
explicit SymbolicOptimizations(bool full_run = true);
bool run_on_model(const std::shared_ptr<ov::Model>& m) override;
std::shared_ptr<ov::pass::Manager> get_manager() {
return m_manager;
};

private:
std::shared_ptr<ov::pass::Manager> m_manager;
};

/**
* @ingroup ie_transformation_common_api
* @brief Assigns labels / symbols to all tensors on shapes and values. Uses shape inference and other special rules to
* propagate labels / symbols
*/
class ov::pass::SymbolicPropagation : public ov::pass::ModelPass {
public:
OPENVINO_RTTI("SymbolicPropagation");
SymbolicPropagation();
bool run_on_model(const std::shared_ptr<ov::Model>& m) override;

private:
std::shared_ptr<ov::TableOfEquivalence> m_te;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#pragma once

#include <transformations_visibility.hpp>

#include "openvino/core/descriptor/tensor.hpp"
#include "openvino/core/dimension.hpp"
#include "openvino/core/partial_shape.hpp"
#include "openvino/core/type/element_type.hpp"

namespace ov {
namespace symbol {
namespace util {

/// \brief Collects labels from shape. Labels of static dimensions are guaranteed to be ov::no_labels
///
/// \param shape Shape object to collect labels from
/// \param labels TensorLabel object to collect labels to
///
/// \return Status of collecting the labels (false if rank is static else true)
TRANSFORMATIONS_API bool get_labels(const ov::PartialShape& shape, ov::TensorLabel& labels);

/// \brief Collects labels from tensor of Output object
///
/// \param output Output object to collect labels from
/// \param labels TensorLabel object to collect labels to
///
/// \return Status of collecting the labels (false if tensor has no labels else true)
TRANSFORMATIONS_API bool get_labels(const ov::Output<ov::Node>& output, ov::TensorLabel& labels);

/// \brief Compares
///
/// \param lhs TensorLabel object to compare
/// \param rhs TensorLabel object to compare
///
/// \return true if labels are unique and equal between lhs and rhs else false
TRANSFORMATIONS_API bool are_unique_and_equal_labels(const ov::TensorLabel& lhs, const ov::TensorLabel& rhs);
} // namespace util
} // namespace symbol
} // namespace ov
28 changes: 26 additions & 2 deletions src/common/transformations/include/transformations/utils/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,11 +214,13 @@ TRANSFORMATIONS_API std::vector<Input<Node>> get_node_target_inputs(const std::s

TRANSFORMATIONS_API std::shared_ptr<Node> node_to_get_shape_value_of_indices_from_shape_node(
const std::shared_ptr<Node>& shape_node,
const std::vector<size_t>& indices);
const std::vector<size_t>& indices,
const std::vector<std::shared_ptr<Node>>& copy_rt_info_from = {});

TRANSFORMATIONS_API std::shared_ptr<Node> node_to_get_shape_value_of_indices_from_shape_source(
const Output<Node>& shape_source,
const std::vector<size_t>& indices);
const std::vector<size_t>& indices,
const std::vector<std::shared_ptr<Node>>& copy_rt_info_from = {});

TRANSFORMATIONS_API bool is_dequantization_subgraph(const Output<Node>& node);

Expand All @@ -230,6 +232,28 @@ TRANSFORMATIONS_API bool is_constant_and_all_values_equal_int(const Output<Node>

TRANSFORMATIONS_API bool is_on_constant_path(const ov::Output<ov::Node>& output);

template <typename T>
ov::pass::pattern::op::ValuePredicate constant_predicate(std::function<bool(const std::vector<T>&)> predicate) {
return pass::pattern::op::as_value_predicate([=](std::shared_ptr<Node> n) -> bool {
if (auto constant = as_type_ptr<v0::Constant>(n)) {
auto values = constant->cast_vector<T>();
return predicate(values);
}
return false;
});
}
} // namespace util
} // namespace op
} // namespace ov

#define INT_CONSTANT_WITH_PREDICATE(expression) \
pattern::wrap_type<op::v0::Constant>( \
ov::op::util::constant_predicate<int64_t>([](const std::vector<int64_t>& value) -> bool { \
return expression; \
}))

#define FLOAT_CONSTANT_WITH_PREDICATE(expression) \
pattern::wrap_type<op::v0::Constant>( \
ov::op::util::constant_predicate<float>([](const std::vector<float>& value) -> bool { \
return expression; \
}))
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
#include "transformations/op_conversions/softmax_decomposition.hpp"
#include "transformations/op_conversions/softsign_decomposition.hpp"
#include "transformations/op_conversions/unique_decomposition.hpp"
#include "transformations/symbolic_transformations/symbolic_optimizations.hpp"

bool ov::pass::CommonOptimizations::run_on_model(const std::shared_ptr<ov::Model>& f) {
RUN_ON_FUNCTION_SCOPE(CommonOptimizations);
Expand Down Expand Up @@ -230,7 +231,8 @@ bool ov::pass::CommonOptimizations::run_on_model(const std::shared_ptr<ov::Model
// StridesOptimization should be at the very end
// because we cannot insert any MaxPools since they may prevent
// other optimizations
manager.register_pass<StridesOptimization>();
REGISTER_PASS(manager, StridesOptimization)
REGISTER_PASS(manager, SymbolicOptimizations)
REGISTER_PASS(manager, Validate)
manager.run_passes(f);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ bool ov::pass::MOCTransformations::run_on_model(const std::shared_ptr<ov::Model>
REGISTER_PASS(manager, Validate)
}
REGISTER_PASS(manager, ConvertQuantizeDequantize)
REGISTER_PASS(manager, SimplifyShapeOfSubGraph)
REGISTER_PASS(manager, SimplifyShapeOfSubGraph, m_use_shapes)

if (!m_use_shapes) {
manager.register_pass<ov::pass::DisableShapeOfConstantFolding>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,8 @@ ov::pass::NopSliceBeforeGatherElements::NopSliceBeforeGatherElements() {

ov::pass::PrepareShapeOpsForEliminationAroundBE::PrepareShapeOpsForEliminationAroundBE() {
MATCHER_SCOPE(PrepareShapeOpsForEliminationAroundBE);
auto first_label = pattern::wrap_type<op::v1::Reshape, op::v0::Squeeze>(pattern::rank_equals(0));
auto first_label = pattern::wrap_type<op::v1::Reshape, op::v0::Squeeze, op::v1::StridedSlice, op::util::GatherBase>(
pattern::rank_equals(0));
auto other_input_label = pattern::any_input(pattern::rank_equals(0));
auto binary_op_label = pattern::wrap_type<op::util::BinaryElementwiseArithmetic,
op::util::BinaryElementwiseComparison,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "openvino/pass/manager.hpp"
#include "openvino/pass/pattern/op/wrap_type.hpp"
#include "transformations/common_optimizations/eliminate_unsqueeze_gather.hpp"
#include "transformations/common_optimizations/nop_elimination.hpp"
#include "transformations/common_optimizations/shared_ops_optimization.hpp"
#include "transformations/utils/utils.hpp"

Expand Down Expand Up @@ -291,8 +292,10 @@ bool pass::SimplifyShapeOfSubGraph::run_on_model(const std::shared_ptr<Model>& f
Manager manager;
manager.set_per_pass_validation(false);

REGISTER_PASS(manager, PrepareShapeOpsForEliminationAroundBE)
REGISTER_PASS(manager, SharedOpOptimization)
REGISTER_PASS(manager, EliminateGatherUnsqueeze) // should run after SharedOpOptimization
REGISTER_PASS(manager, NopElimination, m_use_shapes)
REGISTER_PASS(manager, GroupedGatherElimination)
// GatherNopElimination depends on shape, so it requires shape propagation
// if previous transformations has resolved some dynamic shapes.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#include "transformations/symbolic_transformations/chained_maximum.hpp"

#include <openvino/op/maximum.hpp>
#include <openvino/pass/pattern/op/wrap_type.hpp>

#include "itt.hpp"
#include "openvino/core/dimension_tracker.hpp"
#include "transformations/symbolic_transformations/utils.hpp"

using namespace ov::symbol::util;

ov::pass::ChainedMaximumOptimization::ChainedMaximumOptimization() {
MATCHER_SCOPE(ChainedMaximumOptimization);
auto A_input = pattern::any_input();
auto B_input = pattern::any_input();
auto C_input = pattern::any_input();
auto first_maximum = pattern::wrap_type<op::v1::Maximum>({A_input, B_input});
auto maximum = pattern::wrap_type<op::v1::Maximum>({first_maximum, C_input});

ov::matcher_pass_callback matcher_pass_callback = [=](pattern::Matcher& m) {
const auto& vm = m.get_pattern_value_map();

auto A = vm.at(A_input), B = vm.at(B_input), C = vm.at(C_input);
auto output_to_replace = vm.at(first_maximum);

ov::TensorLabel A_labels, B_labels, C_labels;
bool A_read = get_labels(A, A_labels);
bool B_read = get_labels(B, B_labels);
bool C_read = get_labels(C, C_labels);

if (!A_read && !B_read && !C_read)
return false;

if (are_unique_and_equal_labels(A_labels, C_labels)) {
// Matched Maximum(Maximum(A, B), C) with A == C -> Maximum(B, C)
return ov::replace_output_update_name(output_to_replace, B);
} else if (are_unique_and_equal_labels(B_labels, C_labels)) {
// Matched Maximum(Maximum(A, B), C) with B == C -> Maximum(A, C)
return ov::replace_output_update_name(output_to_replace, A);
}
return false;
};

auto m = std::make_shared<pattern::Matcher>(maximum, matcher_name);
register_matcher(m, matcher_pass_callback);
}
Loading

0 comments on commit c1a8380

Please sign in to comment.