RIC Fusion Transformation (#8905)
* Initial RIC Fusion version * Added automatic RIC inserter/erases * Updated RIC Attr; Added support for new operations; updated tests; simplified matcher registration * New operaitons; layout independency * Cleanup * Added FQ support * Cleanup; fix Conv+FQ * Added accuracy check; fixed GroupConvolution handler * Added Split->Concat and ShapeOf tests * Improved test coverege; cleanup * Function -> Model * Fix node usage * Added rank check and check for two consecutive init patterns
This commit is contained in:
1 parent
37b0b6f7c8
commit
586d97bcb0
8 files changed
+1623
-3
No files matched your search
+639
@@ -0,0 +1,639 @@
|
||||
// Copyright (C) 2018-2021 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include <ngraph/function.hpp>
|
||||
#include <ngraph/opsets/opset8.hpp>
|
||||
#include <transformations/common_optimizations/ric_fusion.hpp>
|
||||
#include <transformations/init_node_info.hpp>
|
||||
#include <ngraph_functions/utils/ngraph_helpers.hpp>
|
||||
#include <openvino/core/preprocess/pre_post_process.hpp>
|
||||
#include <ngraph/pass/constant_folding.hpp>
|
||||
#include <transformations/serialize.hpp>
|
||||
|
||||
#include "common_test_utils/ngraph_test_utils.hpp"
|
||||
|
||||
using namespace testing;
|
||||
using namespace ngraph;
|
||||
using namespace opset8;
|
||||
|
||||
namespace {
|
||||
std::shared_ptr<Convolution> create_conv(Output<Node> input, Output<Node> weights) {
|
||||
return std::make_shared<Convolution>(input, weights, ov::Strides{1, 1},
|
||||
ov::CoordinateDiff{0, 0}, ov::CoordinateDiff{0, 0},
|
||||
ov::Strides{1, 1});
|
||||
}
|
||||
|
||||
std::shared_ptr<Constant> create_weights(const Shape & weigts_shape) {
|
||||
std::vector<float> values(ov::shape_size(weigts_shape));
|
||||
float cur_value = 0.01;
|
||||
for (auto & value : values) {
|
||||
value = cur_value;
|
||||
cur_value += 0.01;
|
||||
}
|
||||
return Constant::create(element::f32, weigts_shape, values);
|
||||
}
|
||||
|
||||
std::shared_ptr<Convolution> create_conv(Output<Node> input, const Shape & weigts_shape) {
|
||||
return create_conv(input, create_weights(weigts_shape));
|
||||
}
|
||||
|
||||
std::shared_ptr<GroupConvolution> create_group_conv(Output<Node> input, Output<Node> weights) {
|
||||
return std::make_shared<GroupConvolution>(input, weights, ov::Strides{1, 1},
|
||||
ov::CoordinateDiff{0, 0}, ov::CoordinateDiff{0, 0},
|
||||
ov::Strides{1, 1});
|
||||
}
|
||||
|
||||
std::shared_ptr<GroupConvolution> create_group_conv(Output<Node> input, const Shape & weigts_shape) {
|
||||
return create_group_conv(input, create_weights(weigts_shape));
|
||||
}
|
||||
|
||||
std::shared_ptr<GroupConvolution> create_group_conv_with_gather(Output<Node> input, const Shape & weigts_shape, const std::vector<int64_t> & order) {
|
||||
auto gather = std::make_shared<Gather>(create_weights(weigts_shape), Constant::create(element::i64, Shape{order.size()}, order),
|
||||
Constant::create(element::i64, Shape{1}, {0}));
|
||||
return std::make_shared<GroupConvolution>(input, gather, ov::Strides{1, 1},
|
||||
ov::CoordinateDiff{0, 0}, ov::CoordinateDiff{0, 0},
|
||||
ov::Strides{1, 1});
|
||||
}
|
||||
|
||||
std::shared_ptr<Convolution> create_conv_with_gather(Output<Node> input, const Shape & weigts_shape, const std::vector<int64_t> & order) {
|
||||
auto gather = std::make_shared<Gather>(create_weights(weigts_shape), Constant::create(element::i64, Shape{order.size()}, order),
|
||||
Constant::create(element::i64, Shape{1}, {1}));
|
||||
return std::make_shared<Convolution>(input, gather, ov::Strides{1, 1},
|
||||
ov::CoordinateDiff{0, 0}, ov::CoordinateDiff{0, 0},
|
||||
ov::Strides{1, 1});
|
||||
}
|
||||
|
||||
std::shared_ptr<Parameter> create_param(const PartialShape & shape) {
|
||||
return std::make_shared<Parameter>(element::f32, shape);
|
||||
}
|
||||
|
||||
std::shared_ptr<Gather> create_gather(Output<Node> input, std::vector<int64_t> order, int64_t axis) {
|
||||
auto order_const = Constant::create(element::i64, Shape{order.size()}, order);
|
||||
auto axis_const = Constant::create(element::i64, Shape{1}, {axis});
|
||||
return std::make_shared<Gather>(input, order_const, axis_const);
|
||||
}
|
||||
|
||||
std::shared_ptr<FakeQuantize> create_fq(Output<Node> input) {
|
||||
return std::make_shared<FakeQuantize>(input, Constant::create(element::f32, Shape{1}, {1}),
|
||||
Constant::create(element::f32, Shape{1}, {2}),
|
||||
Constant::create(element::f32, Shape{1}, {3}),
|
||||
Constant::create(element::f32, Shape{1}, {4}), 255);
|
||||
}
|
||||
|
||||
void apply_reverse_input_channels(std::shared_ptr<Function> f, std::vector<std::pair<int64_t, std::string>> data) {
|
||||
using namespace ov::preprocess;
|
||||
PrePostProcessor p(f);
|
||||
for (auto item : data) {
|
||||
p.input(item.first).tensor().set_layout(ov::Layout(item.second));
|
||||
p.input(item.first).preprocess().reverse_channels();
|
||||
}
|
||||
p.build();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionSimple) {
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto relu = std::make_shared<Relu>(input);
|
||||
auto conv = create_conv(relu, {6, 3, 3, 3});
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
apply_reverse_input_channels(function, {{0, "NCHW"}});
|
||||
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto relu = std::make_shared<Relu>(input);
|
||||
auto conv = create_conv_with_gather(relu, {6, 3, 3, 3}, {2, 1, 0});
|
||||
function_ref = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
}
|
||||
|
||||
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
|
||||
comparator.enable(FunctionsComparator::CmpValues::ATTRIBUTES);
|
||||
disable_rt_info_check();
|
||||
enable_accuracy_check();
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionHard) {
|
||||
{
|
||||
auto input = create_param({ -1, 3, -1, -1 });
|
||||
auto relu = std::make_shared<Relu>(input);
|
||||
|
||||
auto input2 = create_param({ -1, 3, -1, -1 });
|
||||
auto split = std::make_shared<Split>(input2, Constant::create(element::i64, {}, {1}), 3);
|
||||
auto concat = std::make_shared<Concat>(OutputVector{split->output(2), split->output(1), split->output(0)}, 1);
|
||||
auto eltwise = std::make_shared<Add>(relu, concat);
|
||||
auto prelu = std::make_shared<PRelu>(eltwise, Constant::create(element::f32, Shape{}, {6.0f}));
|
||||
|
||||
auto gconv = create_group_conv(prelu, {3, 4, 1, 3, 3});
|
||||
|
||||
auto pow = std::make_shared<Power>(gconv, Constant::create(element::f32, Shape{}, {-1.0f}));
|
||||
auto convert1 = std::make_shared<Convert>(pow, element::f16);
|
||||
auto convert2 = std::make_shared<Convert>(convert1, element::f32);
|
||||
|
||||
auto gconv2 = create_group_conv(convert2, {12, 1, 1, 3, 3});
|
||||
|
||||
auto conv = create_conv(gconv2, {6, 12, 3, 3});
|
||||
auto conv2 = create_conv(concat, {6, 3, 3, 3});
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv, conv2 }, ParameterVector{ input, input2 });
|
||||
|
||||
apply_reverse_input_channels(function, {{0, "NCHW"}});
|
||||
|
||||
manager.register_pass<pass::ConstantFolding>();
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
{
|
||||
auto input = create_param({ -1, 3, -1, -1 });
|
||||
auto relu = std::make_shared<Relu>(input);
|
||||
|
||||
auto input2 = create_param({ -1, 3, -1, -1 });
|
||||
auto eltwise = std::make_shared<Add>(relu, input2);
|
||||
auto prelu = std::make_shared<PRelu>(eltwise, Constant::create(element::f32, Shape{}, {6.0f}));
|
||||
// 0 1 2 2 1 0
|
||||
// [0, 1, 2, 3]-[4, 5, 6, 7]-[8, 9, 10, 11] -> [8, 9, 10, 11]-[4, 5, 6, 7]-[0, 1, 2, 3]
|
||||
auto gconv = create_group_conv_with_gather(prelu, {3, 4, 1, 3, 3}, {2, 1, 0});
|
||||
|
||||
auto pow = std::make_shared<Power>(gconv, Constant::create(element::f32, Shape{}, {-1.0f}));
|
||||
auto convert1 = std::make_shared<Convert>(pow, element::f16);
|
||||
auto convert2 = std::make_shared<Convert>(convert1, element::f32);
|
||||
|
||||
auto gconv2 = create_group_conv_with_gather(convert2, {12, 1, 1, 3, 3}, {8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3});
|
||||
|
||||
auto conv = create_conv_with_gather(gconv2, {6, 12, 3, 3}, {8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3});
|
||||
auto conv2 = create_conv_with_gather(input2, {6, 3, 3, 3}, {2, 1, 0});
|
||||
|
||||
function_ref = std::make_shared<Function>(NodeVector{ conv, conv2 }, ParameterVector{ input, input2 });
|
||||
}
|
||||
|
||||
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
|
||||
comparator.enable(FunctionsComparator::CmpValues::ATTRIBUTES);
|
||||
disable_rt_info_check();
|
||||
enable_accuracy_check();
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionEltwise1) {
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto add = std::make_shared<Add>(input, Constant::create(element::f32, Shape{3, 1, 1}, {0.1, 0.2, 0.3}));
|
||||
auto conv = create_conv(add, {6, 3, 3, 3});
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
apply_reverse_input_channels(function, {{0, "NCHW"}});
|
||||
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto gather = create_gather(Constant::create(element::f32, Shape{3, 1, 1}, {0.1, 0.2, 0.3}), {2, 1, 0}, 0);
|
||||
auto add = std::make_shared<Add>(input, gather);
|
||||
auto conv = create_conv_with_gather(add, {6, 3, 3, 3}, {2, 1, 0});
|
||||
function_ref = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
}
|
||||
|
||||
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
|
||||
comparator.enable(FunctionsComparator::CmpValues::ATTRIBUTES);
|
||||
disable_rt_info_check();
|
||||
enable_accuracy_check();
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionEltwise2) {
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto add = std::make_shared<Add>(input, create_weights({1, 1, 1}));
|
||||
auto conv = create_conv(add, {6, 3, 3, 3});
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
apply_reverse_input_channels(function, {{0, "NCHW"}});
|
||||
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto add = std::make_shared<Add>(input, create_weights({1, 1, 1}));
|
||||
auto conv = create_conv_with_gather(add, {6, 3, 3, 3}, {2, 1, 0});
|
||||
function_ref = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
}
|
||||
|
||||
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
|
||||
comparator.enable(FunctionsComparator::CmpValues::ATTRIBUTES);
|
||||
disable_rt_info_check();
|
||||
enable_accuracy_check();
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionEltwise3) {
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto add = std::make_shared<Add>(input, Constant::create(element::f32, Shape{1}, {0.2}));
|
||||
auto conv = create_conv(add, {6, 3, 3, 3});
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
apply_reverse_input_channels(function, {{0, "NCHW"}});
|
||||
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto add = std::make_shared<Add>(input, Constant::create(element::f32, Shape{1}, {0.2}));
|
||||
auto conv = create_conv_with_gather(add, {6, 3, 3, 3}, {2, 1, 0});
|
||||
function_ref = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
}
|
||||
|
||||
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
|
||||
comparator.enable(FunctionsComparator::CmpValues::ATTRIBUTES);
|
||||
disable_rt_info_check();
|
||||
enable_accuracy_check();
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionEltwise4) {
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto add = std::make_shared<Add>(create_weights({3, 1, 1}), input);
|
||||
auto conv = create_conv(add, {6, 3, 3, 3});
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
apply_reverse_input_channels(function, {{0, "NCHW"}});
|
||||
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto gather = create_gather(create_weights({3, 1, 1}), {2, 1, 0}, 0);
|
||||
auto add = std::make_shared<Add>(gather, input);
|
||||
auto conv = create_conv_with_gather(add, {6, 3, 3, 3}, {2, 1, 0});
|
||||
function_ref = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
}
|
||||
|
||||
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
|
||||
comparator.enable(FunctionsComparator::CmpValues::ATTRIBUTES);
|
||||
disable_rt_info_check();
|
||||
enable_accuracy_check();
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionEltwise5) {
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto add = std::make_shared<Add>(create_weights({1, 3, 1, 1}), input);
|
||||
auto conv = create_conv(add, {6, 3, 3, 3});
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
apply_reverse_input_channels(function, {{0, "NCHW"}});
|
||||
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto gather = create_gather(create_weights({1, 3, 1, 1}), {2, 1, 0}, 1);
|
||||
auto add = std::make_shared<Add>(gather, input);
|
||||
auto conv = create_conv_with_gather(add, {6, 3, 3, 3}, {2, 1, 0});
|
||||
function_ref = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
}
|
||||
|
||||
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
|
||||
comparator.enable(FunctionsComparator::CmpValues::ATTRIBUTES);
|
||||
disable_rt_info_check();
|
||||
enable_accuracy_check();
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionEltwiseNegative) {
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto input2 = create_param({ 1, 3, 64, 64 });
|
||||
auto add = std::make_shared<Add>(input, input2);
|
||||
auto conv = create_conv(add, {6, 3, 3, 3});
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input, input2 });
|
||||
apply_reverse_input_channels(function, {{0, "NCHW"}});
|
||||
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionEltwiseNegative2) {
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto input2 = create_param({ 1, 1, 64, 64 });
|
||||
auto add = std::make_shared<Add>(input, input2);
|
||||
auto conv = create_conv(add, {6, 3, 3, 3});
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input, input2 });
|
||||
apply_reverse_input_channels(function, {{0, "NCHW"}, {1, "NCHW"}});
|
||||
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionEltwiseNegative3) {
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto add = std::make_shared<Add>(input, Constant::create(element::f32, {1, 1, 1, 1, 1}, {1.4}));
|
||||
auto shapeof = std::make_shared<ShapeOf>(add);
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ shapeof }, ParameterVector{ input });
|
||||
apply_reverse_input_channels(function, {{0, "NCHW"}});
|
||||
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionGroupConv) {
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto gconv = create_group_conv(input, {3, 2, 1, 3, 3});
|
||||
auto relu = std::make_shared<Relu>(gconv);
|
||||
auto conv = create_conv(relu, {3, 6, 3, 3});
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
apply_reverse_input_channels(function, {{0, "NCHW"}});
|
||||
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto gconv = create_group_conv_with_gather(input, {3, 2, 1, 3, 3}, {2, 1, 0});
|
||||
// 0 1 2 2 1 0
|
||||
// [0, 1]-[2, 3]-[4, 5] -> [4, 5]-[2, 3]-[0, 1]
|
||||
auto relu = std::make_shared<Relu>(gconv);
|
||||
auto conv = create_conv_with_gather(relu, {3, 6, 3, 3}, {4, 5, 2, 3, 0, 1});
|
||||
function_ref = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
}
|
||||
|
||||
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
|
||||
comparator.enable(FunctionsComparator::CmpValues::ATTRIBUTES);
|
||||
disable_rt_info_check();
|
||||
enable_accuracy_check();
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionGroupConvNegative) {
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto gconv = create_group_conv(input, {1, 3, 3, 3, 3});
|
||||
auto relu = std::make_shared<Relu>(gconv);
|
||||
auto conv = create_conv(relu, {6, 3, 3, 3});
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
apply_reverse_input_channels(function, {{0, "NCHW"}});
|
||||
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionTranspose) {
|
||||
{
|
||||
auto input = create_param({ 1, 64, 64, 3 });
|
||||
auto add = std::make_shared<Add>(input, create_weights({3}));
|
||||
auto transpose = std::make_shared<Transpose>(add, Constant::create(element::i64, Shape{4}, {0, 3, 1, 2}));
|
||||
auto conv = create_conv(transpose, {6, 3, 3, 3});
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
apply_reverse_input_channels(function, {{0, "NHWC"}});
|
||||
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
|
||||
{
|
||||
auto input = create_param({ 1, 64, 64, 3 });
|
||||
auto gather = create_gather(create_weights({3}), {2, 1, 0}, 0);
|
||||
auto add = std::make_shared<Add>(input, gather);
|
||||
auto transpose = std::make_shared<Transpose>(add, Constant::create(element::i64, Shape{4}, {0, 3, 1, 2}));
|
||||
auto conv = create_conv_with_gather(transpose, {6, 3, 3, 3}, {2, 1, 0});
|
||||
function_ref = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
}
|
||||
|
||||
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
|
||||
comparator.enable(FunctionsComparator::CmpValues::ATTRIBUTES);
|
||||
disable_rt_info_check();
|
||||
enable_accuracy_check();
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionFQOnTheWay) {
|
||||
{
|
||||
auto input = create_param({1, 3, 64, 64});
|
||||
auto fq = create_fq(input);
|
||||
auto conv = create_conv(fq, create_fq(create_weights({6, 3, 3, 3})));
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
apply_reverse_input_channels(function, {{0, "NCHW"}});
|
||||
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
|
||||
{
|
||||
auto input = create_param({1, 3, 64, 64});
|
||||
auto fq = create_fq(input);
|
||||
auto conv = create_conv(fq, create_fq(create_gather(create_weights({6, 3, 3, 3}), {2, 1, 0}, 1)));
|
||||
|
||||
function_ref = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
}
|
||||
|
||||
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
|
||||
comparator.enable(FunctionsComparator::CmpValues::ATTRIBUTES);
|
||||
disable_rt_info_check();
|
||||
enable_accuracy_check();
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionFQOnTheWay2) {
|
||||
{
|
||||
auto input = create_param({1, 3, 64, 64});
|
||||
auto fq = create_fq(input);
|
||||
auto weights_const = create_weights({6, 3, 3, 3});
|
||||
auto fq_weights = std::make_shared<FakeQuantize>(weights_const,
|
||||
create_weights({1, 3, 1, 1}),
|
||||
create_weights({1, 1, 1}),
|
||||
create_weights({1}),
|
||||
create_weights({3, 1, 1}), 255);
|
||||
auto conv = create_conv(fq, fq_weights);
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
apply_reverse_input_channels(function, {{0, "NCHW"}});
|
||||
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
|
||||
{
|
||||
auto input = create_param({1, 3, 64, 64});
|
||||
auto fq = create_fq(input);
|
||||
auto weights_const = create_weights({6, 3, 3, 3});
|
||||
auto fq_weights = std::make_shared<FakeQuantize>(create_gather(weights_const, {2, 1, 0}, 1),
|
||||
create_gather(create_weights({1, 3, 1, 1}), {2, 1, 0}, 1),
|
||||
create_weights({1, 1, 1}),
|
||||
create_weights({1}),
|
||||
create_gather(create_weights({3, 1, 1}), {2, 1, 0}, 0),
|
||||
255);
|
||||
auto conv = create_conv(fq, fq_weights);
|
||||
|
||||
function_ref = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
}
|
||||
|
||||
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
|
||||
comparator.enable(FunctionsComparator::CmpValues::ATTRIBUTES);
|
||||
disable_rt_info_check();
|
||||
enable_accuracy_check();
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionFQOnTheWay3) {
|
||||
{
|
||||
auto input = create_param({1, 3, 64, 64});
|
||||
auto fq = create_fq(input);
|
||||
auto weights_const = create_weights({3, 1, 1, 3, 3});
|
||||
auto fq_weights = std::make_shared<FakeQuantize>(weights_const,
|
||||
create_weights({3, 1, 1, 1, 1}),
|
||||
create_weights({1, 1, 1}),
|
||||
create_weights({1}),
|
||||
create_weights({1}), 255);
|
||||
auto gconv = create_group_conv(fq, fq_weights);
|
||||
auto conv = create_conv(gconv, {6, 3, 1, 1});
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
apply_reverse_input_channels(function, {{0, "NCHW"}});
|
||||
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
|
||||
{
|
||||
auto input = create_param({1, 3, 64, 64});
|
||||
auto fq = create_fq(input);
|
||||
auto weights_const = create_weights({3, 1, 1, 3, 3});
|
||||
auto fq_weights = std::make_shared<FakeQuantize>(create_gather(weights_const, {2, 1, 0}, 0),
|
||||
create_gather(create_weights({3, 1, 1, 1, 1}), {2, 1, 0}, 0),
|
||||
create_weights({1, 1, 1}),
|
||||
create_weights({1}),
|
||||
create_weights({1}), 255);
|
||||
auto gconv = create_group_conv(fq, fq_weights);
|
||||
auto conv = create_conv_with_gather(gconv, {6, 3, 1, 1}, {2, 1, 0});
|
||||
|
||||
function_ref = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
}
|
||||
|
||||
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
|
||||
comparator.enable(FunctionsComparator::CmpValues::ATTRIBUTES);
|
||||
disable_rt_info_check();
|
||||
enable_accuracy_check();
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionShapeOf) {
|
||||
{
|
||||
auto input = create_param({1, 3, 64, 64});
|
||||
auto relu = std::make_shared<Relu>(input);
|
||||
auto shape_of = std::make_shared<ShapeOf>(relu);
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ shape_of }, ParameterVector{ input });
|
||||
apply_reverse_input_channels(function, {{0, "NCHW"}});
|
||||
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
|
||||
{
|
||||
auto input = create_param({1, 3, 64, 64});
|
||||
auto relu = std::make_shared<Relu>(input);
|
||||
auto shape_of = std::make_shared<ShapeOf>(relu);
|
||||
|
||||
function_ref = std::make_shared<Function>(NodeVector{ shape_of }, ParameterVector{ input });
|
||||
}
|
||||
|
||||
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
|
||||
comparator.enable(FunctionsComparator::CmpValues::ATTRIBUTES);
|
||||
disable_rt_info_check();
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionGatherDetectionNegative) {
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto gather = create_gather(input, {2, 1, 1}, 1);
|
||||
auto relu = std::make_shared<Relu>(gather);
|
||||
auto conv = create_conv(relu, {6, 3, 3, 3});
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionGatherDetectionNegative2) {
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto input2 = std::make_shared<Parameter>(element::i64, Shape{3});
|
||||
auto gather = std::make_shared<Gather>(input, input2, Constant::create(element::i64, {}, {1}));
|
||||
auto relu = std::make_shared<Relu>(gather);
|
||||
auto conv = create_conv(relu, {6, 3, 3, 3});
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input, input2 });
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionGatherDetectionNegative3) {
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto gather = create_gather(input, {1}, 1);
|
||||
auto relu = std::make_shared<Relu>(gather);
|
||||
auto conv = create_conv(relu, {6, 1, 3, 3});
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionGatherDetectionNegative4) {
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto gather = create_gather(input, {2, 0}, 1);
|
||||
auto relu = std::make_shared<Relu>(gather);
|
||||
auto conv = create_conv(relu, {6, 2, 3, 3});
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionSplitConcatDetectionNegative) {
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto split = std::make_shared<Split>(input, Constant::create(element::i64, {}, {1}), 3);
|
||||
auto concat = std::make_shared<Concat>(OutputVector{split->output(2), split->output(1), split->output(1)}, 1);
|
||||
auto relu = std::make_shared<Relu>(concat);
|
||||
auto conv = create_conv(relu, {6, 3, 3, 3});
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionSplitConcatDetectionNegative2) {
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto split = std::make_shared<Split>(input, Constant::create(element::i64, {}, {1}), 3);
|
||||
auto split2 = std::make_shared<Split>(input, Constant::create(element::i64, {}, {1}), 3);
|
||||
auto concat = std::make_shared<Concat>(OutputVector{split->output(2), split->output(1), split2->output(0)}, 1);
|
||||
auto relu = std::make_shared<Relu>(concat);
|
||||
auto conv = create_conv(relu, {6, 3, 3, 3});
|
||||
|
||||
function = std::make_shared<Function>(NodeVector{ conv }, ParameterVector{ input });
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, RICFusionSplitConcatDetectionNegative3) {
|
||||
{
|
||||
auto input = create_param({ 1, 3, 64, 64 });
|
||||
auto split = std::make_shared<Split>(input, Constant::create(element::i64, {}, {1}), 3);
|
||||
auto concat = std::make_shared<Concat>(OutputVector{split->output(2), split->output(1), split->output(0)}, 1);
|
||||
auto relu = std::make_shared<Relu>(concat);
|
||||
auto conv = create_conv(relu, {6, 3, 3, 3});
|
||||
|
||||
function = std::make_shared<Function>(OutputVector{ conv, split->output(0) }, ParameterVector{ input });
|
||||
manager.register_pass<pass::ReverseInputChannelsFusion>();
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ function(add_common_utils ADD_TARGET_NAME)
|
||||
gtest_main
|
||||
inference_engine_transformations
|
||||
ngraph_test_util
|
||||
ngraphFunctions
|
||||
PRIVATE
|
||||
openvino::util
|
||||
)
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
// Copyright (C) 2018-2021 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include <functional_test_utils/include/functional_test_utils/blob_utils.hpp>
|
||||
#include "ngraph_test_utils.hpp"
|
||||
#include <ngraph_functions/utils/ngraph_helpers.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
template<class T_IE, class T_NGRAPH>
|
||||
static void Compare(const T_NGRAPH *expected, const T_IE *actual, std::size_t size, float threshold, float abs_threshold = -1.f) {
|
||||
for (std::size_t i = 0; i < size; ++i) {
|
||||
const T_NGRAPH &ref = expected[i];
|
||||
const auto &res = actual[i];
|
||||
const auto absoluteDifference = CommonTestUtils::ie_abs(res - ref);
|
||||
if (abs_threshold > 0.f && absoluteDifference > abs_threshold) {
|
||||
IE_THROW() << "Absolute comparison of values expected: " << std::to_string(ref) << " and actual: " << std::to_string(res)
|
||||
<< " at index " << i << " with absolute threshold " << abs_threshold
|
||||
<< " failed";
|
||||
}
|
||||
if (absoluteDifference <= threshold) {
|
||||
continue;
|
||||
}
|
||||
double max;
|
||||
if (sizeof(T_IE) < sizeof(T_NGRAPH)) {
|
||||
max = std::max(CommonTestUtils::ie_abs(T_NGRAPH(res)), CommonTestUtils::ie_abs(ref));
|
||||
} else {
|
||||
max = std::max(CommonTestUtils::ie_abs(res), CommonTestUtils::ie_abs(T_IE(ref)));
|
||||
}
|
||||
double diff = static_cast<float>(absoluteDifference) / max;
|
||||
if (max == 0 || (diff > static_cast<float>(threshold)) ||
|
||||
(std::isnan(static_cast<float>(res)) ^ std::isnan(static_cast<float>(ref)))) {
|
||||
IE_THROW() << "Relative comparison of values expected: " << std::to_string(ref) << " and actual: " << std::to_string(res)
|
||||
<< " at index " << i << " with threshold " << threshold
|
||||
<< " failed";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T_IE>
|
||||
void callCompare(const std::pair<ngraph::element::Type, std::vector<std::uint8_t>> &expected,
|
||||
const T_IE* actualBuffer, size_t size, float threshold, float abs_threshold) {
|
||||
auto expectedBuffer = expected.second.data();
|
||||
switch (expected.first) {
|
||||
case ngraph::element::Type_t::i64:
|
||||
Compare<T_IE, int64_t>(reinterpret_cast<const int64_t *>(expectedBuffer),
|
||||
actualBuffer, size, threshold, abs_threshold);
|
||||
break;
|
||||
case ngraph::element::Type_t::i32:
|
||||
Compare<T_IE, int32_t>(reinterpret_cast<const int32_t *>(expectedBuffer),
|
||||
actualBuffer, size, threshold, abs_threshold);
|
||||
break;
|
||||
case ngraph::element::Type_t::i16:
|
||||
Compare<T_IE, int16_t>(reinterpret_cast<const int16_t *>(expectedBuffer),
|
||||
actualBuffer, size, threshold, abs_threshold);
|
||||
break;
|
||||
case ngraph::element::Type_t::i8:
|
||||
Compare<T_IE, int8_t>(reinterpret_cast<const int8_t *>(expectedBuffer),
|
||||
actualBuffer, size, threshold, abs_threshold);
|
||||
break;
|
||||
case ngraph::element::Type_t::u64:
|
||||
Compare<T_IE, uint64_t>(reinterpret_cast<const uint64_t *>(expectedBuffer),
|
||||
actualBuffer, size, threshold, abs_threshold);
|
||||
break;
|
||||
case ngraph::element::Type_t::u32:
|
||||
Compare<T_IE, uint32_t>(reinterpret_cast<const uint32_t *>(expectedBuffer),
|
||||
actualBuffer, size, threshold, abs_threshold);
|
||||
break;
|
||||
case ngraph::element::Type_t::u16:
|
||||
Compare<T_IE, uint16_t>(reinterpret_cast<const uint16_t *>(expectedBuffer),
|
||||
actualBuffer, size, threshold, abs_threshold);
|
||||
break;
|
||||
case ngraph::element::Type_t::boolean:
|
||||
case ngraph::element::Type_t::u8:
|
||||
Compare<T_IE, uint8_t>(reinterpret_cast<const uint8_t *>(expectedBuffer),
|
||||
actualBuffer, size, threshold, abs_threshold);
|
||||
break;
|
||||
case ngraph::element::Type_t::f64:
|
||||
Compare<T_IE, double>(reinterpret_cast<const double *>(expectedBuffer),
|
||||
actualBuffer, size, threshold, abs_threshold);
|
||||
break;
|
||||
case ngraph::element::Type_t::f32:
|
||||
Compare<T_IE, float>(reinterpret_cast<const float *>(expectedBuffer),
|
||||
actualBuffer, size, threshold, abs_threshold);
|
||||
break;
|
||||
case ngraph::element::Type_t::f16:
|
||||
Compare<T_IE, ngraph::float16>(reinterpret_cast<const ngraph::float16 *>(expectedBuffer),
|
||||
actualBuffer, size, threshold, abs_threshold);
|
||||
break;
|
||||
case ngraph::element::Type_t::bf16:
|
||||
Compare<T_IE, ngraph::bfloat16>(reinterpret_cast<const ngraph::bfloat16 *>(expectedBuffer),
|
||||
actualBuffer, size, threshold, abs_threshold);
|
||||
break;
|
||||
// case ngraph::element::Type_t::i4: {
|
||||
// auto expectedOut = ngraph::helpers::convertOutputPrecision(
|
||||
// expected.second,
|
||||
// expected.first,
|
||||
// ngraph::element::Type_t::i8,
|
||||
// size);
|
||||
// Compare<T_IE, int8_t>(reinterpret_cast<const int8_t *>(expectedOut.data()),
|
||||
// actualBuffer, size, threshold, abs_threshold);
|
||||
// break;
|
||||
// }
|
||||
// case ngraph::element::Type_t::u4: {
|
||||
// auto expectedOut = ngraph::helpers::convertOutputPrecision(
|
||||
// expected.second,
|
||||
// expected.first,
|
||||
// ngraph::element::Type_t::u8,
|
||||
// size);
|
||||
// Compare<T_IE, uint8_t>(reinterpret_cast<const uint8_t *>(expectedOut.data()),
|
||||
// actualBuffer, size, threshold, abs_threshold);
|
||||
// break;
|
||||
// }
|
||||
case ngraph::element::Type_t::dynamic:
|
||||
case ngraph::element::Type_t::undefined:
|
||||
Compare<T_IE, T_IE>(reinterpret_cast<const T_IE *>(expectedBuffer), actualBuffer, size, threshold, abs_threshold);
|
||||
break;
|
||||
default: FAIL() << "Comparator for " << expected.first << " precision isn't supported";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void Compare(const std::pair<ngraph::element::Type, std::vector<std::uint8_t>> &expected,
|
||||
const InferenceEngine::Blob::Ptr &actual,
|
||||
float threshold,
|
||||
float abs_threshold) {
|
||||
const auto &precision = actual->getTensorDesc().getPrecision();
|
||||
auto k = static_cast<float>(expected.first.size()) / precision.size();
|
||||
// W/A for int4, uint4
|
||||
if (expected.first == ngraph::element::Type_t::u4 || expected.first == ngraph::element::Type_t::i4) {
|
||||
k /= 2;
|
||||
} else if (expected.first == ngraph::element::Type_t::undefined || expected.first == ngraph::element::Type_t::dynamic) {
|
||||
k = 1;
|
||||
}
|
||||
ASSERT_EQ(expected.second.size(), actual->byteSize() * k);
|
||||
|
||||
auto memory = InferenceEngine::as<InferenceEngine::MemoryBlob>(actual);
|
||||
IE_ASSERT(memory);
|
||||
const auto lockedMemory = memory->wmap();
|
||||
const auto actualBuffer = lockedMemory.as<const std::uint8_t *>();
|
||||
|
||||
const auto &size = actual->size();
|
||||
switch (precision) {
|
||||
case InferenceEngine::Precision::FP32:
|
||||
callCompare<float>(expected, reinterpret_cast<const float *>(actualBuffer), size, threshold, abs_threshold);
|
||||
break;
|
||||
case InferenceEngine::Precision::I32:
|
||||
callCompare<int32_t>(expected, reinterpret_cast<const int32_t *>(actualBuffer), size, threshold, abs_threshold);
|
||||
break;
|
||||
case InferenceEngine::Precision::I64:
|
||||
callCompare<int64_t>(expected, reinterpret_cast<const int64_t *>(actualBuffer), size, threshold, abs_threshold);
|
||||
break;
|
||||
case InferenceEngine::Precision::I8:
|
||||
callCompare<int8_t>(expected, reinterpret_cast<const int8_t *>(actualBuffer), size, threshold, abs_threshold);
|
||||
break;
|
||||
case InferenceEngine::Precision::U16:
|
||||
callCompare<uint16_t>(expected, reinterpret_cast<const uint16_t *>(actualBuffer), size, threshold, abs_threshold);
|
||||
break;
|
||||
case InferenceEngine::Precision::I16:
|
||||
callCompare<int16_t>(expected, reinterpret_cast<const int16_t *>(actualBuffer), size, threshold, abs_threshold);
|
||||
break;
|
||||
case InferenceEngine::Precision::BOOL:
|
||||
case InferenceEngine::Precision::U8:
|
||||
callCompare<uint8_t>(expected, reinterpret_cast<const uint8_t *>(actualBuffer), size, threshold, abs_threshold);
|
||||
break;
|
||||
case InferenceEngine::Precision::U64:
|
||||
callCompare<uint64_t>(expected, reinterpret_cast<const uint64_t *>(actualBuffer), size, threshold, abs_threshold);
|
||||
break;
|
||||
case InferenceEngine::Precision::BF16:
|
||||
callCompare<ngraph::bfloat16>(expected, reinterpret_cast<const ngraph::bfloat16 *>(actualBuffer), size, threshold, abs_threshold);
|
||||
break;
|
||||
case InferenceEngine::Precision::FP16:
|
||||
callCompare<ngraph::float16>(expected, reinterpret_cast<const ngraph::float16 *>(actualBuffer), size, threshold, abs_threshold);
|
||||
break;
|
||||
default:
|
||||
FAIL() << "Comparator for " << precision << " precision isn't supported";
|
||||
}
|
||||
}
|
||||
|
||||
void Compare(const std::vector<std::pair<ngraph::element::Type, std::vector<std::uint8_t>>> &expectedOutputs,
|
||||
const std::vector<InferenceEngine::Blob::Ptr> &actualOutputs,
|
||||
float threshold, float abs_threshold) {
|
||||
for (std::size_t outputIndex = 0; outputIndex < expectedOutputs.size(); ++outputIndex) {
|
||||
const auto &expected = expectedOutputs[outputIndex];
|
||||
const auto &actual = actualOutputs[outputIndex];
|
||||
Compare(expected, actual, threshold, abs_threshold);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void TransformationTestsF::accuracy_check(std::shared_ptr<ov::Model> ref_function,
|
||||
std::shared_ptr<ov::Model> cur_function) {
|
||||
try {
|
||||
if (ref_function->is_dynamic() || cur_function->is_dynamic()) {
|
||||
return;
|
||||
}
|
||||
std::vector<std::vector<uint8_t>> input_data;
|
||||
ngraph::element::TypeVector types;
|
||||
for (auto param : ref_function->get_parameters()) {
|
||||
types.push_back(param->get_element_type());
|
||||
|
||||
InferenceEngine::TensorDesc td(InferenceEngine::Precision::FP32, param->get_shape(), InferenceEngine::Layout::ANY);
|
||||
const auto &input = FuncTestUtils::createAndFillBlob(td);
|
||||
const auto &input_size = input->byteSize();
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
data.resize(input_size);
|
||||
|
||||
auto memory = InferenceEngine::as<InferenceEngine::MemoryBlob>(input);
|
||||
IE_ASSERT(memory);
|
||||
|
||||
const auto lockedMemory = memory->wmap();
|
||||
const auto buffer = lockedMemory.as<const std::uint8_t *>();
|
||||
std::copy(buffer, buffer + input_size, data.data());
|
||||
|
||||
input_data.push_back(std::move(data));
|
||||
}
|
||||
|
||||
auto ref_outputs = ngraph::helpers::interpreterFunction(ref_function, input_data, types);
|
||||
auto outputs = ngraph::helpers::interpreterFunction(cur_function, input_data, types);
|
||||
|
||||
IE_ASSERT(ref_outputs.size() == outputs.size());
|
||||
|
||||
for (size_t i = 0; i < ref_outputs.size(); ++i) {
|
||||
IE_ASSERT(ref_outputs[i].second.size() == outputs[i].second.size());
|
||||
auto * ref = reinterpret_cast<float *>(ref_outputs[i].second.data());
|
||||
auto * out = reinterpret_cast<float *>(outputs[i].second.data());
|
||||
IE_ASSERT(ref_outputs[i].second.size() / 8);
|
||||
size_t size = ref_outputs[i].second.size() / sizeof(float);
|
||||
Compare<float, float>(ref, out, size, 1e-5);
|
||||
}
|
||||
}
|
||||
catch (const std::runtime_error &re) {
|
||||
GTEST_FATAL_FAILURE_(re.what());
|
||||
} catch (const std::exception &ex) {
|
||||
GTEST_FATAL_FAILURE_(ex.what());
|
||||
} catch (...) {
|
||||
GTEST_FATAL_FAILURE_("Unknown failure occurred.");
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <ngraph/opsets/opset6.hpp>
|
||||
#include <ngraph/op/util/framework_node.hpp>
|
||||
#include <transformations/init_node_info.hpp>
|
||||
#include <openvino/core/model.hpp>
|
||||
|
||||
#include "ie_common.h"
|
||||
|
||||
@@ -44,8 +45,9 @@ public:
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
auto cloned_function = ngraph::clone_function(*function);
|
||||
if (!function_ref) {
|
||||
function_ref = ngraph::clone_function(*function);
|
||||
function_ref = cloned_function;
|
||||
}
|
||||
|
||||
manager.register_pass<ngraph::pass::CheckUniqueNames>(m_unh, m_soft_names_comparison);
|
||||
@@ -56,6 +58,10 @@ public:
|
||||
|
||||
auto res = comparator.compare(function, function_ref);
|
||||
ASSERT_TRUE(res.valid) << res.message;
|
||||
|
||||
if (m_enable_accuracy_check) {
|
||||
accuracy_check(cloned_function, function);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this is temporary solution to disable rt info checks that must be applied by default
|
||||
@@ -68,7 +74,13 @@ public:
|
||||
m_soft_names_comparison = true;
|
||||
}
|
||||
|
||||
std::shared_ptr<ngraph::Function> function, function_ref;
|
||||
void enable_accuracy_check() {
|
||||
m_enable_accuracy_check = true;
|
||||
}
|
||||
|
||||
void accuracy_check(std::shared_ptr<ov::Model> ref_function, std::shared_ptr<ov::Model> cur_function);
|
||||
|
||||
std::shared_ptr<ov::Model> function, function_ref;
|
||||
ngraph::pass::Manager manager;
|
||||
FunctionsComparator comparator;
|
||||
|
||||
@@ -76,4 +88,5 @@ private:
|
||||
std::shared_ptr<ngraph::pass::UniqueNamesHolder> m_unh;
|
||||
bool m_disable_rt_info_check{false};
|
||||
bool m_soft_names_comparison{true};
|
||||
bool m_enable_accuracy_check{false};
|
||||
};
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// Copyright (C) 2018-2021 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <transformations_visibility.hpp>
|
||||
|
||||
#include <openvino/pass/pass.hpp>
|
||||
#include <openvino/core/model.hpp>
|
||||
#include <ngraph/pass/graph_rewrite.hpp>
|
||||
|
||||
namespace ngraph {
|
||||
namespace pass {
|
||||
|
||||
class TRANSFORMATIONS_API ReverseInputChannelsFusion;
|
||||
|
||||
} // namespace pass
|
||||
} // namespace ngraph
|
||||
|
||||
/**
|
||||
* @ingroup ie_transformation_common_api
|
||||
* @brief ReverseInputChannelsFusion
|
||||
*/
|
||||
|
||||
class ngraph::pass::ReverseInputChannelsFusion: public ov::pass::ModelPass {
|
||||
public:
|
||||
NGRAPH_RTTI_DECLARATION;
|
||||
bool run_on_model(const std::shared_ptr<ov::Model> &) override;
|
||||
};
|
||||
@@ -92,9 +92,14 @@ inline std::string get_ie_output_name(const ngraph::Output<ngraph::Node>& output
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool has_constant_value(const std::shared_ptr<ngraph::opset4::Constant>& constant,
|
||||
bool has_constant_value(const std::shared_ptr<Node>& node,
|
||||
const T value,
|
||||
T epsilon = std::numeric_limits<T>::epsilon()) {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto constant = std::dynamic_pointer_cast<opset4::Constant>(node);
|
||||
if (!constant) {
|
||||
return false;
|
||||
}
|
||||
@@ -123,6 +128,32 @@ bool has_constant_value(const std::shared_ptr<ngraph::opset4::Constant>& constan
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool has_constant_value(const std::shared_ptr<Node>& node,
|
||||
const std::vector<T> values,
|
||||
T epsilon = std::numeric_limits<T>::epsilon()) {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto constant = std::dynamic_pointer_cast<opset4::Constant>(node);
|
||||
if (!constant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto const_values = constant->cast_vector<T>();
|
||||
|
||||
if (constant->get_element_type() == ngraph::element::f16 ||
|
||||
constant->get_element_type() == ngraph::element::f32 ||
|
||||
constant->get_element_type() == ngraph::element::f64 ||
|
||||
constant->get_element_type() == ngraph::element::bf16) {
|
||||
return std::equal(const_values.cbegin(), const_values.cend(), values.cbegin(),
|
||||
[&] (T lhs, T rhs) { return std::fabs(lhs - rhs) < epsilon; });
|
||||
}
|
||||
|
||||
return const_values == values;
|
||||
}
|
||||
|
||||
TRANSFORMATIONS_API bool get_single_value(const std::shared_ptr<op::Constant> & const_node, float & value);
|
||||
|
||||
TRANSFORMATIONS_API std::shared_ptr<ngraph::Node> normalize_constant(const std::shared_ptr<op::Constant> & constant,
|
||||
|
||||
+3
@@ -56,6 +56,7 @@
|
||||
#include <transformations/common_optimizations/subtract_fusion.hpp>
|
||||
#include <transformations/common_optimizations/reshape_sequence_fusion.hpp>
|
||||
#include <transformations/common_optimizations/nearest_neighbor_upsampling_fusion.hpp>
|
||||
#include <transformations/common_optimizations/ric_fusion.hpp>
|
||||
|
||||
NGRAPH_RTTI_DEFINITION(ngraph::pass::MOCTransformations, "MOCTransformations", 0);
|
||||
|
||||
@@ -174,6 +175,8 @@ bool ngraph::pass::MOCTransformations::run_on_model(const std::shared_ptr<ngraph
|
||||
conv_fusions->add_matcher<ngraph::pass::MultiplyGroupConvolutionBackpropDataFusion>();
|
||||
conv_fusions->set_name("ngraph::pass::ConvFusions");
|
||||
|
||||
manager.register_pass<ngraph::pass::ReverseInputChannelsFusion>();
|
||||
|
||||
manager.register_pass<ngraph::pass::ConstantFolding>();
|
||||
|
||||
manager.run_passes(f);
|
||||
|
||||
@@ -0,0 +1,661 @@
|
||||
// Copyright (C) 2018-2021 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#include "transformations/common_optimizations/ric_fusion.hpp"
|
||||
#include "transformations/utils/utils.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <ngraph/opsets/opset8.hpp>
|
||||
#include <ngraph/opsets/opset1.hpp>
|
||||
#include <ngraph/op/util/binary_elementwise_arithmetic.hpp>
|
||||
#include <ngraph/rt_info.hpp>
|
||||
#include <ngraph/pattern/op/wrap_type.hpp>
|
||||
#include <ngraph/pattern/op/or.hpp>
|
||||
#include <ngraph/log.hpp>
|
||||
#include <ngraph/pass/manager.hpp>
|
||||
#include <openvino/core/validation_util.hpp>
|
||||
#include "itt.hpp"
|
||||
|
||||
|
||||
NGRAPH_RTTI_DEFINITION(ngraph::pass::ReverseInputChannelsFusion, "ReverseInputChannelsFusion", 0);
|
||||
|
||||
namespace ngraph {
|
||||
namespace pass {
|
||||
namespace ric_attr {
|
||||
|
||||
// Attribute describes RIC type which we propagate.
|
||||
// Also, it contains callback which can expand this attribute to the real RIC sub-graph.
|
||||
// In addition, attribute has some functionality and properties for propagation.
|
||||
class Attribute {
|
||||
public:
|
||||
using callback_t = std::function<void(Input<Node>, const Attribute &)>;
|
||||
|
||||
Attribute(std::vector<int64_t> order, int64_t axis, bool is_final = false, bool is_initial = false)
|
||||
: m_order(std::move(order)), m_axis(axis), m_is_final(is_final), m_is_initial(is_initial) {
|
||||
m_can_be_fused.emplace_back(std::make_shared<bool>(true));
|
||||
}
|
||||
|
||||
// Method which is used to create a copy of attribute for further propagation.
|
||||
// TODO: can be removed and replaced with regular copy but we need to get rid of
|
||||
// is_initial flag and use some other way to detect original RIC output.
|
||||
Attribute propagate() const {
|
||||
Attribute attr(m_order, m_axis);
|
||||
attr.m_can_be_fused = m_can_be_fused;
|
||||
return attr;
|
||||
}
|
||||
|
||||
void set_is_final(bool is_final) { m_is_final = is_final; }
|
||||
|
||||
void set_can_be_fused(bool can_be_fused) {
|
||||
std::for_each(m_can_be_fused.cbegin(), m_can_be_fused.cend(),
|
||||
[can_be_fused](const std::shared_ptr<bool> & state) {
|
||||
*state = can_be_fused;
|
||||
});
|
||||
}
|
||||
|
||||
void set_callback(callback_t callback) {
|
||||
m_callback = std::move(callback);
|
||||
}
|
||||
|
||||
// Apply callback to materialize RIC inside graph
|
||||
void operator() (Input<Node> input) const {
|
||||
m_callback(input, *this);
|
||||
}
|
||||
|
||||
bool can_be_fused() const {
|
||||
return std::all_of(m_can_be_fused.cbegin(), m_can_be_fused.cend(),
|
||||
[](const std::shared_ptr<bool> & state) {
|
||||
return *state;
|
||||
});
|
||||
}
|
||||
|
||||
// For cases when we propagate through operation with multiple inputs like Eltwise
|
||||
// we have to merge RIC attrs from all inputs. To check that given attr be merged with
|
||||
// current we check the order and axis which must be the same.
|
||||
bool can_be_merged_with(const Attribute & other) {
|
||||
return m_order == other.m_order && m_axis == other.m_axis;
|
||||
}
|
||||
|
||||
// When merging two and more attrs for further propagation we have to keep can_be_fused references
|
||||
// for cases when fusion is not possible, so we can update all related attrs.
|
||||
void merge_with(const Attribute & other) {
|
||||
m_can_be_fused.insert(m_can_be_fused.end(),
|
||||
other.m_can_be_fused.begin(),
|
||||
other.m_can_be_fused.end());
|
||||
}
|
||||
|
||||
const std::vector<int64_t> & get_order() const { return m_order; }
|
||||
|
||||
void set_order(const std::vector<int64_t> & order) { m_order = order; }
|
||||
|
||||
int64_t get_axis() const { return m_axis; }
|
||||
|
||||
void set_axis(int64_t axis) { m_axis = axis; }
|
||||
|
||||
bool is_final() const { return m_is_final; }
|
||||
|
||||
bool is_initial() const { return m_is_initial; }
|
||||
|
||||
private:
|
||||
std::vector<int64_t> m_order;
|
||||
int64_t m_axis;
|
||||
|
||||
// Specifies whether RIC can be fused or not. vector is needed to keep references to other
|
||||
// attributes that were participated during merge.
|
||||
std::vector<std::shared_ptr<bool>> m_can_be_fused;
|
||||
|
||||
// true - means that current RIC attribute is final and can be materialized
|
||||
// false - means that current RIC attribute is temporary and need only for propagation
|
||||
bool m_is_final;
|
||||
|
||||
// true - means that current RIC attribute is an initial attribute and belongs to real RIC output
|
||||
// false - means that current RIC attribute is temporary and need only for propagation
|
||||
bool m_is_initial;
|
||||
|
||||
// Callback specifies the action for RIC materialization for given input port.
|
||||
// In most cases it should insert Gather operation for the input.
|
||||
std::function<void(Input<Node>, const Attribute &)> m_callback =
|
||||
[](Input<Node>, const Attribute &) {};
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
using is_port = typename std::enable_if<!std::is_convertible<T, std::shared_ptr<Node>>::value>::type;
|
||||
|
||||
template <typename T, typename = is_port<T>>
|
||||
void set(T port, const Attribute & ric_attr) {
|
||||
auto & attrs = port.get_rt_info();
|
||||
attrs["reverse_input_channel_index"] = ric_attr;
|
||||
}
|
||||
|
||||
// Available only for output ports
|
||||
void init(Output<Node> output, std::vector<int64_t> order, int64_t axis) {
|
||||
set(output, Attribute(std::move(order), axis, false, true));
|
||||
}
|
||||
|
||||
template <typename T, typename = is_port<T>>
|
||||
bool has(const T & port) {
|
||||
const auto & attrs = port.get_rt_info();
|
||||
return attrs.count("reverse_input_channel_index");
|
||||
}
|
||||
|
||||
template <typename T, typename = is_port<T>>
|
||||
Attribute get(const T & port) {
|
||||
const auto & attrs = port.get_rt_info();
|
||||
auto res = attrs.find("reverse_input_channel_index");
|
||||
if (res != attrs.end()) {
|
||||
return res->second.template as<Attribute>();
|
||||
}
|
||||
throw ngraph_error("reverse_input_channel_index is missing in given port");
|
||||
}
|
||||
|
||||
template <typename T, typename = is_port<T>>
|
||||
void erase(T port) {
|
||||
auto & rt_info = port.get_rt_info();
|
||||
rt_info.erase("reverse_input_channel_index");
|
||||
}
|
||||
}// namespace
|
||||
}// namespace ric_attr
|
||||
|
||||
namespace init {
|
||||
class SplitConcat : public ngraph::pass::MatcherPass {
|
||||
public:
|
||||
SplitConcat() {
|
||||
MATCHER_SCOPE(SplitConcat);
|
||||
auto split_p = pattern::wrap_type<opset8::Split>();
|
||||
auto pattern_root = pattern::wrap_type<opset8::Concat>({split_p, split_p, split_p});
|
||||
|
||||
auto callback = [=](pattern::Matcher& m) {
|
||||
const auto & pattern_map = m.get_pattern_value_map();
|
||||
auto concat = ov::as_type_ptr<opset8::Concat>(pattern_map.at(pattern_root).get_node_shared_ptr());
|
||||
auto split = ov::as_type_ptr<opset8::Split>(pattern_map.at(split_p).get_node_shared_ptr());
|
||||
if (!concat || !split) return false;
|
||||
|
||||
// Avoid cases with two consecutive Split->Concat
|
||||
if (ric_attr::has(split->input_value(0))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<int64_t> order;
|
||||
order.reserve(split->get_num_splits());
|
||||
|
||||
for (const auto & input : concat->inputs()) {
|
||||
auto split_output = input.get_source_output();
|
||||
if (split_output.get_node() != split.get()) return false;
|
||||
|
||||
// Check that Concat is the only Split consumer and order of Split outputs
|
||||
// satisfies expected order for reverse input channel case.
|
||||
for (const auto & target_input : split_output.get_target_inputs()) {
|
||||
if (target_input.get_node() != concat.get()) {
|
||||
return false;
|
||||
}
|
||||
order.emplace_back(split_output.get_index());
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all order values are unique, otherwise it is not RIC
|
||||
std::set<int64_t> unique_values(order.cbegin(), order.cend());
|
||||
if (unique_values.size() != order.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark-up RIC output
|
||||
ric_attr::init(concat, order, concat->get_axis());
|
||||
return true;
|
||||
};
|
||||
|
||||
auto m = std::make_shared<pattern::Matcher>(pattern_root, matcher_name);
|
||||
register_matcher(m, callback);
|
||||
}
|
||||
};
|
||||
|
||||
class Gather : public ngraph::pass::MatcherPass {
|
||||
public:
|
||||
Gather() {
|
||||
MATCHER_SCOPE(Gather);
|
||||
auto input_p = pattern::any_input(pattern::has_static_rank());
|
||||
auto indices_p = pattern::any_input();
|
||||
auto axis_p = pattern::wrap_type<opset8::Constant>();
|
||||
auto pattern_root = pattern::wrap_type<opset8::Gather>({input_p, indices_p, axis_p});
|
||||
|
||||
auto callback = [=](pattern::Matcher& m) {
|
||||
const auto & pattern_map = m.get_pattern_value_map();
|
||||
const auto & output = pattern_map.at(pattern_root);
|
||||
|
||||
auto order = ov::get_constant_from_source(pattern_map.at(indices_p));
|
||||
auto axis = ov::get_constant_from_source(pattern_map.at(axis_p));
|
||||
if (!order || !axis) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Avoid cases with two consecutive Gathers
|
||||
if (ric_attr::has(pattern_map.at(input_p))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto axis_value = axis->cast_vector<int64_t>().at(0);
|
||||
|
||||
// This constraint helps to avoid detection of other Gathers that do not perform RIC
|
||||
const auto & data_shape = m.get_match_root()->input(0).get_partial_shape();
|
||||
if (shape_size(order->get_shape()) == 1 ||
|
||||
axis_value < 0 ||
|
||||
axis_value >= data_shape.rank().get_length() ||
|
||||
data_shape[axis_value].is_dynamic() ||
|
||||
shape_size(order->get_shape()) != static_cast<size_t>(data_shape[axis_value].get_length())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that all order values are unique, otherwise it is not RIC
|
||||
const auto & order_values = order->cast_vector<int64_t>();
|
||||
std::set<int64_t> unique_values(order_values.cbegin(), order_values.cend());
|
||||
if (unique_values.size() != order_values.size()) {
|
||||
return false;
|
||||
}
|
||||
ric_attr::init(output, order_values, axis_value);
|
||||
return true;
|
||||
};
|
||||
|
||||
auto m = std::make_shared<pattern::Matcher>(pattern_root, matcher_name);
|
||||
register_matcher(m, callback);
|
||||
}
|
||||
};
|
||||
}// namespace init
|
||||
|
||||
namespace prop {
|
||||
namespace {
|
||||
std::shared_ptr<opset8::Constant> create_const(const std::vector<int64_t> & values) {
|
||||
return opset8::Constant::create(ov::element::i64, ov::Shape{values.size()}, values);
|
||||
}
|
||||
}// namespace
|
||||
|
||||
class Binary : public ngraph::pass::MatcherPass {
|
||||
public:
|
||||
Binary() {
|
||||
MATCHER_SCOPE(Binary);
|
||||
auto pattern_root = pattern::wrap_type<op::util::BinaryElementwiseArithmetic, opset8::FakeQuantize>();
|
||||
|
||||
auto callback = [=](pattern::Matcher& m) {
|
||||
const auto & root = m.get_match_root();
|
||||
const auto & inputs = root->inputs();
|
||||
|
||||
std::map<size_t, ric_attr::Attribute> attrs;
|
||||
for (const auto & input : inputs) {
|
||||
auto output = input.get_source_output();
|
||||
if (ric_attr::has(output)) {
|
||||
attrs.insert({input.get_index(), ric_attr::get(output).propagate()});
|
||||
} else if (!ov::is_type<opset8::Constant>(output.get_node())) {
|
||||
// If number of non-constant inputs and without RIC attr is greater than 0 we have to skip
|
||||
// propagation because it is not efficient to have a lot of RIC copies on data path.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (attrs.empty()) return false;
|
||||
|
||||
// Check that all RIC attrs can be merged and then merge them
|
||||
auto ric = attrs.begin()->second;
|
||||
auto rank = root->get_input_partial_shape(attrs.begin()->first).rank();
|
||||
if (rank.is_dynamic()) return false;
|
||||
auto data_rank = rank.get_length();
|
||||
|
||||
for (const auto & item : attrs) {
|
||||
const auto & input_rank = root->get_input_partial_shape(item.first).rank();
|
||||
if (input_rank.is_static() &&
|
||||
input_rank.get_length() == data_rank &&
|
||||
ric.can_be_merged_with(item.second) ) {
|
||||
ric.merge_with(item.second);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto & input : inputs) {
|
||||
// Skip input that have RIC attribute
|
||||
if (attrs.count(input.get_index())) continue;
|
||||
|
||||
auto const_output = input.get_source_output();
|
||||
const auto & shape = const_output.get_shape();
|
||||
const int64_t & shape_rank = static_cast<int64_t>(shape.size());
|
||||
if (shape_rank > data_rank) {
|
||||
// TODO: handle case when constant input broadcast another one
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data_rank - shape_rank > ric.get_axis()) {
|
||||
// we don't have to insert RIC for constant, so we keep propagating
|
||||
ric_attr::set(m.get_match_value(), ric);
|
||||
continue;
|
||||
}
|
||||
|
||||
const int64_t & new_axis = ric.get_axis() - (data_rank - shape_rank);
|
||||
if (shape[new_axis] == 1) {
|
||||
// we don't have to insert RIC for constant, so we keep propagating
|
||||
ric_attr::set(m.get_match_value(), ric);
|
||||
continue;
|
||||
}
|
||||
|
||||
// finally, insert RIC
|
||||
auto ric_const = ric;
|
||||
ric_const.set_axis(new_axis);
|
||||
ric_const.set_is_final(true);
|
||||
ric_const.set_callback([](Input<Node> input, const ric_attr::Attribute & attr) {
|
||||
auto output = input.get_source_output();
|
||||
auto gather = std::make_shared<opset8::Gather>(output, create_const(attr.get_order()),
|
||||
create_const({attr.get_axis()}));
|
||||
input.replace_source_output(gather);
|
||||
// TODO: copy runtime info from RIC sub-graph
|
||||
});
|
||||
ric_attr::set(input, ric_const);
|
||||
}
|
||||
|
||||
ric_attr::set(m.get_match_value(), ric);
|
||||
return true;
|
||||
};
|
||||
|
||||
auto m = std::make_shared<pattern::Matcher>(pattern_root, matcher_name);
|
||||
register_matcher(m, callback);
|
||||
}
|
||||
};
|
||||
|
||||
class Convolution : public ngraph::pass::MatcherPass {
|
||||
public:
|
||||
Convolution() {
|
||||
MATCHER_SCOPE(Convolution);
|
||||
// Handle Convolution with Constant and FQ on weights. As Convolution is
|
||||
// a terminal node, so we do not propagate RIC attribute further and insert
|
||||
// final RIC attribute to the weights input.
|
||||
auto input_p = pattern::any_input(ric_attr::has<Output<Node>>);
|
||||
auto pattern_root = pattern::wrap_type<opset8::Convolution>({input_p,
|
||||
pattern::wrap_type<opset8::Constant,
|
||||
opset8::FakeQuantize>()});
|
||||
auto callback = [=](pattern::Matcher& m) {
|
||||
auto conv = m.get_match_root();
|
||||
auto ric = ric_attr::get(conv->input_value(0)).propagate();
|
||||
if (ric.get_axis() != 1) return false;
|
||||
|
||||
ric.set_is_final(true);
|
||||
ric.set_callback([](Input<Node> input, const ric_attr::Attribute & attr) {
|
||||
auto weights = input.get_source_output();
|
||||
auto gather = std::make_shared<opset8::Gather>(weights, create_const(attr.get_order()),
|
||||
create_const({1} /* output channel */));
|
||||
input.replace_source_output(gather);
|
||||
// TODO: copy runtime info from RIC sub-graph
|
||||
});
|
||||
|
||||
if (auto fq = std::dynamic_pointer_cast<opset8::FakeQuantize>(conv->get_input_node_shared_ptr(1))) {
|
||||
// Set final RIC attr to the first FQ input
|
||||
ric_attr::set(fq->input(0), ric);
|
||||
|
||||
// Apply Binary transformation for FQ to handle 1..5 inputs
|
||||
ric.set_is_final(false);
|
||||
ric_attr::set(fq->input_value(0), ric); // set ric attr to simulate propagation flow
|
||||
Binary().apply(fq);
|
||||
} else {
|
||||
ric_attr::set(conv->input(1), ric);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto m = std::make_shared<pattern::Matcher>(pattern_root, matcher_name);
|
||||
register_matcher(m, callback);
|
||||
}
|
||||
};
|
||||
|
||||
class GroupConvolution : public ngraph::pass::MatcherPass {
|
||||
public:
|
||||
GroupConvolution() {
|
||||
MATCHER_SCOPE(GroupConvolution);
|
||||
auto input_p = pattern::any_input(ric_attr::has<Output<Node>>);
|
||||
auto pattern_root = pattern::wrap_type<opset8::GroupConvolution>({input_p, pattern::wrap_type<opset8::Constant,
|
||||
opset8::FakeQuantize>(pattern::has_static_shape())});
|
||||
|
||||
auto callback = [=](pattern::Matcher& m) {
|
||||
auto conv = m.get_match_root();
|
||||
const auto & weights_shape = conv->input_value(1).get_shape();
|
||||
const int64_t & group = static_cast<int64_t>(weights_shape.at(0));
|
||||
const int64_t & channels = static_cast<int64_t>(weights_shape.at(1));
|
||||
|
||||
auto ric = ric_attr::get(conv->input_value(0)).propagate();
|
||||
if (ric.get_order().size() != static_cast<size_t>(group) || ric.get_axis() != 1) {
|
||||
// TODO: insert RIC when group == 1
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update weights with RIC attribute
|
||||
auto ric_weights = ric;
|
||||
ric_weights.set_is_final(true);
|
||||
ric_weights.set_axis(0);
|
||||
ric_weights.set_callback([](Input<Node> input, const ric_attr::Attribute & attr) {
|
||||
auto weights = input.get_source_output();
|
||||
auto gather = std::make_shared<opset8::Gather>(weights, create_const(attr.get_order()),
|
||||
create_const({0} /* output channel */));
|
||||
input.replace_source_output(gather);
|
||||
// TODO: copy runtime info from RIC sub-graph
|
||||
});
|
||||
|
||||
if (auto fq = std::dynamic_pointer_cast<opset8::FakeQuantize>(conv->get_input_node_shared_ptr(1))) {
|
||||
// Set final RIC attr to the first FQ input
|
||||
ric_attr::set(fq->input(0), ric_weights);
|
||||
|
||||
// Apply Binary transformation for FQ to handle 1..5 inputs
|
||||
ric_weights.set_is_final(false);
|
||||
ric_attr::set(fq->input_value(0), ric_weights); // set ric attr to simulate propagation flow
|
||||
Binary().apply(fq);
|
||||
} else {
|
||||
ric_attr::set(conv->input(1), ric_weights);
|
||||
}
|
||||
|
||||
// Calculate new order for RIC propagation
|
||||
const int64_t output_channels = group * channels;
|
||||
std::vector<int64_t> new_order;
|
||||
new_order.reserve(output_channels);
|
||||
for (const auto & index : ric.get_order()) {
|
||||
for (int64_t pos = index * channels, i = 0; i < channels; ++i, ++pos) {
|
||||
new_order.emplace_back(pos);
|
||||
}
|
||||
}
|
||||
assert(new_order.size() == static_cast<size_t>(output_channels));
|
||||
|
||||
ric.set_order(new_order);
|
||||
ric_attr::set(conv->output(0), ric);
|
||||
return true;
|
||||
};
|
||||
|
||||
auto m = std::make_shared<pattern::Matcher>(pattern_root, matcher_name);
|
||||
register_matcher(m, callback);
|
||||
}
|
||||
};
|
||||
|
||||
class ShapeOf : public ngraph::pass::MatcherPass {
|
||||
public:
|
||||
ShapeOf() {
|
||||
MATCHER_SCOPE(ShapeOf);
|
||||
auto pattern_root = pattern::wrap_type<opset1::ShapeOf, opset8::ShapeOf>();
|
||||
|
||||
auto callback = [=](pattern::Matcher& m) {
|
||||
// Skip propagation for ShapeOf path
|
||||
return true;
|
||||
};
|
||||
|
||||
auto m = std::make_shared<pattern::Matcher>(pattern_root, matcher_name);
|
||||
register_matcher(m, callback);
|
||||
}
|
||||
};
|
||||
|
||||
class PassThrough : public ngraph::pass::MatcherPass {
|
||||
public:
|
||||
PassThrough() {
|
||||
MATCHER_SCOPE(PassThrough);
|
||||
auto pattern_root = pattern::wrap_type<op::util::UnaryElementwiseArithmetic,
|
||||
opset8::Convert, opset8::Pad, opset8::PRelu>();
|
||||
|
||||
auto callback = [=](pattern::Matcher& m) {
|
||||
auto root = m.get_match_root();
|
||||
if (!ric_attr::has(root->input_value(0))) return false;
|
||||
ric_attr::set(root->output(0), ric_attr::get(root->input_value(0)).propagate());
|
||||
return true;
|
||||
};
|
||||
|
||||
auto m = std::make_shared<pattern::Matcher>(pattern_root, matcher_name);
|
||||
register_matcher(m, callback);
|
||||
}
|
||||
};
|
||||
|
||||
class Transpose : public ngraph::pass::MatcherPass {
|
||||
public:
|
||||
Transpose() {
|
||||
MATCHER_SCOPE(Transpose);
|
||||
auto input_p = pattern::any_input(ric_attr::has<Output<Node>>);
|
||||
auto order_p = pattern::wrap_type<opset8::Constant>();
|
||||
auto pattern_root = pattern::wrap_type<opset8::Transpose>({input_p, order_p});
|
||||
|
||||
auto callback = [=](pattern::Matcher& m) {
|
||||
const auto & pattern_map = m.get_pattern_value_map();
|
||||
auto input = pattern_map.at(input_p);
|
||||
auto ric = ric_attr::get(input).propagate();
|
||||
|
||||
auto order_node = std::dynamic_pointer_cast<opset8::Constant>(pattern_map.at(order_p).get_node_shared_ptr());
|
||||
auto order = order_node->cast_vector<int64_t>();
|
||||
|
||||
int64_t new_axis = std::find(order.begin(), order.end(), ric.get_axis()) - order.begin();
|
||||
ric.set_axis(new_axis);
|
||||
|
||||
ric_attr::set(m.get_match_value(), ric);
|
||||
return true;
|
||||
};
|
||||
|
||||
auto m = std::make_shared<pattern::Matcher>(pattern_root, matcher_name);
|
||||
register_matcher(m, callback);
|
||||
}
|
||||
};
|
||||
|
||||
class Unsupported : public ngraph::pass::MatcherPass {
|
||||
public:
|
||||
Unsupported() {
|
||||
MATCHER_SCOPE(Unsupported);
|
||||
auto pattern_root = pattern::any_input();
|
||||
auto callback = [=](pattern::Matcher& m) {
|
||||
for (const auto & input : m.get_match_root()->input_values()) {
|
||||
if (ric_attr::has(input)) {
|
||||
auto ric = ric_attr::get(input);
|
||||
ric.set_can_be_fused(false);
|
||||
NGRAPH_DEBUG << "Node is unsupported by RIC Fusion: " << *m.get_match_root() << std::endl;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto m = std::make_shared<pattern::Matcher>(pattern_root, matcher_name);
|
||||
register_matcher(m, callback);
|
||||
}
|
||||
};
|
||||
}// namespace prop
|
||||
|
||||
namespace fuse {
|
||||
namespace {
|
||||
bool need_to_erase_ric(const Output<Node> & output) {
|
||||
if (!ric_attr::has(output)) return false;
|
||||
const auto & ric = ric_attr::get(output);
|
||||
return ric.can_be_fused() && ric.is_initial();
|
||||
}
|
||||
}// namespace
|
||||
|
||||
class InsertReverseInputChannel : public ngraph::pass::MatcherPass {
|
||||
public:
|
||||
InsertReverseInputChannel() {
|
||||
MATCHER_SCOPE(InsertReverseInputChannel);
|
||||
auto pattern_root = pattern::any_input();
|
||||
auto callback = [](pattern::Matcher& m) {
|
||||
const auto & node = m.get_match_root();
|
||||
for (const auto & input : node->inputs()) {
|
||||
if (!ric_attr::has(input)) continue;
|
||||
const auto & ric = ric_attr::get(input);
|
||||
if (ric.can_be_fused() && ric.is_final()) {
|
||||
ric(input);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto m = std::make_shared<pattern::Matcher>(pattern_root, matcher_name);
|
||||
register_matcher(m, callback);
|
||||
}
|
||||
};
|
||||
|
||||
class EraseSplitConcat : public ngraph::pass::MatcherPass {
|
||||
public:
|
||||
EraseSplitConcat() {
|
||||
MATCHER_SCOPE(EraseSplitConcat);
|
||||
auto input_p = pattern::any_input();
|
||||
auto split_p = pattern::wrap_type<opset8::Split>({input_p, pattern::any_input()});
|
||||
auto pattern_root = pattern::wrap_type<opset8::Concat>({split_p, split_p, split_p}, need_to_erase_ric);
|
||||
|
||||
auto callback = [=](pattern::Matcher& m) {
|
||||
const auto & pattern_map = m.get_pattern_value_map();
|
||||
auto output = pattern_map.at(pattern_root);
|
||||
auto input = pattern_map.at(input_p);
|
||||
output.replace(input);
|
||||
return true;
|
||||
};
|
||||
|
||||
auto m = std::make_shared<pattern::Matcher>(pattern_root, matcher_name);
|
||||
register_matcher(m, callback);
|
||||
}
|
||||
};
|
||||
|
||||
class EraseGather : public ngraph::pass::MatcherPass {
|
||||
public:
|
||||
EraseGather() {
|
||||
MATCHER_SCOPE(EraseGather);
|
||||
auto input_p = pattern::any_input();
|
||||
auto pattern_root = pattern::wrap_type<opset8::Gather>({input_p, pattern::any_input(),
|
||||
pattern::any_input()},
|
||||
need_to_erase_ric);
|
||||
auto callback = [=](pattern::Matcher& m) {
|
||||
const auto & pattern_map = m.get_pattern_value_map();
|
||||
auto output = pattern_map.at(pattern_root);
|
||||
auto input = pattern_map.at(input_p);
|
||||
output.replace(input);
|
||||
return true;
|
||||
};
|
||||
|
||||
auto m = std::make_shared<pattern::Matcher>(pattern_root, matcher_name);
|
||||
register_matcher(m, callback);
|
||||
}
|
||||
};
|
||||
}// namespace fuse
|
||||
|
||||
bool ngraph::pass::ReverseInputChannelsFusion::run_on_model(const std::shared_ptr<ov::Model> & model) {
|
||||
Manager m;
|
||||
m.set_per_pass_validation(false);
|
||||
|
||||
// First we need to initialize and propagate RIC attributes through entire graph
|
||||
auto ric_prop = m.register_pass<GraphRewrite>();
|
||||
ric_prop->add_matcher<init::SplitConcat>();
|
||||
ric_prop->add_matcher<init::Gather>();
|
||||
ric_prop->add_matcher<prop::Convolution>();
|
||||
ric_prop->add_matcher<prop::GroupConvolution>();
|
||||
ric_prop->add_matcher<prop::Binary>();
|
||||
ric_prop->add_matcher<prop::ShapeOf>();
|
||||
ric_prop->add_matcher<prop::Transpose>();
|
||||
ric_prop->add_matcher<prop::PassThrough>();
|
||||
ric_prop->add_matcher<prop::Unsupported>();
|
||||
|
||||
// TODO: validate attributes by request
|
||||
|
||||
// Second we fuse available RIC into nodes and remove original nodes related to fused RIC
|
||||
auto ric_fuse = m.register_pass<GraphRewrite>();
|
||||
ric_fuse->add_matcher<fuse::InsertReverseInputChannel>();
|
||||
ric_fuse->add_matcher<fuse::EraseSplitConcat>();
|
||||
ric_fuse->add_matcher<fuse::EraseGather>();
|
||||
|
||||
m.run_passes(model);
|
||||
return false;
|
||||
}
|
||||
}// namespace pass
|
||||
}// namespace ngraph
|
||||
Reference in new issue
Block a user