Remove legacy TransposeSinking transformation (#16731)

* delete TransposeSinkingOVTF transformation

* delete include from tf_lite frontend
This commit is contained in:
Ivan Tikhonov 2023-04-04 18:51:10 +04:00 committed by GitHub
parent c034975183
commit 093990118d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 0 additions and 1189 deletions

View File

@ -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"

View File

@ -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

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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"