Compare commits

...

12 Commits

Author SHA1 Message Date
Taylor Yeonbok Lee 43de73cbd6
Let ocl FC to use tile size 1 only when the # of threads is less than the w/ minimal # of threads (#24936)
### Details:
- Fix to resolve perf issue in the platforms with small # of EUs than
128.
### Tickets:
 - *ticket-id*
2024-06-12 00:27:13 +00:00
Alexandra Sidorova 1d226d4edc
[Snippets] Added dynamic Transpose support (#24733)
### Details:
- *Added dynamic Transpose support (updated related operations and
passes)*
 - *Added dynamic tests with Transpose*

### Tickets:
 - *142098*

### Prerequisites:
- https://github.com/openvinotoolkit/openvino/pull/24525
- https://github.com/openvinotoolkit/openvino/pull/24646
2024-06-11 16:45:40 +00:00
tadamczx 0908908227
[DOCS] OpenVINO Python API docs build fix (#24952)
### Details:
 - *item1*
 - *...*

### Tickets:
 - *ticket-id*
2024-06-11 16:24:51 +00:00
Aleksandr Voron 4e6ee7f15e
[CPU][ARM] Enable NHWC in Reduce (#23108)
NHWC was disabled because of accuracy issue:
https://github.com/ARM-software/ComputeLibrary/issues/1044

CVS-114403

---------

Co-authored-by: eshoguli <edward.shogulin@intel.com>
2024-06-11 16:21:23 +00:00
Vladimir Paramuzov f5c3fc3330
[GPU] Model stat print, host time measurement debug knob (#24904)
### Details:
 - Added dev pass to log operations statistics when VERBOSE=1
- Added debug knob to collect network::execute() time to measure time
required to schedule all primitives
- Updated common results comparison routine to print coordinate and to
show first mismatched element info in release build
2024-06-11 15:54:33 +00:00
Jorge Ragde 8eaa65e026
[TF FE Tests] Change HSVToRGB Unit Test To Test Grayscale Image (#24935)
### Details:
- Currently, the unit test for HSVToRGB is creating a tensor of all ones
in the Grayscale special case as mentioned in
[#24875](https://github.com/openvinotoolkit/openvino/pull/24875). Since
the images are expected to be in HSV and not RGB format, this will
create images that are green, and not gray. Grayscale in HSV can be
achieved by creating a tensor with HSV entries of [0, 0, .5].

### Tickets:
 - N/A

---------

Co-authored-by: Roman Kazantsev <roman.kazantsev@intel.com>
2024-06-11 14:16:48 +00:00
Andrii Staikov 91b3977169
Documentation for Transformations Python API (#24121)
### Details:

- Updating Transformations documentation
- Adding examples for Python API usage
- Adding examples for C++ API usage
- Adding patterns unit-tests

### Tickets:
 - *CVS-76333*
2024-06-11 14:07:39 +00:00
Sergey Shlyapnikov 00f4c99074
[GPU] Relax SDPA head size limitations for LLMs (#24930)
### Details:
- Relax SDPA head size limitations for LLMs from 128 only to a range of
64 to 256
- Fix accuracy issue in SDPA first token processing for
`TARGET_SEQ_LEN_BLOCK_SIZE % SUBGROUPS_PER_WG != 0` case

### Tickets:
 - *ticket-id*
2024-06-11 14:05:53 +00:00
Sebastian Golebiewski 5393b8a77a
[DOCS] Performance hint and thread scheduling update (#24946)
Fixing reference and formatting. Proofreading.
2024-06-11 10:08:07 +00:00
Edward Shogulin deb5e63ff2
[LPT] Quantization to point support (#24872)
### Details:
 - *Quaintization to point support*
 - Tests: wip

### Tickets:
 - *CVS-139557*
2024-06-11 09:11:04 +00:00
Ilya Lavrenov 395d6b7e09
Revert "More accurate shape of optimization" (#24941)
Reverts openvinotoolkit/openvino#24845

Issue CVS-143648
2024-06-11 08:43:17 +00:00
Vishniakov Nikolai 6e2286086f
[OV JS] Fix multiple Core instances (#24053)
### Details:
- New instance of Core creating on execution of readModel method. This
PR is fixing this behavior.

### Tickets:
 - 138160

---------

Co-authored-by: Alicja Miloszewska <alicja.miloszewska@intel.com>
2024-06-11 08:35:12 +00:00
60 changed files with 1852 additions and 330 deletions

View File

@ -33,6 +33,7 @@ function(build_docs)
set(DOXYGEN_MAPPING_SCRIPT "${SCRIPTS_DIR}/create_mapping.py")
set(DOCS_MAPPING_SCRIPT "${SCRIPTS_DIR}/create_doc_mapping.py")
set(BREATHE_APIDOC_SCRIPT "${SCRIPTS_DIR}/apidoc.py")
set(OV_INSTALLATION_SCRIPT "${SCRIPTS_DIR}/install_appropriate_openvino_version.py")
# Doxygen/Sphinx setup
set(DOXYGEN_XML_OUTPUT "${DOCS_BUILD_DIR}/xml")
@ -62,7 +63,9 @@ function(build_docs)
if(${ENABLE_PYTHON_API})
list(APPEND commands COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --green "STARTED preprocessing OpenVINO Python API")
list(APPEND commands COMMAND ${Python3_EXECUTABLE} -m pip install openvino)
list(APPEND commands COMMAND ${Python3_EXECUTABLE} ${OV_INSTALLATION_SCRIPT}
--ov_dir=${SPHINX_SETUP_DIR}
--python=${Python3_EXECUTABLE})
list(APPEND commands COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --green "FINISHED preprocessing OpenVINO Python API")
endif()

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

@ -37,7 +37,7 @@ compiled_model_3 = core.compile_model(
# ! [ov:intel_cpu:multi_threading:part0]
# ! [ov:intel_cpu:multi_threading:part1]
# Disable CPU threads pinning for inference when the system supports it
# Disable CPU thread pinning for inference when the system supports it
compiled_model_4 = core.compile_model(
model=model,
device_name=device_name,

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

@ -7,7 +7,7 @@ CPU Device
:maxdepth: 1
:hidden:
cpu-device/performance-hint-and-threads-scheduling
cpu-device/performance-hint-and-thread-scheduling
.. meta::
:description: The CPU plugin in the Intel® Distribution of OpenVINO™ toolkit
@ -234,6 +234,8 @@ This can be achieved by specifying ``MULTI:CPU,GPU.0`` as a target device in cas
For more details, see the :doc:`Multi-device execution <multi-device>` article.
.. _multi_stream_execution:
Multi-stream Execution
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@ -242,7 +244,7 @@ property is set for CPU plugin, then multiple streams are created for the model.
host thread, which means that incoming infer requests can be processed simultaneously. Each stream is pinned to its own group of
physical cores with respect to NUMA nodes physical memory usage to minimize overhead on data transfer between NUMA nodes.
For more details, see the :doc:`optimization guide <../optimize-inference>` and :doc:`threads scheduling introduction <cpu-device/performance-hint-and-threads-scheduling>`.
For more details, see the :doc:`optimization guide <../optimize-inference>` and :doc:`thread scheduling introduction <cpu-device/performance-hint-and-thread-scheduling>`.
.. note::
@ -390,7 +392,7 @@ Multi-Threading Optimization
CPU inference will infer an input or multiple inputs in parallel on multiple logical processors.
For more details, see the :doc:`threads scheduling introduction <cpu-device/performance-hint-and-threads-scheduling>`.
For more details, see the :doc:`thread scheduling introduction <cpu-device/performance-hint-and-thread-scheduling>`.
Denormals Optimization

View File

@ -1,48 +1,48 @@
Performance Hints and Threads Scheduling
Performance Hints and Thread Scheduling
========================================
.. meta::
:description: The Threads Scheduling of CPU plugin in OpenVINO™ Runtime
:description: Thread Scheduling of the CPU plugin in OpenVINO™ Runtime
detects CPU architecture and sets low-level properties based
on performance hints automatically.
While all supported devices in OpenVINO offer low-level performance settings, it is advisable
not to use these settings widely unless targeting specific platforms and models. The recommended
approach is to configure performance in OpenVINO Runtime using the high-level performance hints
property ``ov::hint::performance_mode``. Performance hints ensure optimal portability and
scalability of applications across various platforms and models.
To simplify the configuration of hardware devices, OpenVINO offers two performance hints: the
latency hint ``ov::hint::PerformanceMode::LATENCY`` and the throughput hint
``ov::hint::PerformanceMode::THROUGHPUT``.
To simplify the configuration of hardware devices, it is recommended to use the
:doc:` ov::hint::PerformanceMode::LATENCY and ov::hint::PerformanceMode::THROUGHPUT <../../../../optimize-inference/high-level-performance-hints>`
high-level performance hints. Both performance hints ensure optimal portability
and scalability of applications across various platforms and models.
- ``ov::inference_num_threads`` limits the number of logical processors used for CPU inference.
If the number set by the user is greater than the number of logical processors on the platform,
the multi-threading scheduler only uses the platform number for CPU inference.
- ``ov::num_streams`` limits the number of infer requests that can be run in parallel.
If the number set by the user is greater than the number of inference threads, multi-threading
scheduler only uses the number of inference threads to ensure that there is at least one thread per stream.
- ``ov::hint::scheduling_core_type`` specifies the type of CPU cores for CPU inference when the user runs
inference on a hybird platform that includes both Performance-cores (P-cores) and Efficient-cores (E-cores).
If the user platform only has one type of CPU core, this property has no effect, and CPU inference always uses this unique core type.
scheduler only uses the number of inference threads to ensure that there is at least
one thread per stream.
- ``ov::hint::scheduling_core_type`` specifies the type of CPU cores for CPU inference when
the user runs inference on a hybird platform that includes both Performance-cores (P-cores)
and Efficient-cores (E-cores). If the user platform only has one type of CPU core, this
property has no effect, and CPU inference always uses this unique core type.
- ``ov::hint::enable_hyper_threading`` limits the use of one or two logical processors per CPU
core when the platform has CPU hyperthreading enabled.
If there is only one logical processor per CPU core, such as Efficient-cores, this property has no effect, and CPU inference uses all logical processors.
If there is only one logical processor per CPU core, such as Efficient-cores, this
property has no effect, and CPU inference uses all logical processors.
- ``ov::hint::enable_cpu_pinning`` enables CPU pinning during CPU inference.
If the user enables this property but the inference scenario does not support it, this property will be disabled during model compilation.
If the user enables this property but the inference scenario does not support it, this
property will be disabled during model compilation.
For additional details on the above configurations, refer to `Multi-stream Execution <https://docs.openvino.ai/2024/openvino-workflow/running-inference/inference-devices-and-modes/cpu-device.html#multi-stream-execution>`__.
For additional details on the above configurations, refer to
:ref:`Multi-stream Execution <multi_stream_execution>`.
Latency Hint
###################################
#####################
In this scenario, the default setting of ``ov::hint::scheduling_core_type`` is determined by
the model precision and the ratio of P-cores and E-cores.
.. note::
P-cores is short for Performance-cores and E-cores stands for Efficient-cores. These types of cores are available starting with the 12th Gen Intel® Core™ processors.
P-cores is short for Performance-cores and E-cores stands for Efficient-cores. These
types of cores are available starting with the 12th Gen Intel® Core™ processors.
.. _Core Type Table of Latency Hint:
+----------------------------+---------------------+---------------------+
@ -57,7 +57,8 @@ the model precision and the ratio of P-cores and E-cores.
.. note::
Both P-cores and E-cores may be used for any configuration starting with 14th Gen Intel® Core™ processors on Windows.
Both P-cores and E-cores may be used for any configuration starting with 14th Gen Intel®
Core™ processors on Windows.
Then the default settings for low-level performance properties on Windows and Linux are as follows:
@ -77,13 +78,20 @@ Then the default settings for low-level performance properties on Windows and Li
.. note::
- ``ov::hint::scheduling_core_type`` may be adjusted for a particular inferred model on a specific platform based on internal heuristics to guarantee optimal performance.
- Both P-cores and E-cores are used for the Latency Hint on Intel® Core™ Ultra Processors on Windows, except in the case of large language models.
- In case hyper-threading is enabled, two logical processors share the hardware resources of one CPU core. OpenVINO does not expect to use both logical processors in one stream for a single infer request. So ``ov::hint::enable_hyper_threading`` is set to ``No`` in this scenario.
- ``ov::hint::enable_cpu_pinning`` is disabled by default on Windows and macOS, and enabled on Linux. Such default settings are aligned with typical workloads running in the corresponding environments to guarantee better out-of-the-box (OOB) performance.
- ``ov::hint::scheduling_core_type`` may be adjusted for a particular inferred model on a
specific platform based on internal heuristics to guarantee optimal performance.
- Both P-cores and E-cores are used for the Latency Hint on Intel® Core™ Ultra Processors
on Windows, except in the case of large language models.
- In case hyper-threading is enabled, two logical processors share the hardware resources
of one CPU core. OpenVINO does not expect to use both logical processors in one stream
for a single infer request. So ``ov::hint::enable_hyper_threading`` is set to
``No`` in this scenario.
- ``ov::hint::enable_cpu_pinning`` is disabled by default on Windows and macOS, and
enabled on Linux. Such default settings are aligned with typical workloads running
in the corresponding environments to guarantee better out-of-the-box (OOB) performance.
Throughput Hint
######################################
#####################
In this scenario, thread scheduling first evaluates the memory pressure of the model being
inferred on the current platform, and determines the number of threads per stream, as shown below.
@ -99,7 +107,8 @@ inferred on the current platform, and determines the number of threads per strea
+-----------------+-----------------------+
Then the value of ``ov::num_streams`` is calculated by dividing ``ov::inference_num_threads``
by the number of threads per stream. The default settings for low-level performance properties on Windows and Linux are as follows:
by the number of threads per stream. The default settings for low-level performance
properties on Windows and Linux are as follows:
+--------------------------------------+-------------------------------+-------------------------------+
| Property | Windows | Linux |
@ -117,13 +126,15 @@ by the number of threads per stream. The default settings for low-level performa
.. note::
- By default, different core types are not mixed within a single stream in this scenario. The cores from different NUMA nodes are not mixed within a single stream.
By default, different core types are not mixed within a single stream in this scenario.
The cores from different NUMA nodes are not mixed within a single stream.
Multi-Threading Optimization
##############################################
############################
The following properties can be used to limit the available CPU resources for model inference.
If the platform or operating system supports this behavior, the OpenVINO Runtime will perform multi-threading scheduling based on the limited available CPU.
If the platform or operating system supports this behavior, the OpenVINO Runtime will
perform multi-threading scheduling based on the limited available CPU.
- ``ov::inference_num_threads``
- ``ov::hint::scheduling_core_type``
@ -148,11 +159,13 @@ If the platform or operating system supports this behavior, the OpenVINO Runtime
.. note::
``ov::hint::scheduling_core_type`` and ``ov::hint::enable_hyper_threading`` only support Intel® x86-64 CPU on Linux and Windows in the current release.
``ov::hint::scheduling_core_type`` and ``ov::hint::enable_hyper_threading`` only support
Intel® x86-64 CPU on Linux and Windows in the current release.
In some use cases, OpenVINO Runtime will enable CPU thread pinning by default for better performance.
Users can also turn this feature on or off using the property ``ov::hint::enable_cpu_pinning``.
Disabling thread pinning may be beneficial in complex applications where several workloads are executed in parallel.
Disabling thread pinning may be beneficial in complex applications where several workloads
are executed in parallel.
.. tab-set::
@ -172,4 +185,4 @@ Disabling thread pinning may be beneficial in complex applications where several
For details on multi-stream execution check the
:doc:`optimization guide <../../optimize-inference/optimizing-throughput/advanced_throughput_options>`.
:doc:`optimization guide <../../optimize-inference/optimizing-throughput/advanced_throughput_options>`.

View File

@ -0,0 +1,32 @@
{{ fullname | escape | underline}}
.. currentmodule:: {{ module }}
.. autoclass:: {{ objname }}
:members:
:show-inheritance:
:inherited-members:
{% block methods %}
.. automethod:: __init__
{% if methods %}
.. rubric:: Methods
.. autosummary::
{% for met in methods %}
~{{ name }}.{{ met }}
{%- endfor %}
{% endif %}
{% endblock %}
{% block attributes %}
{% if attributes %}
.. rubric:: Attributes
.. autosummary::
{% for att in attributes %}
~{{ name }}.{{ att }}
{%- endfor %}
{% endif %}
{% endblock %}

View File

@ -0,0 +1,68 @@
{{ fullname | escape | underline}}
.. automodule:: {{ fullname }}
{% block attributes %}
{% if attributes %}
.. rubric:: Module Attributes
.. autosummary::
:toctree:
{% for attr in attributes %}
{{ attr }}
{% endfor %}
{% endif %}
{% endblock %}
{% block functions %}
{% if functions %}
.. rubric:: Functions
.. autosummary::
:toctree:
{% for func in functions %}
{% if not func.startswith('_') %}
{{ func }}
{% endif %}
{%- endfor %}
{% endif %}
{% endblock %}
{% block classes %}
{% if classes %}
.. rubric:: Classes
.. autosummary::
:toctree:
:template: custom-class-template.rst
{% for cl in classes %}
{{ cl }}
{%- endfor %}
{% endif %}
{% endblock %}
{% block exceptions %}
{% if exceptions %}
.. rubric:: Exceptions
.. autosummary::
:toctree:
{% for item in exceptions %}
{{ item }}
{%- endfor %}
{% endif %}
{% endblock %}
{% block modules %}
{% if modules %}
.. rubric:: Modules
.. autosummary::
:toctree:
:template: custom-module-template.rst
:recursive:
{% for mod in modules %}
{{ mod }}
{%- endfor %}
{% endif %}
{% endblock %}

View File

@ -0,0 +1,66 @@
import re
import argparse
import subprocess
import requests
import pkg_resources
from packaging import version
from pathlib import Path
def determine_openvino_version(file_path):
pattern = r"version_name\s*=\s*['\"]([^'\"]+)['\"]"
with open(file_path, 'r') as file:
content = file.read()
match = re.search(pattern, content)
if match:
return match.group(1)
else:
return None
def get_latest_version(major_version):
url = f"https://pypi.org/pypi/openvino/json"
response = requests.get(url)
if response.status_code == 200:
data = response.json()
versions = data['releases'].keys()
# Filter versions by the major version prefix
matching_versions = [v for v in versions if v.startswith(major_version)]
# Sort the matching versions and return the latest one
if matching_versions:
matching_versions.sort(key=version.parse)
return matching_versions[-1]
return None
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--ov_dir', type=Path, help='OpenVINO docs directory')
parser.add_argument('--python', type=Path, help='Python executable')
args = parser.parse_args()
ov_dir = args.ov_dir
python_executable = args.python
version_name = determine_openvino_version(ov_dir.joinpath("conf.py"))
if version_name is None:
ov_version = "openvino"
elif version_name == "nightly":
ov_version = "openvino-nightly"
else:
latest_version = get_latest_version(version_name)
if latest_version:
ov_version = f"openvino=={latest_version}"
else:
ov_version = f"openvino=={version_name}"
subprocess.check_call([f'{python_executable}', '-m', 'pip', 'install', '-U', ov_version, '--no-cache-dir'])
if __name__ == "__main__":
main()

View File

@ -12,11 +12,13 @@ class ReaderWorker : public Napi::AsyncWorker {
public:
/**
* @brief Constructs ReaderWorker class that is responisible for reading the model asynchronously.
* @note In the Execute() method, the Core object might be used concurrently to call read_model().
* @param info contains passed arguments. Can be empty.
*/
ReaderWorker(const Napi::Env& env, ReadModelArgs* args)
ReaderWorker(const Napi::Env& env, ov::Core& core, ReadModelArgs* args)
: Napi::AsyncWorker{env, "ReaderWorker"},
_deferred{env},
_core{core},
_args{args},
_model{} {
OPENVINO_ASSERT(_args, "Invalid pointer to ReadModelArgs.");
@ -35,6 +37,7 @@ protected:
///@}
private:
Napi::Promise::Deferred _deferred;
ov::Core& _core;
ReadModelArgs* _args;
std::shared_ptr<ov::Model> _model;
};

View File

@ -6,12 +6,10 @@
#include "node/include/model_wrap.hpp"
void ReaderWorker::Execute() {
ov::Core core;
if (_args->model_str.empty())
_model = core.read_model(_args->model_path, _args->bin_path);
_model = _core.read_model(_args->model_path, _args->bin_path);
else
_model = core.read_model(_args->model_str, _args->weight_tensor);
_model = _core.read_model(_args->model_str, _args->weight_tensor);
}
void ReaderWorker::OnOK() {

View File

@ -76,7 +76,7 @@ Napi::Value CoreWrap::read_model_sync(const Napi::CallbackInfo& info) {
Napi::Value CoreWrap::read_model_async(const Napi::CallbackInfo& info) {
try {
ReadModelArgs* args = new ReadModelArgs(info);
ReaderWorker* _readerWorker = new ReaderWorker(info.Env(), args);
ReaderWorker* _readerWorker = new ReaderWorker(info.Env(), _core, args);
_readerWorker->Queue();
return _readerWorker->GetPromise();

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

@ -691,6 +691,15 @@ size_t NetworkHelper::calculateLevels(
float& dequantizationSub,
float& updatedOutputLowValue,
float& updatedOutputHighValue) {
if (combinedIntervalHigh == combinedIntervalLow) {
// degenerate case: quantization to a point
dequantizationMul = 1.f;
dequantizationSub = 0.f;
updatedOutputLowValue = combinedIntervalLow;
updatedOutputHighValue = combinedIntervalHigh;
return 1ull;
}
const float maxOutputInterval = combinedIntervalHigh - combinedIntervalLow;
// FQ -> SUB_quantization -> MUL_quantization -[INT8]-> SUB_dequantization -> MUL_dequantization ->
const float quantizationMul = (dataPrecisionMax - dataPrecisionMin) / maxOutputInterval;
@ -882,38 +891,27 @@ std::tuple<std::shared_ptr<Node>, std::shared_ptr<Node>> NetworkHelper::decompos
std::vector<float> shifts(outputSize, 0.f);
std::vector<float> scales(outputSize);
// compute dequantizations (in double for INT32)
if (precision == element::i32 || precision == element::u32) {
for (size_t i = 0; i < outputSize; ++i) {
if (outputHighValues[i] != outputLowValues[i]) {
for (size_t i = 0; i < outputSize; ++i) {
if (outputHighValues[i] != outputLowValues[i]) {
// compute dequantizations (in double for INT32)
if (precision == element::i32 || precision == element::u32) {
shifts[i] = static_cast<float>(
(static_cast<double>(min) * outputHighValues[i] - static_cast<double>(max) * outputLowValues[i]) /
(static_cast<double>(outputHighValues[i]) - outputLowValues[i]));
scales[i] = static_cast<float>(
(static_cast<double>(outputHighValues[i]) - outputLowValues[i]) / (static_cast<double>(max) - min));
if (shifts[i] == -0.f) {
shifts[i] = 0.f;
}
} else {
scales[i] = outputHighValues[i];
minValues[i] = 1.f;
maxValues[i] = 1.f;
}
}
} else {
for (size_t i = 0; i < outputSize; ++i) {
if (outputHighValues[i] != outputLowValues[i]) {
shifts[i] = (min * outputHighValues[i] - max * outputLowValues[i]) /
(outputHighValues[i] - outputLowValues[i]);
scales[i] = (outputHighValues[i] - outputLowValues[i]) / (max - min);
if (shifts[i] == -0.f) {
shifts[i] = 0.f;
}
} else {
scales[i] = outputHighValues[i];
minValues[i] = 1.f;
maxValues[i] = 1.f;
}
if (shifts[i] == -0.f) {
shifts[i] = 0.f;
}
} else {
scales[i] = 1.f;
minValues[i] = outputHighValues[i];
maxValues[i] = outputHighValues[i];
}
}
@ -981,15 +979,12 @@ std::tuple<std::shared_ptr<Node>, std::shared_ptr<Node>> NetworkHelper::decompos
std::shared_ptr<ov::Node> convert2;
if (updatePrecision) {
std::shared_ptr<Node> convert;
std::shared_ptr<ov::opset1::Constant> newFqConstant = ov::as_type_ptr<ov::opset1::Constant>(newFQ);
if (ov::is_type<ov::opset1::Constant>(newFQ)) {
convert = foldConvert(newFQ->output(0), precision);
} else if (ov::is_type<ov::opset1::FakeQuantize>(newFQ)) {
if (ov::is_type<ov::opset1::FakeQuantize>(newFQ)) {
newFQ = setOutDataPrecision(ov::as_type_ptr<ov::opset1::FakeQuantize>(newFQ), precision);
convert = newFQ;
} else {
THROW_IE_LPT_EXCEPTION(*newFQ) << "unexpected operation type";
convert = foldConvert(newFQ->output(0), precision);
}
convert2 = std::make_shared<ov::opset1::Convert>(convert, element::f32);

View File

@ -44,11 +44,10 @@ std::shared_ptr<Node> Load::clone_with_new_inputs(const OutputVector& new_args)
LoadReshape::LoadReshape(const Output<ov::Node>& x, const size_t count, const size_t offset, std::vector<size_t> order)
: Load(x, count, offset), m_order(std::move(order)) {
const auto& in_shape = x.get_partial_shape();
OPENVINO_ASSERT(in_shape.is_static(), "LoadReshape supports only static input shapes");
const auto in_shape_size = in_shape.size();
OPENVINO_ASSERT(m_order.size() == in_shape_size, "LoadReshape got new_order of invalid size");
OPENVINO_ASSERT(*std::max_element(m_order.begin(), m_order.end()) == in_shape_size - 1 &&
*std::min_element(m_order.begin(), m_order.end()) == 0, "LoadReshape detected invalid values in new_order");
*std::min_element(m_order.begin(), m_order.end()) == 0, "LoadReshape detected invalid values in new_order");
const std::set<size_t> unique_dims(order.begin(), order.end());
OPENVINO_ASSERT(unique_dims.size() == order.size(), "LoadReshape order must not contain repeated elements");
constructor_validate_and_infer_types();

View File

@ -59,8 +59,7 @@ auto is_supported_op(const std::shared_ptr<const Node> &n) -> bool {
};
auto is_supported_transpose = [](const std::shared_ptr<const Node>& n) -> bool {
const auto& transpose = as_type_ptr<const opset1::Transpose>(n);
const auto& out_shape = n->get_output_partial_shape(0);
if (transpose && out_shape.is_static()) {
if (transpose) {
const auto parent = transpose->get_input_node_shared_ptr(0);
const auto child = transpose->get_output_target_inputs(0).begin()->get_node()->shared_from_this();
auto is_brgemm_case = ov::is_type<opset1::MatMul>(parent) || ov::is_type<opset1::MatMul>(child);

View File

@ -57,8 +57,8 @@ void ov::snippets::pass::ExplicitTransposeMatMulInputs::extract(const ov::Input<
OPENVINO_ASSERT(consumers.size() == 1,
"ExplicitTransposeMatMulInputs expects Parameter with one consumer in cases when there isn't existing Transpose on input");
// Extract Transpose from MatMul
OPENVINO_ASSERT(input.get_partial_shape().is_static(), "ExplicitTransposeMatMulInputs supports only static shapes");
const auto rank = input.get_shape().size();
OPENVINO_ASSERT(input.get_partial_shape().rank().is_static(), "ExplicitTransposeMatMulInputs supports only static ranks of shapes");
const auto rank = input.get_partial_shape().size();
std::vector<size_t> transpose_order(rank, 0);
std::iota(transpose_order.begin(), transpose_order.end(), 0);
std::swap(transpose_order[rank - 1], transpose_order[rank - 2]);
@ -72,9 +72,7 @@ void ov::snippets::pass::ExplicitTransposeMatMulInputs::extract(const ov::Input<
ov::snippets::pass::ExplicitTransposeMatMulInputs::ExplicitTransposeMatMulInputs() {
MATCHER_SCOPE(ExplicitTransposeMatMulInputs);
auto m_matmul0 = std::make_shared<ov::op::v0::MatMul>(
ov::pass::pattern::any_input(ov::pass::pattern::has_static_shape()),
ov::pass::pattern::any_input(ov::pass::pattern::has_static_shape()));
auto m_matmul0 = std::make_shared<ov::op::v0::MatMul>(ov::pass::pattern::any_input(), ov::pass::pattern::any_input());
register_matcher(std::make_shared<ov::pass::pattern::Matcher>(m_matmul0, matcher_name),
[=](ov::pass::pattern::Matcher &m) {

View File

@ -61,7 +61,7 @@ FuseTransposeBrgemm::FuseTransposeBrgemm() {
const auto& transpose_out = m.get_match_value();
const auto& const_order = ov::as_type_ptr<ov::op::v0::Constant>(transpose_out.get_node_shared_ptr()->get_input_node_shared_ptr(1));
const auto& original_port = ov::snippets::lowered::PortDescriptorUtils::get_port_descriptor_ptr(brgemm_out);
original_port->set_shape(transpose_out.get_shape());
original_port->set_shape(utils::pshape_to_vdims(transpose_out.get_partial_shape()));
original_port->set_layout(const_order->cast_vector<size_t>());
for (const auto& in : transpose_out.get_target_inputs())
in.replace_source_output(brgemm->output(0));
@ -75,7 +75,7 @@ FuseTransposeBrgemm::FuseTransposeBrgemm() {
const auto& const_order = ov::as_type_ptr<ov::op::v0::Constant>(transpose->get_input_node_shared_ptr(1));
brgemm->set_argument(i, transpose->input_value(0));
const auto& original_port = ov::snippets::lowered::PortDescriptorUtils::get_port_descriptor_ptr(in);
original_port->set_shape(transpose->get_input_shape(0));
original_port->set_shape(utils::pshape_to_vdims(transpose->get_input_partial_shape(0)));
original_port->set_layout(const_order->cast_vector<size_t>());
}
}

View File

@ -49,7 +49,7 @@ TransposeDecomposition::TransposeDecomposition() {
const auto transpose = ov::as_type_ptr<ov::opset1::Transpose>(pattern_to_output.at(match_transpose).get_node_shared_ptr());
const auto order = ov::as_type_ptr<ov::op::v0::Constant>(pattern_to_output.at(match_order).get_node_shared_ptr());
if (transformation_callback(transpose) || transpose->is_dynamic())
if (transformation_callback(transpose))
return false;
auto order_value = order->cast_vector<int>();
@ -65,10 +65,14 @@ TransposeDecomposition::TransposeDecomposition() {
auto load = std::make_shared<snippets::op::LoadReshape>(data_input, subtensor[0], 0, layout);
auto store = std::make_shared<snippets::op::Store>(load, subtensor[0]);
PortDescriptorUtils::set_port_descriptor_ptr(load->input(0), std::make_shared<PortDescriptor>(load->get_input_shape(0), subtensor, layout));
PortDescriptorUtils::set_port_descriptor_ptr(load->output(0), std::make_shared<PortDescriptor>(load->get_output_shape(0), subtensor));
PortDescriptorUtils::set_port_descriptor_ptr(store->input(0), std::make_shared<PortDescriptor>(store->get_input_shape(0), subtensor));
PortDescriptorUtils::set_port_descriptor_ptr(store->output(0), std::make_shared<PortDescriptor>(store->get_output_shape(0), subtensor));
PortDescriptorUtils::set_port_descriptor_ptr(load->input(0),
std::make_shared<PortDescriptor>(utils::pshape_to_vdims(load->get_input_partial_shape(0)), subtensor, layout));
PortDescriptorUtils::set_port_descriptor_ptr(load->output(0),
std::make_shared<PortDescriptor>(utils::pshape_to_vdims(load->get_output_partial_shape(0)), subtensor));
PortDescriptorUtils::set_port_descriptor_ptr(store->input(0),
std::make_shared<PortDescriptor>(utils::pshape_to_vdims(store->get_input_partial_shape(0)), subtensor));
PortDescriptorUtils::set_port_descriptor_ptr(store->output(0),
std::make_shared<PortDescriptor>(utils::pshape_to_vdims(store->get_output_partial_shape(0)), subtensor));
for (auto& input : transpose->output(0).get_target_inputs()) {
input.replace_source_output(store->output(0));

View File

@ -555,7 +555,7 @@ ov::pass::RoPEFusionQwen::RoPEFusionQwen(int split_output_id) {
{{"special_zero", true}});
auto slice_Slice_543 = GenSlice(view_Reshape_424, 0, head_size, 1, 3); // tensor_array<f32[?,?,32,128]>
auto hidden_states = makePattern(); //
auto hidden_states = makePattern("f32[?,?,?]"); //
auto ShapeOf_485735 = makePattern<opset1::ShapeOf>({hidden_states}, {});
auto Multiply_567524 = makePattern<opset1::Multiply>({ShapeOf_485735, {-1}}, {{"auto_broadcast", "numpy"}});
auto Gather_377635 = makePattern<opset8::Gather>({Multiply_567524, {1}, 0}, {{"batch_dims", 0}});

View File

@ -16,7 +16,6 @@
#include "openvino/op/shape_of.hpp"
#include "openvino/op/squeeze.hpp"
#include "openvino/op/util/multi_subgraph_base.hpp"
#include "openvino/op/util/op_types.hpp"
#include "transformations/utils/utils.hpp"
namespace {
@ -223,101 +222,7 @@ void optimize_value_usage(ov::Output<ov::Node>& output, STS_map& symbol_shape_so
}
}
std::vector<std::shared_ptr<ov::Node>> topological_order(const std::shared_ptr<ov::Model>& m) {
auto order = m->get_ordered_ops();
// step 1: split model into parameter related and parameter non-related ops
const std::string op_depends_on_parameter = "topological_sort_op_depends_on";
// values: true - parameter dependent; false otherwise
for (const auto& op : order) {
if (ov::as_type_ptr<ov::op::v0::Parameter>(op)) {
op->get_rt_info()[op_depends_on_parameter] = true;
} else if (ov::as_type_ptr<ov::op::v0::Constant>(op) || ov::as_type_ptr<ov::op::v0::ShapeOf>(op) ||
ov::as_type_ptr<ov::op::v3::ShapeOf>(op) ||
std::dynamic_pointer_cast<ov::op::util::VariableExtension>(op)) {
op->get_rt_info()[op_depends_on_parameter] = false;
} else { // deduce op type from inputs
const auto& inputs = op->input_values();
op->get_rt_info()[op_depends_on_parameter] =
std::any_of(inputs.begin(),
inputs.end(),
[&op_depends_on_parameter](const ov::Output<ov::Node>& input) {
return input.get_node_shared_ptr()->get_rt_info()[op_depends_on_parameter].as<bool>();
});
}
}
// step 2: starting from Result -- assign weight to ops:
// if parameter dependant, weights is maximum of output indices plus one
// else weights is maximum of output indices
// this step doesn't assign weights to all the ops, this is intentional and will be used in the following step
const std::string weight_rt_info_name = "topological_sort_weight";
for (auto it = order.rbegin(); it != order.rend(); ++it) {
const auto& op = *it;
int64_t weight = 0;
if (ov::as_type_ptr<ov::op::v0::Result>(op)) {
op->get_rt_info()[weight_rt_info_name] = weight;
} else {
bool output_has_weight = false;
for (const auto& output : op->outputs()) {
for (const auto& input : output.get_target_inputs()) {
const auto& output_op = input.get_node();
const auto& rt_info = output_op->get_rt_info();
if (!rt_info.count(weight_rt_info_name))
continue;
output_has_weight = true;
auto output_weight = rt_info.at(weight_rt_info_name).as<int64_t>();
weight = output_weight > weight ? output_weight : weight;
}
}
if (output_has_weight) {
if (op->get_rt_info()[op_depends_on_parameter].as<bool>()) {
weight += 1;
}
op->get_rt_info()[weight_rt_info_name] = weight;
}
}
}
// step 3: make propagation for all the nodes:
// if weight is already assigned -- skip operation
// else operation weights is minimum of input indices
// if all operation inputs have no weights -- this op is isolated and this algorithm doesn't make sense,
// such cases are extremely rare and rather theoretical, to handle them we return original ov::Model op order
std::map<int64_t, std::vector<std::shared_ptr<ov::Node>>> level_to_vector;
for (const auto& op : order) {
if (!op->get_rt_info().count(weight_rt_info_name)) {
int64_t weight = std::numeric_limits<int64_t>::max();
for (const auto& input : op->input_values()) {
const auto& rt_info = input.get_node_shared_ptr()->get_rt_info();
if (!rt_info.count(weight_rt_info_name))
continue;
auto input_weight = rt_info.at(weight_rt_info_name).as<int64_t>();
weight = input_weight < weight ? input_weight : weight;
}
if (weight != std::numeric_limits<int64_t>::max())
op->get_rt_info()[weight_rt_info_name] = weight;
else
return m->get_ordered_ops();
}
level_to_vector[op->get_rt_info().at(weight_rt_info_name).as<int64_t>()].push_back(op);
}
// finalization: descending order for levels and ops within level are ordered by get_ordered_ops
std::vector<std::shared_ptr<ov::Node>> result;
result.reserve(order.size());
for (auto it = level_to_vector.rbegin(); it != level_to_vector.rend(); ++it) {
const auto& item = *it;
result.insert(result.end(), item.second.begin(), item.second.end());
for (const auto& op : item.second) {
op->get_rt_info().erase(weight_rt_info_name);
op->get_rt_info().erase(op_depends_on_parameter);
}
}
return result;
}
void save_shape_sources(const std::shared_ptr<ov::Node>& op, STS_map& symbol_shape_source) {
if (!ov::is_type<ov::op::v0::ShapeOf>(op) && !ov::is_type<ov::op::v3::ShapeOf>(op))
return;
const auto& output = op->input_value(0);
void save_shape_sources(const ov::Output<ov::Node>& output, STS_map& symbol_shape_source) {
for (const auto& d : output.get_partial_shape()) {
if (d.is_static())
continue;
@ -335,7 +240,7 @@ bool ov::pass::OptimizeSymbolsUsedAsValues::run_on_model(const std::shared_ptr<o
RUN_ON_FUNCTION_SCOPE(OptimizeSymbolsUsedAsValues);
STS_map symbol_shape_source;
STS_map symbol_value_source;
for (const auto& op : topological_order(m)) {
for (const auto& op : m->get_ordered_ops()) {
// Result has output port which has shared (during validate_and_infer_type) tensor with input port.
// Transformations may replace input of Result. After replacement and before Result::validate_and_infer_type --
// output tensor of Result may contain inaccurate shape / symbols due to the sharing with tensor which may be
@ -347,9 +252,10 @@ bool ov::pass::OptimizeSymbolsUsedAsValues::run_on_model(const std::shared_ptr<o
// LTS maps aren't shared with sub-graphs because inner graph can not access outer graph for label sources
ov::op::util::process_subgraph(*this, op);
for (auto& output : op->outputs())
for (auto& output : op->outputs()) {
optimize_value_usage(output, symbol_shape_source, symbol_value_source);
save_shape_sources(op, symbol_shape_source);
save_shape_sources(output, symbol_shape_source);
}
}
return true;
}

View File

@ -75,16 +75,22 @@ TEST_F(TransformationTestsF, ApplySymbolEquivalence_Concat_Values) {
auto input_2 = make_shared<v0::Parameter>(element::f32, PartialShape::dynamic(4));
auto concat = make_shared<v0::Concat>(OutputVector{input_1, input_2}, -1);
auto shape = make_shared<v3::ShapeOf>(concat);
auto gather = make_shared<v8::Gather>(shape,
v0::Constant::create(element::i64, {1}, {-1}),
v0::Constant::create(element::i64, {}, {0}));
auto shape_1 = make_shared<v3::ShapeOf>(input_1);
auto gather_1 = make_shared<v8::Gather>(shape_1,
v0::Constant::create(element::i64, {1}, {3}),
v0::Constant::create(element::i64, {}, {0}));
auto shape_2 = make_shared<v3::ShapeOf>(input_2);
auto gather_2 = make_shared<v8::Gather>(shape_2,
v0::Constant::create(element::i64, {1}, {3}),
v0::Constant::create(element::i64, {}, {0}));
auto sum = make_shared<v1::Add>(gather_1, gather_2);
auto reshape = make_shared<v1::Reshape>(
concat,
make_shared<v0::Concat>(OutputVector{gather, v0::Constant::create(element::i64, {1}, {-1})}, 0),
make_shared<v0::Concat>(OutputVector{sum, v0::Constant::create(element::i64, {1}, {-1})}, 0),
false);
model_ref = make_shared<Model>(NodeVector{reshape}, ParameterVector{input_2, input_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));
}

View File

@ -36,24 +36,36 @@ bool AclReduceExecutor::init(const ReduceAttrs& reduceAttrs,
this->reduceAttrs = reduceAttrs;
auto srcDims = srcDescs[0]->getShape().getStaticDims();
auto dstDims = dstDescs[0]->getShape().getStaticDims();
const auto& srcDims = srcDescs[0]->getShape().getStaticDims();
const auto& dstDims = dstDescs[0]->getShape().getStaticDims();
bool hasSrcNspcLayout = srcDescs[0]->hasLayoutType(LayoutType::nspc);
bool hasDstNspcLayout = dstDescs[0]->hasLayoutType(LayoutType::nspc);
auto srcShape = shapeCast(srcDims);
auto dstShape = shapeCast(dstDims);
if (hasSrcNspcLayout && hasDstNspcLayout) {
changeLayoutToNH_C({&srcShape, &dstShape});
}
TensorInfo srcTensorInfo = TensorInfo(shapeCast(srcDims), 1,
TensorInfo srcTensorInfo = TensorInfo(srcShape, 1,
precisionToAclDataType(srcDescs[0]->getPrecision()), getAclDataLayoutByMemoryDesc(srcDescs[0]));
TensorInfo dstTensorInfo = TensorInfo(shapeCast(dstDims), 1,
TensorInfo dstTensorInfo = TensorInfo(dstShape, 1,
precisionToAclDataType(dstDescs[0]->getPrecision()), getAclDataLayoutByMemoryDesc(dstDescs[0]));
srcTensor.allocator()->init(srcTensorInfo);
dstTensor.allocator()->init(dstTensorInfo);
std::function<std::unique_ptr<IFunction>(void)> exec_func;
std::vector<int> castedAxes;
for (size_t i = 0; i < reduceAttrs.axes.size(); ++i) {
int axis = axisCast(reduceAttrs.axes[i], srcDims.size(), hasSrcNspcLayout ? NHWC_TO_NCHW : NO_LAYOUT_CONVERSION);
if (hasSrcNspcLayout && axis == -1) return false;
castedAxes.push_back(axis);
}
switch (reduceAttrs.operation) {
case Algorithm::ReduceMean: {
for (size_t i = 0; i < reduceAttrs.axes.size(); ++i) {
auto axe = axisCast(reduceAttrs.axes[i], srcDims.size());
auto pos = axisCast(i, reduceAttrs.axes.size());
axesMean.set(pos, axe);
axesMean.set(pos, castedAxes[i]);
}
Status reduceMeanStatus = NEReduceMean::validate(&srcTensorInfo, axesMean, reduceAttrs.keepDims, &dstTensorInfo);
if (!reduceMeanStatus) {
@ -71,15 +83,15 @@ bool AclReduceExecutor::init(const ReduceAttrs& reduceAttrs,
case Algorithm::ReduceMin:
case Algorithm::ReduceSum:
case Algorithm::ReduceProd: {
Status reductionOperationStatus = NEReductionOperation::validate(&srcTensorInfo, &dstTensorInfo, axisCast(reduceAttrs.axes[0], srcDims.size()),
Status reductionOperationStatus = NEReductionOperation::validate(&srcTensorInfo, &dstTensorInfo, castedAxes[0],
getAclReductionOperationByAlgorithm(reduceAttrs.operation), reduceAttrs.keepDims);
if (!reductionOperationStatus) {
DEBUG_LOG("NEReductionOperation validation with indices failed: ", reductionOperationStatus.error_description());
return false;
}
exec_func = [this, srcDims]() -> std::unique_ptr<IFunction> {
exec_func = [this, castedAxes]() -> std::unique_ptr<IFunction> {
auto acl_op = std::make_unique<arm_compute::NEReductionOperation>();
acl_op->configure(&srcTensor, &dstTensor, axisCast(this->reduceAttrs.axes[0], srcDims.size()),
acl_op->configure(&srcTensor, &dstTensor, castedAxes[0],
getAclReductionOperationByAlgorithm(this->reduceAttrs.operation), this->reduceAttrs.keepDims);
return acl_op;
};
@ -103,4 +115,4 @@ void AclReduceExecutor::exec(const std::vector<MemoryCPtr>& src, const std::vect
}
} // namespace intel_cpu
} // namespace ov
} // namespace ov

View File

@ -67,8 +67,33 @@ inline arm_compute::TensorShape shapeCast(const VectorDims& dims) {
return tensorShape;
}
inline std::size_t axisCast(const std::size_t axis, const std::size_t shapeSize) {
return shapeSize - axis - 1;
enum ACLAxisCastMode {
NO_LAYOUT_CONVERSION,
NHWC_TO_NCHW,
NCHW_TO_NHWC
};
/**
* @brief Return reverted axis used in ACL. If axis cast mode is
* @param axis axis that needs to be converted
* @param shapeSize size of the shape, which axis needs to be converted
* @param axisCastMode specifies whether layout conversion is required or not
* @return reverted axis
*/
inline int axisCast(const std::size_t axis, const std::size_t shapeSize, ACLAxisCastMode axisCastMode = NO_LAYOUT_CONVERSION) {
// CWHN (reverted NHWC) (0, 1, 2, 3) into WHCN (reverted NCHW) (1, 2, 0, 3)
static std::vector<size_t> nhwcToNchw = {1, 2, 0, 3};
// WHCN (reverted NCHW) (0, 1, 2, 3) into CWHN (reverted NHWC) (2, 0, 1, 3)
static std::vector<size_t> nchwToNhwc = {2, 0, 1, 3};
size_t revertedAxis = shapeSize - axis - 1;
switch (axisCastMode) {
case NHWC_TO_NCHW:
return revertedAxis > 3 ? -1 : nhwcToNchw[revertedAxis];
case NCHW_TO_NHWC:
return revertedAxis > 3 ? -1 : nchwToNhwc[revertedAxis];
default:
return revertedAxis;
}
}
inline Dim vectorProduct(const VectorDims& vec, size_t size) {

View File

@ -1992,8 +1992,7 @@ void Reduce::initSupportedPrimitiveDescriptors() {
if (axis < 0)
axis += static_cast<int>(getInputShapeAtPort(REDUCE_DATA).getRank());
}
// TODO: Per-channel layout is disabled due to accuracy issue in ACL Reduce Executor
// pushDesc(LayoutType::nspc, LayoutType::nspc, input_prec, output_prec, undef, true);
pushDesc(LayoutType::nspc, LayoutType::nspc, input_prec, output_prec, impl_desc_type::undef, true);
pushDesc(LayoutType::ncsp, LayoutType::ncsp, input_prec, output_prec, impl_desc_type::undef, true);
canUseAclExecutor = !supportedPrimitiveDescriptors.empty();
if (canUseAclExecutor)

View File

@ -0,0 +1,38 @@
// Copyright (C) 2020-2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
#include "convert_reduce_no_keep_dims.hpp"
#include "openvino/core/rt_info.hpp"
#include "openvino/opsets/opset8.hpp"
template <class T>
ov::matcher_pass_callback ov::intel_cpu::ConvertReduceNoKeepDimsBase::convert_reduce() {
return [&](ov::pass::pattern::Matcher& m) {
auto reduce = std::dynamic_pointer_cast<T>(m.get_match_root());
if (!reduce || reduce->get_keep_dims()) {
return false;
}
reduce->set_keep_dims(true);
const auto reduce_new = reduce->clone_with_new_inputs({reduce->input_value(0), reduce->input_value(1)});
std::shared_ptr<ov::Node> squeeze = std::make_shared<ov::op::v0::Squeeze>(reduce_new, reduce->input_value(1));
squeeze->set_friendly_name(reduce_new->get_friendly_name());
ov::copy_runtime_info(reduce, {reduce_new, squeeze});
ov::replace_node(reduce, squeeze);
return true;
};
}
template <typename ReductionType>
ov::intel_cpu::ConvertReduction<ReductionType>::ConvertReduction() {
auto m = std::make_shared<ov::pass::pattern::Matcher>(
ov::pass::pattern::wrap_type<ReductionType>({ov::pass::pattern::any_input(),
ov::pass::pattern::wrap_type<ov::opset8::Constant>()}), "ConvertReduction");
register_matcher(m, convert_reduce<ReductionType>());
}
template class ov::intel_cpu::ConvertReduction<ov::op::util::LogicalReductionKeepDims>;
template class ov::intel_cpu::ConvertReduction<ov::op::util::ArithmeticReductionKeepDims>;

View File

@ -0,0 +1,71 @@
// Copyright (C) 2020-2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include "openvino/pass/pattern/op/wrap_type.hpp"
#include "openvino/pass/graph_rewrite.hpp"
#include "openvino/op/util/arithmetic_reductions_keep_dims.hpp"
#include "openvino/op/util/logical_reduction_keep_dims.hpp"
/*
* Description:
* ConvertReduceNoKeepDimsBase detects Reduce operations with keepDims = false.
* Such Reduce operation is replaced with Reduce operation with keepDims = true and Squeeze
* which removes undesired dimensions.
*
* Before:
*
* +--------------+ +-----------------+
* | Data | | Axes tensor |
* +-----------+--+ +-+---------------+
* | |
* +---------------------------+
* | Reduce (keepDims = false) |
* +---------------------------+
*
* After:
*
* +--------------+ +-----------------+
* | Data | | Axes tensor |
* +-----------+--+ +-+------------+--+
* | | |
* +---------------------------+ |
* | Reduce (keepDims = true) | |
* +-----------------------+---+ |
* | |
* +--------v------v-+
* | Squeeze |
* +-----------------+
*
*/
namespace ov {
namespace intel_cpu {
class ConvertReduceNoKeepDimsBase: public ov::pass::MatcherPass {
public:
OPENVINO_RTTI("ConvertReduceNoKeepDims", "0");
template <class T>
ov::matcher_pass_callback convert_reduce();
};
template <typename ReductionType>
class ConvertReduction: public ConvertReduceNoKeepDimsBase {
public:
OPENVINO_RTTI("ConvertReduction", "0");
ConvertReduction();
};
class ConvertReduceNoKeepDims: public ov::pass::GraphRewrite {
public:
OPENVINO_RTTI("ConvertReduceNoKeepDims", "0");
ConvertReduceNoKeepDims() {
add_matcher<ConvertReduction<ov::op::util::LogicalReductionKeepDims>>();
add_matcher<ConvertReduction<ov::op::util::ArithmeticReductionKeepDims>>();
}
};
} // namespace intel_cpu
} // namespace ov

View File

@ -122,6 +122,7 @@
#include "transformations/cpu_opset/arm/pass/convert_group_conv1d.hpp"
#include "transformations/cpu_opset/arm/pass/convert_reduce_multi_axis.hpp"
#include "transformations/cpu_opset/arm/pass/mish_decomposition.hpp"
#include "transformations/cpu_opset/arm/pass/convert_reduce_no_keep_dims.hpp"
#include "transformations/cpu_opset/common/pass/decompose_integer_divide.hpp"
#include "transformations/cpu_opset/common/pass/convert_fq_rnn_to_quantized_rnn.hpp"
#include "transformations/cpu_opset/common/pass/insert_convert_after_extension.hpp"
@ -432,6 +433,7 @@ void Transformations::PreLpt(const std::vector<ov::element::Type>& defaultPrecis
CPU_REGISTER_PASS_COMMON(manager, SwapConvertTranspose);
CPU_REGISTER_PASS_X64(manager, ConvertToInteraction);
CPU_REGISTER_PASS_X64(manager, ConvertInteractionInt8);
CPU_REGISTER_PASS_ARM(manager, ConvertReduceNoKeepDims);
CPU_REGISTER_PASS_ARM(manager, ConvertReduceMultiAxis);
CPU_REGISTER_PASS_ARM32(manager, MishDecomposition);
CPU_REGISTER_PASS_ARM(manager, ConvertConv1D);

View File

@ -46,10 +46,7 @@ std::vector<std::vector<ov::test::InputShape>> inputShapes_SingleBatch = {
std::vector<CPUSpecificParams> cpuParams_4D = {
CPUSpecificParams({nchw}, {nchw}, {}, {}),
//NHWC layout is disabled on ARM due to accuracy issue: https://github.com/ARM-software/ComputeLibrary/issues/1044
#if defined(OPENVINO_ARCH_X86) || defined(OPENVINO_ARCH_X86_64)
CPUSpecificParams({nhwc}, {nhwc}, {}, {}),
#endif
};
/* ================================ 1.1 No fusion - Arithmetic ================================ */
@ -160,4 +157,4 @@ INSTANTIATE_TEST_SUITE_P(
} // namespace Reduce
} // namespace test
} // namespace ov
} // namespace ov

View File

@ -141,6 +141,14 @@ const std::vector<LayerTestsDefinitions::ConvolutionTransformationParam> params
false,
"Convolution",
"f32"
},
{
{ 256ul, ov::Shape { 1, 1, 1, 1 }, { 0.f }, { 0.f }, { 0.f }, { 0.f } },
false,
{ 255ul, ov::Shape { 1, 1, 1, 1 }, { 0.f }, { 254.f }, { -12.7f }, { 12.7f } },
false,
"Convolution",
"u8"
}
};

View File

@ -11,8 +11,26 @@ namespace snippets {
namespace {
std::vector<ov::PartialShape> input_shapes_4D{{2, 3, 5, 13}, {2, 3, 2, 4}, {1, 7, 1, 4}};
std::vector<ov::PartialShape> input_shapes_3D{{3, 5, 13}, {3, 2, 4}, {7, 1, 4}};
const std::vector<InputShape> input_shapes_3D = {
{{}, {{3, 5, 13}}},
{{}, {{3, 2, 4}}},
{{}, {{7, 1, 4}}},
{{-1, -1, -1}, {{7, 1, 4}, {5, 7, 18}, {5, 7, 18}, {7, 1, 4}}},
{{10, -1, -1}, {{10, 1, 4}, {10, 17, 18}, {10, 7, 18}, {10, 1, 4}}},
{{10, -1, 15}, {{10, 1, 15}, {10, 1, 15}, {10, 7, 15}, {10, 15, 15}}},
};
const std::vector<InputShape> input_shapes_4D = {
{{}, {{2, 3, 5, 13}}},
{{}, {{2, 3, 2, 4}}},
{{}, {{1, 7, 1, 4}}},
{{-1, -1, -1, -1}, {{1, 7, 1, 4}, {2, 3, 2, 4}, {8, 7, 1, 4}, {2, 3, 2, 4}, {1, 7, 1, 4}}},
{{-1, 9, -1, -1}, {{1, 9, 1, 4}, {2, 9, 2, 4}, {8, 9, 1, 4}, {1, 9, 1, 4}, {1, 9, 1, 4}, {2, 9, 2, 4}}},
{{-1, -1, -1, 5}, {{2, 8, 2, 5}, {2, 8, 2, 5}, {8, 2, 5, 5}, {1, 2, 5, 5}, {8, 3, 5, 5}}},
{{-1, 9, -1, 5}, {{1, 9, 1, 5}, {2, 9, 2, 5}, {8, 9, 5, 5}, {1, 9, 5, 5}, {8, 9, 5, 5}}},
{{2, -1, -1, -1}, {{2, 7, 1, 4}, {2, 3, 2, 4}, {2, 7, 1, 4}, {2, 3, 2, 4}, {2, 7, 1, 4}}},
};
std::vector<std::vector<int32_t>> orders_4D{{0, 2, 3, 1}};
std::vector<std::vector<int32_t>> orders_3D{{1, 2, 0}};
@ -34,10 +52,23 @@ INSTANTIATE_TEST_SUITE_P(smoke_Snippets_Transpose_4D, Transpose,
::testing::Values(ov::test::utils::DEVICE_CPU)),
Transpose::getTestCaseName);
const std::vector<std::pair<InputShape, InputShape>> inputShapesPair = {
{{{}, {{2, 31, 3, 5}}}, {{}, {{2, 3, 5, 31}}}},
{{{-1, -1, -1, -1}, {{2, 31, 3, 5}, {2, 33, 4, 5}, {2, 33, 4, 5}}},
{{-1, -1, -1, -1}, {{2, 3, 5, 31}, {2, 4, 5, 33}, {2, 4, 5, 33}}}},
{{{-1, -1, -1, -1}, {{2, 31, 3, 5}, {2, 33, 4, 5}, {2, 33, 4, 5}}},
{{-1, -1, 1, 1}, {{2, 3, 1, 1}, {2, 4, 1, 1}, {2, 4, 1, 1}}}},
{{{-1, 33, -1, -1}, {{2, 33, 3, 5}, {2, 33, 4, 5}, {2, 33, 2, 5}}},
{{-1, -1, 1, 1}, {{2, 3, 1, 1}, {2, 4, 1, 1}, {2, 2, 1, 1}}}},
{{{-1, -1, -1, -1}, {{2, 16, 3, 5}, {2, 8, 4, 5}, {2, 4, 2, 5}}},
{{-1, -1, -1, 1}, {{2, 3, 1, 1}, {2, 4, 1, 1}, {2, 2, 1, 1}}}},
{{{-1, 18, -1, -1}, {{2, 18, 3, 5}, {2, 18, 4, 5}, {2, 18, 2, 6}}},
{{-1, -1, -1, 18}, {{2, 3, 5, 18}, {2, 4, 5, 18}, {2, 2, 6, 18}}}},
};
INSTANTIATE_TEST_SUITE_P(smoke_Snippets_TransposeMul, TransposeMul,
::testing::Combine(
::testing::Values(ov::PartialShape {2, 31, 3, 5}),
::testing::ValuesIn(std::vector<ov::PartialShape>{{2, 3, 5, 31}}),
::testing::ValuesIn(inputShapesPair),
::testing::Values(std::vector<int> {0, 2, 3, 1}),
::testing::Values(1), // Transpose
::testing::Values(1), // Tokenized Transpose

View File

@ -12,13 +12,29 @@ namespace snippets {
namespace {
const std::vector<ov::Shape> inputShape = {
ov::Shape{1, 128, 3, 16},
const std::vector<std::vector<InputShape>> inputShape = {
{{{}, {{1, 128, 3, 16}}}},
{{{-1, -1, -1, -1}, {{1, 128, 3, 16}, {1, 64, 3, 8}, {1, 128, 3, 16}}}},
{{{-1, 100, -1, -1}, {{1, 100, 3, 16}, {1, 100, 3, 8}, {1, 100, 2, 16}, {1, 100, 3, 8}}}},
{{{-1, -1, -1, 16}, {{1, 128, 3, 16}, {1, 32, 3, 16}, {1, 32, 3, 16}, {1, 100, 2, 16}}}},
};
const std::vector<std::vector<InputShape>> inputShapeWithEltwise = {
{{{}, {{1, 128, 3, 16}}},
{{}, {{1, 3, 16, 128}}}},
{{{-1, -1, -1, -1}, {{1, 128, 3, 16}, {1, 64, 3, 8}, {1, 128, 3, 16}}},
{{-1, -1, -1, -1}, {{1, 3, 16, 128}, {1, 3, 8, 64}, {1, 3, 16, 128}}}},
{{{-1, 100, -1, -1}, {{1, 100, 3, 16}, {1, 100, 3, 8}, {1, 100, 2, 16}, {1, 100, 3, 8}}},
{{-1, -1, -1, 100}, {{1, 1, 1, 100}, {1, 3, 8, 100}, {1, 2, 16, 100}, {1, 3, 8, 100}}}},
{{{-1, 100, -1, 3}, {{1, 100, 3, 3}, {1, 100, 3, 3}, {1, 100, 2, 3}, {1, 100, 3, 3}}},
{{-1, -1, -1, 100}, {{1, 1, 1, 100}, {1, 3, 3, 100}, {1, 2, 3, 100}, {1, 1, 1, 100}}}},
{{{-1, -1, -1, 16}, {{1, 128, 3, 16}, {1, 32, 3, 16}, {1, 32, 3, 16}, {1, 100, 2, 16}}},
{{-1, -1, 16, -1}, {{1, 3, 16, 128}, {1, 1, 16, 32}, {1, 3, 16, 32}, {1, 2, 16, 100}}}},
};
INSTANTIATE_TEST_SUITE_P(smoke_Snippets_TransposeSoftmax, TransposeSoftmax,
::testing::Combine(
::testing::Values(inputShape),
::testing::ValuesIn(inputShape),
::testing::Values(std::vector<int64_t>{0, 2, 3, 1}),
::testing::Values(-1),
::testing::Values(1),
@ -28,7 +44,7 @@ INSTANTIATE_TEST_SUITE_P(smoke_Snippets_TransposeSoftmax, TransposeSoftmax,
INSTANTIATE_TEST_SUITE_P(smoke_Snippets_TransposeSoftmaxEltwise, TransposeSoftmaxEltwise,
::testing::Combine(
::testing::Values(inputShape),
::testing::ValuesIn(inputShapeWithEltwise),
::testing::Values(std::vector<int64_t>{0, 2, 3, 1}),
::testing::Values(-1),
::testing::Values(1),

View File

@ -0,0 +1,91 @@
// Copyright (C) 2018-2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include <gtest/gtest.h>
#include <openvino/opsets/opset1.hpp>
#include <transformations/cpu_opset/arm/pass/convert_reduce_no_keep_dims.hpp>
#include "common_test_utils/ov_test_utils.hpp"
using namespace ov::intel_cpu;
template <class T>
class ConvertReduceNoKeepDimsTest : public testing::Test {};
template <class T>
static std::shared_ptr<ov::Model> createInitGraph(std::shared_ptr<ov::opset1::Parameter> param) {
auto axes = ov::opset1::Constant::create(ov::element::i64, ov::Shape{2}, {0, 1});
auto reduce = std::make_shared<T>(param, axes, false);
return std::make_shared<ov::Model>(ov::NodeVector{ reduce }, ov::ParameterVector{ param });
}
template <class T>
static std::shared_ptr<ov::Model> createRefGraph(std::shared_ptr<ov::opset1::Parameter> param) {
auto axes = ov::opset1::Constant::create(ov::element::i64, ov::Shape{2}, {0, 1});
auto reduce = std::make_shared<T>(param, axes, true);
auto squeeze = std::make_shared<ov::opset1::Squeeze>(reduce, axes);
return std::make_shared<ov::Model>(ov::NodeVector{ squeeze }, ov::ParameterVector{ param });
}
template <class T>
static bool registerAndRunReducePass(std::shared_ptr<ov::Model> model) {
ov::pass::Manager manager;
if (std::is_base_of<ov::op::util::LogicalReductionKeepDims, T>::value) {
manager.register_pass<ConvertReduction<ov::op::util::LogicalReductionKeepDims>>();
} else if (std::is_base_of<ov::op::util::ArithmeticReductionKeepDims, T>::value) {
manager.register_pass<ConvertReduction<ov::op::util::ArithmeticReductionKeepDims>>();
} else {
return false;
}
manager.run_passes(model);
return true;
}
static ov::Shape static_param_shape = ov::Shape{2, 19, 2, 9};
static ov::PartialShape dynamic_param_shape = ov::PartialShape{2, -1, 2, 9};
TYPED_TEST_SUITE_P(ConvertReduceNoKeepDimsTest);
TYPED_TEST_P(ConvertReduceNoKeepDimsTest, CheckConvertReduceTransformationIsAppliedForStaticShapes) {
ov::element::Type_t dataType = std::is_base_of<ov::op::util::LogicalReductionKeepDims, TypeParam>::value ?
ov::element::boolean : ov::element::f32;
auto param = std::make_shared<ov::opset1::Parameter>(dataType, static_param_shape);
auto model = createInitGraph<TypeParam>(param);
auto model_ref = createRefGraph<TypeParam>(param);
if (!registerAndRunReducePass<TypeParam>(model)) {
FAIL() << "Reduce pass is not registered.";
}
auto res = compare_functions(model, model_ref);
ASSERT_TRUE(res.first) << res.second;
}
TYPED_TEST_P(ConvertReduceNoKeepDimsTest, CheckConvertReduceTransformationIsAppliedForDynaimcShapes) {
ov::element::Type_t dataType = std::is_base_of<ov::op::util::LogicalReductionKeepDims, TypeParam>::value ?
ov::element::boolean : ov::element::f32;
auto param = std::make_shared<ov::opset1::Parameter>(dataType, dynamic_param_shape);
auto model = createInitGraph<TypeParam>(param);
auto model_ref = createRefGraph<TypeParam>(param);
if (!registerAndRunReducePass<TypeParam>(model)) {
FAIL() << "Reduce pass is not registered.";
}
auto res = compare_functions(model, model_ref);
ASSERT_TRUE(res.first) << res.second;
}
REGISTER_TYPED_TEST_SUITE_P(ConvertReduceNoKeepDimsTest,
CheckConvertReduceTransformationIsAppliedForStaticShapes,
CheckConvertReduceTransformationIsAppliedForDynaimcShapes);
using reduceTypes = ::testing::Types<ov::opset1::ReduceMin,
ov::opset1::ReduceMax,
ov::opset1::ReduceSum,
ov::opset1::ReduceProd,
ov::opset1::ReduceMean,
ov::opset1::ReduceLogicalAnd,
ov::opset1::ReduceLogicalOr>;
INSTANTIATE_TYPED_TEST_SUITE_P(ConvertReduce, ConvertReduceNoKeepDimsTest, reduceTypes);

View File

@ -282,6 +282,7 @@ private:
void set_variables_state_info(const std::string& variable_id, const layout& variable_layout, ov::element::Type user_specified_type, const primitive* p);
void dump_memory_pool(std::string dump_path, int64_t curr_iter);
std::vector<uint64_t> host_exec_times;
#ifdef GPU_DEBUG_CONFIG
int64_t iteration = 0;
#endif

View File

@ -109,6 +109,7 @@ public:
int disable_onednn_opt_post_ops; // Disable onednn optimize post operators
std::string dump_profiling_data; // Enables dump of extended performance profiling to specified dir
int dump_profiling_data_per_iter; // Enables dump of extended performance profiling to specified dir for each iteration
int host_time_profiling; // Enables measurement of scheduling time spend on the host
std::string dump_graphs; // Dump optimized graph
std::string dump_sources; // Dump opencl sources
std::string dump_layers_path; // Enable dumping intermediate buffers and set the dest path

View File

@ -433,6 +433,22 @@ network::network(program::ptr program, stream::ptr stream, uint16_t stream_id)
: network(program, program->get_config(), stream, false, stream_id == 0) {}
network::~network() {
GPU_DEBUG_IF(debug_configuration::get_instance()->host_time_profiling) {
if (host_exec_times.size() >= 2) {
double first = static_cast<double>(host_exec_times[0]);
double avg = static_cast<double>(std::accumulate(host_exec_times.begin() + 1, host_exec_times.end(), (size_t)0, std::plus<size_t>()));
avg /= (host_exec_times.size() - 1);
std::string resolution = " us";
if (avg > 1000.0) {
resolution = " ms";
avg /= 1000.0;
first /= 1000.0;
}
GPU_DEBUG_COUT << "Network[" << net_id << "] First infer host time: " << first << resolution << std::endl;
GPU_DEBUG_COUT << "Network[" << net_id << "] Infer avg host time: " << avg << resolution << std::endl;
}
}
if (_program != nullptr)
_program->cancel_compilation_context();
_memory_pool->clear_pool_for_network(net_id);
@ -915,7 +931,12 @@ void network::add_to_exec_order(const primitive_id& id) {
}
std::map<primitive_id, network_output> network::execute(const std::vector<event::ptr>& dependencies) {
auto start = std::chrono::high_resolution_clock::now();
execute_impl(dependencies);
auto end = std::chrono::high_resolution_clock::now();
GPU_DEBUG_IF(debug_configuration::get_instance()->host_time_profiling)
host_exec_times.push_back(std::chrono::duration_cast<std::chrono::microseconds>(end - start).count());
auto output_ids = get_output_ids();
std::map<primitive_id, network_output> result;

View File

@ -795,7 +795,7 @@ KERNEL(sdpa_opt)(
// If the number of partitions is greater than 1, save exm_sums and max_logits to the temporary buffers
// Use single WI in the WG, since all the WIs have the same value
if (num_of_partitions > 1 && sglid == 0) {
for (uint i = 0; i < QK_MAX_NUMS_PER_SG; i++) {
for (uint i = 0; i < QK_ITERS_END; i++) {
if (target_seq_idx + sgid + (i * SUBGROUPS_PER_WG) < TARGET_SEQ_LEN) {
const uint exp_sums_offset = b0_idx * (NUM_HEADS * TARGET_SEQ_LEN * num_of_partitions) +
b1_idx * (TARGET_SEQ_LEN * num_of_partitions) +

View File

@ -247,10 +247,11 @@ FullyConnected_bf_tiled::GetAutoTuneParams(const fully_connected_params& params,
if (params.weights.GetDType() == WeightsType::UINT4 || params.weights.GetDType() == WeightsType::INT4) {
if (!params.is_shape_agnostic && batch == 1) {
// Tuning for Meteor Lake
size_t ideal_num_threads = params.engineInfo.maxThreadsPerDevice * simd;
if (output_f / 2 < ideal_num_threads * 0.8 && params.weights.GetLayout() == WeightsLayout::os_is_yx_osv32_isv2) {
size_t min_num_threads = params.engineInfo.computeUnitsCount * simd;
if (output_f / 2 < min_num_threads && params.weights.GetLayout() == WeightsLayout::os_is_yx_osv32_isv2) {
GPU_DEBUG_TRACE_DETAIL << "FC bf tiled: Set ofm_tile 1. (output_f : " << output_f
<< ", ideal threads : " << ideal_num_threads << ")" << std::endl;
<< ", computeUnitsCount : " << params.engineInfo.computeUnitsCount
<< " min_num_threads : " << min_num_threads << ")" << std::endl;
return selector.Default(tune_params(1, 1, 4, 2, 1, 1, EXE_MODE_DEFAULT));
} else {
return selector.Default(tune_params(1, 2, 4, 2, 1, 1, EXE_MODE_DEFAULT));

View File

@ -0,0 +1,54 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "print_model_statistics.hpp"
#include "intel_gpu/runtime/debug_configuration.hpp"
#include "openvino/core/type.hpp"
#include "openvino/op/util/multi_subgraph_base.hpp"
#include <memory>
namespace ov {
namespace intel_gpu {
namespace {
size_t collect_stats(const std::shared_ptr<ov::Model>& m, std::map<DiscreteTypeInfo, size_t>& ops_stat) {
const std::vector<std::shared_ptr<ov::Node>> ops = m->get_ops();
size_t total = ops.size();
for (auto& op : ops) {
const auto& tinfo = op->get_type_info();
if (ops_stat.find(tinfo) == ops_stat.end()) {
ops_stat[tinfo] = 0;
}
ops_stat[tinfo]++;
if (auto subgraph_op = std::dynamic_pointer_cast<ov::op::util::MultiSubGraphOp>(op)) {
for (const auto& subgraph : subgraph_op->get_functions()) {
total += collect_stats(subgraph, ops_stat);
}
}
}
return total;
}
} // namespace
bool PrintModelStatistics::run_on_model(const std::shared_ptr<ov::Model>& m) {
std::map<DiscreteTypeInfo, size_t> ops_stat;
size_t total = collect_stats(m, ops_stat);
std::stringstream ss;
ss << "Operations statistics:\n";
for (auto& kv : ops_stat) {
ss << "\t" << kv.first.version_id << "::" << kv.first.name << " " << kv.second << std::endl;
}
ss << "\tTotal: " << total;
GPU_DEBUG_INFO << ss.str() << std::endl;;
return false;
}
} // namespace intel_gpu
} // namespace ov

View File

@ -0,0 +1,21 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "openvino/pass/pass.hpp"
namespace ov {
namespace intel_gpu {
class PrintModelStatistics : public ov::pass::ModelPass {
public:
OPENVINO_RTTI("PrintModelStatistics", "0");
PrintModelStatistics() = default;
bool run_on_model(const std::shared_ptr<ov::Model>& m) override;
};
} // namespace intel_gpu
} // namespace ov

View File

@ -61,6 +61,7 @@
#include "plugin/transformations/kv_cache_fusion.hpp"
#include "plugin/transformations/move_fc_reshape_to_weights.hpp"
#include "plugin/transformations/bcast_and_pad_zp_buffers.hpp"
#include "plugin/transformations/print_model_statistics.hpp"
#include "plugin/transformations/swiglu_fusion.hpp"
#include "plugin/transformations/transpose_fusion.hpp"
#include "plugin/transformations/indirect_kv_cache.hpp"
@ -340,11 +341,19 @@ void TransformationsPipeline::apply(std::shared_ptr<ov::Model> func) {
return false;
}
// - The head size should be divisible by 16
// - Head size should be static dim
const auto head_size_dim = query_ps[query_ps.size() - 1];
if (head_size_dim.is_dynamic())
return false;
// - Head size should be 128 for any model type; or should be in the range of 64 to 256 for stateful LLMs because of performance reasons.
// This limitations is recommended to prevent performance drop in models with small head size, such as SD,
// until the SDPA operation is optimized for these cases
const auto optimal_subgroup_size = 16;
if (query_ps[query_ps.size() - 1].is_dynamic() ||
query_ps[query_ps.size() - 1].get_length() != 128 ||
query_ps[query_ps.size() - 1].get_length() % optimal_subgroup_size != 0) {
const auto head_size = query_ps[query_ps.size() - 1].get_length();
bool valid_head_size = head_size % optimal_subgroup_size == 0;
valid_head_size &= (head_size == 128) || (func->get_variables().size() > 0 && head_size >= 64 && head_size <= 256);
if (!valid_head_size) {
return false;
}
@ -823,6 +832,9 @@ void TransformationsPipeline::apply(std::shared_ptr<ov::Model> func) {
// This is supposed to be the last pass to ensure that we don't have name collisions until
// GPU plugin stops using friendly names for program creation
manager.register_pass<ov::pass::ResolveNameCollisions>(true);
GPU_DEBUG_IF(cldnn::debug_configuration::get_instance()->verbose >= 1) {
manager.register_pass<ov::intel_gpu::PrintModelStatistics>();
}
manager.run_passes(func);
}

View File

@ -144,6 +144,7 @@ static void print_help_messages() {
"from one specific iteration by giving the same values for the start and end, and the open "
"ended range is also available by range from given start to the last iteration as -1. e.g. "
"OV_GPU_DumpProfilingDataIteration='10..-1'");
message_list.emplace_back("OV_GPU_HostTimeProfiling", "Enable collecting of model enqueue time spent on the host");
message_list.emplace_back("OV_GPU_DumpGraphs", "1) dump ngraph before and after transformation. 2) dump graph in model compiling."
"3) dump graph in execution.");
message_list.emplace_back("OV_GPU_DumpSources", "Dump opencl sources");
@ -218,6 +219,7 @@ debug_configuration::debug_configuration()
, disable_onednn_opt_post_ops(0)
, dump_profiling_data(std::string(""))
, dump_profiling_data_per_iter(0)
, host_time_profiling(0)
, dump_graphs(std::string())
, dump_sources(std::string())
, dump_layers_path(std::string())
@ -266,6 +268,7 @@ debug_configuration::debug_configuration()
get_gpu_debug_env_var("DisableOnednnOptPostOps", disable_onednn_opt_post_ops);
get_gpu_debug_env_var("DumpProfilingData", dump_profiling_data);
get_gpu_debug_env_var("DumpProfilingDataPerIter", dump_profiling_data_per_iter);
get_gpu_debug_env_var("HostTimeProfiling", host_time_profiling);
std::string dump_prof_data_iter_str;
get_gpu_debug_env_var("DumpProfilingDataIteration", dump_prof_data_iter_str);
get_gpu_debug_env_var("DryRunPath", dry_run_path);

View File

@ -110,6 +110,14 @@ const std::vector<LayerTestsDefinitions::ConvolutionTransformationParam> params
false,
"Convolution",
"FP32"
},
{
{ 256ul, ov::Shape { 1, 1, 1, 1 }, { 0.f }, { 0.f }, { 0.f }, { 0.f } },
false,
{ 255ul, ov::Shape { 1, 1, 1, 1 }, { 0.f }, { 254.f }, { -12.7f }, { 12.7f } },
false,
"Convolution",
"u8"
}
};

View File

@ -11,20 +11,19 @@ namespace test {
namespace snippets {
typedef std::tuple<
ov::PartialShape, // Input 0 Shape
std::vector<int>, // Transpose order
size_t, // Expected num nodes
size_t, // Expected num subgraphs
std::string // Target Device
InputShape, // Input 0 Shape
std::vector<int>, // Transpose order
size_t, // Expected num nodes
size_t, // Expected num subgraphs
std::string // Target Device
> TransposeParams;
typedef std::tuple<
ov::PartialShape, // Input 0 Shape
ov::PartialShape, // Input 1 Shape
std::vector<int>, // Transpose order
size_t, // Expected num nodes
size_t, // Expected num subgraphs
std::string // Target Device
std::pair<InputShape, InputShape>, // Input Shapes
std::vector<int>, // Transpose order
size_t, // Expected num nodes
size_t, // Expected num subgraphs
std::string // Target Device
> TransposeMulParams;
class Transpose : public testing::WithParamInterface<ov::test::snippets::TransposeParams>,

View File

@ -11,12 +11,12 @@ namespace test {
namespace snippets {
typedef std::tuple<
std::vector<ov::Shape>, // Input shapes
std::vector<int64_t>, // Transpose Order
int64_t, // Softmax Axis
size_t, // Expected num nodes
size_t, // Expected num subgraphs
std::string // Target Device
std::vector<InputShape>, // Input shapes
std::vector<int64_t>, // Transpose Order
int64_t, // Softmax Axis
size_t, // Expected num nodes
size_t, // Expected num subgraphs
std::string // Target Device
> TransposeSoftmaxParams;

View File

@ -12,14 +12,14 @@ namespace test {
namespace snippets {
std::string Transpose::getTestCaseName(testing::TestParamInfo<ov::test::snippets::TransposeParams> obj) {
ov::PartialShape inputShape;
InputShape inputShapes;
std::vector<int> order;
std::string targetDevice;
size_t num_nodes, num_subgraphs;
std::tie(inputShape, order, num_nodes, num_subgraphs, targetDevice) = obj.param;
std::tie(inputShapes, order, num_nodes, num_subgraphs, targetDevice) = obj.param;
std::ostringstream result;
result << "IS=" << ov::test::utils::partialShape2str({inputShape}) << "_";
result << "IS=" << inputShapes << "_";
result << "Order=" << ov::test::utils::vec2str(order) << "_";
result << "#N=" << num_nodes << "_";
result << "#S=" << num_subgraphs << "_";
@ -28,12 +28,12 @@ std::string Transpose::getTestCaseName(testing::TestParamInfo<ov::test::snippets
}
void Transpose::SetUp() {
ov::PartialShape inputShape;
InputShape inputShape;
std::vector<int> order;
std::tie(inputShape, order, ref_num_nodes, ref_num_subgraphs, targetDevice) = this->GetParam();
init_input_shapes({{{inputShape}, {inputShape.get_shape(), }}});
init_input_shapes({inputShape});
auto f = ov::test::snippets::TransposeFunction({inputShape}, order);
auto f = ov::test::snippets::TransposeFunction(inputDynamicShapes, order);
function = f.getOriginal();
if (!configuration.count("SNIPPETS_MODE")) {
configuration.insert({"SNIPPETS_MODE", "IGNORE_CALLBACK"});
@ -41,15 +41,15 @@ void Transpose::SetUp() {
}
std::string TransposeMul::getTestCaseName(testing::TestParamInfo<ov::test::snippets::TransposeMulParams> obj) {
std::vector<ov::PartialShape> inputShapes(2);
std::pair<InputShape, InputShape> inputShapes;
std::vector<int> order;
std::string targetDevice;
size_t num_nodes, num_subgraphs;
std::tie(inputShapes[0], inputShapes[1], order, num_nodes, num_subgraphs, targetDevice) = obj.param;
std::tie(inputShapes, order, num_nodes, num_subgraphs, targetDevice) = obj.param;
std::ostringstream result;
for (int i = 0; i < inputShapes.size(); i++)
result << "IS[" << i << "]=" << ov::test::utils::partialShape2str({inputShapes[i]}) << "_";
result << "IS[0]=" << inputShapes.first << "_";
result << "IS[1]=" << inputShapes.second << "_";
result << "Order=" << ov::test::utils::vec2str(order) << "_";
result << "#N=" << num_nodes << "_";
result << "#S=" << num_subgraphs << "_";
@ -58,11 +58,11 @@ std::string TransposeMul::getTestCaseName(testing::TestParamInfo<ov::test::snipp
}
void TransposeMul::SetUp() {
std::vector<ov::PartialShape> inputShapes(2);
std::pair<InputShape, InputShape> inputShapes;
std::vector<int> order;
std::tie(inputShapes[0], inputShapes[1], order, ref_num_nodes, ref_num_subgraphs, targetDevice) = this->GetParam();
init_input_shapes(static_partial_shapes_to_test_representation(inputShapes));
auto f = ov::test::snippets::TransposeMulFunction(inputShapes, order);
std::tie(inputShapes, order, ref_num_nodes, ref_num_subgraphs, targetDevice) = this->GetParam();
init_input_shapes({inputShapes.first, inputShapes.second});
auto f = ov::test::snippets::TransposeMulFunction(inputDynamicShapes, order);
function = f.getOriginal();
if (!configuration.count("SNIPPETS_MODE")) {
configuration.insert({"SNIPPETS_MODE", "IGNORE_CALLBACK"});

View File

@ -12,7 +12,7 @@ namespace test {
namespace snippets {
std::string TransposeSoftmax::getTestCaseName(testing::TestParamInfo<ov::test::snippets::TransposeSoftmaxParams> obj) {
std::vector<ov::Shape> inputShapes;
std::vector<InputShape> inputShapes;
std::vector<int64_t> order;
int axis;
std::string targetDevice;
@ -20,8 +20,13 @@ std::string TransposeSoftmax::getTestCaseName(testing::TestParamInfo<ov::test::s
std::tie(inputShapes, order, axis, num_nodes, num_subgraphs, targetDevice) = obj.param;
std::ostringstream result;
for (size_t i = 0; i < inputShapes.size(); ++i)
result << "IS[" << i << "]=" << ov::test::utils::vec2str(inputShapes[i]) << "_";
for (size_t i = 0; i < inputShapes.size(); ++i) {
result << "IS[" << i<< "]=" << ov::test::utils::partialShape2str({inputShapes[i].first}) << "_";
result << "TS[" << i<< "]=";
for (const auto& shape : inputShapes[i].second) {
result << "(" << ov::test::utils::vec2str(shape) << ")_";
}
}
result << "TO=" << ov::test::utils::vec2str(order) << "_";
result << "Axis=" << axis << "_";
result << "#N=" << num_nodes << "_";
@ -31,11 +36,11 @@ std::string TransposeSoftmax::getTestCaseName(testing::TestParamInfo<ov::test::s
}
void TransposeSoftmax::SetUp() {
std::vector<ov::Shape> inputShapes;
std::vector<InputShape> inputShapes;
std::vector<int64_t> order;
int64_t axis;
std::tie(inputShapes, order, axis, ref_num_nodes, ref_num_subgraphs, targetDevice) = this->GetParam();
init_input_shapes(static_shapes_to_test_representation(inputShapes));
init_input_shapes(inputShapes);
auto f = ov::test::snippets::TransposeSoftmaxFunction(inputDynamicShapes, order, axis);
function = f.getOriginal();
@ -46,11 +51,11 @@ void TransposeSoftmax::SetUp() {
}
void TransposeSoftmaxEltwise::SetUp() {
std::vector<ov::Shape> inputShapes;
std::vector<InputShape> inputShapes;
std::vector<int64_t> order;
int64_t axis;
std::tie(inputShapes, order, axis, ref_num_nodes, ref_num_subgraphs, targetDevice) = this->GetParam();
init_input_shapes(static_shapes_to_test_representation(inputShapes));
init_input_shapes(inputShapes);
auto f = ov::test::snippets::TransposeSoftmaxEltwiseFunction(inputDynamicShapes, order, axis);
function = f.getOriginal();

View File

@ -56,7 +56,9 @@ protected:
class TransposeSoftmaxEltwiseFunction : public TransposeSoftmaxFunction {
public:
explicit TransposeSoftmaxEltwiseFunction(const std::vector<PartialShape>& inputShapes, const std::vector<int64_t>& order, const int64_t axis)
: TransposeSoftmaxFunction(inputShapes, order, axis) {}
: TransposeSoftmaxFunction(inputShapes, order, axis) {
OPENVINO_ASSERT(input_shapes.size() == 2, "Got invalid number of input shapes");
}
protected:
std::shared_ptr<ov::Model> initOriginal() const override;
};

View File

@ -78,11 +78,11 @@ std::shared_ptr<ov::Model> TransposeSoftmaxEltwiseFunction::initOriginal() const
const auto transpose0Param = std::make_shared<ov::opset1::Parameter>(precision, input_shapes[0]);
const auto transpose0Const = ov::op::v0::Constant::create(ov::element::i64, ov::Shape{m_order.size()}, m_order);
const auto transpose2 = std::make_shared<ov::op::v1::Transpose>(transpose0Param, transpose0Const);
const auto mulConst = ov::test::utils::make_constant(ov::element::f32, transpose2->get_shape());
const auto mul = std::make_shared<ov::op::v1::Multiply>(transpose2, mulConst);
const auto mul1Param = std::make_shared<ov::opset1::Parameter>(precision, input_shapes[1]);
const auto mul = std::make_shared<ov::op::v1::Multiply>(transpose2, mul1Param);
const auto softMax = std::make_shared<ov::op::v8::Softmax>(mul, m_axis);
const auto hswish = std::make_shared<ov::op::v4::HSwish>(softMax);
return std::make_shared<ov::Model>(ov::NodeVector{hswish}, ov::ParameterVector{transpose0Param},
return std::make_shared<ov::Model>(ov::NodeVector{hswish}, ov::ParameterVector{transpose0Param, mul1Param},
"softmax_transpose");
}

View File

@ -430,10 +430,6 @@ public:
mvn_results /= tensor_size ? tensor_size : 1;
if (!incorrect_values_abs.empty() && equal(1.f, topk_threshold) ||
incorrect_values_abs.size() > static_cast<int>(std::floor(topk_threshold * tensor_size))) {
#ifdef NDEBUG
std::string msg = "[ COMPARATION ] COMPARATION IS FAILED!";
msg += " Use DEBUG mode to print `incorrect_values_abs` and get detailed information!";
#else
std::string msg = "[ COMPARATION ] COMPARATION IS FAILED! incorrect elem counter: ";
msg += std::to_string(incorrect_values_abs.size());
msg += " among ";
@ -441,11 +437,14 @@ public:
msg += " shapes.";
for (auto val : incorrect_values_abs) {
std::cout << "\nExpected: " << val.expected_value << " Actual: " << val.actual_value
<< " Coordinate: " << val.coordinate
<< " Diff: " << std::fabs(val.expected_value - val.actual_value)
<< " calculated_abs_threshold: " << val.threshold << " abs_threshold: " << abs_threshold
<< " rel_threshold: " << rel_threshold << "\n";
}
#ifdef NDEBUG
break;
#endif
}
throw std::runtime_error(msg);
} else if (!less_or_equal(mvn_results, mvn_threshold)) {
std::string msg = "[ COMPARATION ] COMPARATION IS FAILED due to MVN THRESHOLD: ";

View File

@ -18,7 +18,7 @@ class TestHSVToRGB(CommonTFLayerTest):
elif self.special_case == "Grayscale Image":
images_shape = inputs_info['images:0']
inputs_data = {}
inputs_data['images:0'] = np.ones(images_shape).astype(self.input_type) * np.random.rand()
inputs_data['images:0'] = np.broadcast_to([0, 0, 0.5], images_shape).astype(self.input_type)
else:
images_shape = inputs_info['images:0']
inputs_data = {}