Remove legacy TransposeSinking transformation (#16731)
* delete TransposeSinkingOVTF transformation * delete include from tf_lite frontend
This commit is contained in:
parent
c034975183
commit
093990118d
@ -20,7 +20,6 @@
|
||||
#include "openvino/pass/manager.hpp"
|
||||
#include "openvino/util/common_util.hpp"
|
||||
#include "openvino/util/log.hpp"
|
||||
#include "pass/transpose_sinking.hpp"
|
||||
#include "so_extension.hpp"
|
||||
#include "tf_framework_node.hpp"
|
||||
#include "transformations/common_optimizations/reverse_shape_and_type_infer.hpp"
|
||||
|
@ -1,24 +0,0 @@
|
||||
// Copyright (C) 2018-2023 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "openvino/pass/pass.hpp"
|
||||
|
||||
namespace ov {
|
||||
namespace frontend {
|
||||
namespace tensorflow {
|
||||
namespace pass {
|
||||
|
||||
class TransposeSinking : public ov::pass::ModelPass {
|
||||
public:
|
||||
OPENVINO_RTTI("ov::frontend::tensorflow::pass::TransposeSinking");
|
||||
TransposeSinking() = default;
|
||||
bool run_on_model(const std::shared_ptr<ov::Model>& function) override;
|
||||
};
|
||||
|
||||
} // namespace pass
|
||||
} // namespace tensorflow
|
||||
} // namespace frontend
|
||||
} // namespace ov
|
@ -1,518 +0,0 @@
|
||||
// Copyright (C) 2018-2023 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include "pass/transpose_sinking.hpp"
|
||||
|
||||
#include "openvino/op/util/op_types.hpp"
|
||||
#include "openvino/opsets/opset8.hpp"
|
||||
#include "openvino/pass/pattern/op/label.hpp"
|
||||
#include "openvino/util/common_util.hpp"
|
||||
#include "openvino/util/log.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace ov;
|
||||
using namespace ov::frontend::tensorflow;
|
||||
using namespace opset8;
|
||||
|
||||
using TransposeMap = unordered_map<string, shared_ptr<Transpose>>;
|
||||
|
||||
template <class T>
|
||||
static T apply_permutation(const T& input, AxisVector order) {
|
||||
T output(input.size());
|
||||
for (size_t i = 0; i < order.size(); i++) {
|
||||
output[i] = input.at(order.at(i));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
static AxisVector permutation_to_default_order(const AxisVector& axis_order) {
|
||||
AxisVector out(axis_order.size());
|
||||
for (size_t i = 0; i < axis_order.size(); i++) {
|
||||
out.at(axis_order[i]) = i;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static AxisVector get_default_order(size_t rank) {
|
||||
AxisVector default_order(rank);
|
||||
std::iota(begin(default_order), end(default_order), 0);
|
||||
return default_order;
|
||||
}
|
||||
|
||||
static size_t get_static_rank(const Output<Node>& output) {
|
||||
auto rank = output.get_partial_shape().rank();
|
||||
OPENVINO_ASSERT(rank.is_static(), "Dynamic rank is not supported in TransposeSinking transformation.");
|
||||
return rank.get_length();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static string describe(shared_ptr<Node> node) {
|
||||
// ensure that it's either a reshape or a transpose
|
||||
// TODO: use static_assert
|
||||
if (!(std::is_base_of<Reshape, T>::value || std::is_base_of<Transpose, T>::value)) {
|
||||
throw runtime_error("describe template specialization has to be either reshape or "
|
||||
"transpose");
|
||||
}
|
||||
stringstream ss;
|
||||
auto transpose = as_type_ptr<T>(node);
|
||||
auto const1 = as_type_ptr<Constant>(transpose->get_input_node_shared_ptr(1));
|
||||
if (transpose) {
|
||||
ss << "transpose name: " << transpose->get_name();
|
||||
ss << " , input = " << transpose->input_value(0).get_node()->get_name();
|
||||
if (transpose->output(0).get_partial_shape().is_static()) {
|
||||
ss << " , shape = " << ov::util::vector_to_string(transpose->output(0).get_shape());
|
||||
}
|
||||
if (const1) {
|
||||
ss << " , axis order = " << ov::util::vector_to_string(const1->get_axis_vector_val());
|
||||
} else {
|
||||
ss << " , axis order = (unknown, not constant values)";
|
||||
}
|
||||
} else {
|
||||
ss << "Node can not be cast to Transpose/Reshape operations.";
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
static shared_ptr<Reshape> make_reshape(const Output<Node>& arg, const AxisVector& input_order) {
|
||||
auto order = std::make_shared<Constant>(element::i64, Shape{input_order.size()}, input_order);
|
||||
auto transpose = make_shared<Reshape>(arg, order, false);
|
||||
OPENVINO_DEBUG << "Make Reshape " << describe<Reshape>(transpose);
|
||||
return transpose;
|
||||
}
|
||||
|
||||
static void write_transposemap(TransposeMap& reorders,
|
||||
const Output<Node>& target,
|
||||
const shared_ptr<Transpose>& transpose) {
|
||||
auto name = target.get_node()->get_name() + "." + to_string(target.get_index());
|
||||
OPENVINO_DEBUG << "Write TransposeMap[" << name << "] = " << describe<Transpose>(transpose);
|
||||
reorders[name] = transpose;
|
||||
}
|
||||
|
||||
static shared_ptr<Transpose> read_transposemap(TransposeMap& reorders, const Output<Node>& target) {
|
||||
auto name = target.get_node()->get_name() + "." + to_string(target.get_index());
|
||||
auto transpose = reorders.at(name);
|
||||
OPENVINO_DEBUG << "Read TransposeMap[" << name << "] -> " << describe<Transpose>(transpose);
|
||||
return transpose;
|
||||
}
|
||||
|
||||
static shared_ptr<Transpose> combine_transposes(const shared_ptr<Transpose>& t1, const shared_ptr<Transpose>& t2) {
|
||||
auto default_order = get_default_order(get_static_rank(t1));
|
||||
auto t1_const = as_type_ptr<Constant>(t1->input_value(1).get_node_shared_ptr());
|
||||
auto t2_const = as_type_ptr<Constant>(t2->input_value(1).get_node_shared_ptr());
|
||||
|
||||
if (t1_const && t2_const) {
|
||||
auto perm_t1 = apply_permutation(default_order, t1_const->get_axis_vector_val());
|
||||
auto perm_t2 = apply_permutation(perm_t1, t2_const->get_axis_vector_val());
|
||||
|
||||
auto combined = make_transpose(t2->input_value(0), perm_t2);
|
||||
OPENVINO_DEBUG << "Combining " << describe<Transpose>(t1) << " and " << describe<Transpose>(t2) << " into "
|
||||
<< describe<Transpose>(combined);
|
||||
return combined;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static void insert_transpose(const shared_ptr<Node>& target, const shared_ptr<Node>& transpose, size_t input_index) {
|
||||
OPENVINO_DEBUG << "Inserting transpose at input " << target->get_name() << " input index " << input_index;
|
||||
auto arg = target->input(input_index).get_source_output();
|
||||
if (arg.get_partial_shape().is_static()) {
|
||||
OPENVINO_DEBUG << "Arg shape: " << arg.get_shape();
|
||||
}
|
||||
auto new_order = as_type_ptr<Constant>(transpose->input_value(1).get_node_shared_ptr());
|
||||
auto new_transpose = make_transpose(arg.get_node_shared_ptr(), new_order->get_axis_vector_val());
|
||||
OPENVINO_DEBUG << "Inserting transpose " << describe<Transpose>(new_transpose) << " at input " << target->get_name()
|
||||
<< " input index " << input_index;
|
||||
|
||||
target->input(input_index).replace_source_output(new_transpose->output(0));
|
||||
if (std::dynamic_pointer_cast<Result>(target)) {
|
||||
new_transpose->output(0).add_names(arg.get_names());
|
||||
arg.set_names({});
|
||||
}
|
||||
}
|
||||
|
||||
static void delete_transpose(const shared_ptr<Node>& transpose) {
|
||||
OPENVINO_DEBUG << "Removing transpose " << transpose->get_name();
|
||||
if (!transpose->get_users().empty()) {
|
||||
Output<Node> output = transpose->output(0);
|
||||
OPENVINO_DEBUG << "output " << output.get_node_shared_ptr()->get_name();
|
||||
OPENVINO_DEBUG << "target input size " << output.get_target_inputs().size();
|
||||
output.replace(transpose->input_value(0));
|
||||
}
|
||||
}
|
||||
|
||||
static void mark_transpose_for_deletion(const shared_ptr<Node>& transpose,
|
||||
set<shared_ptr<Node>>& transposes_to_delete) {
|
||||
OPENVINO_DEBUG << "Marking transpose " << transpose->get_name() << " for deletion";
|
||||
transposes_to_delete.insert(transpose);
|
||||
}
|
||||
|
||||
static shared_ptr<Transpose> create_default_transpose(const Output<Node>& n) {
|
||||
auto default_order = get_default_order(get_static_rank(n));
|
||||
auto order = std::make_shared<Constant>(element::i64, Shape{default_order.size()}, default_order);
|
||||
return make_shared<Transpose>(n, order);
|
||||
}
|
||||
|
||||
// convert_binary_to_default_order is used when one of the arguments
|
||||
// of a binary op isn't in the default format (i.e. nhwc instead of nchw)
|
||||
// We normalize the "left" argument to match the order of the "right" argument
|
||||
// by either inserting a transpose or a reshape, depending on the shape of the
|
||||
// "left" argument.
|
||||
static void convert_binary_to_default_order(const shared_ptr<Node>& binary,
|
||||
const Input<Node>& input,
|
||||
const Output<Node>& right,
|
||||
TransposeMap& reorders,
|
||||
set<shared_ptr<Node>>& transposes_to_delete) {
|
||||
auto left = input.get_source_output();
|
||||
auto right_t = read_transposemap(reorders, right);
|
||||
auto right_const = as_type_ptr<Constant>(right_t->input_value(1).get_node_shared_ptr());
|
||||
auto perm_to_def = permutation_to_default_order(right_const->get_axis_vector_val());
|
||||
|
||||
// if right input is being implicitly broadcasted, insert a reshape
|
||||
// instead of a transpose
|
||||
shared_ptr<Node> new_node;
|
||||
auto left_rank = get_static_rank(left);
|
||||
if (left_rank < perm_to_def.size() && left.get_partial_shape().is_static()) {
|
||||
auto left_shape = left.get_shape();
|
||||
left_shape.insert(left_shape.begin(), perm_to_def.size() - left_rank, 1);
|
||||
|
||||
auto new_shape = apply_permutation(left_shape, perm_to_def);
|
||||
new_node = make_reshape(left, new_shape);
|
||||
} else if (left_rank == perm_to_def.size()) {
|
||||
new_node = make_transpose(left, perm_to_def);
|
||||
} else {
|
||||
throw runtime_error("case not supported when converting binary to default order");
|
||||
}
|
||||
input.replace_source_output(new_node->output(0));
|
||||
|
||||
if (right.get_partial_shape().is_static()) {
|
||||
OPENVINO_DEBUG << "right = " << ov::util::vector_to_string(right.get_shape()) << ", "
|
||||
<< right.get_node_shared_ptr()->get_name();
|
||||
} else {
|
||||
OPENVINO_DEBUG << "right = "
|
||||
<< "dynamic shape, " << right.get_node_shared_ptr()->get_name();
|
||||
}
|
||||
// this should now insert transpose on right
|
||||
mark_transpose_for_deletion(right_t, transposes_to_delete);
|
||||
write_transposemap(reorders, binary, right_t);
|
||||
}
|
||||
|
||||
static void materialize_shapes(const shared_ptr<Node>& n,
|
||||
TransposeMap& reorders,
|
||||
set<shared_ptr<Node>>& transposes_to_delete) {
|
||||
// For each node, create a default transpose for
|
||||
// each of the outputs and store in the map
|
||||
for (auto& it : n->outputs()) {
|
||||
write_transposemap(reorders, it, create_default_transpose(it));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < n->input_values().size(); i++) {
|
||||
// materialize all pending transposes, flush pending transposes
|
||||
auto arg = n->input_value(i);
|
||||
auto arg_transpose = read_transposemap(reorders, arg);
|
||||
OPENVINO_DEBUG << "Materializing " << describe<Transpose>(arg_transpose) << " for "
|
||||
<< arg.get_node_shared_ptr()->get_name();
|
||||
mark_transpose_for_deletion(arg_transpose, transposes_to_delete);
|
||||
auto arg_transpose_order = as_type_ptr<Constant>(arg_transpose->input_value(1).get_node_shared_ptr());
|
||||
if (arg_transpose_order &&
|
||||
arg_transpose_order->get_axis_vector_val() != get_default_order(get_static_rank(arg))) {
|
||||
// Insert if arg needs to be transposed.
|
||||
insert_transpose(n, arg_transpose, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool sink_transpose(const shared_ptr<Transpose>& transpose,
|
||||
TransposeMap& reorders,
|
||||
set<shared_ptr<Node>>& transposes_to_delete) {
|
||||
OPENVINO_DEBUG << "Sinking Transpose :" << describe<Transpose>(transpose);
|
||||
auto transpose_in = transpose->input_value(0);
|
||||
auto orig_transpose = read_transposemap(reorders, transpose_in);
|
||||
// combine both transposes
|
||||
auto new_transpose = combine_transposes(orig_transpose, transpose);
|
||||
if (new_transpose) {
|
||||
// remove original transpose now it's combined with a new one
|
||||
// should be safe to remove an already detached node
|
||||
mark_transpose_for_deletion(orig_transpose, transposes_to_delete);
|
||||
// replace transpose with combined one
|
||||
replace_node(transpose, new_transpose);
|
||||
mark_transpose_for_deletion(new_transpose, transposes_to_delete);
|
||||
write_transposemap(reorders, new_transpose, new_transpose);
|
||||
} else {
|
||||
// combine_transposes failed
|
||||
// transpose remains in the graph
|
||||
OPENVINO_DEBUG << "CombineTranspose has failed. Writing original transpose to the transpose map.";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sink_unary(const shared_ptr<Node>& n,
|
||||
TransposeMap& reorders,
|
||||
set<shared_ptr<Node>>& /* transposes_to_delete */) {
|
||||
auto arg_transpose = read_transposemap(reorders, n->input_value(0));
|
||||
OPENVINO_DEBUG << "Propagating " << describe<Transpose>(arg_transpose) << " for " << n->get_name();
|
||||
write_transposemap(reorders, n, arg_transpose);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sink_binary(const shared_ptr<Node>& binary,
|
||||
TransposeMap& reorders,
|
||||
set<shared_ptr<Node>>& transposes_to_delete) {
|
||||
auto left = binary->input_value(0);
|
||||
auto right = binary->input_value(1);
|
||||
auto left_t = read_transposemap(reorders, left);
|
||||
auto right_t = read_transposemap(reorders, right);
|
||||
auto left_const = as_type_ptr<Constant>(left_t->input_value(1).get_node_shared_ptr());
|
||||
auto right_const = as_type_ptr<Constant>(right_t->input_value(1).get_node_shared_ptr());
|
||||
if (!(left_const && right_const)) {
|
||||
OPENVINO_DEBUG << "TransposeSinking failed for binary op " << binary->get_name()
|
||||
<< "2nd inputs to Transposes must be constants.";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto left_order = left_const->get_axis_vector_val();
|
||||
auto right_order = right_const->get_axis_vector_val();
|
||||
|
||||
auto left_rank = get_static_rank(left);
|
||||
auto right_rank = get_static_rank(right);
|
||||
auto left_mismatch = left_order != get_default_order(left_rank);
|
||||
auto right_mismatch = right_order != get_default_order(right_rank);
|
||||
|
||||
OPENVINO_DEBUG << "Sink binary " << binary->get_name()
|
||||
<< " left transpose: " << ov::util::vector_to_string(left_order)
|
||||
<< " left default: " << ov::util::vector_to_string(get_default_order(left_rank))
|
||||
<< " right transpose: " << ov::util::vector_to_string(right_order)
|
||||
<< " right default: " << ov::util::vector_to_string(get_default_order(right_rank));
|
||||
|
||||
if ((left_order.size() == right_order.size() && left_order == right_order) || (!left_mismatch && !right_mismatch)) {
|
||||
// Propagate the reshape which matches the shape of the binary node
|
||||
auto new_transpose = (binary->get_output_shape(0).size() == left.get_shape().size()) ? left_t : right_t;
|
||||
OPENVINO_DEBUG << "Propagating " << describe<Transpose>(new_transpose) << " for " << binary->get_name();
|
||||
write_transposemap(reorders, binary, new_transpose);
|
||||
// at this point, both transposes will be eventually removed
|
||||
mark_transpose_for_deletion(left_t, transposes_to_delete);
|
||||
mark_transpose_for_deletion(right_t, transposes_to_delete);
|
||||
} else {
|
||||
try {
|
||||
if (right_mismatch) {
|
||||
convert_binary_to_default_order(binary, binary->input(0), right, reorders, transposes_to_delete);
|
||||
} else {
|
||||
if (left_mismatch) {
|
||||
convert_binary_to_default_order(binary, binary->input(1), left, reorders, transposes_to_delete);
|
||||
}
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sink_pad(shared_ptr<Pad> n, TransposeMap& reorders, set<shared_ptr<Node>>& /* transposes_to_delete */) {
|
||||
auto n_in = n->input_value(0);
|
||||
auto arg_transpose = read_transposemap(reorders, n_in);
|
||||
describe<Transpose>(arg_transpose);
|
||||
if (arg_transpose->get_output_partial_shape(0).is_static()) {
|
||||
auto arg_transpose_order = as_type_ptr<Constant>(arg_transpose->input_value(1).get_node_shared_ptr());
|
||||
auto order = arg_transpose_order->get_axis_vector_val();
|
||||
// we need the correct input shape to produce the right output shape
|
||||
// we are going to create a label of the right input shape,
|
||||
// so a new pad will have the right shape
|
||||
auto def_order = permutation_to_default_order(order);
|
||||
|
||||
auto input_shape = apply_permutation(arg_transpose->get_shape(), def_order);
|
||||
|
||||
auto dummy_correct_shape =
|
||||
make_shared<ov::pass::pattern::op::Label>(arg_transpose->get_element_type(), input_shape);
|
||||
|
||||
auto pad_begin = apply_permutation(n->get_pads_begin(), def_order);
|
||||
auto pad_end = apply_permutation(n->get_pads_end(), def_order);
|
||||
|
||||
auto new_begin = make_shared<Constant>(element::i64, Shape{pad_begin.size()}, pad_begin);
|
||||
auto new_end = make_shared<Constant>(element::i64, Shape{pad_end.size()}, pad_end);
|
||||
auto new_pad = make_shared<Pad>(dummy_correct_shape, new_begin, new_end, n->input_value(3), n->get_pad_mode());
|
||||
replace_node(dummy_correct_shape, n->input_value(0).get_node_shared_ptr());
|
||||
OPENVINO_DEBUG << "Replacing " << n->get_name() << " with " << new_pad->get_name();
|
||||
replace_node(n, new_pad);
|
||||
auto new_transpose = make_transpose(new_pad, order);
|
||||
OPENVINO_DEBUG << "Propagating " << describe<Transpose>(new_transpose) << " for " << n->get_name();
|
||||
write_transposemap(reorders, new_pad, new_transpose);
|
||||
} else {
|
||||
OPENVINO_DEBUG << "TransposeSinking failed for Pad op " << n->get_name()
|
||||
<< " . Output shape of Transpose op must be static.";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sink_concat(const shared_ptr<Concat>& n,
|
||||
TransposeMap& reorders,
|
||||
set<shared_ptr<Node>>& transposes_to_delete) {
|
||||
auto n_in = n->input_value(0);
|
||||
auto arg_transpose = read_transposemap(reorders, n_in);
|
||||
if (arg_transpose->get_output_partial_shape(0).is_static()) {
|
||||
auto arg_transpose_order = as_type_ptr<Constant>(arg_transpose->input_value(1).get_node_shared_ptr());
|
||||
auto order = arg_transpose_order->get_axis_vector_val();
|
||||
// we need the correct input shape to produce the right output shape
|
||||
// we are going to create a label of the right input shape,
|
||||
// so a new concat will have the right shape
|
||||
auto def_order = permutation_to_default_order(order);
|
||||
|
||||
auto input_shape = apply_permutation(arg_transpose->get_shape(), def_order);
|
||||
|
||||
auto dummy_correct_shape =
|
||||
make_shared<ov::pass::pattern::op::Label>(arg_transpose->get_element_type(), input_shape);
|
||||
|
||||
NodeVector new_args;
|
||||
new_args.push_back(dummy_correct_shape);
|
||||
|
||||
for (size_t i = 1; i < n->get_input_size(); i++) {
|
||||
auto iarg = n->input_value(i);
|
||||
auto iarg_transpose = read_transposemap(reorders, iarg);
|
||||
auto iarg_transpose_order = as_type_ptr<Constant>(iarg_transpose->input_value(1).get_node_shared_ptr());
|
||||
auto iorder = iarg_transpose_order->get_axis_vector_val();
|
||||
if (iorder != order) {
|
||||
OPENVINO_DEBUG << " input order at " << i << "-th arg is different from first arg";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (iarg_transpose->get_output_partial_shape(0).is_dynamic()) {
|
||||
OPENVINO_DEBUG << "TransposeSinking failed for Concat op " << n->get_name()
|
||||
<< " . Input Transpose ops"
|
||||
" must have static shapes. ";
|
||||
return false;
|
||||
}
|
||||
auto iinput_shape = apply_permutation(iarg_transpose->get_shape(), def_order);
|
||||
|
||||
auto idummy_correct_shape =
|
||||
make_shared<ov::pass::pattern::op::Label>(iarg_transpose->get_element_type(), iinput_shape);
|
||||
new_args.push_back(idummy_correct_shape);
|
||||
}
|
||||
|
||||
auto new_axis = order.at(n->get_concatenation_axis());
|
||||
auto new_concat = make_shared<Concat>(new_args, new_axis);
|
||||
// put back the original arguments
|
||||
for (size_t i = 0; i < new_concat->get_input_size(); i++) {
|
||||
OPENVINO_DEBUG << "Replacing " << new_concat->get_name() << " input " << i << " with " << n->get_name()
|
||||
<< " input " << i;
|
||||
new_concat->input(i).replace_source_output(n->input_value(i));
|
||||
}
|
||||
OPENVINO_DEBUG << "Replacing " << n->get_name() << " with " << new_concat->get_name();
|
||||
replace_node(n, new_concat);
|
||||
auto new_transpose = make_transpose(new_concat, order);
|
||||
OPENVINO_DEBUG << "Propagating " << describe<Transpose>(new_transpose) << " for " << n->get_name();
|
||||
write_transposemap(reorders, new_concat, new_transpose);
|
||||
} else {
|
||||
OPENVINO_DEBUG << "TransposeSinking failed for Concat op " << n->get_name()
|
||||
<< " . Output shape of Transpose op must be static.";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sink_prelu(const shared_ptr<PRelu>& prelu,
|
||||
TransposeMap& reorders,
|
||||
set<shared_ptr<Node>>& transposes_to_delete) {
|
||||
FRONT_END_GENERAL_CHECK(prelu, "Null pointer is given to PRelu node.");
|
||||
FRONT_END_GENERAL_CHECK(prelu->get_input_size() > 1, "The PRelu node must contain at least two inputs.");
|
||||
auto slope_shape = prelu->input_value(1).get_partial_shape();
|
||||
if (slope_shape.is_static() && shape_size(slope_shape.to_shape()) == 1) {
|
||||
// handle a case covering LeakyRelu decomposition
|
||||
auto arg_transpose = read_transposemap(reorders, prelu->input_value(0));
|
||||
OPENVINO_DEBUG << "Propagating " << describe<Transpose>(arg_transpose) << " for " << prelu->get_name();
|
||||
write_transposemap(reorders, prelu, arg_transpose);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void purge_transposes(const set<shared_ptr<Node>>& transposes_to_delete) {
|
||||
for (const auto& r : transposes_to_delete) {
|
||||
delete_transpose(r);
|
||||
}
|
||||
}
|
||||
|
||||
// The goal of TransposeSinking is to remove
|
||||
// round-trip transposes(i.e. nhwc->nchw(nchw-only-op)->nhwc)
|
||||
// around nchw-only-op (e.g.Convolution, Batchnorm, Avg/MaxPool)
|
||||
// This is achieved by both **sinking**, propagating transposes
|
||||
// through ops towards op::Results,
|
||||
// or **swimming** Transposes up towards op::Parameter
|
||||
// For each op type we support we can either combine
|
||||
// two transposes by replacing the existing Transpose,
|
||||
// materialize pending transposes if they can't be propagated through op
|
||||
bool ov::frontend::tensorflow::pass::TransposeSinking::run_on_model(const shared_ptr<Model>& f) {
|
||||
TransposeMap reorders;
|
||||
set<shared_ptr<Node>> transposes_to_delete;
|
||||
unordered_map<std::string, PartialShape> orig_result_out_shape;
|
||||
|
||||
// STEP 1 : Sink or Swim transposes away for op clusters
|
||||
try {
|
||||
for (const auto& n : f->get_ordered_ops()) {
|
||||
OPENVINO_DEBUG << "Processing " << n->get_name();
|
||||
// collect output shape of all Result nodes for a sanity check
|
||||
if (ov::op::util::is_output(n)) {
|
||||
orig_result_out_shape[n->get_name()] = n->get_output_partial_shape(0);
|
||||
}
|
||||
|
||||
bool sink_res = false;
|
||||
if (auto transpose = as_type_ptr<opset8::Transpose>(n)) {
|
||||
sink_res = sink_transpose(transpose, reorders, transposes_to_delete);
|
||||
} else if (ov::op::util::is_unary_elementwise_arithmetic(n) || as_type_ptr<Clamp>(n) ||
|
||||
as_type_ptr<Elu>(n) || as_type_ptr<SoftPlus>(n) || as_type_ptr<LogicalNot>(n)) {
|
||||
// Some unary operations are inherrited from Op class
|
||||
// so we need explicitly to check them
|
||||
sink_res = sink_unary(n, reorders, transposes_to_delete);
|
||||
} else if (ov::op::util::is_binary_elementwise_arithmetic(n)) {
|
||||
sink_res = sink_binary(n, reorders, transposes_to_delete);
|
||||
} else if (auto pad = as_type_ptr<Pad>(n)) {
|
||||
sink_res = sink_pad(pad, reorders, transposes_to_delete);
|
||||
} else if (auto concat = as_type_ptr<Concat>(n)) {
|
||||
sink_res = sink_concat(concat, reorders, transposes_to_delete);
|
||||
} else if (auto prelu = as_type_ptr<PRelu>(n)) {
|
||||
sink_res = sink_prelu(prelu, reorders, transposes_to_delete);
|
||||
}
|
||||
|
||||
if (!sink_res) {
|
||||
materialize_shapes(n, reorders, transposes_to_delete);
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
OPENVINO_DEBUG << "Caught exception while sinking op";
|
||||
purge_transposes(transposes_to_delete);
|
||||
return false;
|
||||
}
|
||||
|
||||
// STEP 2: purge all the transposes we either sunk or swam.
|
||||
OPENVINO_DEBUG << "Purging transposes ";
|
||||
purge_transposes(transposes_to_delete);
|
||||
|
||||
// STEP 3: fix wrong shape info wholesale
|
||||
OPENVINO_DEBUG << "Fixing wrong shape info for the whole graph";
|
||||
for (const auto& n : f->get_ordered_ops()) {
|
||||
n->revalidate_and_infer_types();
|
||||
}
|
||||
|
||||
const ResultVector& results = f->get_results();
|
||||
for (const auto& r : results) {
|
||||
// make sure shapes are always materialized before results
|
||||
FRONT_END_GENERAL_CHECK(r->get_output_partial_shape(0) == r->get_input_partial_shape(0) &&
|
||||
r->get_element_type() == r->input_value(0).get_element_type(),
|
||||
" op::Result = ",
|
||||
*r,
|
||||
", Arg = ",
|
||||
r->input_value(0).get_node());
|
||||
|
||||
// make sure that after TransposeSinking pass the output_shape for Result
|
||||
// does not change from the expected output_shape before the pass
|
||||
FRONT_END_GENERAL_CHECK(r->get_output_partial_shape(0) == orig_result_out_shape[r->get_name()],
|
||||
" op::Result = ",
|
||||
*r,
|
||||
" expected output shape = ",
|
||||
orig_result_out_shape[r->get_name()]);
|
||||
}
|
||||
return true;
|
||||
}
|
@ -1,645 +0,0 @@
|
||||
// Copyright (C) 2018-2023 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include "pass/transpose_sinking.hpp"
|
||||
|
||||
#include <frontend/shared/include/utils.hpp>
|
||||
#include <openvino/frontend/manager.hpp>
|
||||
#include <openvino/opsets/opset7.hpp>
|
||||
#include <openvino/opsets/opset8.hpp>
|
||||
#include <openvino/pass/manager.hpp>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace ov;
|
||||
using namespace opset8;
|
||||
using namespace frontend::tensorflow::pass;
|
||||
|
||||
template <class T>
|
||||
int64_t count_ops_of_type(const shared_ptr<Model>& f) {
|
||||
int64_t cnt = 0;
|
||||
for (const auto& op : f->get_ops()) {
|
||||
cnt += dynamic_pointer_cast<T>(op) != nullptr;
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
TEST(TransposeSinkingTest, DynamicShape) {
|
||||
ov::PartialShape shape_nhwc(vector<Dimension>(4, Dimension::dynamic()));
|
||||
auto a = make_shared<Parameter>(ngraph::element::i32, shape_nhwc);
|
||||
auto ng_order = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 3, 1, 2});
|
||||
auto transpose = make_shared<Transpose>(a, ng_order);
|
||||
auto absn = make_shared<Abs>(transpose);
|
||||
auto absn2 = make_shared<Abs>(absn);
|
||||
absn2->output(0).set_names({"out_name"});
|
||||
auto res = make_shared<Result>(absn2);
|
||||
auto func = make_shared<ngraph::Function>(ngraph::OutputVector{res}, ngraph::ParameterVector{a});
|
||||
|
||||
ov::pass::Manager pass_manager;
|
||||
pass_manager.register_pass<TransposeSinking>();
|
||||
pass_manager.run_passes(func);
|
||||
|
||||
auto new_transpose =
|
||||
ngraph::as_type_ptr<Transpose>(func->get_results().at(0)->input_value(0).get_node_shared_ptr());
|
||||
ASSERT_TRUE(new_transpose);
|
||||
EXPECT_EQ(new_transpose->output(0).get_names(), std::unordered_set<std::string>({"out_name"}));
|
||||
}
|
||||
|
||||
TEST(TransposeSinkingTest, TensorNames) {
|
||||
ngraph::Shape shape_nhwc{16, 28, 28, 1};
|
||||
auto a = make_shared<Parameter>(ngraph::element::i32, shape_nhwc);
|
||||
auto ng_order = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 3, 1, 2});
|
||||
auto transpose = make_shared<Transpose>(a, ng_order);
|
||||
auto absn = make_shared<Abs>(transpose);
|
||||
auto absn2 = make_shared<Abs>(absn);
|
||||
absn2->output(0).set_names({"out_name"});
|
||||
auto res = make_shared<Result>(absn2);
|
||||
auto func = make_shared<ngraph::Function>(ngraph::OutputVector{res}, ngraph::ParameterVector{a});
|
||||
|
||||
ov::pass::Manager pass_manager;
|
||||
pass_manager.register_pass<TransposeSinking>();
|
||||
pass_manager.run_passes(func);
|
||||
|
||||
auto new_transpose =
|
||||
ngraph::as_type_ptr<Transpose>(func->get_results().at(0)->input_value(0).get_node_shared_ptr());
|
||||
ASSERT_TRUE(new_transpose);
|
||||
EXPECT_EQ(new_transpose->output(0).get_names(), std::unordered_set<std::string>({"out_name"}));
|
||||
}
|
||||
|
||||
TEST(TransposeSinkingTest, TensorNamesCombineTransposes) {
|
||||
ngraph::Shape shape_nhwc{16, 28, 28, 1};
|
||||
auto a = make_shared<Parameter>(ngraph::element::i32, shape_nhwc);
|
||||
auto ng_order = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 3, 1, 2});
|
||||
auto transpose_1 = make_shared<Transpose>(a, ng_order);
|
||||
auto transpose_2 = make_shared<Transpose>(transpose_1, ng_order);
|
||||
transpose_2->output(0).set_names({"out_name"});
|
||||
auto res = make_shared<Result>(transpose_2);
|
||||
auto func = make_shared<ngraph::Function>(ngraph::OutputVector{res}, ngraph::ParameterVector{a});
|
||||
|
||||
ov::pass::Manager pass_manager;
|
||||
pass_manager.register_pass<TransposeSinking>();
|
||||
pass_manager.run_passes(func);
|
||||
|
||||
auto new_transpose =
|
||||
ngraph::as_type_ptr<Transpose>(func->get_results().at(0)->input_value(0).get_node_shared_ptr());
|
||||
ASSERT_TRUE(new_transpose);
|
||||
EXPECT_EQ(new_transpose->output(0).get_names(), std::unordered_set<std::string>({"out_name"}));
|
||||
size_t transpose_cnt = count_ops_of_type<Transpose>(func);
|
||||
EXPECT_EQ(transpose_cnt, 1);
|
||||
}
|
||||
|
||||
TEST(TransposeSinkingTest, EdgeSplitting) {
|
||||
// checks if Transpose is pushed through Abs, but stopped by
|
||||
// ReduceSum
|
||||
ngraph::Shape shape_nhwc{16, 28, 28, 1};
|
||||
ngraph::Shape shape_nchw{16, 1, 28, 28};
|
||||
|
||||
auto a = make_shared<Parameter>(ngraph::element::i32, shape_nhwc);
|
||||
auto ng_order = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 3, 1, 2});
|
||||
auto transpose = make_shared<Transpose>(a, ng_order);
|
||||
auto absn = make_shared<Abs>(transpose);
|
||||
auto absn2 = make_shared<Abs>(absn);
|
||||
|
||||
auto axes = make_shared<Constant>(ngraph::element::i64, ngraph::Shape{4}, vector<int64_t>{0, 1, 2, 3});
|
||||
auto sum = make_shared<ReduceSum>(transpose, axes, true);
|
||||
|
||||
auto func = make_shared<ngraph::Function>(ngraph::OutputVector{absn2, sum}, ngraph::ParameterVector{a});
|
||||
size_t before_count = count_ops_of_type<Transpose>(func);
|
||||
|
||||
ov::pass::Manager pass_manager;
|
||||
pass_manager.register_pass<TransposeSinking>();
|
||||
pass_manager.run_passes(func);
|
||||
|
||||
ASSERT_EQ(before_count, 1);
|
||||
size_t after_count = count_ops_of_type<Transpose>(func);
|
||||
ASSERT_EQ(after_count, 2);
|
||||
ASSERT_EQ(func->get_results().at(1)->input_value(0), sum);
|
||||
auto new_transpose =
|
||||
ngraph::as_type_ptr<Transpose>(func->get_results().at(0)->input_value(0).get_node_shared_ptr());
|
||||
ASSERT_TRUE(new_transpose);
|
||||
ASSERT_EQ(new_transpose->get_output_shape(0), shape_nchw);
|
||||
}
|
||||
|
||||
// X (NHWC)
|
||||
// |
|
||||
// Transpose
|
||||
// |
|
||||
// AvgPool (NCHW)
|
||||
// |
|
||||
// Transpose
|
||||
// | Const (NHWC)
|
||||
// | /
|
||||
// | /
|
||||
// | /
|
||||
// Add (NHWC)
|
||||
// |
|
||||
// Result
|
||||
TEST(TransposeSinkingTest, PoolAdd1) {
|
||||
ngraph::Shape input_shape{1, 3, 3, 1}; // NHWC (N=1, H=3, W=3, C=1)
|
||||
|
||||
auto input_type = ngraph::element::f32;
|
||||
|
||||
auto X = make_shared<Parameter>(input_type, input_shape); // NHWC
|
||||
|
||||
auto ng_order1 = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 3, 1, 2});
|
||||
auto transpose1 = make_shared<Transpose>(X, ng_order1); // NCHW (1,1,3,3)
|
||||
|
||||
auto avgpool = make_shared<AvgPool>(transpose1,
|
||||
ngraph::Strides{1, 1},
|
||||
ngraph::Shape{0, 0},
|
||||
ngraph::Shape{0, 0},
|
||||
ngraph::Shape{1, 1},
|
||||
true,
|
||||
ngraph::op::RoundingType::FLOOR,
|
||||
ngraph::op::PadType::VALID);
|
||||
|
||||
auto ng_order2 = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 2, 3, 1});
|
||||
auto transpose2 = make_shared<Transpose>(avgpool, ng_order2); // NHWC (1,3,3,1)
|
||||
|
||||
auto const1 = Constant::create(input_type, ngraph::Shape{1, 3, 3, 1}, {3}); // NHWC (1,3,3,1)
|
||||
auto add1 = make_shared<Add>(transpose2, const1);
|
||||
auto func = make_shared<ngraph::Function>(add1, ngraph::ParameterVector{X});
|
||||
|
||||
ov::pass::Manager pass_manager;
|
||||
size_t before_count = count_ops_of_type<Transpose>(func);
|
||||
pass_manager.register_pass<TransposeSinking>();
|
||||
pass_manager.run_passes(func);
|
||||
|
||||
size_t after_count = count_ops_of_type<Transpose>(func);
|
||||
ASSERT_LE(before_count, after_count);
|
||||
ASSERT_EQ(3, after_count);
|
||||
auto new_transpose =
|
||||
ngraph::as_type_ptr<Transpose>(func->get_results().at(0)->input_value(0).get_node_shared_ptr());
|
||||
ASSERT_TRUE(new_transpose);
|
||||
ASSERT_EQ(new_transpose->get_output_shape(0), (ngraph::Shape{1, 3, 3, 1}));
|
||||
}
|
||||
|
||||
TEST(TransposeSinkingTest, PoolAdd2) {
|
||||
ngraph::Shape input_shape{1, 3, 3, 1}; // NHWC (N=1, H=3, W=3, C=1)
|
||||
|
||||
auto input_type = ngraph::element::f32;
|
||||
|
||||
auto X = make_shared<Parameter>(input_type, input_shape); // NHWC
|
||||
|
||||
auto ng_order1 = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 3, 1, 2});
|
||||
auto transpose1 = make_shared<Transpose>(X, ng_order1); // NCHW (1,1,3,3)
|
||||
|
||||
auto avgpool = make_shared<AvgPool>(transpose1,
|
||||
ngraph::Strides{1, 1},
|
||||
ngraph::Shape{0, 0},
|
||||
ngraph::Shape{0, 0},
|
||||
ngraph::Shape{1, 1},
|
||||
true,
|
||||
ngraph::op::RoundingType::FLOOR,
|
||||
ngraph::op::PadType::VALID);
|
||||
|
||||
auto ng_order2 = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 2, 3, 1});
|
||||
auto transpose2 = make_shared<Transpose>(avgpool, ng_order2); // NHWC (1,3,3,1)
|
||||
auto maxpool = make_shared<opset7::MaxPool>(transpose1,
|
||||
ngraph::Strides{1, 1},
|
||||
ngraph::Shape{0, 0},
|
||||
ngraph::Shape{0, 0},
|
||||
ngraph::Shape{1, 1},
|
||||
ngraph::op::RoundingType::FLOOR,
|
||||
ngraph::op::PadType::VALID);
|
||||
|
||||
auto ng_order3 = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 2, 3, 1});
|
||||
auto transpose3 = make_shared<Transpose>(maxpool, ng_order3);
|
||||
|
||||
auto const1 = Constant::create(input_type, ngraph::Shape{1, 3, 3, 1}, {3}); // NHWC (1,3,3,1)
|
||||
auto add1 = make_shared<Add>(transpose3, const1);
|
||||
auto add2 = make_shared<Add>(add1, transpose2);
|
||||
auto func = make_shared<ngraph::Function>(add2, ngraph::ParameterVector{X});
|
||||
|
||||
ov::pass::Manager pass_manager;
|
||||
size_t before_count = count_ops_of_type<Transpose>(func); // 3
|
||||
pass_manager.register_pass<TransposeSinking>();
|
||||
pass_manager.run_passes(func);
|
||||
|
||||
size_t after_count = count_ops_of_type<Transpose>(func); // 4
|
||||
ASSERT_LE(before_count, after_count);
|
||||
ASSERT_EQ(4, after_count);
|
||||
auto new_transpose =
|
||||
ngraph::as_type_ptr<Transpose>(func->get_results().at(0)->input_value(0).get_node_shared_ptr());
|
||||
ASSERT_TRUE(new_transpose);
|
||||
ASSERT_EQ(new_transpose->get_output_shape(0), (ngraph::Shape{1, 3, 3, 1}));
|
||||
}
|
||||
|
||||
// Different rank constant input to Add1. After TransposeSinking the const
|
||||
// would need a Reshape to have the same order as the other input to
|
||||
// Add1.
|
||||
TEST(TransposeSinkingTest, PoolAdd3) {
|
||||
ngraph::Shape input_shape{1, 3, 3, 1}; // NHWC (N=1, H=3, W=3, C=1)
|
||||
|
||||
auto input_type = ngraph::element::f32;
|
||||
|
||||
auto X = make_shared<Parameter>(input_type, input_shape); // NHWC
|
||||
|
||||
auto ng_order1 = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 3, 1, 2});
|
||||
auto transpose1 = make_shared<Transpose>(X, ng_order1); // NCHW (1,1,3,3)
|
||||
|
||||
auto avgpool = make_shared<AvgPool>(transpose1,
|
||||
ngraph::Strides{1, 1},
|
||||
ngraph::Shape{0, 0},
|
||||
ngraph::Shape{0, 0},
|
||||
ngraph::Shape{1, 1},
|
||||
true,
|
||||
ngraph::op::RoundingType::FLOOR,
|
||||
ngraph::op::PadType::VALID);
|
||||
|
||||
auto ng_order2 = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 2, 3, 1});
|
||||
auto transpose2 = make_shared<Transpose>(avgpool, ng_order2); // NHWC (1,3,3,1)
|
||||
|
||||
auto const1 = Constant::create(input_type, ngraph::Shape{1}, {1}); // NHWC (1,3,3,1)
|
||||
auto add1 = make_shared<Add>(transpose2, const1);
|
||||
auto func = make_shared<ngraph::Function>(add1, ngraph::ParameterVector{X});
|
||||
|
||||
ov::pass::Manager pass_manager;
|
||||
size_t before_count = count_ops_of_type<Transpose>(func);
|
||||
pass_manager.register_pass<TransposeSinking>();
|
||||
pass_manager.run_passes(func);
|
||||
|
||||
size_t after_count = count_ops_of_type<Transpose>(func);
|
||||
ASSERT_LE(after_count, before_count);
|
||||
auto new_transpose =
|
||||
ngraph::as_type_ptr<Transpose>(func->get_results().at(0)->input_value(0).get_node_shared_ptr());
|
||||
ASSERT_TRUE(new_transpose);
|
||||
ASSERT_EQ(new_transpose->get_output_shape(0), (ngraph::Shape{1, 3, 3, 1}));
|
||||
}
|
||||
|
||||
TEST(TransposeSinkingTest, Concat) {
|
||||
// checks if Transpose is pushed through Concat
|
||||
ngraph::Shape shape_nhwc{16, 28, 28, 1};
|
||||
auto a = make_shared<Parameter>(ngraph::element::i32, shape_nhwc);
|
||||
auto b = make_shared<Parameter>(ngraph::element::i32, shape_nhwc);
|
||||
auto to_nchw = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 3, 1, 2});
|
||||
auto a_transpose = make_shared<Transpose>(a, to_nchw);
|
||||
auto b_transpose = make_shared<Transpose>(b, to_nchw);
|
||||
auto concat = make_shared<Concat>(ngraph::OutputVector{a_transpose, b_transpose}, 0);
|
||||
auto to_nhwc = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 2, 3, 1});
|
||||
auto c = make_shared<Transpose>(concat, to_nhwc);
|
||||
auto func = make_shared<ngraph::Function>(ngraph::OutputVector{c}, ngraph::ParameterVector{a, b});
|
||||
|
||||
ov::pass::Manager pass_manager;
|
||||
pass_manager.register_pass<TransposeSinking>();
|
||||
pass_manager.run_passes(func);
|
||||
|
||||
size_t transpose_count = count_ops_of_type<Transpose>(func);
|
||||
ASSERT_EQ(0, transpose_count);
|
||||
auto result = func->get_results().at(0)->input_value(0).get_node_shared_ptr();
|
||||
ngraph::Shape expected_shape{32, 28, 28, 1};
|
||||
ASSERT_EQ(result->get_output_shape(0), expected_shape);
|
||||
}
|
||||
|
||||
TEST(TransposeSinkingTest, Concat_DummyShape) {
|
||||
// checks if Transpose is pushed through Concat
|
||||
ngraph::Shape shape1{4, 3, 3, 1};
|
||||
ngraph::Shape shape2{4, 3, 3, 2};
|
||||
ngraph::Shape shape3{4, 3, 3, 3};
|
||||
ngraph::Shape shape4{4, 3, 3, 4};
|
||||
auto to_nchw = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 3, 1, 2});
|
||||
auto to_nhwc = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 2, 3, 1});
|
||||
|
||||
auto a1 = make_shared<Parameter>(ngraph::element::i32, shape1);
|
||||
auto a2 = make_shared<Parameter>(ngraph::element::i32, shape2);
|
||||
auto a3 = make_shared<Parameter>(ngraph::element::i32, shape3);
|
||||
auto a4 = make_shared<Parameter>(ngraph::element::i32, shape4);
|
||||
auto a1_transpose = make_shared<Transpose>(a1, to_nchw);
|
||||
auto a2_transpose = make_shared<Transpose>(a2, to_nchw);
|
||||
auto a3_transpose = make_shared<Transpose>(a3, to_nchw);
|
||||
auto a4_transpose = make_shared<Transpose>(a4, to_nchw);
|
||||
auto concat = make_shared<Concat>(ngraph::NodeVector{a1_transpose, a2_transpose, a3_transpose, a4_transpose}, 1);
|
||||
auto out = make_shared<Transpose>(concat, to_nchw);
|
||||
auto func = make_shared<ngraph::Function>(ngraph::OutputVector{out}, ngraph::ParameterVector{a1, a2, a3, a4});
|
||||
|
||||
ov::pass::Manager pass_manager;
|
||||
pass_manager.register_pass<TransposeSinking>();
|
||||
pass_manager.run_passes(func);
|
||||
|
||||
size_t transpose_count = count_ops_of_type<Transpose>(func); // 1
|
||||
ASSERT_EQ(1, transpose_count);
|
||||
auto result = func->get_results().at(0)->input_value(0).get_node_shared_ptr();
|
||||
ngraph::Shape expected_shape{4, 3, 10, 3};
|
||||
ASSERT_EQ(result->get_output_shape(0), expected_shape);
|
||||
}
|
||||
|
||||
// The Transpose should sink through Pad op but stopped by ReduceSum
|
||||
TEST(TransposeSinkingTest, Pad) {
|
||||
ngraph::Shape shape_nhwc{100, 8, 8, 1};
|
||||
|
||||
auto a = make_shared<Parameter>(ngraph::element::f32, shape_nhwc);
|
||||
auto pad_value = Constant::create<float>(ngraph::element::f32, ngraph::Shape{}, std::vector<float>{0.0f});
|
||||
|
||||
ngraph::CoordinateDiff pad_end{0, 0, 0, 0};
|
||||
ngraph::CoordinateDiff pad_begin{0, 1, 1, 0};
|
||||
|
||||
auto a_to_nchw = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 3, 1, 2});
|
||||
auto a_transpose = make_shared<Transpose>(a, a_to_nchw);
|
||||
|
||||
auto maxpool = make_shared<opset7::MaxPool>(a_transpose,
|
||||
ngraph::Strides{2, 2},
|
||||
ngraph::Shape{0, 0},
|
||||
ngraph::Shape{0, 0},
|
||||
ngraph::Shape{1, 1},
|
||||
ngraph::op::RoundingType::FLOOR,
|
||||
ngraph::op::PadType::VALID);
|
||||
|
||||
auto m_to_nhwc = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 2, 3, 1});
|
||||
auto m_transpose = make_shared<Transpose>(maxpool, m_to_nhwc);
|
||||
|
||||
shared_ptr<Constant> pads_begin_node, pads_end_node;
|
||||
pads_begin_node = make_shared<Constant>(ngraph::element::i64, ngraph::Shape{pad_begin.size()}, pad_begin);
|
||||
pads_end_node = make_shared<Constant>(ngraph::element::i64, ngraph::Shape{pad_end.size()}, pad_end);
|
||||
auto pad = make_shared<Pad>(m_transpose, pads_begin_node, pads_end_node, pad_value, ngraph::op::PadMode::CONSTANT);
|
||||
|
||||
auto axes = make_shared<Constant>(ngraph::element::i64, ngraph::Shape{4}, vector<int64_t>{0, 1, 2, 3});
|
||||
auto sum = make_shared<ReduceSum>(pad, axes, true);
|
||||
|
||||
auto func = make_shared<ngraph::Function>(ngraph::OutputVector{sum}, ngraph::ParameterVector{a});
|
||||
|
||||
ov::pass::Manager pass_manager;
|
||||
size_t before_count = count_ops_of_type<Transpose>(func); // 2
|
||||
pass_manager.register_pass<TransposeSinking>();
|
||||
pass_manager.run_passes(func);
|
||||
|
||||
size_t after_count = count_ops_of_type<Transpose>(func); // 2
|
||||
ASSERT_EQ(after_count, before_count);
|
||||
auto result = func->get_results().at(0)->input_value(0).get_node_shared_ptr();
|
||||
ngraph::Shape expected_shape{1, 1, 1, 1};
|
||||
ASSERT_EQ(result->get_output_shape(0), expected_shape);
|
||||
auto out = ngraph::as_type_ptr<ReduceSum>(func->get_results().at(0)->input_value(0).get_node_shared_ptr());
|
||||
ASSERT_TRUE(out);
|
||||
}
|
||||
|
||||
TEST(TransposeSinkingTest, SimpleUnary) {
|
||||
ngraph::Shape shape_nhwc{16, 28, 28, 1};
|
||||
ngraph::Shape shape_nchw{16, 1, 28, 28};
|
||||
auto a = make_shared<Parameter>(ngraph::element::i32, shape_nhwc);
|
||||
|
||||
auto ng_order = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 3, 1, 2});
|
||||
auto transpose = make_shared<Transpose>(a, ng_order);
|
||||
|
||||
auto a_t = make_shared<Transpose>(a, ng_order);
|
||||
auto absn = make_shared<Abs>(a_t);
|
||||
auto absn2 = make_shared<Abs>(absn);
|
||||
|
||||
auto tf_order = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 2, 3, 1});
|
||||
auto absn2_t = make_shared<Transpose>(absn2, tf_order);
|
||||
|
||||
auto func = make_shared<ngraph::Function>(ngraph::OutputVector{absn2_t}, ngraph::ParameterVector{a});
|
||||
size_t before_count = count_ops_of_type<Transpose>(func); // 2
|
||||
|
||||
ov::pass::Manager pass_manager;
|
||||
pass_manager.register_pass<TransposeSinking>();
|
||||
pass_manager.run_passes(func);
|
||||
|
||||
size_t after_count = count_ops_of_type<Transpose>(func); // 0
|
||||
ASSERT_EQ(func->get_results().at(0)->input_value(0), absn2);
|
||||
EXPECT_NE(before_count, after_count);
|
||||
EXPECT_EQ(after_count, 0);
|
||||
}
|
||||
|
||||
TEST(TransposeSinkingTest, SinkingThroughPreLUWithScalarSlope) {
|
||||
auto input = make_shared<Parameter>(ov::element::f32, ov::Shape{1, 105, 30, 30});
|
||||
auto transpose_before =
|
||||
make_shared<Transpose>(input,
|
||||
make_shared<Constant>(ov::element::i64, ov::Shape{4}, std::vector<int64_t>{0, 2, 3, 1}));
|
||||
|
||||
auto prelu = make_shared<PRelu>(transpose_before,
|
||||
make_shared<Constant>(ov::element::f32, ov::Shape{1}, std::vector<float>{0.8f}));
|
||||
auto transpose_after =
|
||||
make_shared<Transpose>(prelu,
|
||||
make_shared<Constant>(ov::element::i64, ov::Shape{4}, std::vector<int64_t>{0, 3, 1, 2}));
|
||||
|
||||
auto model = make_shared<ov::Model>(ov::OutputVector{transpose_after}, ov::ParameterVector{input});
|
||||
size_t before_count = count_ops_of_type<Transpose>(model);
|
||||
|
||||
ov::pass::Manager pass_manager;
|
||||
pass_manager.register_pass<TransposeSinking>();
|
||||
pass_manager.run_passes(model);
|
||||
|
||||
size_t after_count = count_ops_of_type<Transpose>(model);
|
||||
|
||||
EXPECT_EQ(before_count, 2);
|
||||
EXPECT_EQ(after_count, 0);
|
||||
}
|
||||
|
||||
TEST(TransposeSinkingTest, SinkingThroughPreLUWithNonScalarSlope) {
|
||||
auto input = make_shared<Parameter>(ov::element::f32, ov::Shape{1, 3, 3, 3});
|
||||
auto transpose_before =
|
||||
make_shared<Transpose>(input,
|
||||
make_shared<Constant>(ov::element::i64, ov::Shape{4}, std::vector<int64_t>{0, 2, 3, 1}));
|
||||
|
||||
auto prelu =
|
||||
make_shared<PRelu>(transpose_before,
|
||||
make_shared<Constant>(ov::element::f32, ov::Shape{3}, std::vector<float>{0.8f, 0.7f, 0.1f}));
|
||||
auto transpose_after =
|
||||
make_shared<Transpose>(prelu,
|
||||
make_shared<Constant>(ov::element::i64, ov::Shape{4}, std::vector<int64_t>{0, 3, 1, 2}));
|
||||
|
||||
auto model = make_shared<ov::Model>(ov::OutputVector{transpose_after}, ov::ParameterVector{input});
|
||||
size_t before_count = count_ops_of_type<Transpose>(model);
|
||||
|
||||
ov::pass::Manager pass_manager;
|
||||
pass_manager.register_pass<TransposeSinking>();
|
||||
pass_manager.run_passes(model);
|
||||
|
||||
size_t after_count = count_ops_of_type<Transpose>(model);
|
||||
|
||||
EXPECT_EQ(before_count, 2);
|
||||
// Now Transpose Sinking is not applied to Prelu with non-scalar slope
|
||||
EXPECT_EQ(after_count, 2);
|
||||
}
|
||||
|
||||
/* X (NCHW)
|
||||
* |
|
||||
* Transpose1
|
||||
* |
|
||||
* Split (NHWC)
|
||||
* / \
|
||||
* / \
|
||||
* Transpose2 Transpose3 (NCHW)
|
||||
* | |
|
||||
* Const | | Const (NCHW)
|
||||
* \ | | /
|
||||
* \ | | /
|
||||
* \ | | /
|
||||
* Add Add (NCHW)
|
||||
* \ /
|
||||
* \ /
|
||||
* \ /
|
||||
* Add (NCHW)
|
||||
* |
|
||||
* Result (NCHW)
|
||||
*/
|
||||
TEST(TransposeSinkingTest, MultiOutput) {
|
||||
ngraph::Shape shape_nhwc{1, 4, 4, 1};
|
||||
ngraph::Shape shape_nchw{1, 1, 4, 6};
|
||||
|
||||
auto input_type = ngraph::element::f32;
|
||||
|
||||
auto X = make_shared<Parameter>(input_type, shape_nchw); // NCHW
|
||||
|
||||
auto ng_order1 = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 2, 3, 1});
|
||||
auto transpose1 = make_shared<Transpose>(X, ng_order1); // NHWC (1, 4, 6, 1)
|
||||
auto ng_split_dim = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{}, 2);
|
||||
|
||||
auto split = make_shared<Split>(transpose1, ng_split_dim, 2); // (1, 4, 3, 1)
|
||||
|
||||
auto ng_order2 = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 3, 1, 2});
|
||||
auto transpose2 = make_shared<Transpose>(split, ng_order2); // (1, 1, 4, 3) NCHW
|
||||
|
||||
auto ng_order3 = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 3, 1, 2});
|
||||
auto transpose3 = make_shared<Transpose>(split, ng_order3); // (1, 1, 4, 3) NCHW
|
||||
|
||||
auto const1 = Constant::create(input_type, ngraph::Shape{1, 1, 4, 3}, {3}); // NCHW
|
||||
auto add1 = make_shared<Add>(transpose2, const1);
|
||||
auto const2 = Constant::create(input_type, ngraph::Shape{1, 1, 4, 3}, {3}); // NCHW
|
||||
auto add2 = make_shared<Add>(transpose3, const2);
|
||||
auto add3 = make_shared<Add>(add1, add2);
|
||||
auto func = make_shared<ngraph::Function>(add3, ngraph::ParameterVector{X});
|
||||
|
||||
ov::pass::Manager pass_manager;
|
||||
size_t before_count = count_ops_of_type<Transpose>(func); // 3
|
||||
pass_manager.register_pass<TransposeSinking>();
|
||||
pass_manager.run_passes(func);
|
||||
|
||||
size_t after_count = count_ops_of_type<Transpose>(func); // 4
|
||||
ASSERT_LE(before_count, after_count);
|
||||
ASSERT_EQ(4, after_count);
|
||||
auto new_transpose =
|
||||
ngraph::as_type_ptr<Transpose>(func->get_results().at(0)->input_value(0).get_node_shared_ptr());
|
||||
ASSERT_TRUE(new_transpose);
|
||||
ASSERT_EQ(new_transpose->get_output_shape(0), (ngraph::Shape{1, 1, 4, 3}));
|
||||
}
|
||||
|
||||
/* X (NHWC)
|
||||
* |
|
||||
* Transpose (NCHW)
|
||||
* |
|
||||
* AvgPool0
|
||||
* |
|
||||
* Transpose0 (NHWC)
|
||||
* |
|
||||
* Split (NHWC)
|
||||
* / \
|
||||
* / \
|
||||
* Transpose1 Transpose2 (NCHW)
|
||||
* | |
|
||||
* AvgPool1 AvgPool2
|
||||
* | |
|
||||
* Transpose3 Transpose4 (NHWC)
|
||||
* \ /
|
||||
* \ /
|
||||
* \ /
|
||||
* Concat (NHWC)
|
||||
* Const /
|
||||
* \ /
|
||||
* \ /
|
||||
* \ /
|
||||
* \ /
|
||||
* Add (NHWC)
|
||||
* |
|
||||
* Result
|
||||
*/
|
||||
TEST(TransposeSinkingTest, AlexnetPattern) {
|
||||
ngraph::Shape shape_nhwc{1, 55, 55, 96};
|
||||
ngraph::Shape shape_nchw{1, 96, 55, 55};
|
||||
|
||||
auto input_type = ngraph::element::f32;
|
||||
|
||||
// X
|
||||
auto X = make_shared<Parameter>(input_type, shape_nhwc); // NHWC
|
||||
|
||||
// T -> AvgPool0 -> T0
|
||||
auto ng_order = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 3, 1, 2});
|
||||
auto transpose = make_shared<Transpose>(X, ng_order); // NCHW
|
||||
auto avgpool0 = make_shared<AvgPool>(transpose,
|
||||
ngraph::Strides{1, 1},
|
||||
ngraph::Shape{0, 0},
|
||||
ngraph::Shape{0, 0},
|
||||
ngraph::Shape{1, 1},
|
||||
true,
|
||||
ngraph::op::RoundingType::FLOOR,
|
||||
ngraph::op::PadType::VALID);
|
||||
auto ng_order0 = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 2, 3, 1});
|
||||
auto transpose0 = make_shared<Transpose>(avgpool0, ng_order0); // NHWC
|
||||
|
||||
// Split
|
||||
auto ng_split_dim = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{}, 3);
|
||||
auto split = make_shared<Split>(transpose0, ng_split_dim, 2); // NHWC
|
||||
|
||||
// T1 -> AvgPool1 -> T2
|
||||
auto ng_order1 = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 3, 1, 2});
|
||||
auto transpose1 = make_shared<Transpose>(split, ng_order1); // NCHW
|
||||
auto avgpool1 = make_shared<AvgPool>(transpose1,
|
||||
ngraph::Strides{1, 1},
|
||||
ngraph::Shape{0, 0},
|
||||
ngraph::Shape{0, 0},
|
||||
ngraph::Shape{1, 1},
|
||||
true,
|
||||
ngraph::op::RoundingType::FLOOR,
|
||||
ngraph::op::PadType::VALID);
|
||||
auto ng_order2 = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 2, 3, 1});
|
||||
auto transpose2 = make_shared<Transpose>(avgpool1, ng_order2); // NHWC
|
||||
|
||||
// T3 -> AvgPool2 -> T4
|
||||
auto ng_order3 = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 3, 1, 2});
|
||||
auto transpose3 = make_shared<Transpose>(split, ng_order1); // NCHW
|
||||
auto avgpool2 = make_shared<AvgPool>(transpose3,
|
||||
ngraph::Strides{1, 1},
|
||||
ngraph::Shape{0, 0},
|
||||
ngraph::Shape{0, 0},
|
||||
ngraph::Shape{1, 1},
|
||||
true,
|
||||
ngraph::op::RoundingType::FLOOR,
|
||||
ngraph::op::PadType::VALID);
|
||||
auto ng_order4 = std::make_shared<Constant>(ngraph::element::u64, ngraph::Shape{4}, ngraph::Shape{0, 2, 3, 1});
|
||||
auto transpose4 = make_shared<Transpose>(avgpool2, ng_order4); // NHWC
|
||||
|
||||
// Concat
|
||||
auto concat = make_shared<Concat>(ngraph::OutputVector{transpose2, transpose4}, 3); // NHWC
|
||||
|
||||
// Add
|
||||
auto const1 = Constant::create(input_type, ngraph::Shape{96}, {1}); // NCHW
|
||||
auto add1 = make_shared<Add>(concat, const1);
|
||||
|
||||
auto func = make_shared<ngraph::Function>(add1, ngraph::ParameterVector{X});
|
||||
|
||||
ov::pass::Manager pass_manager;
|
||||
size_t before_count = count_ops_of_type<Transpose>(func);
|
||||
pass_manager.register_pass<TransposeSinking>();
|
||||
pass_manager.run_passes(func);
|
||||
|
||||
size_t after_count = count_ops_of_type<Transpose>(func);
|
||||
ASSERT_LE(after_count, before_count);
|
||||
ASSERT_EQ(5, after_count);
|
||||
auto new_transpose =
|
||||
ngraph::as_type_ptr<Transpose>(func->get_results().at(0)->input_value(0).get_node_shared_ptr());
|
||||
ASSERT_TRUE(new_transpose);
|
||||
ASSERT_EQ(new_transpose->get_output_shape(0), (ngraph::Shape{1, 55, 55, 96}));
|
||||
}
|
||||
|
||||
Output<Node> make_transpose(const Output<Node>& input, const vector<int64_t>& order) {
|
||||
return std::make_shared<opset8::Transpose>(input, opset8::Constant::create(element::i64, {order.size()}, order));
|
||||
}
|
||||
|
||||
TEST(TransposeSinkingTest, BinarySubTrickyShapes) {
|
||||
auto a = make_shared<Parameter>(ngraph::element::i32, ngraph::Shape{1, 17});
|
||||
auto a_t = make_transpose(a, {0, 1});
|
||||
auto b = make_shared<Parameter>(ngraph::element::i32, ngraph::Shape{48, 48, 17});
|
||||
auto b_t = make_transpose(b, {0, 1, 2});
|
||||
auto binary = make_shared<Subtract>(a, b);
|
||||
|
||||
auto res = make_shared<Result>(binary);
|
||||
auto func = make_shared<ngraph::Function>(ngraph::OutputVector{res}, ngraph::ParameterVector{a, b});
|
||||
|
||||
ov::pass::Manager pass_manager;
|
||||
pass_manager.register_pass<TransposeSinking>();
|
||||
pass_manager.run_passes(func);
|
||||
|
||||
size_t transpose_cnt = count_ops_of_type<Transpose>(func);
|
||||
EXPECT_EQ(transpose_cnt, 0);
|
||||
}
|
@ -10,7 +10,6 @@
|
||||
#include "op_table.hpp"
|
||||
#include "openvino/frontend/tensorflow_lite/extension/op.hpp"
|
||||
#include "openvino/util/common_util.hpp"
|
||||
#include "pass/transpose_sinking.hpp"
|
||||
#include "so_extension.hpp"
|
||||
#include "tensor_lite_place.hpp"
|
||||
#include "tf_framework_node.hpp"
|
||||
|
Loading…
Reference in New Issue
Block a user