Load Time Improvements (#9946)
* Fix ConstantFolding; update ric fusion * Update MarkPrecisionSensitiveDivdes pass * Move back RIC fusion; Update copy_runtime_info logic + tests * Update CF + tests * Code style fix * Code clean-up * fix cf attr propagation * Fix windows build
This commit is contained in:
parent
f0e2a2e811
commit
45f98cece5
|
|
@ -180,9 +180,9 @@ TRANSFORMATIONS_API std::shared_ptr<Node> clone_try_fold(const std::shared_ptr<N
|
|||
|
||||
TRANSFORMATIONS_API bool shapes_equal_except_dynamic_expected_batch(const ngraph::PartialShape& expected, const ngraph::PartialShape& actual);
|
||||
|
||||
TRANSFORMATIONS_API void visit_shape_path(const std::shared_ptr<ov::Node>& node,
|
||||
std::unordered_set<std::shared_ptr<ov::Node>>& visited,
|
||||
std::function<void(std::shared_ptr<ov::Node>)> func);
|
||||
TRANSFORMATIONS_API void visit_shape_path(ov::Node * node,
|
||||
std::unordered_set<ov::Node*>& visited,
|
||||
std::function<void(ov::Node*)> func);
|
||||
|
||||
template <typename T, typename... Args>
|
||||
std::shared_ptr<Node> make_try_fold(Args&&... args) {
|
||||
|
|
|
|||
|
|
@ -13,34 +13,44 @@
|
|||
#include "transformations/utils/utils.hpp"
|
||||
|
||||
bool ov::pass::MarkPrecisionSensitiveDivides::run_on_model(const std::shared_ptr<ov::Model>& m) {
|
||||
std::deque<std::shared_ptr<Node>> nodes;
|
||||
std::unordered_set<std::shared_ptr<Node>> visited;
|
||||
for (auto& r : m->get_results())
|
||||
nodes.push_back(r);
|
||||
for (auto& r : m->get_sinks())
|
||||
nodes.emplace_back(r);
|
||||
std::deque<Node *> nodes;
|
||||
std::unordered_set<Node *> visited, precision_sensitive_visited;
|
||||
for (auto& r : m->get_results()) {
|
||||
nodes.push_back(r.get());
|
||||
visited.insert(r.get());
|
||||
}
|
||||
for (auto& r : m->get_sinks()) {
|
||||
nodes.emplace_back(r.get());
|
||||
visited.insert(r.get());
|
||||
}
|
||||
|
||||
auto markup_func = [](std::shared_ptr<Node> node) {
|
||||
auto markup_func = [](Node* node) {
|
||||
if (ov::is_type<ov::opset8::Divide>(node) && node->get_output_element_type(0) == ngraph::element::f16) {
|
||||
ov::disable_divide_conversion(node);
|
||||
ov::disable_divide_conversion(node->shared_from_this());
|
||||
}
|
||||
};
|
||||
|
||||
while (!nodes.empty()) {
|
||||
auto curr_node = nodes.front();
|
||||
nodes.pop_front();
|
||||
if (visited.count(curr_node))
|
||||
continue;
|
||||
for (auto& input : curr_node->inputs()) {
|
||||
if (ov::is_precision_sensitive(input))
|
||||
ngraph::op::util::visit_shape_path(input.get_source_output().get_node_shared_ptr(), visited, markup_func);
|
||||
if (ov::is_precision_sensitive(input)) {
|
||||
visited.insert(input.get_source_output().get_node());
|
||||
// visit_shape_path shouldn't depend on "visited" nodes because we can approach Divide
|
||||
// earlier from some non precision sensitive path. So we use dedicated "precision_sensitive_visited"
|
||||
// set for precision sensitive nodes, so they can be visited twice and finally marked-up.
|
||||
ngraph::op::util::visit_shape_path(input.get_source_output().get_node(),
|
||||
precision_sensitive_visited,
|
||||
markup_func);
|
||||
}
|
||||
}
|
||||
visited.insert(curr_node);
|
||||
|
||||
for (auto& input_value : curr_node->input_values()) {
|
||||
// continue searching
|
||||
const auto& input_node = input_value.get_node_shared_ptr();
|
||||
const auto& input_node = input_value.get_node();
|
||||
if (visited.count(input_node)) continue;
|
||||
nodes.push_front(input_node);
|
||||
visited.insert(input_node);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -18,34 +18,40 @@
|
|||
using namespace std;
|
||||
|
||||
bool ov::pass::MarkPrecisionSensitiveSubgraphs::run_on_model(const std::shared_ptr<ov::Model>& f) {
|
||||
deque<shared_ptr<Node>> nodes;
|
||||
unordered_set<shared_ptr<Node>> visited;
|
||||
for (auto& r : f->get_results())
|
||||
nodes.push_back(r);
|
||||
for (auto& r : f->get_sinks())
|
||||
nodes.emplace_back(r);
|
||||
deque<Node*> nodes;
|
||||
unordered_set<Node*> visited, precision_sensitive_visited;
|
||||
for (const auto& r : f->get_results()) {
|
||||
nodes.push_back(r.get());
|
||||
visited.insert(r.get());
|
||||
}
|
||||
for (const auto& r : f->get_sinks()) {
|
||||
nodes.emplace_back(r.get());
|
||||
visited.insert(r.get());
|
||||
}
|
||||
|
||||
auto markup_func = [](shared_ptr<Node> node) {
|
||||
auto markup_func = [](Node * node) {
|
||||
if (ov::is_type<ov::opset8::Constant>(node)) {
|
||||
ov::disable_fp16_compression(node);
|
||||
ov::disable_fp16_compression(node->shared_from_this());
|
||||
}
|
||||
};
|
||||
|
||||
while (!nodes.empty()) {
|
||||
auto curr_node = nodes.front();
|
||||
nodes.pop_front();
|
||||
if (visited.count(curr_node))
|
||||
continue;
|
||||
for (auto& input : curr_node->inputs()) {
|
||||
if (ov::is_precision_sensitive(input))
|
||||
ngraph::op::util::visit_shape_path(input.get_source_output().get_node_shared_ptr(), visited, markup_func);
|
||||
for (const auto& input : curr_node->inputs()) {
|
||||
if (ov::is_precision_sensitive(input)) {
|
||||
visited.insert(input.get_source_output().get_node());
|
||||
ngraph::op::util::visit_shape_path(input.get_source_output().get_node(),
|
||||
precision_sensitive_visited, markup_func);
|
||||
}
|
||||
}
|
||||
visited.insert(curr_node);
|
||||
|
||||
for (auto& input_value : curr_node->input_values()) {
|
||||
// continue searching
|
||||
const auto& input_node = input_value.get_node_shared_ptr();
|
||||
const auto& input_node = input_value.get_node();
|
||||
if (visited.count(input_node)) continue;
|
||||
nodes.push_front(input_node);
|
||||
visited.insert(input_node);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -178,13 +178,13 @@ bool shapes_equal_except_dynamic_expected_batch(const ngraph::PartialShape& expe
|
|||
}
|
||||
}
|
||||
|
||||
void visit_shape_path(const std::shared_ptr<ov::Node>& node,
|
||||
std::unordered_set<std::shared_ptr<ov::Node>>& visited,
|
||||
std::function<void(std::shared_ptr<ov::Node>)> func) {
|
||||
void visit_shape_path(Node * node,
|
||||
std::unordered_set<ov::Node*>& visited,
|
||||
std::function<void(ov::Node*)> func) {
|
||||
if (!node)
|
||||
return;
|
||||
visited.insert(node);
|
||||
std::deque<std::shared_ptr<ov::Node>> nodes{node};
|
||||
std::deque<ov::Node*> nodes{node};
|
||||
while (!nodes.empty()) {
|
||||
auto curr_node = nodes.front();
|
||||
nodes.pop_front();
|
||||
|
|
@ -193,12 +193,13 @@ void visit_shape_path(const std::shared_ptr<ov::Node>& node,
|
|||
continue;
|
||||
}
|
||||
|
||||
visited.insert(curr_node);
|
||||
func(curr_node);
|
||||
for (auto& input_value : curr_node->input_values()) {
|
||||
// continue searching
|
||||
const auto& input_node = input_value.get_node_shared_ptr();
|
||||
const auto& input_node = input_value.get_node();
|
||||
if (visited.count(input_node)) continue;
|
||||
nodes.push_front(input_node);
|
||||
visited.insert(input_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
namespace ov {
|
||||
namespace pass {
|
||||
|
||||
/**
|
||||
* @brief Constant folding iterates over the function and tries to evaluate nodes
|
||||
* with constant inputs. Such nodes are then replaced with new Constants containing
|
||||
|
|
@ -19,13 +20,23 @@ public:
|
|||
OPENVINO_RTTI("ConstantFolding");
|
||||
bool run_on_model(const std::shared_ptr<ov::Model>& f) override;
|
||||
|
||||
private:
|
||||
protected:
|
||||
void copy_runtime_info_to_target_inputs(const std::shared_ptr<Node>& node, const Output<Node>& replacement);
|
||||
/// \brief Folds pre-calculated output tensor values to constants in case lower and
|
||||
/// upper estimations are equal. Traverses graph backwards starting from the results.
|
||||
bool pre_calculated_values_folding(const std::shared_ptr<ov::Model>& f);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief this method disables constant folding for given node. Under constant folding we consider ConstantFolding
|
||||
* transformation, so other type of constant folding like `get_constant_from_source` doesn't work with
|
||||
* this attribute. Also before using this attribute please consider two corner cases:
|
||||
* 1. If for sub-graph like ShapeOf->ShapeOf we disable cf for first ShapeOf node, it doesn't spread
|
||||
* to the second ShapeOf, so the entire sub-graph will be folded. (In case if first ShapeOf has exactly one
|
||||
* consumer)
|
||||
* 2. If node with disable_constant_folding was replaced with another node, the attribute will be lost because
|
||||
* it is not copyable.
|
||||
*/
|
||||
OPENVINO_API void disable_constant_folding(const std::shared_ptr<Node>& node);
|
||||
|
||||
OPENVINO_API void enable_constant_folding(const std::shared_ptr<Node>& node);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
#include <ngraph/op/constant.hpp>
|
||||
|
||||
#include "ngraph/op/util/sub_graph_base.hpp"
|
||||
#include "ngraph/opsets/opset1.hpp"
|
||||
#include "ngraph/opsets/opset3.hpp"
|
||||
#include "ngraph/rt_info.hpp"
|
||||
#include "ngraph/validation_util.hpp"
|
||||
|
||||
|
|
@ -21,7 +23,11 @@ bool ov::pass::ConstantFolding::run_on_model(const std::shared_ptr<ov::Model>& f
|
|||
}
|
||||
|
||||
OutputVector replacements(node->get_output_size());
|
||||
if (node->constant_fold(replacements, node->input_values())) {
|
||||
|
||||
// We have to check node for DisableConstantFolding because operations can override constant_folding
|
||||
// method, so we can't always rely on attribute check inside default node->constant_fold method
|
||||
if (node->get_rt_info().count(DisableConstantFolding::get_type_info_static()) == 0 &&
|
||||
node->constant_fold(replacements, node->input_values())) {
|
||||
NGRAPH_CHECK(replacements.size() == node->get_output_size(),
|
||||
"constant_fold_default returned incorrect number of replacements for ",
|
||||
node);
|
||||
|
|
@ -66,6 +72,30 @@ void ngraph::pass::ConstantFolding::copy_runtime_info_to_target_inputs(const std
|
|||
}
|
||||
|
||||
bool ngraph::pass::ConstantFolding::pre_calculated_values_folding(const std::shared_ptr<ngraph::Function>& f) {
|
||||
// To avoid excess graph traversals we have to manually propagate DisableConstantFolding with some
|
||||
// temporary attribute which indicates that the node which is marked with this attribute can't be folded because
|
||||
// it is included into not foldable sub-graph.
|
||||
for (auto&& node : f->get_ordered_ops()) {
|
||||
const auto& inputs = node->input_values();
|
||||
auto& rt_info = node->get_rt_info();
|
||||
bool can_be_folded = true;
|
||||
if (rt_info.count(DisableConstantFolding::get_type_info_static())) {
|
||||
can_be_folded = false;
|
||||
} else if (is_type<ngraph::opset1::ShapeOf>(node) || is_type<ngraph::opset3::ShapeOf>(node)) {
|
||||
// In case if node is ShapeOf operation we stop propagation of can_be_folded attribute. We have to limit
|
||||
// propagation because we can't detect borders of shape_of sub-graphs, so we propagate can_be_folded
|
||||
// attribute through all nodes including nodes on data path. So to limit the spread of attribute to other
|
||||
// shape-of sub-graphs we do not propagate it through ShapeOf nodes.
|
||||
can_be_folded = true;
|
||||
} else if (std::any_of(inputs.cbegin(), inputs.cend(), [](const Output<Node>& output) {
|
||||
const auto& rt_info = output.get_node()->get_rt_info();
|
||||
return rt_info.count("can_be_folded") && !rt_info.at("can_be_folded").as<bool>();
|
||||
})) {
|
||||
can_be_folded = false;
|
||||
}
|
||||
rt_info["can_be_folded"] = can_be_folded;
|
||||
}
|
||||
|
||||
deque<shared_ptr<Node>> nodes;
|
||||
set<shared_ptr<Node>> visited;
|
||||
for (auto& r : f->get_results())
|
||||
|
|
@ -81,31 +111,20 @@ bool ngraph::pass::ConstantFolding::pre_calculated_values_folding(const std::sha
|
|||
continue;
|
||||
visited.insert(curr_node);
|
||||
|
||||
for (auto& input_value : curr_node->input_values()) {
|
||||
// Check that ConstantFolding is not disabled on this path
|
||||
std::vector<Node*> order;
|
||||
auto status = ngraph::could_propagate(input_value, order);
|
||||
if (status) {
|
||||
for (const auto& node : order) {
|
||||
const auto& rt_info = node->get_rt_info();
|
||||
if (rt_info.count(DisableConstantFolding::get_type_info_static())) {
|
||||
status = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (status && input_value.get_tensor().has_and_set_bound()) {
|
||||
auto input_node = input_value.get_node_shared_ptr();
|
||||
auto replacement = std::make_shared<ngraph::op::Constant>(input_value.get_tensor().get_lower_value());
|
||||
for (auto& output : curr_node->input_values()) {
|
||||
const auto& rt_info = output.get_node()->get_rt_info();
|
||||
auto can_be_folded = !rt_info.count("can_be_folded") || rt_info.at("can_be_folded").as<bool>();
|
||||
if (can_be_folded && output.get_tensor().has_and_set_bound()) {
|
||||
auto input_node = output.get_node_shared_ptr();
|
||||
auto replacement = std::make_shared<ngraph::op::Constant>(output.get_tensor().get_lower_value());
|
||||
if (replacement && !ov::is_type<ngraph::op::Constant>(input_node)) {
|
||||
if (input_node->get_output_size() == 1) {
|
||||
replacement->set_friendly_name(input_node->get_friendly_name());
|
||||
} else {
|
||||
replacement->set_friendly_name(input_node->get_friendly_name() + "." +
|
||||
std::to_string(input_value.get_index()));
|
||||
std::to_string(output.get_index()));
|
||||
}
|
||||
input_value.replace(replacement);
|
||||
output.replace(replacement);
|
||||
// Propagate runtime info attributes to replacement consumer nodes
|
||||
copy_runtime_info_to_target_inputs(input_node, replacement);
|
||||
|
||||
|
|
@ -113,7 +132,7 @@ bool ngraph::pass::ConstantFolding::pre_calculated_values_folding(const std::sha
|
|||
}
|
||||
} else {
|
||||
// continue searching
|
||||
const auto& input_node = input_value.get_node_shared_ptr();
|
||||
const auto& input_node = output.get_node_shared_ptr();
|
||||
nodes.push_front(input_node);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,9 @@ ov::Any get_opset(const ngraph::Node::RTMap& rt_info) {
|
|||
|
||||
void assign_runtime_info(const ngraph::Node::RTMap& from, ngraph::Node::RTMap& to) {
|
||||
auto opset = get_opset(to);
|
||||
to = from;
|
||||
for (auto& item : from) {
|
||||
to[item.first] = item.second;
|
||||
}
|
||||
if (!opset.empty()) {
|
||||
to["opset"] = opset;
|
||||
}
|
||||
|
|
@ -62,7 +64,6 @@ void assign_runtime_info(const ngraph::Node::RTMap& from, ngraph::Node::RTMap& t
|
|||
void ngraph::copy_runtime_info(std::shared_ptr<ngraph::Node> from, std::shared_ptr<ngraph::Node> to) {
|
||||
auto& attrs = to->get_rt_info();
|
||||
auto opset = get_opset(attrs);
|
||||
attrs.clear();
|
||||
|
||||
for (const auto& item : from->get_rt_info()) {
|
||||
bool copy = item.first != "opset";
|
||||
|
|
|
|||
|
|
@ -1204,25 +1204,7 @@ void propagate_rt_info(Node* node, const Output<Node>& final_port) {
|
|||
if (stop_nodes.count(in.get_node()))
|
||||
continue;
|
||||
auto consumer = in.get_node()->shared_from_this();
|
||||
// FIXME: Here we have a WA in order to save some original fields
|
||||
// if we have conflicts because Variant merge doesn't work.
|
||||
// We can restore original fields because we don't change the operation
|
||||
auto orig_rt_info = consumer->get_rt_info();
|
||||
|
||||
copy_runtime_info({curr_node, consumer}, consumer);
|
||||
|
||||
auto& rt_info = consumer->get_rt_info();
|
||||
for (const auto& it : orig_rt_info) {
|
||||
if (rt_info.find(it.first) == rt_info.end()) {
|
||||
bool copy = true;
|
||||
if (it.second.is<ov::RuntimeAttribute>()) {
|
||||
copy = it.second.as<ov::RuntimeAttribute>().is_copyable();
|
||||
}
|
||||
if (copy) {
|
||||
rt_info[it.first] = it.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ set(SRC
|
|||
coordinate.cpp
|
||||
coordinate_range.cpp
|
||||
copy.cpp
|
||||
copy_runtime_info.cpp
|
||||
element_type.cpp
|
||||
eval.cpp
|
||||
extension.cpp
|
||||
|
|
|
|||
|
|
@ -4,8 +4,11 @@
|
|||
|
||||
#include "ngraph/pass/constant_folding.hpp"
|
||||
|
||||
#include <transformations/utils/utils.hpp>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "ngraph/ngraph.hpp"
|
||||
#include "ngraph/opsets/opset1.hpp"
|
||||
#include "ngraph/opsets/opset5.hpp"
|
||||
#include "ngraph/pass/manager.hpp"
|
||||
#include "util/all_close_f.hpp"
|
||||
|
|
@ -3195,19 +3198,107 @@ TEST(constant_folding, constant_dyn_reshape_v1_pattern_with_zero_dims) {
|
|||
}
|
||||
|
||||
TEST(constant_folding, disable_constant_folding) {
|
||||
auto input = make_shared<op::Parameter>(element::f32, Shape{1, 3});
|
||||
auto constant_shape = op::Constant::create(element::i64, Shape{1}, {3});
|
||||
auto dyn_reshape = make_shared<op::v1::Reshape>(input, constant_shape, true);
|
||||
auto& rt_info = dyn_reshape->get_rt_info();
|
||||
rt_info[ov::pass::DisableConstantFolding::get_type_info_static()];
|
||||
auto f = make_shared<Function>(dyn_reshape, ParameterVector{input});
|
||||
auto data = std::make_shared<op::Parameter>(element::f16, Shape{1, 3, 22, 22});
|
||||
|
||||
pass::Manager pass_manager;
|
||||
pass_manager.register_pass<pass::ConstantFolding>();
|
||||
pass_manager.run_passes(f);
|
||||
// In this test case following sub-graph will be consumed by Interpolate, so during shape inference Interpolate
|
||||
// will request values from this sub-graph and ConstantFolding pass will try to use this pre-calculated values
|
||||
// to fold it. But in our case we are disabling CF for this sub-graph first and then enable CF to check that all
|
||||
// checks inside ConstantFolding transformation are working and doesn't cache anytihng.
|
||||
auto gather = op::util::node_to_get_shape_value_of_indices_from_shape_source(data, {2, 3});
|
||||
auto convert = std::make_shared<opset5::Convert>(gather, element::f16);
|
||||
auto divide_constant = op::Constant::create(element::f16, Shape{1}, {0.5});
|
||||
auto divide = std::make_shared<opset5::Divide>(convert, divide_constant);
|
||||
auto convert_after = std::make_shared<opset5::Convert>(divide, element::i32);
|
||||
|
||||
ASSERT_EQ(count_ops_of_type<op::v1::Reshape>(f), 1);
|
||||
ASSERT_EQ(count_ops_of_type<op::Constant>(f), 1);
|
||||
opset1::Interpolate::Attributes interp_attr;
|
||||
interp_attr.antialias = false;
|
||||
interp_attr.axes = {2, 3};
|
||||
interp_attr.mode = "nearest";
|
||||
interp_attr.pads_begin = {0, 0, 0, 0};
|
||||
interp_attr.pads_end = {0, 0, 0, 0};
|
||||
|
||||
auto interpolate = std::make_shared<opset1::Interpolate>(data, convert_after, interp_attr);
|
||||
auto f = std::make_shared<Function>(NodeVector{interpolate}, ParameterVector{data});
|
||||
|
||||
ov::disable_constant_folding(convert);
|
||||
|
||||
pass::Manager m;
|
||||
m.register_pass<pass::ConstantFolding>();
|
||||
m.run_passes(f);
|
||||
|
||||
// Check that sub-graph on second Interpolate input wasn't folded
|
||||
ASSERT_EQ(interpolate->input_value(1), convert_after->output(0));
|
||||
|
||||
ov::enable_constant_folding(convert);
|
||||
|
||||
m.run_passes(f);
|
||||
|
||||
// After we enabled CF the sub-graph will be folded to Constant
|
||||
ASSERT_TRUE(ov::is_type<op::Constant>(interpolate->get_input_node_shared_ptr(1)));
|
||||
|
||||
// Check that DisableConstantFolding attribute wasn't propagated to some other nodes during CF
|
||||
for (auto node : f->get_ordered_ops()) {
|
||||
ASSERT_FALSE(ov::pass::constant_folding_is_disabled(node));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(constant_folding, disable_constant_folding_simple) {
|
||||
// This test case checks the behaviour of CF pass when output values are not precalculated
|
||||
// so CF triggers another branch where it goes through nodes and trying to fold one by one.
|
||||
auto data = std::make_shared<op::Parameter>(element::f32, Shape{1, 3, 22, 22});
|
||||
auto reshape = std::make_shared<opset5::Reshape>(op::Constant::create(element::f32, Shape{3}, {1, 2, 3}),
|
||||
op::Constant::create(element::i64, Shape{3}, {3, 1, 1}),
|
||||
true);
|
||||
auto divide = std::make_shared<opset5::Divide>(data, reshape);
|
||||
auto f = std::make_shared<Function>(NodeVector{divide}, ParameterVector{data});
|
||||
|
||||
ov::disable_constant_folding(reshape);
|
||||
|
||||
pass::Manager m;
|
||||
m.register_pass<pass::ConstantFolding>();
|
||||
m.run_passes(f);
|
||||
|
||||
// Check that Reshape is not folded
|
||||
ASSERT_EQ(divide->input_value(1), reshape->output(0));
|
||||
|
||||
ov::enable_constant_folding(reshape);
|
||||
|
||||
m.run_passes(f);
|
||||
|
||||
// After we enabled CF the sub-graph will be folded to Constant
|
||||
ASSERT_TRUE(ov::is_type<op::Constant>(divide->get_input_node_shared_ptr(1)));
|
||||
|
||||
// Check that DisableConstantFolding attribute wasn't propagated to some other nodes during CF
|
||||
for (auto node : f->get_ordered_ops()) {
|
||||
ASSERT_FALSE(ov::pass::constant_folding_is_disabled(node));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(constant_folding, disable_constant_folding_check) {
|
||||
auto data = std::make_shared<op::Parameter>(element::f32, Shape{1, 3, 22, 22});
|
||||
auto shapeof1 = std::make_shared<opset5::ShapeOf>(data);
|
||||
auto reshape1 = std::make_shared<opset5::Reshape>(data, shapeof1, true);
|
||||
auto shapeof2 = std::make_shared<opset5::ShapeOf>(reshape1);
|
||||
auto reshape2 = std::make_shared<opset5::Reshape>(reshape1, shapeof2, true);
|
||||
auto f = std::make_shared<Function>(NodeVector{reshape2}, ParameterVector{data});
|
||||
|
||||
ov::disable_constant_folding(shapeof1);
|
||||
|
||||
class ConstantFoldingAccessor : public pass::ConstantFolding {
|
||||
public:
|
||||
ConstantFoldingAccessor() = default;
|
||||
using ConstantFolding::pre_calculated_values_folding;
|
||||
};
|
||||
|
||||
ConstantFoldingAccessor().pre_calculated_values_folding(f);
|
||||
|
||||
ASSERT_TRUE(shapeof1->get_rt_info().count("can_be_folded"));
|
||||
ASSERT_FALSE(shapeof1->get_rt_info().at("can_be_folded").as<bool>());
|
||||
|
||||
ASSERT_TRUE(shapeof2->get_rt_info().count("can_be_folded"));
|
||||
ASSERT_TRUE(shapeof2->get_rt_info().at("can_be_folded").as<bool>());
|
||||
|
||||
ASSERT_TRUE(ov::is_type<op::Constant>(reshape2->get_input_node_shared_ptr(1)));
|
||||
}
|
||||
|
||||
TEST(constant_folding, constant_loop) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,199 @@
|
|||
// Copyright (C) 2018-2022 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <ngraph/pattern/op/wrap_type.hpp>
|
||||
#include <ngraph/rt_info.hpp>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "ngraph/graph_util.hpp"
|
||||
#include "ngraph/log.hpp"
|
||||
#include "ngraph/ngraph.hpp"
|
||||
#include "ngraph/opsets/opset3.hpp"
|
||||
#include "ngraph/pass/graph_rewrite.hpp"
|
||||
#include "ngraph/pass/manager.hpp"
|
||||
|
||||
using namespace ngraph;
|
||||
using namespace std;
|
||||
|
||||
class TestAttributeNoCopyable : public ov::RuntimeAttribute {
|
||||
public:
|
||||
OPENVINO_RTTI("TestAttributeNoCopyable");
|
||||
TestAttributeNoCopyable() = default;
|
||||
bool is_copyable() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
static void set(std::shared_ptr<Node> node) {
|
||||
auto& rt_info = node->get_rt_info();
|
||||
rt_info[TestAttributeNoCopyable::get_type_info_static()] = TestAttributeNoCopyable();
|
||||
}
|
||||
|
||||
static bool exists_in(std::shared_ptr<Node> node) {
|
||||
const auto& rt_info = node->get_rt_info();
|
||||
return rt_info.count(TestAttributeNoCopyable::get_type_info_static());
|
||||
}
|
||||
};
|
||||
|
||||
class TestAttributeCopyable : public ov::RuntimeAttribute {
|
||||
public:
|
||||
OPENVINO_RTTI("TestAttributeCopyable");
|
||||
TestAttributeCopyable() = default;
|
||||
|
||||
static void set(std::shared_ptr<Node> node) {
|
||||
auto& rt_info = node->get_rt_info();
|
||||
rt_info[TestAttributeCopyable::get_type_info_static()] = TestAttributeCopyable();
|
||||
}
|
||||
|
||||
static bool exists_in(std::shared_ptr<Node> node) {
|
||||
const auto& rt_info = node->get_rt_info();
|
||||
return rt_info.count(TestAttributeCopyable::get_type_info_static());
|
||||
}
|
||||
};
|
||||
|
||||
class TestAttributeMergable : public ov::RuntimeAttribute {
|
||||
public:
|
||||
OPENVINO_RTTI("TestAttributeMergable");
|
||||
TestAttributeMergable() = default;
|
||||
|
||||
static void set(std::shared_ptr<Node> node) {
|
||||
auto& rt_info = node->get_rt_info();
|
||||
rt_info[TestAttributeMergable::get_type_info_static()] = TestAttributeMergable();
|
||||
}
|
||||
|
||||
static bool exists_in(std::shared_ptr<Node> node) {
|
||||
const auto& rt_info = node->get_rt_info();
|
||||
return rt_info.count(TestAttributeMergable::get_type_info_static());
|
||||
}
|
||||
|
||||
ov::Any merge(const ngraph::NodeVector& nodes) const override {
|
||||
return {TestAttributeMergable()};
|
||||
}
|
||||
};
|
||||
|
||||
TEST(copy_runtime_info, node_to_node_1) {
|
||||
auto a = make_shared<opset3::Parameter>(element::f32, Shape{1});
|
||||
auto b = make_shared<opset3::Parameter>(element::f32, Shape{1});
|
||||
|
||||
TestAttributeCopyable::set(a);
|
||||
TestAttributeNoCopyable::set(b);
|
||||
|
||||
copy_runtime_info(a, b);
|
||||
|
||||
ASSERT_TRUE(TestAttributeCopyable::exists_in(a));
|
||||
ASSERT_TRUE(TestAttributeCopyable::exists_in(b));
|
||||
|
||||
ASSERT_TRUE(TestAttributeNoCopyable::exists_in(b));
|
||||
|
||||
copy_runtime_info(b, b);
|
||||
ASSERT_TRUE(TestAttributeCopyable::exists_in(b));
|
||||
ASSERT_TRUE(TestAttributeNoCopyable::exists_in(b));
|
||||
}
|
||||
|
||||
TEST(copy_runtime_info, node_to_node_2) {
|
||||
auto a = make_shared<opset3::Parameter>(element::f32, Shape{1});
|
||||
auto b = make_shared<opset3::Parameter>(element::f32, Shape{1});
|
||||
|
||||
TestAttributeCopyable::set(a);
|
||||
TestAttributeNoCopyable::set(a);
|
||||
|
||||
copy_runtime_info(a, b);
|
||||
|
||||
ASSERT_TRUE(TestAttributeCopyable::exists_in(a));
|
||||
ASSERT_TRUE(TestAttributeNoCopyable::exists_in(a));
|
||||
|
||||
ASSERT_TRUE(TestAttributeCopyable::exists_in(b));
|
||||
ASSERT_FALSE(TestAttributeNoCopyable::exists_in(b));
|
||||
}
|
||||
|
||||
TEST(copy_runtime_info, node_to_nodes) {
|
||||
auto a = make_shared<opset3::Parameter>(element::f32, Shape{1});
|
||||
auto b = make_shared<opset3::Parameter>(element::f32, Shape{1});
|
||||
auto c = make_shared<opset3::Parameter>(element::f32, Shape{1});
|
||||
|
||||
TestAttributeCopyable::set(a);
|
||||
TestAttributeNoCopyable::set(b);
|
||||
TestAttributeNoCopyable::set(c);
|
||||
|
||||
copy_runtime_info(a, {b, c});
|
||||
|
||||
ASSERT_TRUE(TestAttributeCopyable::exists_in(a));
|
||||
ASSERT_TRUE(TestAttributeCopyable::exists_in(b));
|
||||
ASSERT_TRUE(TestAttributeCopyable::exists_in(c));
|
||||
|
||||
ASSERT_FALSE(TestAttributeNoCopyable::exists_in(a));
|
||||
ASSERT_TRUE(TestAttributeNoCopyable::exists_in(b));
|
||||
ASSERT_TRUE(TestAttributeNoCopyable::exists_in(c));
|
||||
}
|
||||
|
||||
TEST(copy_runtime_info, nodes_to_node_1) {
|
||||
auto a = make_shared<opset3::Parameter>(element::f32, Shape{1});
|
||||
auto b = make_shared<opset3::Parameter>(element::f32, Shape{1});
|
||||
auto c = make_shared<opset3::Parameter>(element::f32, Shape{1});
|
||||
|
||||
TestAttributeCopyable::set(a);
|
||||
TestAttributeNoCopyable::set(a);
|
||||
|
||||
TestAttributeCopyable::set(b);
|
||||
TestAttributeNoCopyable::set(b);
|
||||
|
||||
copy_runtime_info({a, b}, c);
|
||||
|
||||
ASSERT_FALSE(TestAttributeCopyable::exists_in(c));
|
||||
ASSERT_FALSE(TestAttributeNoCopyable::exists_in(c));
|
||||
}
|
||||
|
||||
TEST(copy_runtime_info, nodes_to_node_2) {
|
||||
auto a = make_shared<opset3::Parameter>(element::f32, Shape{1});
|
||||
auto b = make_shared<opset3::Parameter>(element::f32, Shape{1});
|
||||
auto c = make_shared<opset3::Parameter>(element::f32, Shape{1});
|
||||
|
||||
TestAttributeMergable::set(a);
|
||||
TestAttributeMergable::set(b);
|
||||
TestAttributeNoCopyable::set(c);
|
||||
|
||||
copy_runtime_info({a, b}, c);
|
||||
|
||||
ASSERT_TRUE(TestAttributeMergable::exists_in(c));
|
||||
ASSERT_TRUE(TestAttributeNoCopyable::exists_in(c));
|
||||
}
|
||||
|
||||
TEST(copy_runtime_info, nodes_to_node_3) {
|
||||
auto a = make_shared<opset3::Parameter>(element::f32, Shape{1});
|
||||
auto b = make_shared<opset3::Parameter>(element::f32, Shape{1});
|
||||
|
||||
TestAttributeCopyable::set(a);
|
||||
TestAttributeNoCopyable::set(b);
|
||||
|
||||
copy_runtime_info({a, b}, b);
|
||||
|
||||
ASSERT_TRUE(TestAttributeCopyable::exists_in(b));
|
||||
ASSERT_TRUE(TestAttributeNoCopyable::exists_in(b));
|
||||
}
|
||||
|
||||
TEST(copy_runtime_info, replace_output_update_name) {
|
||||
auto a = make_shared<opset3::Parameter>(element::f32, Shape{1});
|
||||
auto b = make_shared<opset3::Relu>(a);
|
||||
auto c = make_shared<opset3::Relu>(b);
|
||||
auto d = make_shared<opset3::Relu>(c);
|
||||
|
||||
TestAttributeMergable::set(b);
|
||||
TestAttributeMergable::set(c);
|
||||
|
||||
TestAttributeCopyable::set(c);
|
||||
|
||||
TestAttributeNoCopyable::set(b);
|
||||
TestAttributeNoCopyable::set(c);
|
||||
|
||||
// performs copy_runtime_info like copy_runtime_info({b, c}, b);
|
||||
ov::replace_output_update_name(c->output(0), b->output(0));
|
||||
|
||||
ASSERT_TRUE(TestAttributeCopyable::exists_in(b));
|
||||
ASSERT_TRUE(TestAttributeMergable::exists_in(b));
|
||||
ASSERT_TRUE(TestAttributeNoCopyable::exists_in(b));
|
||||
}
|
||||
|
|
@ -19,8 +19,7 @@
|
|||
|
||||
using namespace testing;
|
||||
|
||||
TEST(TransformationTests, CompressConstants_f32) {
|
||||
std::shared_ptr<ov::Model> f(nullptr), f_ref(nullptr);
|
||||
TEST_F(TransformationTestsF, CompressConstants_f32) {
|
||||
{
|
||||
auto input = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::Shape{ 1, 3, 12, 12 });
|
||||
auto const_weights = ov::opset8::Constant::create(ov::element::f32,
|
||||
|
|
@ -49,14 +48,10 @@ TEST(TransformationTests, CompressConstants_f32) {
|
|||
|
||||
auto resize = std::make_shared<ov::opset8::Interpolate>(conv, convert2, default_scales_node, axes_node, interpolate4_attr);
|
||||
|
||||
f = std::make_shared<ov::Model>(ov::NodeVector{ resize }, ov::ParameterVector{ input });
|
||||
function = std::make_shared<ov::Model>(ov::NodeVector{ resize }, ov::ParameterVector{ input });
|
||||
|
||||
ov::pass::Manager manager;
|
||||
manager.register_pass<ngraph::pass::InitNodeInfo>();
|
||||
manager.register_pass<ov::pass::MarkPrecisionSensitiveSubgraphs>();
|
||||
manager.register_pass<ov::pass::CompressFloatConstants>();
|
||||
manager.run_passes(f);
|
||||
ASSERT_NO_THROW(check_rt_info(f));
|
||||
}
|
||||
|
||||
{
|
||||
|
|
@ -88,15 +83,11 @@ TEST(TransformationTests, CompressConstants_f32) {
|
|||
|
||||
auto resize = std::make_shared<ov::opset8::Interpolate>(conv, convert2, default_scales_node, axes_node, interpolate4_attr);
|
||||
|
||||
f_ref = std::make_shared<ov::Model>(ov::NodeVector{ resize }, ov::ParameterVector{ input });
|
||||
function_ref = std::make_shared<ov::Model>(ov::NodeVector{ resize }, ov::ParameterVector{ input });
|
||||
}
|
||||
|
||||
auto res = compare_functions(f, f_ref, true);
|
||||
ASSERT_TRUE(res.first) << res.second;
|
||||
}
|
||||
|
||||
TEST(TransformationTests, CompressConstants_f64) {
|
||||
std::shared_ptr<ov::Model> f(nullptr), f_ref(nullptr);
|
||||
TEST_F(TransformationTestsF, CompressConstants_f64) {
|
||||
{
|
||||
auto input = std::make_shared<ov::opset8::Parameter>(ov::element::f64, ov::Shape{ 1, 3, 12, 12 });
|
||||
auto const_weights = ov::opset8::Constant::create(ov::element::f64,
|
||||
|
|
@ -108,14 +99,10 @@ TEST(TransformationTests, CompressConstants_f64) {
|
|||
ov::CoordinateDiff{ 0, 0 },
|
||||
ov::CoordinateDiff{ 0, 0 },
|
||||
ov::Strides{ 1, 1 });
|
||||
f = std::make_shared<ov::Model>(ov::NodeVector{ conv }, ov::ParameterVector{ input });
|
||||
function = std::make_shared<ov::Model>(ov::NodeVector{ conv }, ov::ParameterVector{ input });
|
||||
|
||||
ov::pass::Manager manager;
|
||||
manager.register_pass<ngraph::pass::InitNodeInfo>();
|
||||
manager.register_pass<ov::pass::MarkPrecisionSensitiveSubgraphs>();
|
||||
manager.register_pass<ov::pass::CompressFloatConstants>();
|
||||
manager.run_passes(f);
|
||||
ASSERT_NO_THROW(check_rt_info(f));
|
||||
}
|
||||
|
||||
{
|
||||
|
|
@ -130,9 +117,6 @@ TEST(TransformationTests, CompressConstants_f64) {
|
|||
ov::CoordinateDiff{ 0, 0 },
|
||||
ov::CoordinateDiff{ 0, 0 },
|
||||
ov::Strides{ 1, 1 });
|
||||
f_ref = std::make_shared<ov::Model>(ov::NodeVector{ conv }, ov::ParameterVector{ input });
|
||||
function_ref = std::make_shared<ov::Model>(ov::NodeVector{ conv }, ov::ParameterVector{ input });
|
||||
}
|
||||
|
||||
auto res = compare_functions(f, f_ref, true);
|
||||
ASSERT_TRUE(res.first) << res.second;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,7 +171,44 @@ TEST_F(TransformationTestsF, ConvertDivideFP16ShapeOfSubgraphNegative) {
|
|||
|
||||
function = std::make_shared<ngraph::Function>(ngraph::NodeVector{interpolate}, ngraph::ParameterVector{data});
|
||||
|
||||
ov::pass::MarkPrecisionSensitiveDivides().run_on_model(function);
|
||||
manager.register_pass<ov::pass::MarkPrecisionSensitiveDivides>();
|
||||
manager.register_pass<ngraph::pass::ConvertDivide>();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, ConvertDivideFP16ShapeOfSubgraphNegative2) {
|
||||
{
|
||||
// This test case checks that MarkPrecisionSensitiveDivides works correctly when Divide is included
|
||||
// into precision sensitive and non precision sensitive sub-graphs. So the potential problem here is
|
||||
// that MarkPrecisionSensitiveDivides could traverse graph first form "add" output so all nodes including
|
||||
// Divide will be marked as visited, but Divide and other nodes must also be visited again because of
|
||||
// precision sensitive Interpolate second input. And to handle this MarkPrecisionSensitiveDivides has
|
||||
// special visited set for precision sensitive nodes which needs to be tested as well. So in the worst case
|
||||
// we will traverse each node twice.
|
||||
auto data = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f16, ngraph::Shape{1, 3, 22, 22});
|
||||
auto gather = ngraph::op::util::node_to_get_shape_value_of_indices_from_shape_source(data, {2, 3});
|
||||
auto convert = std::make_shared<ngraph::opset1::Convert>(gather, ngraph::element::f16);
|
||||
auto divide_constant = ngraph::opset1::Constant::create(ngraph::element::f16, ngraph::Shape{1}, {0.5});
|
||||
auto divide = std::make_shared<ngraph::opset1::Divide>(convert, divide_constant);
|
||||
auto convert_after = std::make_shared<ngraph::opset1::Convert>(divide, ngraph::element::i32);
|
||||
|
||||
auto data2 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::i32, ngraph::Shape{2});
|
||||
auto add = std::make_shared<ngraph::opset1::Add>(data2, convert_after);
|
||||
|
||||
ngraph::opset1::Interpolate::Attributes interp_attr;
|
||||
interp_attr.antialias = false;
|
||||
interp_attr.axes = {2, 3};
|
||||
interp_attr.mode = "nearest";
|
||||
interp_attr.pads_begin = {0, 0, 0, 0};
|
||||
interp_attr.pads_end = {0, 0, 0, 0};
|
||||
|
||||
auto interpolate = std::make_shared<ngraph::opset1::Interpolate>(data, convert_after, interp_attr);
|
||||
|
||||
// "add" node specially set as a first output, so MarkPrecisionSensitiveDivides will start graph traversal from it
|
||||
// and after all nodes above are visited it will start traverse from "interpolate"
|
||||
function = std::make_shared<ngraph::Function>(ngraph::NodeVector{add, interpolate}, ngraph::ParameterVector{data, data2});
|
||||
|
||||
manager.register_pass<ov::pass::MarkPrecisionSensitiveDivides>();
|
||||
manager.register_pass<ngraph::pass::ConvertDivide>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue