[DOCS] Transformations python api docs for 24.2 (#24949)

Porting: 
https://github.com/openvinotoolkit/openvino/pull/24121
This commit is contained in:
Sebastian Golebiewski 2024-06-12 06:49:39 +02:00 committed by GitHub
parent c2b15154b4
commit 23fc29a6db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1067 additions and 54 deletions

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b78713020891a6655e3860338b7db430840560d8f2860162eca3d00b84533f24
size 36362

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:353c5b7f35a8f9f6c2c3074485a5a4e51f8c6ee776214444666da4037cc44832
size 45347

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:696672a8c38260147f2fa68d266866ab4390ef919fa092cea1149b0eb529c68e
size 58557

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d378718b968f6a40555b31bf54af83fac978c3bfc07e234f9b1370229222cccd
size 31092

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6b3511899521a49c1bfdc569e16b8ad1e12157fe107d2b86d25ee04ff67a85b2
size 43066

View File

@ -171,8 +171,8 @@ if (m->match(node->output(0))) {
bool openvino_api_examples(std::shared_ptr<ov::Node> node) {
{
// ! [ov:ports_example]
// Let's suppose that node is opset8::Convolution operation
// as we know opset8::Convolution has two input ports (data, weights) and one output port
// Let's suppose that node is of ov::op::v0::Convolution type.
// As we know ov::op::v0::Convolution has two input ports (data, weights) and one output port.
ov::Input<ov::Node> data = node->input(0);
ov::Input<ov::Node> weights = node->input(1);
ov::Output<ov::Node> output = node->output(0);
@ -181,7 +181,7 @@ ov::Output<ov::Node> output = node->output(0);
auto pshape = data.get_partial_shape();
auto el_type = data.get_element_type();
// Getting parent for input port
// Getting parent for input port i.e. Output mapped by the input
ov::Output<ov::Node> parent_output;
parent_output = data.get_source_output();
@ -216,15 +216,15 @@ return true;
// ! [ov:replace_node]
bool ov_replace_node(std::shared_ptr<ov::Node> node) {
// Step 1. Verify that node has opset8::Negative type
auto neg = std::dynamic_pointer_cast<ov::opset8::Negative>(node);
// Step 1. Verify that node is of type ov::op::v0::Negative
auto neg = std::dynamic_pointer_cast<ov::op::v0::Negative>(node);
if (!neg) {
return false;
}
// Step 2. Create opset8::Multiply operation where the first input is negative operation input and second as Constant with -1 value
auto mul = std::make_shared<ov::opset8::Multiply>(neg->input_value(0),
ov::opset8::Constant::create(neg->get_element_type(), ov::Shape{1}, {-1}));
// Step 2. Create ov::op::v1::Multiply operation with the first input being the output going into Negative and second as Constant with -1 value
auto mul = std::make_shared<ov::op::v1::Multiply>(neg->input_value(0),
ov::op::v0::Constant::create(neg->get_element_type(), ov::Shape{1}, {-1}));
mul->set_friendly_name(neg->get_friendly_name());
ov::copy_runtime_info(neg, mul);
@ -233,18 +233,18 @@ bool ov_replace_node(std::shared_ptr<ov::Node> node) {
ov::replace_node(neg, mul);
return true;
// Step 4. Negative operation will be removed automatically because all consumers was moved to Multiply operation
// Step 4. Negative operation will be removed automatically because all consumers were moved to Multiply operation
}
// ! [ov:replace_node]
bool ov_manual_replace_node(std::shared_ptr<ov::Node> node) {
auto neg = std::dynamic_pointer_cast<ov::opset8::Negative>(node);
auto neg = std::dynamic_pointer_cast<ov::op::v0::Negative>(node);
if (!neg) {
return false;
}
auto mul = std::make_shared<ov::opset8::Multiply>(neg->input_value(0),
ov::opset8::Constant::create(neg->get_element_type(), ov::Shape{1}, {-1}));
auto mul = std::make_shared<ov::op::v1::Multiply>(neg->input_value(0),
ov::op::v0::Constant::create(neg->get_element_type(), ov::Shape{1}, {-1}));
mul->set_friendly_name(neg->get_friendly_name());
ov::copy_runtime_info(neg, mul);
@ -262,8 +262,8 @@ void insert_example(std::shared_ptr<ov::Node> node) {
// Get all consumers for node
auto consumers = node->output(0).get_target_inputs();
// Step 2. Create new node. Let it be opset8::Relu.
auto new_node = std::make_shared<ov::opset8::Relu>(node);
// Step 2. Create new node ov::op::v0::Relu.
auto new_node = std::make_shared<ov::op::v0::Relu>(node);
// Step 3. Reconnect all consumers to new_node
for (auto input : consumers) {
@ -277,7 +277,7 @@ void insert_example_with_copy(std::shared_ptr<ov::Node> node) {
// Make a node copy
auto node_copy = node->clone_with_new_inputs(node->input_values());
// Create new node
auto new_node = std::make_shared<ov::opset8::Relu>(node_copy);
auto new_node = std::make_shared<ov::op::v0::Relu>(node_copy);
ov::replace_node(node, new_node);
}
// ! [ov:insert_node_with_copy]
@ -290,12 +290,12 @@ bool success = ov::replace_output_update_name(node->output(0), node->input_value
}
void replace_friendly_name() {
auto div = std::make_shared<ov::opset8::Divide>();
auto div = std::make_shared<ov::op::v1::Divide>();
// ! [ov:replace_friendly_name]
// Replace Div operation with Power and Multiply sub-graph and set original friendly name to Multiply operation
auto pow = std::make_shared<ov::opset8::Power>(div->input(1).get_source_output(),
auto pow = std::make_shared<ov::op::v1::Power>(div->input(1).get_source_output(),
ov::op::v0::Constant::create(div->get_input_element_type(1), ov::Shape{1}, {-1}));
auto mul = std::make_shared<ov::opset8::Multiply>(div->input(0).get_source_output(), pow);
auto mul = std::make_shared<ov::op::v1::Multiply>(div->input(0).get_source_output(), pow);
mul->set_friendly_name(div->get_friendly_name());
ov::replace_node(div, mul);
// ! [ov:replace_friendly_name]
@ -304,10 +304,10 @@ ov::replace_node(div, mul);
void constant_subgraph() {
// ! [ov:constant_subgraph]
// After ConstantFolding pass Power will be replaced with Constant
auto input = std::make_shared<ov::opset8::Parameter>(ov::element::f32, ov::Shape{1});
auto pow = std::make_shared<ov::opset8::Power>(ov::opset8::Constant::create(ov::element::f32, ov::Shape{1}, {2}),
ov::opset8::Constant::create(ov::element::f32, ov::Shape{1}, {3}));
auto mul = std::make_shared<ov::opset8::Multiply>(input /* not constant input */, pow);
auto input = std::make_shared<ov::op::v0::Parameter>(ov::element::f32, ov::Shape{1});
auto pow = std::make_shared<ov::op::v1::Power>(ov::op::v0::Constant::create(ov::element::f32, ov::Shape{1}, {2}),
ov::op::v0::Constant::create(ov::element::f32, ov::Shape{1}, {3}));
auto mul = std::make_shared<ov::op::v1::Multiply>(input /* not constant input */, pow);
// ! [ov:constant_subgraph]
}

View File

@ -0,0 +1,251 @@
// Copyright (C) 2018-2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
// ! [ov:imports]
#include <gtest/gtest.h>
#include "common_test_utils/matcher.hpp"
#include "openvino/op/abs.hpp"
#include "openvino/op/add.hpp"
#include "openvino/op/matmul.hpp"
#include "openvino/op/parameter.hpp"
#include "openvino/op/relu.hpp"
#include "openvino/op/sigmoid.hpp"
#include "openvino/pass/pattern/op/optional.hpp"
#include "openvino/pass/pattern/op/or.hpp"
#include "openvino/pass/pattern/op/wrap_type.hpp"
#include "transformations/utils/utils.hpp"
using namespace ov;
using namespace ov::pass;
using namespace std;
// ! [ov:imports]
// ! [ov:create_simple_model_and_pattern]
TEST(pattern, simple_model_and_pattern) {
// Create a sample model
PartialShape shape{2, 2};
auto model_param1 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_param2 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_add = std::make_shared<ov::op::v1::Add>(model_param1->output(0), model_param2->output(0));
auto model_param3 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_mul = std::make_shared<ov::op::v0::MatMul>(model_add->output(0), model_param3->output(0), false, false);
auto model_abs = std::make_shared<ov::op::v0::Abs>(model_mul->output(0));
auto model_relu = std::make_shared<ov::op::v0::Relu>(model_abs->output(0));
auto model_result = std::make_shared<ov::op::v0::Result>(model_relu->output(0));
// Create a sample model
auto pattern_mul = std::make_shared<ov::op::v0::MatMul>(pattern::any_input(), pattern::any_input(), false, false);
auto pattern_abs = std::make_shared<ov::op::v0::Abs>(pattern_mul->output(0));
auto pattern_relu = std::make_shared<ov::op::v0::Relu>(pattern_abs->output(0));
// Create a matcher and try to match the nodes
TestMatcher tm;
// Should perfectly match
ASSERT_TRUE(tm.match(pattern_relu, model_relu));
}
// ! [ov:create_simple_model_and_pattern]
// ! [ov:create_simple_model_and_pattern_wrap_type]
TEST(pattern, simple_model_and_pattern_wrap_type) {
// Create a sample model
PartialShape shape{2, 2};
auto model_param1 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_param2 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_add = std::make_shared<ov::op::v1::Add>(model_param1->output(0), model_param2->output(0));
auto model_param3 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_mul = std::make_shared<ov::op::v0::MatMul>(model_add->output(0), model_param3->output(0), false, false);
auto model_abs = std::make_shared<ov::op::v0::Abs>(model_mul->output(0));
auto model_relu = std::make_shared<ov::op::v0::Relu>(model_abs->output(0));
auto model_result = std::make_shared<ov::op::v0::Result>(model_relu->output(0));
// Create a sample model
auto pattern_mul = ov::pass::pattern::wrap_type<ov::op::v0::MatMul>({pattern::any_input(), pattern::any_input()});
auto pattern_abs = ov::pass::pattern::wrap_type<ov::op::v0::Abs>({pattern_mul->output(0)});
auto pattern_relu = ov::pass::pattern::wrap_type<ov::op::v0::Relu>({pattern_abs->output(0)});
// Create a matcher and try to match the nodes
TestMatcher tm;
// Should perfectly match
ASSERT_TRUE(tm.match(pattern_relu, model_relu));
}
// ! [ov:create_simple_model_and_pattern_wrap_type]
// ! [ov:wrap_type_list]
TEST(pattern, wrap_type_list) {
// Create a sample model
PartialShape shape{2, 2};
auto model_param1 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_param2 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_add = std::make_shared<ov::op::v1::Add>(model_param1->output(0), model_param2->output(0));
auto model_param3 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_mul = std::make_shared<ov::op::v0::MatMul>(model_add->output(0), model_param3->output(0), false, false);
auto model_abs = std::make_shared<ov::op::v0::Abs>(model_mul->output(0));
auto model_relu = std::make_shared<ov::op::v0::Relu>(model_abs->output(0));
auto model_result = std::make_shared<ov::op::v0::Result>(model_relu->output(0));
auto model_sig = std::make_shared<ov::op::v0::Sigmoid>(model_abs->output(0));
auto model_result1 = std::make_shared<ov::op::v0::Result>(model_sig->output(0));
// Create a sample model
auto pattern_mul = ov::pass::pattern::wrap_type<ov::op::v0::MatMul>({pattern::any_input(), pattern::any_input()});
auto pattern_abs = ov::pass::pattern::wrap_type<ov::op::v0::Abs>({pattern_mul->output(0)});
auto pattern_relu = ov::pass::pattern::wrap_type<ov::op::v0::Relu, ov::op::v0::Sigmoid>({pattern_abs->output(0)});
// Create a matcher and try to match the nodes
TestMatcher tm;
// The same pattern perfectly matches 2 different nodes
ASSERT_TRUE(tm.match(pattern_relu, model_relu));
ASSERT_TRUE(tm.match(pattern_relu, model_sig));
}
// ! [ov:wrap_type_list]
void patterns_misc() {
// ! [ov:any_input]
auto pattern_mul = ov::pass::pattern::wrap_type<ov::op::v0::MatMul>({pattern::any_input(), pattern::any_input()});
auto pattern_abs = ov::pass::pattern::wrap_type<ov::op::v0::Abs>({pattern_mul->output(0)});
auto pattern_relu = ov::pass::pattern::wrap_type<ov::op::v0::Relu>({pattern_abs->output(0)});
// ! [ov:any_input]
// ! [ov:wrap_type_predicate]
ov::pass::pattern::wrap_type<ov::op::v0::Relu>({pattern::any_input()}, pattern::consumers_count(2));
// ! [ov:wrap_type_predicate]
// ! [ov:any_input_predicate]
auto pattern_mul = ov::pass::pattern::wrap_type<ov::op::v0::MatMul>({pattern::any_input([](const Output<Node>& value){
return value.get_shape().size() == 4;}),
pattern::any_input([](const Output<Node>& value){
return value.get_shape().size() == 4;})});
auto pattern_abs = ov::pass::pattern::wrap_type<ov::op::v0::Abs>({pattern_mul->output(0)});
auto pattern_relu = ov::pass::pattern::wrap_type<ov::op::v0::Relu>({pattern_abs->output(0)});
// ! [ov:any_input_predicate]
// ! [ov:optional_predicate]
auto pattern_sig_opt = ov::pass::pattern::optional<ov::op::v0::Sigmoid>(pattern_relu, pattern::consumers_count(2));
// ! [ov:optional_predicate]
}
// ! [ov:pattern_or]
TEST(pattern, pattern_or) {
// Create a sample model
PartialShape shape{2, 2};
auto model_param1 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_param2 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_add = std::make_shared<ov::op::v1::Add>(model_param1->output(0), model_param2->output(0));
auto model_param3 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_mul = std::make_shared<ov::op::v0::MatMul>(model_add->output(0), model_param3->output(0), false, false);
auto model_abs = std::make_shared<ov::op::v0::Abs>(model_mul->output(0));
auto model_relu = std::make_shared<ov::op::v0::Relu>(model_abs->output(0));
auto model_result = std::make_shared<ov::op::v0::Result>(model_relu->output(0));
// Create a red branch
auto red_pattern_add = ov::pass::pattern::wrap_type<ov::op::v0::MatMul>({pattern::any_input(), pattern::any_input()});
auto red_pattern_relu = ov::pass::pattern::wrap_type<ov::op::v0::Relu>({red_pattern_add->output(0)});
auto red_pattern_sigmoid = ov::pass::pattern::wrap_type<ov::op::v0::Sigmoid>({red_pattern_relu->output(0)});
// Create a blue branch
auto blue_pattern_mul = ov::pass::pattern::wrap_type<ov::op::v0::MatMul>({pattern::any_input(), pattern::any_input()});
auto blue_pattern_abs = ov::pass::pattern::wrap_type<ov::op::v0::Abs>({blue_pattern_mul->output(0)});
auto blue_pattern_relu = ov::pass::pattern::wrap_type<ov::op::v0::Relu>({blue_pattern_abs->output(0)});
// Create Or node
auto pattern_or = std::make_shared<ov::pass::pattern::op::Or>(OutputVector{red_pattern_sigmoid->output(0), blue_pattern_relu->output(0)});
// Create a matcher and try to match the nodes
TestMatcher tm;
// The same pattern perfectly matches 2 different nodes
ASSERT_TRUE(tm.match(pattern_or, model_relu));
}
// ! [ov:pattern_or]
// ! [ov:pattern_optional_middle]
TEST(pattern, pattern_optional_middle) {
// Create a sample model
PartialShape shape{2, 2};
auto model_param1 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_param2 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_add = std::make_shared<ov::op::v1::Add>(model_param1->output(0), model_param2->output(0));
auto model_param3 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_mul = std::make_shared<ov::op::v0::MatMul>(model_add->output(0), model_param3->output(0), false, false);
auto model_abs = std::make_shared<ov::op::v0::Abs>(model_mul->output(0));
auto model_relu = std::make_shared<ov::op::v0::Relu>(model_abs->output(0));
auto model_result = std::make_shared<ov::op::v0::Result>(model_relu->output(0));
// Create a sample pattern with an Optional node in the middle
auto pattern_mul = ov::pass::pattern::wrap_type<ov::op::v0::MatMul>({pattern::any_input(), pattern::any_input()});
auto pattern_abs = ov::pass::pattern::wrap_type<ov::op::v0::Abs>({pattern_mul->output(0)});
auto pattern_sig_opt = ov::pass::pattern::optional<ov::op::v0::Sigmoid>({pattern_abs->output(0)});
auto pattern_relu = ov::pass::pattern::wrap_type<ov::op::v0::Relu>({pattern_sig_opt->output(0)});
// Create a matcher and try to match the nodes
TestMatcher tm;
// Should perfectly match
ASSERT_TRUE(tm.match(pattern_relu, model_relu));
}
// ! [ov:pattern_optional_middle]
// ! [ov:pattern_optional_top]
TEST(pattern, pattern_optional_top) {
// Create a sample model
PartialShape shape{2, 2};
auto model_param1 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_param2 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_add = std::make_shared<ov::op::v1::Add>(model_param1->output(0), model_param2->output(0));
auto model_param3 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_mul = std::make_shared<ov::op::v0::MatMul>(model_add->output(0), model_param3->output(0), false, false);
auto model_abs = std::make_shared<ov::op::v0::Abs>(model_mul->output(0));
auto model_relu = std::make_shared<ov::op::v0::Relu>(model_abs->output(0));
auto model_result = std::make_shared<ov::op::v0::Result>(model_relu->output(0));
// Create a sample pattern an optional top node
auto pattern_sig_opt = ov::pass::pattern::optional<ov::op::v0::Sigmoid>(pattern::any_input());
auto pattern_mul = ov::pass::pattern::wrap_type<ov::op::v0::MatMul>({pattern_sig_opt, pattern::any_input()});
auto pattern_abs = ov::pass::pattern::wrap_type<ov::op::v0::Abs>({pattern_mul->output(0)});
auto pattern_relu = ov::pass::pattern::wrap_type<ov::op::v0::Relu>({pattern_abs->output(0)});
// Create a matcher and try to match the nodes
TestMatcher tm;
// Should perfectly match
ASSERT_TRUE(tm.match(pattern_relu, model_relu));
}
// ! [ov:pattern_optional_top]
// ! [ov:pattern_optional_root]
TEST(pattern, pattern_optional_root) {
// Create a sample model
PartialShape shape{2, 2};
auto model_param1 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_param2 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_add = std::make_shared<ov::op::v1::Add>(model_param1->output(0), model_param2->output(0));
auto model_param3 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_mul = std::make_shared<ov::op::v0::MatMul>(model_add->output(0), model_param3->output(0), false, false);
auto model_abs = std::make_shared<ov::op::v0::Abs>(model_mul->output(0));
auto model_relu = std::make_shared<ov::op::v0::Relu>(model_abs->output(0));
auto model_result = std::make_shared<ov::op::v0::Result>(model_relu->output(0));
// Create a sample pattern an optional top node
auto pattern_mul = ov::pass::pattern::wrap_type<ov::op::v0::MatMul>({pattern::any_input(), pattern::any_input()});
auto pattern_abs = ov::pass::pattern::wrap_type<ov::op::v0::Abs>({pattern_mul->output(0)});
auto pattern_relu = ov::pass::pattern::wrap_type<ov::op::v0::Relu>({pattern_abs->output(0)});
auto pattern_sig_opt = ov::pass::pattern::optional<ov::op::v0::Sigmoid>(pattern_relu);
// Create a matcher and try to match the nodes
TestMatcher tm;
// Should perfectly match
ASSERT_TRUE(tm.match(pattern_relu, model_relu));
}
// ! [ov:pattern_optional_root]

View File

@ -0,0 +1,217 @@
# Copyright (C) 2018-2024 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
#
# ! [ov:imports]
import pytest
from openvino import PartialShape
from openvino.runtime import opset13 as ops
from openvino.runtime.passes import Matcher, WrapType, Or, AnyInput, Optional
# ! [ov:imports]
from openvino.runtime.passes import (
consumers_count,
has_static_dim,
has_static_dims,
has_static_shape,
has_static_rank,
type_matches,
type_matches_any,
)
# ! [ov:create_simple_model_and_pattern]
def simple_model_and_pattern():
# Create a sample model
model_param1 = ops.parameter(PartialShape([2, 2]))
model_param2 = ops.parameter(PartialShape([2, 2]))
model_add = ops.add(model_param1, model_param2)
model_param3 = ops.parameter(PartialShape([2, 2]))
model_mul = ops.matmul(model_add, model_param3, False, False)
model_abs = ops.abs(model_mul)
model_relu = ops.relu(model_abs)
model_result = ops.result(model_relu)
# Create a sample pattern
pattern_mul = ops.matmul(AnyInput(), AnyInput(), False, False)
pattern_abs = ops.abs(pattern_mul)
pattern_relu = ops.relu(pattern_abs)
# Create a matcher and try to match the nodes
matcher = Matcher(pattern_relu, "FindPattern")
# Should perfectly match
assert matcher.match(model_relu)
# ! [ov:create_simple_model_and_pattern]
# ! [ov:create_simple_model_and_pattern_wrap_type]
def simple_model_and_pattern_wrap_type():
model_param1 = ops.parameter(PartialShape([2, 2]))
model_param2 = ops.parameter(PartialShape([2, 2]))
model_add = ops.add(model_param1, model_param2)
model_param3 = ops.parameter(PartialShape([2, 2]))
model_mul = ops.matmul(model_add, model_param3, False, False)
model_abs = ops.abs(model_mul)
model_relu = ops.relu(model_abs)
model_result = ops.result(model_relu)
# Create a sample pattern
pattern_mul = WrapType("opset13.MatMul", [AnyInput(), AnyInput()])
pattern_abs = WrapType("opset13.Abs", pattern_mul)
pattern_relu = WrapType("opset13.Relu", pattern_abs)
# Create a matcher and try to match the nodes
matcher = Matcher(pattern_relu, "FindPattern")
# Should perfectly match
assert matcher.match(model_relu)
# ! [ov:create_simple_model_and_pattern_wrap_type]
# ! [ov:wrap_type_list]
def wrap_type_list():
model_param1 = ops.parameter(PartialShape([2, 2]))
model_param2 = ops.parameter(PartialShape([2, 2]))
model_add = ops.add(model_param1, model_param2)
model_param3 = ops.parameter(PartialShape([2, 2]))
model_mul = ops.matmul(model_add, model_param3, False, False)
model_abs = ops.abs(model_mul)
model_relu = ops.relu(model_abs)
model_result = ops.result(model_relu)
model_sig = ops.sigmoid(model_abs) # Note that we've added a Sigmoid node after Abs
model_result1 = ops.result(model_sig)
# Create a sample pattern
pattern_mul = WrapType("opset13.MatMul", [AnyInput(), AnyInput()])
pattern_abs = WrapType("opset13.Abs", pattern_mul)
pattern_relu = WrapType(["opset13.Relu", "opset13.Sigmoid"], pattern_abs)
# Create a matcher and try to match the nodes
matcher = Matcher(pattern_relu, "FindPattern")
# The same pattern perfectly matches 2 different nodes
assert matcher.match(model_relu)
assert matcher.match(model_sig)
# ! [ov:wrap_type_list]
def any_input():
# ! [ov:any_input]
# Create a pattern with a MatMul node taking any inputs.
pattern_mul = WrapType("opset13.MatMul", [AnyInput(), AnyInput()])
pattern_abs = WrapType("opset13.Abs", pattern_mul)
pattern_relu = WrapType("opset13.Relu", pattern_abs)
# ! [ov:any_input]
def wrap_type_predicate():
# ! [ov:wrap_type_predicate]
WrapType("opset13.Relu", AnyInput(), consumers_count(2))
# ! [ov:wrap_type_predicate]
# ! [ov:any_input_predicate]
# Create a pattern with an MatMul node taking any input that has a rank 4.
pattern_mul = WrapType("opset13.MatMul", [AnyInput(lambda output: len(output.get_shape()) == 4), AnyInput(lambda output: len(output.get_shape()) == 4)])
pattern_abs = WrapType("opset13.Abs", pattern_mul)
pattern_relu = WrapType("opset13.Relu", pattern_abs)
# ! [ov:any_input_predicate]
# ! [ov:pattern_or]
def pattern_or():
model_param1 = ops.parameter(PartialShape([2, 2]))
model_param2 = ops.parameter(PartialShape([2, 2]))
model_add = ops.add(model_param1, model_param2)
model_param3 = ops.parameter(PartialShape([2, 2]))
model_mul = ops.matmul(model_add, model_param3, False, False)
model_abs = ops.abs(model_mul)
model_relu = ops.relu(model_abs)
model_result = ops.result(model_relu)
# Create a red branch
red_pattern_add = WrapType("opset13.Add", [AnyInput(), AnyInput()])
red_pattern_relu = WrapType("opset13.Relu", red_pattern_add)
red_pattern_sigmoid = WrapType(["opset13.Sigmoid"], red_pattern_relu)
# Create a blue branch
blue_pattern_mul = WrapType("opset13.MatMul", [AnyInput(), AnyInput()])
blue_pattern_abs = WrapType("opset13.Abs", blue_pattern_mul)
blue_pattern_relu = WrapType(["opset13.Relu"], blue_pattern_abs)
#Create Or node
pattern_or = Or([red_pattern_sigmoid, blue_pattern_relu])
# Create a matcher and try to match the nodes
matcher = Matcher(pattern_or, "FindPattern")
# The same pattern perfectly matches 2 different nodes
assert matcher.match(model_relu)
# ! [ov:pattern_or]
# ! [ov:pattern_optional_middle]
def pattern_optional_middle():
model_param1 = ops.parameter(PartialShape([2, 2]))
model_param2 = ops.parameter(PartialShape([2, 2]))
model_add = ops.add(model_param1, model_param2)
model_param3 = ops.parameter(PartialShape([2, 2]))
model_mul = ops.matmul(model_add, model_param3, False, False)
model_abs = ops.abs(model_mul)
model_relu = ops.relu(model_abs)
model_result = ops.result(model_relu)
# Create a sample pattern with an Optional node in the middle
pattern_mul = WrapType("opset13.MatMul", [AnyInput(), AnyInput()])
pattern_abs = WrapType("opset13.Abs", pattern_mul)
pattern_sig_opt = Optional(["opset13.Sigmoid"], pattern_abs)
pattern_relu = WrapType("opset13.Relu", pattern_sig_opt)
# Create a matcher and try to match the nodes
matcher = Matcher(pattern_relu, "FindPattern")
# Should perfectly match
assert matcher.match(model_relu)
# ! [ov:pattern_optional_middle]
# ! [ov:pattern_optional_top]
def pattern_optional_top():
model_param1 = ops.parameter(PartialShape([2, 2]))
model_param2 = ops.parameter(PartialShape([2, 2]))
model_add = ops.add(model_param1, model_param2)
model_param3 = ops.parameter(PartialShape([2, 2]))
model_mul = ops.matmul(model_add, model_param3, False, False)
model_abs = ops.abs(model_mul)
model_relu = ops.relu(model_abs)
model_result = ops.result(model_relu)
# Create a sample pattern an optional top node
pattern_sig_opt = Optional(["opset13.Sigmoid"], AnyInput())
pattern_mul = WrapType("opset13.MatMul", [pattern_sig_opt, AnyInput()])
pattern_abs = WrapType("opset13.Abs", pattern_mul)
pattern_relu = WrapType("opset13.Relu", pattern_abs)
matcher = Matcher(pattern_relu, "FindPattern")
# Should perfectly match even though there's no Sigmoid going into MatMul
assert matcher.match(model_relu)
# ! [ov:pattern_optional_top]
# ! [ov:pattern_optional_root]
def pattern_optional_root():
model_param1 = ops.parameter(PartialShape([2, 2]))
model_param2 = ops.parameter(PartialShape([2, 2]))
model_add = ops.add(model_param1, model_param2)
model_param3 = ops.parameter(PartialShape([2, 2]))
model_mul = ops.matmul(model_add, model_param3, False, False)
model_abs = ops.abs(model_mul)
model_relu = ops.relu(model_abs)
model_result = ops.result(model_relu)
# Create a sample pattern
pattern_mul = WrapType("opset13.MatMul", [AnyInput(), AnyInput()])
pattern_abs = WrapType("opset13.Abs", pattern_mul)
pattern_relu = WrapType("opset13.Relu", pattern_abs)
pattern_sig_opt = Optional(["opset13.Sigmoid"], pattern_relu)
matcher = Matcher(pattern_sig_opt, "FindPattern")
# Should perfectly match even though there's no Sigmoid as root
assert matcher.match(model_relu)
# ! [ov:pattern_optional_root]
# ! [ov:optional_predicate]
pattern_sig_opt = Optional(["opset13.Sigmoid"], pattern_relu, consumers_count(1))
# ! [ov:optional_predicate]

View File

@ -16,25 +16,26 @@ Overview of Transformations API
transformation-api/model-pass
transformation-api/matcher-pass
transformation-api/graph-rewrite-pass
transformation-api/patterns-python-api
OpenVINO Transformation mechanism allows to develop transformation passes to modify ``ov::Model``. You can use this mechanism to apply additional optimizations to the original Model or transform unsupported subgraphs and operations to new operations which are supported by the plugin.
This guide contains all necessary information that you need to start implementing OpenVINO™ transformations.
OpenVINO Transformation mechanism allows to develop transformation passes to modify ``ov::Model``. You can use this mechanism to apply additional optimizations to the original Model or transform unsupported subgraphs and operations to new operations supported by the plugin.
This guide contains all the necessary information to start implementing OpenVINO™ transformations.
Working with Model
##################
Before the moving to transformation part it is needed to say several words about functions which allow to modify ``ov::Model``.
This chapter extends the :doc:`model representation guide <../../openvino-workflow/running-inference/integrate-openvino-with-your-application/model-representation>` and shows an API that allows us to manipulate with ``ov::Model``.
Before moving to the transformation part, it is important to say a few words about the functions which allow modifying ``ov::Model``.
This section extends the :doc:`model representation guide <../../openvino-workflow/running-inference/integrate-openvino-with-your-application/model-representation>` and introduces an API for ``ov::Model`` manipulation.
Working with node input and output ports
++++++++++++++++++++++++++++++++++++++++
First of all let's talk about ``ov::Node`` input/output ports. Each OpenVINO™ operation has input and output ports except cases when operation has ``Parameter`` or ``Constant`` type.
Each OpenVINO operation has ``ov::Node`` input and output ports, except for ``Parameter`` and ``Constant`` types.
The terms ``node`` and ``operation`` are used interchangeably in OpenVINO, but this article will maintain consistency in their use.
Every port belongs to its node, so using a port we can access parent node, get shape and type for particular input/output, get all consumers in case of output port, and get producer node in case of input port.
With output port we can set inputs for newly created operations.
Every port is associated with a node, allowing access to the node it belongs to, including its shape, type, all consumers for output ports and the producer node for input ports.
Lets look at the code example.
Take a look at the code example:
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_model_snippets.cpp
:language: cpp
@ -47,7 +48,7 @@ OpenVINO™ provides two ways for node replacement: via OpenVINO™ helper funct
Let's start with OpenVINO™ helper functions. The most popular function is ``ov::replace_node(old_node, new_node)``.
We will review real replacement case where Negative operation is replaced with Multiply.
Let's review a replacement case where a Negative operation is replaced with Multiply.
.. image:: ../../assets/images/ov_replace_node.png
@ -55,7 +56,7 @@ We will review real replacement case where Negative operation is replaced with M
:language: cpp
:fragment: [ov:replace_node]
``ov::replace_node`` has a constraint that number of output ports for both of ops must be the same; otherwise, it raises an exception.
``ov::replace_node`` has a constraint that number of output ports for both nodes must be the same. Otherwise, the attempt to replace the nodes will result in an exception.
The alternative way to do the same replacement is the following:
@ -63,7 +64,7 @@ The alternative way to do the same replacement is the following:
:language: cpp
:fragment: [ov:manual_replace]
Another transformation example is insertion.
Another transformation example is insertion. Let's insert an additional Relu node.
.. image:: ../../assets/images/ov_insert_node.png
@ -71,7 +72,7 @@ Another transformation example is insertion.
:language: cpp
:fragment: [ov:insert_node]
The alternative way to the insert operation is to make a node copy and use ``ov::replace_node()``:
The alternative way of inserting a node is to make a copy of the node and use ``ov::replace_node()``:
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_model_snippets.cpp
:language: cpp
@ -80,15 +81,15 @@ The alternative way to the insert operation is to make a node copy and use ``ov:
Node elimination
++++++++++++++++
Another type of node replacement is its elimination.
Another type of node replacement is elimination of a node.
To eliminate operation, OpenVINO™ has special method that considers all limitations related to OpenVINO™ Runtime.
To eliminate a node, OpenVINO provides a method that considers all limitations of the OpenVINO Runtime.
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_model_snippets.cpp
:language: cpp
:fragment: [ov:eliminate_node]
``ov::replace_output_update_name()`` in case of successful replacement it automatically preserves friendly name and runtime info.
If the replacement is successful, ``ov::replace_output_update_name()`` automatically preserves the friendly name and runtime info.
.. _transformations_types:
@ -99,7 +100,7 @@ OpenVINO™ Runtime has three main transformation types:
* :doc:`Model pass <transformation-api/model-pass>` - straightforward way to work with ``ov::Model`` directly
* :doc:`Matcher pass <transformation-api/matcher-pass>` - pattern-based transformation approach
* :doc:`Graph rewrite pass <transformation-api/graph-rewrite-pass>` - container for matcher passes needed for efficient execution
* :doc:`Graph rewrite pass <transformation-api/graph-rewrite-pass>` - container for matcher passes used for efficient execution
.. image:: ../../assets/images/transformations_structure.png
@ -116,36 +117,36 @@ Transformation library has two internal macros to support conditional compilatio
Transformation writing essentials
#################################
When developing a transformation, you need to follow these transformation rules:
To develop a transformation, follow these transformation rules:
1. Friendly Names
+++++++++++++++++
Each ``ov::Node`` has an unique name and a friendly name. In transformations we care only about friendly name because it represents the name from the model.
To avoid losing friendly name when replacing node with other node or subgraph, set the original friendly name to the latest node in replacing subgraph. See the example below.
Each ``ov::Node`` has a unique name and a friendly name. In transformations, only the friendly name matters because it represents the name from the model's perspective.
To prevent losing the friendly name when replacing a node with another node or a subgraph, the original friendly name is set to the last node in the replacing subgraph. See the example below.
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_model_snippets.cpp
:language: cpp
:fragment: [ov:replace_friendly_name]
In more advanced cases, when replaced operation has several outputs and we add additional consumers to its outputs, we make a decision how to set friendly name by arrangement.
In more complicated cases, when a replaced operation has several outputs and additional consumers are added to its outputs, the decision on how to set the friendly name is determined by an agreement.
2. Runtime Info
+++++++++++++++
Runtime info is a map ``std::map<std::string, ov::Any>`` located inside ``ov::Node`` class. It represents additional attributes in ``ov::Node``.
These attributes can be set by users or by plugins and when executing transformation that changes ``ov::Model`` we need to preserve these attributes as they will not be automatically propagated.
Runtime info is a map ``std::map<std::string, ov::Any>`` located inside the ``ov::Node`` class. It represents additional attributes of the ``ov::Node``.
These attributes, which can be set by users or plugins, need to be preserved when executing a transformation that changes ``ov::Model``, as they are not automatically propagated.
In most cases, transformations have the following types: 1:1 (replace node with another node), 1:N (replace node with a sub-graph), N:1 (fuse sub-graph into a single node), N:M (any other transformation).
Currently, there is no mechanism that automatically detects transformation types, so we need to propagate this runtime information manually. See the examples below.
Currently, there is no mechanism that automatically detects transformation types, so this runtime information needs to be propagated manually. See the example below:
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_model_snippets.cpp
:language: cpp
:fragment: [ov:copy_runtime_info]
When transformation has multiple fusions or decompositions, ``ov::copy_runtime_info`` must be called multiple times for each case.
When a transformation has multiple fusions or decompositions, ``ov::copy_runtime_info`` must be called multiple times for each case.
.. note:: ``copy_runtime_info`` removes ``rt_info`` from destination nodes. If you want to keep it, you need to specify them in source nodes like this: ``copy_runtime_info({a, b, c}, {a, b})``
.. note:: ``copy_runtime_info`` removes ``rt_info`` from destination nodes. If you want to keep it, specify them in source nodes as following: ``copy_runtime_info({a, b, c}, {a, b})``
3. Constant Folding
+++++++++++++++++++
@ -172,21 +173,21 @@ Common mistakes in transformations
In transformation development process:
* Do not use deprecated OpenVINO™ API. Deprecated methods has the ``OPENVINO_DEPRECATED`` macros in its definition.
* Do not pass ``shared_ptr<Node>`` as an input for other node if type of node is unknown or it has multiple outputs. Use explicit output port.
* If you replace node with another node that produces different shape, remember that new shape will not be propagated until the first ``validate_nodes_and_infer_types`` call for ``ov::Model``. If you are using ``ov::pass::Manager``, it will automatically call this method after each transformation execution.
* Do not use deprecated OpenVINO™ API. Deprecated methods are marked with ``OPENVINO_DEPRECATED`` macro in their definition.
* Do not pass ``shared_ptr<Node>`` as input for another node if the type of the node is unknown or if it has multiple outputs. Instead, use explicit output ports.
* If you replace a node with another node that produces different shape, note that the new shape will not be propagated until the first ``validate_nodes_and_infer_types`` call for ``ov::Model``. If you are using ``ov::pass::Manager``, it will automatically call this method after each transformation execution.
* Do not forget to call the ``ov::pass::ConstantFolding`` pass if your transformation creates constant subgraphs.
* Use latest OpSet if you are not developing downgrade transformation pass.
* When developing a callback for ``ov::pass::MatcherPass``, do not change nodes that come after the root node in topological order.
* When developing a callback for ``ov::pass::MatcherPass``, do not change nodes that come after the root node in the topological order.
.. _using_pass_manager:
Using pass manager
##################
``ov::pass::Manager`` is a container class that can store the list of transformations and execute them. The main idea of this class is to have high-level representation for grouped list of transformations.
It can register and apply any `transformation pass <#transformations_types>`__ on model.
In addition, ``ov::pass::Manager`` has extended debug capabilities (find more information in the `how to debug transformations <#how_to_debug_transformations>`__ section).
``ov::pass::Manager`` is a container class that can store a list of transformations and execute them. The main idea of this class is to have a high-level representation for grouped list of transformations.
It can register and apply any `transformation pass <#transformations_types>`__ on a model.
In addition, ``ov::pass::Manager`` has extended debug capabilities (find more information in the `how to debug transformations <#how-to-debug-transformations>`__ section).
The example below shows basic usage of ``ov::pass::Manager``

View File

@ -0,0 +1,175 @@
Transformation Patterns with OpenVINO API
==================================================
.. meta::
:description: Learn how to apply additional model optimizations or transform
unsupported subgraphs and operations using OpenVINO™ Transformations API.
Pattern matching is an essential component of OpenVINO™ transformations. Before performing any transformation on a subgraph of a graph, it is necessary to find that subgraph in the graph.
Patterns serve as a searching utility to identify nodes intended for transformations. This article covers the basics of pattern
creation using OpenVINO™ API and helpful utilities to facilitate working with them. While this guide focuses on creating patterns, if you want to learn more about ``MatcherPass``, refer to the :doc:`OpenVINO Matcher Pass article <./matcher-pass>`. Note that some examples may be intentionally simplified for ease of understanding.
Before proceeding further, it is necessary to add some imports. These imports include the operations to be used and additional utilities described in this guide.
Add the following lines to your file:
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.py
:language: python
:fragment: [ov:imports]
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.cpp
:language: cpp
:fragment: [ov:imports]
Pattern Creation
+++++++++++++++++++++
A pattern is a simplified model comprised of nodes aimed to be matched. It lacks some features of a model and cannot function as one.
Consider a straightforward pattern consisting of three nodes to be found in a given model.
.. image:: ./../../../assets/images/simple_pattern_example.png
Let's create the model and the pattern:
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.py
:language: python
:fragment: [ov:create_simple_model_and_pattern]
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.cpp
:language: cpp
:fragment: [ov:create_simple_model_and_pattern]
.. note:: This example uses testing utilities that directly compare given sequences of nodes. In reality, the process of finding a pattern within a model is more complicated. However, to focus only on patterns and their functionality, these details are intentionally omitted.
Although the code is functional, in OpenVINO, patterns are typically not created using the same nodes as those used for creating the model. Instead, wrappers are preferred, providing additional functionality.
For the given case, ``WrapType`` is used and the code looks as following:
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.py
:language: python
:fragment: [ov:create_simple_model_and_pattern_wrap_type]
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.cpp
:language: cpp
:fragment: [ov:create_simple_model_and_pattern_wrap_type]
1. WrapType
++++++++++++++++++++++++++++++++++++++++
``WrapType`` is a wrapper used to store one or many types to match them. As demonstrated earlier, it is possible to specify a single type in ``WrapType`` and use it for matching.
However, you can also list all possible types for a given node, for example:
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.py
:language: python
:fragment: [ov:wrap_type_list]
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.cpp
:language: cpp
:fragment: [ov:wrap_type_list]
Note that ``pattern_sig`` is created with the list ``["opset13.Relu", "opset13.Sigmoid"]``, meaning it can be either a ``Relu`` or a ``Sigmoid``.
This feature enables matching the same pattern against different nodes. Essentially, ``WrapType`` can represent "one of listed" types. ``WrapType`` supports specifying more than two types.
To add additional checks for your node, create a predicate by providing a function or a lambda. This function will be executed during matching, performing the additional validation specified in the logic of the function. For example, you might want to check the consumers count of a given node:
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.py
:language: python
:fragment: [ov:wrap_type_predicate]
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.cpp
:language: cpp
:fragment: [ov:wrap_type_predicate]
2. AnyInput
++++++++++++++++++++++++++++++++++++++++
``AnyInput`` is used when there is no need to specify a particular input for a given node.
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.py
:language: python
:fragment: [ov:any_input]
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.cpp
:language: cpp
:fragment: [ov:any_input]
You can also create ``AnyInput()`` with a predicate, if you want additional checks for you input. It will look similar to ``WrapType`` with a lambda or a function. For example, to ensure that the input has a rank of 4:
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.py
:language: python
:fragment: [ov:any_input_predicate]
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.cpp
:language: cpp
:fragment: [ov:any_input_predicate]
3. Or
++++++++++++++++++++++++++++++++++++++++
``Or`` functions similar to ``WrapType``, however, while ``WrapType`` can only match one of the types provided in the list, ``Or`` is used to match different branches of nodes.
Suppose the goal is to match the model against two different sequences of nodes. The ``Or`` type
facilitates this by creating two different branches (``Or`` supports more than two branches), looking as follows:
.. image:: ./../../../assets/images/or_branches.png
The red branch will not match, but it will work perfectly for the blue one.
Here is how it looks in code:
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.py
:language: python
:fragment: [ov:pattern_or]
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.cpp
:language: cpp
:fragment: [ov:pattern_or]
Note that matching will succeed for the first matching branch and the remaining ones will not be checked.
4. Optional
++++++++++++++++++++++++++++++++++++++++
``Optional`` is a bit tricky. It allows specifying whether a node might be present or absent in the model. Under the hood,
the pattern will create two branches using ``Or``: one with the optional node present and another one without it. Here is what it would look like with the ``Optional``
unfolding into two branches:
.. image:: ./../../../assets/images/optional.png
The code for our model looks as follows:
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.py
:language: python
:fragment: [ov:pattern_optional_middle]
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.cpp
:language: cpp
:fragment: [ov:pattern_optional_middle]
The ``Optional`` does not necessarily have to be in the middle of the pattern. It can be a top node and a root node.
Top node:
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.py
:language: python
:fragment: [ov:pattern_optional_top]
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.cpp
:language: cpp
:fragment: [ov:pattern_optional_top]
Root node:
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.py
:language: python
:fragment: [ov:pattern_optional_root]
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.cpp
:language: cpp
:fragment: [ov:pattern_optional_root]
``Optional`` also supports adding a predicate the same way ``WrapType`` and ``AnyInput`` do:
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.py
:language: python
:fragment: [ov:optional_predicate]
.. doxygensnippet:: docs/articles_en/assets/snippets/ov_patterns.cpp
:language: cpp
:fragment: [ov:optional_predicate]

View File

@ -21,6 +21,173 @@ from openvino.runtime.utils.types import get_element_type
from tests.test_transformations.utils.utils import expect_exception
def test_simple_model_and_pattern():
# Create a sample model
model_param1 = ops.parameter(PartialShape([2, 2]))
model_param2 = ops.parameter(PartialShape([2, 2]))
model_add = ops.add(model_param1, model_param2)
model_param3 = ops.parameter(PartialShape([2, 2]))
model_mul = ops.matmul(model_add, model_param3, False, False)
model_abs = ops.abs(model_mul)
model_relu = ops.relu(model_abs)
model_result = ops.result(model_relu) # noqa
# Create a sample pattern
pattern_mul = ops.matmul(AnyInput(), AnyInput(), False, False)
pattern_abs = ops.abs(pattern_mul)
pattern_relu = ops.relu(pattern_abs)
# Create a matcher and try to match the nodes
matcher = Matcher(pattern_relu, "FindPattern")
# Should perfectly match
assert matcher.match(model_relu)
def test_simple_model_and_pattern_wrap_type():
model_param1 = ops.parameter(PartialShape([2, 2]))
model_param2 = ops.parameter(PartialShape([2, 2]))
model_add = ops.add(model_param1, model_param2)
model_param3 = ops.parameter(PartialShape([2, 2]))
model_mul = ops.matmul(model_add, model_param3, False, False)
model_abs = ops.abs(model_mul)
model_relu = ops.relu(model_abs)
model_result = ops.result(model_relu) # noqa
# Create a sample pattern
pattern_mul = WrapType("opset13.MatMul", [AnyInput(), AnyInput()])
pattern_abs = WrapType("opset13.Abs", pattern_mul)
pattern_relu = WrapType("opset13.Relu", pattern_abs)
# Create a matcher and try to match the nodes
matcher = Matcher(pattern_relu, "FindPattern")
# Should perfectly match
assert matcher.match(model_relu)
def test_wrap_type_list():
model_param1 = ops.parameter(PartialShape([2, 2]))
model_param2 = ops.parameter(PartialShape([2, 2]))
model_add = ops.add(model_param1, model_param2)
model_param3 = ops.parameter(PartialShape([2, 2]))
model_mul = ops.matmul(model_add, model_param3, False, False)
model_abs = ops.abs(model_mul)
model_relu = ops.relu(model_abs)
model_result = ops.result(model_relu) # noqa
model_sig = ops.sigmoid(model_abs) # Note that we've added a Sigmoid node after Abs
model_result1 = ops.result(model_sig) # noqa
# Create a sample pattern
pattern_mul = WrapType("opset13.MatMul", [AnyInput(), AnyInput()])
pattern_abs = WrapType("opset13.Abs", pattern_mul)
pattern_relu = WrapType(["opset13.Relu", "opset13.Sigmoid"], pattern_abs)
# Create a matcher and try to match the nodes
matcher = Matcher(pattern_relu, "FindPattern")
# The same pattern perfectly matches 2 different nodes
assert matcher.match(model_relu)
assert matcher.match(model_sig)
def test_pattern_or():
model_param1 = ops.parameter(PartialShape([2, 2]))
model_param2 = ops.parameter(PartialShape([2, 2]))
model_add = ops.add(model_param1, model_param2)
model_param3 = ops.parameter(PartialShape([2, 2]))
model_mul = ops.matmul(model_add, model_param3, False, False)
model_abs = ops.abs(model_mul)
model_relu = ops.relu(model_abs)
model_result = ops.result(model_relu) # noqa
# Create a red branch
red_pattern_add = WrapType("opset13.Add", [AnyInput(), AnyInput()])
red_pattern_relu = WrapType("opset13.Relu", red_pattern_add)
red_pattern_sigmoid = WrapType(["opset13.Sigmoid"], red_pattern_relu)
# Create a blue branch
blue_pattern_mul = WrapType("opset13.MatMul", [AnyInput(), AnyInput()])
blue_pattern_abs = WrapType("opset13.Abs", blue_pattern_mul)
blue_pattern_relu = WrapType(["opset13.Relu"], blue_pattern_abs)
# Create Or node
pattern_or = Or([red_pattern_sigmoid, blue_pattern_relu])
# Create a matcher and try to match the nodes
matcher = Matcher(pattern_or, "FindPattern")
# The same pattern perfectly matches 2 different nodes
assert matcher.match(model_relu)
def test_pattern_optional_middle():
model_param1 = ops.parameter(PartialShape([2, 2]))
model_param2 = ops.parameter(PartialShape([2, 2]))
model_add = ops.add(model_param1, model_param2)
model_param3 = ops.parameter(PartialShape([2, 2]))
model_mul = ops.matmul(model_add, model_param3, False, False)
model_abs = ops.abs(model_mul)
model_relu = ops.relu(model_abs)
model_result = ops.result(model_relu) # noqa
# Create a sample pattern with an Optional node in the middle
pattern_mul = WrapType("opset13.MatMul", [AnyInput(), AnyInput()])
pattern_abs = WrapType("opset13.Abs", pattern_mul)
pattern_sig_opt = Optional(["opset13.Sigmoid"], pattern_abs)
pattern_relu = WrapType("opset13.Relu", pattern_sig_opt)
# Create a matcher and try to match the nodes
matcher = Matcher(pattern_relu, "FindPattern")
# Should perfectly match
assert matcher.match(model_relu)
def test_pattern_optional_top():
model_param1 = ops.parameter(PartialShape([2, 2]))
model_param2 = ops.parameter(PartialShape([2, 2]))
model_add = ops.add(model_param1, model_param2)
model_param3 = ops.parameter(PartialShape([2, 2]))
model_mul = ops.matmul(model_add, model_param3, False, False)
model_abs = ops.abs(model_mul)
model_relu = ops.relu(model_abs)
model_result = ops.result(model_relu) # noqa
# Create a sample pattern an optional top node
pattern_sig_opt = Optional(["opset13.Sigmoid"], AnyInput())
pattern_mul = WrapType("opset13.MatMul", [pattern_sig_opt, AnyInput()])
pattern_abs = WrapType("opset13.Abs", pattern_mul)
pattern_relu = WrapType("opset13.Relu", pattern_abs)
matcher = Matcher(pattern_relu, "FindPattern")
# Should perfectly match even though there's no Sigmoid going into MatMul
assert matcher.match(model_relu)
def test_pattern_optional_root():
model_param1 = ops.parameter(PartialShape([2, 2]))
model_param2 = ops.parameter(PartialShape([2, 2]))
model_add = ops.add(model_param1, model_param2)
model_param3 = ops.parameter(PartialShape([2, 2]))
model_mul = ops.matmul(model_add, model_param3, False, False)
model_abs = ops.abs(model_mul)
model_relu = ops.relu(model_abs)
model_result = ops.result(model_relu) # noqa
# Create a sample pattern with an optional root node
pattern_mul = WrapType("opset13.MatMul", [AnyInput(), AnyInput()])
pattern_abs = WrapType("opset13.Abs", pattern_mul)
pattern_relu = WrapType("opset13.Relu", pattern_abs)
pattern_sig_opt = Optional(["opset13.Sigmoid"], pattern_relu)
matcher = Matcher(pattern_sig_opt, "FindPattern")
# Should perfectly match even though there's no Sigmoid as root
assert matcher.match(model_relu)
def test_wrap_type_pattern_type():
last_opset_number = 15
for i in range(1, last_opset_number + 1):

View File

@ -24,11 +24,13 @@
#include "openvino/op/equal.hpp"
#include "openvino/op/exp.hpp"
#include "openvino/op/greater.hpp"
#include "openvino/op/matmul.hpp"
#include "openvino/op/multiply.hpp"
#include "openvino/op/non_max_suppression.hpp"
#include "openvino/op/parameter.hpp"
#include "openvino/op/reduce_sum.hpp"
#include "openvino/op/relu.hpp"
#include "openvino/op/sigmoid.hpp"
#include "openvino/op/strided_slice.hpp"
#include "openvino/op/subtract.hpp"
#include "openvino/op/transpose.hpp"
@ -1101,3 +1103,188 @@ TEST(pattern, wrap_type_multi_op) {
ASSERT_FALSE(matcher->match(static_pointer_cast<Node>(c)));
}
}
TEST(pattern, simple_model_and_pattern) {
// Create a sample model
PartialShape shape{2, 2};
auto model_param1 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_param2 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_add = std::make_shared<ov::op::v1::Add>(model_param1->output(0), model_param2->output(0));
auto model_param3 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_mul = std::make_shared<ov::op::v0::MatMul>(model_add->output(0), model_param3->output(0), false, false);
auto model_abs = std::make_shared<ov::op::v0::Abs>(model_mul->output(0));
auto model_relu = std::make_shared<ov::op::v0::Relu>(model_abs->output(0));
auto model_result = std::make_shared<ov::op::v0::Result>(model_relu->output(0));
// Create a sample model
auto pattern_mul = std::make_shared<ov::op::v0::MatMul>(pattern::any_input(), pattern::any_input(), false, false);
auto pattern_abs = std::make_shared<ov::op::v0::Abs>(pattern_mul->output(0));
auto pattern_relu = std::make_shared<ov::op::v0::Relu>(pattern_abs->output(0));
// Create a matcher and try to match the nodes
TestMatcher tm;
// Should perfectly match
ASSERT_TRUE(tm.match(pattern_relu, model_relu));
}
TEST(pattern, simple_model_and_pattern_wrap_type) {
// Create a sample model
PartialShape shape{2, 2};
auto model_param1 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_param2 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_add = std::make_shared<ov::op::v1::Add>(model_param1->output(0), model_param2->output(0));
auto model_param3 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_mul = std::make_shared<ov::op::v0::MatMul>(model_add->output(0), model_param3->output(0), false, false);
auto model_abs = std::make_shared<ov::op::v0::Abs>(model_mul->output(0));
auto model_relu = std::make_shared<ov::op::v0::Relu>(model_abs->output(0));
auto model_result = std::make_shared<ov::op::v0::Result>(model_relu->output(0));
// Create a sample model
auto pattern_mul = ov::pass::pattern::wrap_type<ov::op::v0::MatMul>({pattern::any_input(), pattern::any_input()});
auto pattern_abs = ov::pass::pattern::wrap_type<ov::op::v0::Abs>({pattern_mul->output(0)});
auto pattern_relu = ov::pass::pattern::wrap_type<ov::op::v0::Relu>({pattern_abs->output(0)});
// Create a matcher and try to match the nodes
TestMatcher tm;
// Should perfectly match
ASSERT_TRUE(tm.match(pattern_relu, model_relu));
}
TEST(pattern, wrap_type_list) {
// Create a sample model
PartialShape shape{2, 2};
auto model_param1 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_param2 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_add = std::make_shared<ov::op::v1::Add>(model_param1->output(0), model_param2->output(0));
auto model_param3 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_mul = std::make_shared<ov::op::v0::MatMul>(model_add->output(0), model_param3->output(0), false, false);
auto model_abs = std::make_shared<ov::op::v0::Abs>(model_mul->output(0));
auto model_relu = std::make_shared<ov::op::v0::Relu>(model_abs->output(0));
auto model_result = std::make_shared<ov::op::v0::Result>(model_relu->output(0));
auto model_sig = std::make_shared<ov::op::v0::Sigmoid>(model_abs->output(0));
auto model_result1 = std::make_shared<ov::op::v0::Result>(model_sig->output(0));
// Create a sample model
auto pattern_mul = ov::pass::pattern::wrap_type<ov::op::v0::MatMul>({pattern::any_input(), pattern::any_input()});
auto pattern_abs = ov::pass::pattern::wrap_type<ov::op::v0::Abs>({pattern_mul->output(0)});
auto pattern_relu = ov::pass::pattern::wrap_type<ov::op::v0::Relu, ov::op::v0::Sigmoid>({pattern_abs->output(0)});
// Create a matcher and try to match the nodes
TestMatcher tm;
// The same pattern perfectly matches 2 different nodes
ASSERT_TRUE(tm.match(pattern_relu, model_relu));
ASSERT_TRUE(tm.match(pattern_relu, model_sig));
}
TEST(pattern, pattern_or) {
// Create a sample model
PartialShape shape{2, 2};
auto model_param1 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_param2 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_add = std::make_shared<ov::op::v1::Add>(model_param1->output(0), model_param2->output(0));
auto model_param3 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_mul = std::make_shared<ov::op::v0::MatMul>(model_add->output(0), model_param3->output(0), false, false);
auto model_abs = std::make_shared<ov::op::v0::Abs>(model_mul->output(0));
auto model_relu = std::make_shared<ov::op::v0::Relu>(model_abs->output(0));
auto model_result = std::make_shared<ov::op::v0::Result>(model_relu->output(0));
// Create a red branch
auto red_pattern_add =
ov::pass::pattern::wrap_type<ov::op::v0::MatMul>({pattern::any_input(), pattern::any_input()});
auto red_pattern_relu = ov::pass::pattern::wrap_type<ov::op::v0::Relu>({red_pattern_add->output(0)});
auto red_pattern_sigmoid = ov::pass::pattern::wrap_type<ov::op::v0::Sigmoid>({red_pattern_relu->output(0)});
// Create a blue branch
auto blue_pattern_mul =
ov::pass::pattern::wrap_type<ov::op::v0::MatMul>({pattern::any_input(), pattern::any_input()});
auto blue_pattern_abs = ov::pass::pattern::wrap_type<ov::op::v0::Abs>({blue_pattern_mul->output(0)});
auto blue_pattern_relu = ov::pass::pattern::wrap_type<ov::op::v0::Relu>({blue_pattern_abs->output(0)});
// Create Or node
auto pattern_or = std::make_shared<ov::pass::pattern::op::Or>(
OutputVector{red_pattern_sigmoid->output(0), blue_pattern_relu->output(0)});
// Create a matcher and try to match the nodes
TestMatcher tm;
// The same pattern perfectly matches 2 different nodes
ASSERT_TRUE(tm.match(pattern_or, model_relu));
}
TEST(pattern, pattern_optional_middle) {
// Create a sample model
PartialShape shape{2, 2};
auto model_param1 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_param2 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_add = std::make_shared<ov::op::v1::Add>(model_param1->output(0), model_param2->output(0));
auto model_param3 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_mul = std::make_shared<ov::op::v0::MatMul>(model_add->output(0), model_param3->output(0), false, false);
auto model_abs = std::make_shared<ov::op::v0::Abs>(model_mul->output(0));
auto model_relu = std::make_shared<ov::op::v0::Relu>(model_abs->output(0));
auto model_result = std::make_shared<ov::op::v0::Result>(model_relu->output(0));
// Create a sample pattern with an Optional node in the middle
auto pattern_mul = ov::pass::pattern::wrap_type<ov::op::v0::MatMul>({pattern::any_input(), pattern::any_input()});
auto pattern_abs = ov::pass::pattern::wrap_type<ov::op::v0::Abs>({pattern_mul->output(0)});
auto pattern_sig_opt = ov::pass::pattern::optional<ov::op::v0::Sigmoid>({pattern_abs->output(0)});
auto pattern_relu = ov::pass::pattern::wrap_type<ov::op::v0::Relu>({pattern_sig_opt->output(0)});
// Create a matcher and try to match the nodes
TestMatcher tm;
// Should perfectly match
ASSERT_TRUE(tm.match(pattern_relu, model_relu));
}
TEST(pattern, pattern_optional_top) {
// Create a sample model
PartialShape shape{2, 2};
auto model_param1 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_param2 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_add = std::make_shared<ov::op::v1::Add>(model_param1->output(0), model_param2->output(0));
auto model_param3 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_mul = std::make_shared<ov::op::v0::MatMul>(model_add->output(0), model_param3->output(0), false, false);
auto model_abs = std::make_shared<ov::op::v0::Abs>(model_mul->output(0));
auto model_relu = std::make_shared<ov::op::v0::Relu>(model_abs->output(0));
auto model_result = std::make_shared<ov::op::v0::Result>(model_relu->output(0));
// Create a sample pattern an optional top node
auto pattern_sig_opt = ov::pass::pattern::optional<ov::op::v0::Sigmoid>(pattern::any_input());
auto pattern_mul = ov::pass::pattern::wrap_type<ov::op::v0::MatMul>({pattern_sig_opt, pattern::any_input()});
auto pattern_abs = ov::pass::pattern::wrap_type<ov::op::v0::Abs>({pattern_mul->output(0)});
auto pattern_relu = ov::pass::pattern::wrap_type<ov::op::v0::Relu>({pattern_abs->output(0)});
// Create a matcher and try to match the nodes
TestMatcher tm;
// Should perfectly match
ASSERT_TRUE(tm.match(pattern_relu, model_relu));
}
TEST(pattern, pattern_optional_root) {
// Create a sample model
PartialShape shape{2, 2};
auto model_param1 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_param2 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_add = std::make_shared<ov::op::v1::Add>(model_param1->output(0), model_param2->output(0));
auto model_param3 = std::make_shared<ov::op::v0::Parameter>(element::i32, shape);
auto model_mul = std::make_shared<ov::op::v0::MatMul>(model_add->output(0), model_param3->output(0), false, false);
auto model_abs = std::make_shared<ov::op::v0::Abs>(model_mul->output(0));
auto model_relu = std::make_shared<ov::op::v0::Relu>(model_abs->output(0));
auto model_result = std::make_shared<ov::op::v0::Result>(model_relu->output(0));
// Create a sample pattern an optional top node
auto pattern_mul = ov::pass::pattern::wrap_type<ov::op::v0::MatMul>({pattern::any_input(), pattern::any_input()});
auto pattern_abs = ov::pass::pattern::wrap_type<ov::op::v0::Abs>({pattern_mul->output(0)});
auto pattern_relu = ov::pass::pattern::wrap_type<ov::op::v0::Relu>({pattern_abs->output(0)});
auto pattern_sig_opt = ov::pass::pattern::optional<ov::op::v0::Sigmoid>(pattern_relu);
// Create a matcher and try to match the nodes
TestMatcher tm;
// Should perfectly match
ASSERT_TRUE(tm.match(pattern_relu, model_relu));
}