From 6e2286086f7c3c752c9320e35611ca4579043263 Mon Sep 17 00:00:00 2001 From: Vishniakov Nikolai Date: Tue, 11 Jun 2024 10:35:12 +0200 Subject: [PATCH 01/12] [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 --- src/bindings/js/node/include/async_reader.hpp | 5 ++++- src/bindings/js/node/src/async_reader.cpp | 6 ++---- src/bindings/js/node/src/core_wrap.cpp | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/bindings/js/node/include/async_reader.hpp b/src/bindings/js/node/include/async_reader.hpp index 2e67fd48715..6666872651f 100644 --- a/src/bindings/js/node/include/async_reader.hpp +++ b/src/bindings/js/node/include/async_reader.hpp @@ -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 _model; }; diff --git a/src/bindings/js/node/src/async_reader.cpp b/src/bindings/js/node/src/async_reader.cpp index c4616da838a..8faaf47ab0a 100644 --- a/src/bindings/js/node/src/async_reader.cpp +++ b/src/bindings/js/node/src/async_reader.cpp @@ -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() { diff --git a/src/bindings/js/node/src/core_wrap.cpp b/src/bindings/js/node/src/core_wrap.cpp index 8951a8c7afb..6a96eb8fa9b 100644 --- a/src/bindings/js/node/src/core_wrap.cpp +++ b/src/bindings/js/node/src/core_wrap.cpp @@ -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(); From 395d6b7e0972c742e062f7e701bceac82fc2c18b Mon Sep 17 00:00:00 2001 From: Ilya Lavrenov Date: Tue, 11 Jun 2024 12:43:17 +0400 Subject: [PATCH 02/12] Revert "More accurate shape of optimization" (#24941) Reverts openvinotoolkit/openvino#24845 Issue CVS-143648 --- .../fuse_rotary_positional_embeddings.cpp | 2 +- .../symbol_optimization.cpp | 104 +----------------- .../label_optimization.cpp | 18 ++- 3 files changed, 18 insertions(+), 106 deletions(-) diff --git a/src/common/transformations/src/transformations/common_optimizations/fuse_rotary_positional_embeddings.cpp b/src/common/transformations/src/transformations/common_optimizations/fuse_rotary_positional_embeddings.cpp index 86507326c25..2f3ddd5d843 100644 --- a/src/common/transformations/src/transformations/common_optimizations/fuse_rotary_positional_embeddings.cpp +++ b/src/common/transformations/src/transformations/common_optimizations/fuse_rotary_positional_embeddings.cpp @@ -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 - auto hidden_states = makePattern(); // + auto hidden_states = makePattern("f32[?,?,?]"); // auto ShapeOf_485735 = makePattern({hidden_states}, {}); auto Multiply_567524 = makePattern({ShapeOf_485735, {-1}}, {{"auto_broadcast", "numpy"}}); auto Gather_377635 = makePattern({Multiply_567524, {1}, 0}, {{"batch_dims", 0}}); diff --git a/src/common/transformations/src/transformations/symbolic_transformations/symbol_optimization.cpp b/src/common/transformations/src/transformations/symbolic_transformations/symbol_optimization.cpp index 3bf315bebf4..1a4507c08dc 100644 --- a/src/common/transformations/src/transformations/symbolic_transformations/symbol_optimization.cpp +++ b/src/common/transformations/src/transformations/symbolic_transformations/symbol_optimization.cpp @@ -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& output, STS_map& symbol_shape_so } } -std::vector> topological_order(const std::shared_ptr& 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(op)) { - op->get_rt_info()[op_depends_on_parameter] = true; - } else if (ov::as_type_ptr(op) || ov::as_type_ptr(op) || - ov::as_type_ptr(op) || - std::dynamic_pointer_cast(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& input) { - return input.get_node_shared_ptr()->get_rt_info()[op_depends_on_parameter].as(); - }); - } - } - // 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(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(); - weight = output_weight > weight ? output_weight : weight; - } - } - if (output_has_weight) { - if (op->get_rt_info()[op_depends_on_parameter].as()) { - 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>> level_to_vector; - for (const auto& op : order) { - if (!op->get_rt_info().count(weight_rt_info_name)) { - int64_t weight = std::numeric_limits::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(); - weight = input_weight < weight ? input_weight : weight; - } - if (weight != std::numeric_limits::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()].push_back(op); - } - // finalization: descending order for levels and ops within level are ordered by get_ordered_ops - std::vector> 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& op, STS_map& symbol_shape_source) { - if (!ov::is_type(op) && !ov::is_type(op)) - return; - const auto& output = op->input_value(0); +void save_shape_sources(const ov::Output& 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_ptrget_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_ptroutputs()) + 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; } diff --git a/src/common/transformations/tests/symbolic_transformations/label_optimization.cpp b/src/common/transformations/tests/symbolic_transformations/label_optimization.cpp index eb108e4c659..881d02b20d2 100644 --- a/src/common/transformations/tests/symbolic_transformations/label_optimization.cpp +++ b/src/common/transformations/tests/symbolic_transformations/label_optimization.cpp @@ -75,16 +75,22 @@ TEST_F(TransformationTestsF, ApplySymbolEquivalence_Concat_Values) { auto input_2 = make_shared(element::f32, PartialShape::dynamic(4)); auto concat = make_shared(OutputVector{input_1, input_2}, -1); - auto shape = make_shared(concat); - auto gather = make_shared(shape, - v0::Constant::create(element::i64, {1}, {-1}), - v0::Constant::create(element::i64, {}, {0})); + auto shape_1 = make_shared(input_1); + auto gather_1 = make_shared(shape_1, + v0::Constant::create(element::i64, {1}, {3}), + v0::Constant::create(element::i64, {}, {0})); + + auto shape_2 = make_shared(input_2); + auto gather_2 = make_shared(shape_2, + v0::Constant::create(element::i64, {1}, {3}), + v0::Constant::create(element::i64, {}, {0})); + + auto sum = make_shared(gather_1, gather_2); auto reshape = make_shared( concat, - make_shared(OutputVector{gather, v0::Constant::create(element::i64, {1}, {-1})}, 0), + make_shared(OutputVector{sum, v0::Constant::create(element::i64, {1}, {-1})}, 0), false); - model_ref = make_shared(NodeVector{reshape}, ParameterVector{input_2, input_1}); } } From deb5e63ff20f2bee3e3064229bde1f8700fe8d0a Mon Sep 17 00:00:00 2001 From: Edward Shogulin Date: Tue, 11 Jun 2024 10:11:04 +0100 Subject: [PATCH 03/12] [LPT] Quantization to point support (#24872) ### Details: - *Quaintization to point support* - Tests: wip ### Tickets: - *CVS-139557* --- .../src/network_helper.cpp | 49 +++++++++---------- .../convolution_transformation.cpp | 8 +++ .../convolution_transformation.cpp | 8 +++ 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/common/low_precision_transformations/src/network_helper.cpp b/src/common/low_precision_transformations/src/network_helper.cpp index d0a7470a6cf..f2ea9cbc61f 100644 --- a/src/common/low_precision_transformations/src/network_helper.cpp +++ b/src/common/low_precision_transformations/src/network_helper.cpp @@ -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> NetworkHelper::decompos std::vector shifts(outputSize, 0.f); std::vector 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( (static_cast(min) * outputHighValues[i] - static_cast(max) * outputLowValues[i]) / (static_cast(outputHighValues[i]) - outputLowValues[i])); scales[i] = static_cast( (static_cast(outputHighValues[i]) - outputLowValues[i]) / (static_cast(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> NetworkHelper::decompos std::shared_ptr convert2; if (updatePrecision) { std::shared_ptr convert; - std::shared_ptr newFqConstant = ov::as_type_ptr(newFQ); - if (ov::is_type(newFQ)) { - convert = foldConvert(newFQ->output(0), precision); - } else if (ov::is_type(newFQ)) { + if (ov::is_type(newFQ)) { newFQ = setOutDataPrecision(ov::as_type_ptr(newFQ), precision); convert = newFQ; } else { - THROW_IE_LPT_EXCEPTION(*newFQ) << "unexpected operation type"; + convert = foldConvert(newFQ->output(0), precision); } convert2 = std::make_shared(convert, element::f32); diff --git a/src/plugins/intel_cpu/tests/functional/shared_tests_instances/low_precision_transformations/convolution_transformation.cpp b/src/plugins/intel_cpu/tests/functional/shared_tests_instances/low_precision_transformations/convolution_transformation.cpp index 5ab2357dd05..b611a0edb76 100644 --- a/src/plugins/intel_cpu/tests/functional/shared_tests_instances/low_precision_transformations/convolution_transformation.cpp +++ b/src/plugins/intel_cpu/tests/functional/shared_tests_instances/low_precision_transformations/convolution_transformation.cpp @@ -141,6 +141,14 @@ const std::vector 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" } }; diff --git a/src/plugins/intel_gpu/tests/functional/shared_tests_instances/low_precision_transformations/convolution_transformation.cpp b/src/plugins/intel_gpu/tests/functional/shared_tests_instances/low_precision_transformations/convolution_transformation.cpp index 9817ca7239d..5ee51c51199 100644 --- a/src/plugins/intel_gpu/tests/functional/shared_tests_instances/low_precision_transformations/convolution_transformation.cpp +++ b/src/plugins/intel_gpu/tests/functional/shared_tests_instances/low_precision_transformations/convolution_transformation.cpp @@ -110,6 +110,14 @@ const std::vector 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" } }; From 5393b8a77a671b82586f7122745da4299bf55ae2 Mon Sep 17 00:00:00 2001 From: Sebastian Golebiewski Date: Tue, 11 Jun 2024 12:08:07 +0200 Subject: [PATCH 04/12] [DOCS] Performance hint and thread scheduling update (#24946) Fixing reference and formatting. Proofreading. --- .../assets/snippets/multi_threading.py | 2 +- .../cpu-device.rst | 8 +- ...erformance-hint-and-thread-scheduling.rst} | 81 +++++++++++-------- 3 files changed, 53 insertions(+), 38 deletions(-) rename docs/articles_en/openvino-workflow/running-inference/inference-devices-and-modes/cpu-device/{performance-hint-and-threads-scheduling.rst => performance-hint-and-thread-scheduling.rst} (74%) diff --git a/docs/articles_en/assets/snippets/multi_threading.py b/docs/articles_en/assets/snippets/multi_threading.py index 6994b26a0d6..edefa760318 100644 --- a/docs/articles_en/assets/snippets/multi_threading.py +++ b/docs/articles_en/assets/snippets/multi_threading.py @@ -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, diff --git a/docs/articles_en/openvino-workflow/running-inference/inference-devices-and-modes/cpu-device.rst b/docs/articles_en/openvino-workflow/running-inference/inference-devices-and-modes/cpu-device.rst index d95f97959f5..e2f87fed9d3 100644 --- a/docs/articles_en/openvino-workflow/running-inference/inference-devices-and-modes/cpu-device.rst +++ b/docs/articles_en/openvino-workflow/running-inference/inference-devices-and-modes/cpu-device.rst @@ -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 ` 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 `. +For more details, see the :doc:`optimization guide <../optimize-inference>` and :doc:`thread scheduling introduction `. .. 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 `. +For more details, see the :doc:`thread scheduling introduction `. Denormals Optimization diff --git a/docs/articles_en/openvino-workflow/running-inference/inference-devices-and-modes/cpu-device/performance-hint-and-threads-scheduling.rst b/docs/articles_en/openvino-workflow/running-inference/inference-devices-and-modes/cpu-device/performance-hint-and-thread-scheduling.rst similarity index 74% rename from docs/articles_en/openvino-workflow/running-inference/inference-devices-and-modes/cpu-device/performance-hint-and-threads-scheduling.rst rename to docs/articles_en/openvino-workflow/running-inference/inference-devices-and-modes/cpu-device/performance-hint-and-thread-scheduling.rst index 3087bcf2d95..5abd4dbcec6 100644 --- a/docs/articles_en/openvino-workflow/running-inference/inference-devices-and-modes/cpu-device/performance-hint-and-threads-scheduling.rst +++ b/docs/articles_en/openvino-workflow/running-inference/inference-devices-and-modes/cpu-device/performance-hint-and-thread-scheduling.rst @@ -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 `__. +For additional details on the above configurations, refer to +:ref:`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>`. \ No newline at end of file +:doc:`optimization guide <../../optimize-inference/optimizing-throughput/advanced_throughput_options>`. From 00f4c9907411ce0e6899c80a7006f4875c7160c1 Mon Sep 17 00:00:00 2001 From: Sergey Shlyapnikov Date: Tue, 11 Jun 2024 18:05:53 +0400 Subject: [PATCH 05/12] [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* --- .../src/kernel_selector/cl_kernels/sdpa_opt.cl | 2 +- .../src/plugin/transformations_pipeline.cpp | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/sdpa_opt.cl b/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/sdpa_opt.cl index b1aaded5ad7..32f5f9dbbfd 100644 --- a/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/sdpa_opt.cl +++ b/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/sdpa_opt.cl @@ -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) + diff --git a/src/plugins/intel_gpu/src/plugin/transformations_pipeline.cpp b/src/plugins/intel_gpu/src/plugin/transformations_pipeline.cpp index faac47298f5..3c87af97b0c 100644 --- a/src/plugins/intel_gpu/src/plugin/transformations_pipeline.cpp +++ b/src/plugins/intel_gpu/src/plugin/transformations_pipeline.cpp @@ -340,11 +340,19 @@ void TransformationsPipeline::apply(std::shared_ptr 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; } From 91b39771692fa7b17911c731329a4cd2054e1468 Mon Sep 17 00:00:00 2001 From: Andrii Staikov Date: Tue, 11 Jun 2024 16:07:39 +0200 Subject: [PATCH 06/12] 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* --- docs/articles_en/assets/images/optional.png | 3 + .../articles_en/assets/images/or-branches.png | 3 + .../articles_en/assets/images/or_branches.png | 3 + docs/articles_en/assets/images/python-api.png | 3 + .../assets/images/simple_pattern_example.png | 3 + .../assets/snippets/ov_model_snippets.cpp | 44 +-- .../assets/snippets/ov_patterns.cpp | 251 ++++++++++++++++++ .../assets/snippets/ov_patterns.py | 217 +++++++++++++++ .../transformation-api.rst | 65 ++--- .../patterns-python-api.rst | 175 ++++++++++++ .../test_transformations/test_pattern_ops.py | 167 ++++++++++++ src/core/tests/pattern.cpp | 187 +++++++++++++ 12 files changed, 1067 insertions(+), 54 deletions(-) create mode 100644 docs/articles_en/assets/images/optional.png create mode 100644 docs/articles_en/assets/images/or-branches.png create mode 100644 docs/articles_en/assets/images/or_branches.png create mode 100644 docs/articles_en/assets/images/python-api.png create mode 100644 docs/articles_en/assets/images/simple_pattern_example.png create mode 100644 docs/articles_en/assets/snippets/ov_patterns.cpp create mode 100644 docs/articles_en/assets/snippets/ov_patterns.py create mode 100644 docs/articles_en/documentation/openvino-extensibility/transformation-api/patterns-python-api.rst diff --git a/docs/articles_en/assets/images/optional.png b/docs/articles_en/assets/images/optional.png new file mode 100644 index 00000000000..2c77f9288d4 --- /dev/null +++ b/docs/articles_en/assets/images/optional.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b78713020891a6655e3860338b7db430840560d8f2860162eca3d00b84533f24 +size 36362 diff --git a/docs/articles_en/assets/images/or-branches.png b/docs/articles_en/assets/images/or-branches.png new file mode 100644 index 00000000000..c71766873ae --- /dev/null +++ b/docs/articles_en/assets/images/or-branches.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:353c5b7f35a8f9f6c2c3074485a5a4e51f8c6ee776214444666da4037cc44832 +size 45347 diff --git a/docs/articles_en/assets/images/or_branches.png b/docs/articles_en/assets/images/or_branches.png new file mode 100644 index 00000000000..765b0622eeb --- /dev/null +++ b/docs/articles_en/assets/images/or_branches.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:696672a8c38260147f2fa68d266866ab4390ef919fa092cea1149b0eb529c68e +size 58557 diff --git a/docs/articles_en/assets/images/python-api.png b/docs/articles_en/assets/images/python-api.png new file mode 100644 index 00000000000..191ff8bace1 --- /dev/null +++ b/docs/articles_en/assets/images/python-api.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d378718b968f6a40555b31bf54af83fac978c3bfc07e234f9b1370229222cccd +size 31092 diff --git a/docs/articles_en/assets/images/simple_pattern_example.png b/docs/articles_en/assets/images/simple_pattern_example.png new file mode 100644 index 00000000000..b9fb29d34f6 --- /dev/null +++ b/docs/articles_en/assets/images/simple_pattern_example.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b3511899521a49c1bfdc569e16b8ad1e12157fe107d2b86d25ee04ff67a85b2 +size 43066 diff --git a/docs/articles_en/assets/snippets/ov_model_snippets.cpp b/docs/articles_en/assets/snippets/ov_model_snippets.cpp index 0ca99b76564..31ba0bc8028 100644 --- a/docs/articles_en/assets/snippets/ov_model_snippets.cpp +++ b/docs/articles_en/assets/snippets/ov_model_snippets.cpp @@ -171,8 +171,8 @@ if (m->match(node->output(0))) { bool openvino_api_examples(std::shared_ptr 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 data = node->input(0); ov::Input weights = node->input(1); ov::Output output = node->output(0); @@ -181,7 +181,7 @@ ov::Output 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 parent_output; parent_output = data.get_source_output(); @@ -216,15 +216,15 @@ return true; // ! [ov:replace_node] bool ov_replace_node(std::shared_ptr node) { - // Step 1. Verify that node has opset8::Negative type - auto neg = std::dynamic_pointer_cast(node); + // Step 1. Verify that node is of type ov::op::v0::Negative + auto neg = std::dynamic_pointer_cast(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(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(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 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 node) { -auto neg = std::dynamic_pointer_cast(node); +auto neg = std::dynamic_pointer_cast(node); if (!neg) { return false; } -auto mul = std::make_shared(neg->input_value(0), - ov::opset8::Constant::create(neg->get_element_type(), ov::Shape{1}, {-1})); +auto mul = std::make_shared(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 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(node); + // Step 2. Create new node ov::op::v0::Relu. + auto new_node = std::make_shared(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 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(node_copy); + auto new_node = std::make_shared(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(); +auto div = std::make_shared(); // ! [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(div->input(1).get_source_output(), +auto pow = std::make_shared(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(div->input(0).get_source_output(), pow); +auto mul = std::make_shared(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::element::f32, ov::Shape{1}); -auto pow = std::make_shared(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(input /* not constant input */, pow); +auto input = std::make_shared(ov::element::f32, ov::Shape{1}); +auto pow = std::make_shared(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(input /* not constant input */, pow); // ! [ov:constant_subgraph] } diff --git a/docs/articles_en/assets/snippets/ov_patterns.cpp b/docs/articles_en/assets/snippets/ov_patterns.cpp new file mode 100644 index 00000000000..8f3f3fd053a --- /dev/null +++ b/docs/articles_en/assets/snippets/ov_patterns.cpp @@ -0,0 +1,251 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +// ! [ov::imports] +#include + +#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(element::i32, shape); + auto model_param2 = std::make_shared(element::i32, shape); + auto model_add = std::make_shared(model_param1->output(0), model_param2->output(0)); + auto model_param3 = std::make_shared(element::i32, shape); + auto model_mul = std::make_shared(model_add->output(0), model_param3->output(0), false, false); + auto model_abs = std::make_shared(model_mul->output(0)); + auto model_relu = std::make_shared(model_abs->output(0)); + auto model_result = std::make_shared(model_relu->output(0)); + + // Create a sample model + auto pattern_mul = std::make_shared(pattern::any_input(), pattern::any_input(), false, false); + auto pattern_abs = std::make_shared(pattern_mul->output(0)); + auto pattern_relu = std::make_shared(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(element::i32, shape); + auto model_param2 = std::make_shared(element::i32, shape); + auto model_add = std::make_shared(model_param1->output(0), model_param2->output(0)); + auto model_param3 = std::make_shared(element::i32, shape); + auto model_mul = std::make_shared(model_add->output(0), model_param3->output(0), false, false); + auto model_abs = std::make_shared(model_mul->output(0)); + auto model_relu = std::make_shared(model_abs->output(0)); + auto model_result = std::make_shared(model_relu->output(0)); + + // Create a sample model + auto pattern_mul = ov::pass::pattern::wrap_type({pattern::any_input(), pattern::any_input()}); + auto pattern_abs = ov::pass::pattern::wrap_type({pattern_mul->output(0)}); + auto pattern_relu = ov::pass::pattern::wrap_type({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(element::i32, shape); + auto model_param2 = std::make_shared(element::i32, shape); + auto model_add = std::make_shared(model_param1->output(0), model_param2->output(0)); + auto model_param3 = std::make_shared(element::i32, shape); + auto model_mul = std::make_shared(model_add->output(0), model_param3->output(0), false, false); + auto model_abs = std::make_shared(model_mul->output(0)); + auto model_relu = std::make_shared(model_abs->output(0)); + auto model_result = std::make_shared(model_relu->output(0)); + auto model_sig = std::make_shared(model_abs->output(0)); + auto model_result1 = std::make_shared(model_sig->output(0)); + + // Create a sample model + auto pattern_mul = ov::pass::pattern::wrap_type({pattern::any_input(), pattern::any_input()}); + auto pattern_abs = ov::pass::pattern::wrap_type({pattern_mul->output(0)}); + auto pattern_relu = ov::pass::pattern::wrap_type({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({pattern::any_input(), pattern::any_input()}); + auto pattern_abs = ov::pass::pattern::wrap_type({pattern_mul->output(0)}); + auto pattern_relu = ov::pass::pattern::wrap_type({pattern_abs->output(0)}); +// ! [ov:any_input] + +// ! [ov:wrap_type_predicate] + ov::pass::pattern::wrap_type({pattern::any_input()}, pattern::consumers_count(2)); +// ! [ov:wrap_type_predicate] + + +// ! [ov:any_input_predicate] + auto pattern_mul = ov::pass::pattern::wrap_type({pattern::any_input([](const Output& value){ + return value.get_shape().size() == 4;}), + pattern::any_input([](const Output& value){ + return value.get_shape().size() == 4;})}); + auto pattern_abs = ov::pass::pattern::wrap_type({pattern_mul->output(0)}); + auto pattern_relu = ov::pass::pattern::wrap_type({pattern_abs->output(0)}); +// ! [ov:any_input_predicate] + + +// ! [ov:optional_predicate] + auto pattern_sig_opt = ov::pass::pattern::optional(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(element::i32, shape); + auto model_param2 = std::make_shared(element::i32, shape); + auto model_add = std::make_shared(model_param1->output(0), model_param2->output(0)); + auto model_param3 = std::make_shared(element::i32, shape); + auto model_mul = std::make_shared(model_add->output(0), model_param3->output(0), false, false); + auto model_abs = std::make_shared(model_mul->output(0)); + auto model_relu = std::make_shared(model_abs->output(0)); + auto model_result = std::make_shared(model_relu->output(0)); + + // Create a red branch + auto red_pattern_add = ov::pass::pattern::wrap_type({pattern::any_input(), pattern::any_input()}); + auto red_pattern_relu = ov::pass::pattern::wrap_type({red_pattern_add->output(0)}); + auto red_pattern_sigmoid = ov::pass::pattern::wrap_type({red_pattern_relu->output(0)}); + + // Create a blue branch + auto blue_pattern_mul = ov::pass::pattern::wrap_type({pattern::any_input(), pattern::any_input()}); + auto blue_pattern_abs = ov::pass::pattern::wrap_type({blue_pattern_mul->output(0)}); + auto blue_pattern_relu = ov::pass::pattern::wrap_type({blue_pattern_abs->output(0)}); + + // Create Or node + auto pattern_or = std::make_shared(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(element::i32, shape); + auto model_param2 = std::make_shared(element::i32, shape); + auto model_add = std::make_shared(model_param1->output(0), model_param2->output(0)); + auto model_param3 = std::make_shared(element::i32, shape); + auto model_mul = std::make_shared(model_add->output(0), model_param3->output(0), false, false); + auto model_abs = std::make_shared(model_mul->output(0)); + auto model_relu = std::make_shared(model_abs->output(0)); + auto model_result = std::make_shared(model_relu->output(0)); + + // Create a sample pattern with an Optional node in the middle + auto pattern_mul = ov::pass::pattern::wrap_type({pattern::any_input(), pattern::any_input()}); + auto pattern_abs = ov::pass::pattern::wrap_type({pattern_mul->output(0)}); + auto pattern_sig_opt = ov::pass::pattern::optional({pattern_abs->output(0)}); + auto pattern_relu = ov::pass::pattern::wrap_type({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(element::i32, shape); + auto model_param2 = std::make_shared(element::i32, shape); + auto model_add = std::make_shared(model_param1->output(0), model_param2->output(0)); + auto model_param3 = std::make_shared(element::i32, shape); + auto model_mul = std::make_shared(model_add->output(0), model_param3->output(0), false, false); + auto model_abs = std::make_shared(model_mul->output(0)); + auto model_relu = std::make_shared(model_abs->output(0)); + auto model_result = std::make_shared(model_relu->output(0)); + + // Create a sample pattern an optional top node + auto pattern_sig_opt = ov::pass::pattern::optional(pattern::any_input()); + auto pattern_mul = ov::pass::pattern::wrap_type({pattern_sig_opt, pattern::any_input()}); + auto pattern_abs = ov::pass::pattern::wrap_type({pattern_mul->output(0)}); + auto pattern_relu = ov::pass::pattern::wrap_type({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(element::i32, shape); + auto model_param2 = std::make_shared(element::i32, shape); + auto model_add = std::make_shared(model_param1->output(0), model_param2->output(0)); + auto model_param3 = std::make_shared(element::i32, shape); + auto model_mul = std::make_shared(model_add->output(0), model_param3->output(0), false, false); + auto model_abs = std::make_shared(model_mul->output(0)); + auto model_relu = std::make_shared(model_abs->output(0)); + auto model_result = std::make_shared(model_relu->output(0)); + + // Create a sample pattern an optional top node + auto pattern_mul = ov::pass::pattern::wrap_type({pattern::any_input(), pattern::any_input()}); + auto pattern_abs = ov::pass::pattern::wrap_type({pattern_mul->output(0)}); + auto pattern_relu = ov::pass::pattern::wrap_type({pattern_abs->output(0)}); + auto pattern_sig_opt = ov::pass::pattern::optional(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] \ No newline at end of file diff --git a/docs/articles_en/assets/snippets/ov_patterns.py b/docs/articles_en/assets/snippets/ov_patterns.py new file mode 100644 index 00000000000..f2991148ca7 --- /dev/null +++ b/docs/articles_en/assets/snippets/ov_patterns.py @@ -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] diff --git a/docs/articles_en/documentation/openvino-extensibility/transformation-api.rst b/docs/articles_en/documentation/openvino-extensibility/transformation-api.rst index 5e28a22e69a..c179e628f6d 100644 --- a/docs/articles_en/documentation/openvino-extensibility/transformation-api.rst +++ b/docs/articles_en/documentation/openvino-extensibility/transformation-api.rst @@ -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 ` - straightforward way to work with ``ov::Model`` directly * :doc:`Matcher pass ` - pattern-based transformation approach -* :doc:`Graph rewrite pass ` - container for matcher passes needed for efficient execution +* :doc:`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`` 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`` 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`` 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`` 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`` diff --git a/docs/articles_en/documentation/openvino-extensibility/transformation-api/patterns-python-api.rst b/docs/articles_en/documentation/openvino-extensibility/transformation-api/patterns-python-api.rst new file mode 100644 index 00000000000..bca893718e8 --- /dev/null +++ b/docs/articles_en/documentation/openvino-extensibility/transformation-api/patterns-python-api.rst @@ -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] \ No newline at end of file diff --git a/src/bindings/python/tests/test_transformations/test_pattern_ops.py b/src/bindings/python/tests/test_transformations/test_pattern_ops.py index c38e8bcb1d5..7e07af9cd80 100644 --- a/src/bindings/python/tests/test_transformations/test_pattern_ops.py +++ b/src/bindings/python/tests/test_transformations/test_pattern_ops.py @@ -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): diff --git a/src/core/tests/pattern.cpp b/src/core/tests/pattern.cpp index 3d004ba5c54..b7eedbadc33 100644 --- a/src/core/tests/pattern.cpp +++ b/src/core/tests/pattern.cpp @@ -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(c))); } } + +TEST(pattern, simple_model_and_pattern) { + // Create a sample model + PartialShape shape{2, 2}; + auto model_param1 = std::make_shared(element::i32, shape); + auto model_param2 = std::make_shared(element::i32, shape); + auto model_add = std::make_shared(model_param1->output(0), model_param2->output(0)); + auto model_param3 = std::make_shared(element::i32, shape); + auto model_mul = std::make_shared(model_add->output(0), model_param3->output(0), false, false); + auto model_abs = std::make_shared(model_mul->output(0)); + auto model_relu = std::make_shared(model_abs->output(0)); + auto model_result = std::make_shared(model_relu->output(0)); + + // Create a sample model + auto pattern_mul = std::make_shared(pattern::any_input(), pattern::any_input(), false, false); + auto pattern_abs = std::make_shared(pattern_mul->output(0)); + auto pattern_relu = std::make_shared(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(element::i32, shape); + auto model_param2 = std::make_shared(element::i32, shape); + auto model_add = std::make_shared(model_param1->output(0), model_param2->output(0)); + auto model_param3 = std::make_shared(element::i32, shape); + auto model_mul = std::make_shared(model_add->output(0), model_param3->output(0), false, false); + auto model_abs = std::make_shared(model_mul->output(0)); + auto model_relu = std::make_shared(model_abs->output(0)); + auto model_result = std::make_shared(model_relu->output(0)); + + // Create a sample model + auto pattern_mul = ov::pass::pattern::wrap_type({pattern::any_input(), pattern::any_input()}); + auto pattern_abs = ov::pass::pattern::wrap_type({pattern_mul->output(0)}); + auto pattern_relu = ov::pass::pattern::wrap_type({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(element::i32, shape); + auto model_param2 = std::make_shared(element::i32, shape); + auto model_add = std::make_shared(model_param1->output(0), model_param2->output(0)); + auto model_param3 = std::make_shared(element::i32, shape); + auto model_mul = std::make_shared(model_add->output(0), model_param3->output(0), false, false); + auto model_abs = std::make_shared(model_mul->output(0)); + auto model_relu = std::make_shared(model_abs->output(0)); + auto model_result = std::make_shared(model_relu->output(0)); + auto model_sig = std::make_shared(model_abs->output(0)); + auto model_result1 = std::make_shared(model_sig->output(0)); + + // Create a sample model + auto pattern_mul = ov::pass::pattern::wrap_type({pattern::any_input(), pattern::any_input()}); + auto pattern_abs = ov::pass::pattern::wrap_type({pattern_mul->output(0)}); + auto pattern_relu = ov::pass::pattern::wrap_type({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(element::i32, shape); + auto model_param2 = std::make_shared(element::i32, shape); + auto model_add = std::make_shared(model_param1->output(0), model_param2->output(0)); + auto model_param3 = std::make_shared(element::i32, shape); + auto model_mul = std::make_shared(model_add->output(0), model_param3->output(0), false, false); + auto model_abs = std::make_shared(model_mul->output(0)); + auto model_relu = std::make_shared(model_abs->output(0)); + auto model_result = std::make_shared(model_relu->output(0)); + + // Create a red branch + auto red_pattern_add = + ov::pass::pattern::wrap_type({pattern::any_input(), pattern::any_input()}); + auto red_pattern_relu = ov::pass::pattern::wrap_type({red_pattern_add->output(0)}); + auto red_pattern_sigmoid = ov::pass::pattern::wrap_type({red_pattern_relu->output(0)}); + + // Create a blue branch + auto blue_pattern_mul = + ov::pass::pattern::wrap_type({pattern::any_input(), pattern::any_input()}); + auto blue_pattern_abs = ov::pass::pattern::wrap_type({blue_pattern_mul->output(0)}); + auto blue_pattern_relu = ov::pass::pattern::wrap_type({blue_pattern_abs->output(0)}); + + // Create Or node + auto pattern_or = std::make_shared( + 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(element::i32, shape); + auto model_param2 = std::make_shared(element::i32, shape); + auto model_add = std::make_shared(model_param1->output(0), model_param2->output(0)); + auto model_param3 = std::make_shared(element::i32, shape); + auto model_mul = std::make_shared(model_add->output(0), model_param3->output(0), false, false); + auto model_abs = std::make_shared(model_mul->output(0)); + auto model_relu = std::make_shared(model_abs->output(0)); + auto model_result = std::make_shared(model_relu->output(0)); + + // Create a sample pattern with an Optional node in the middle + auto pattern_mul = ov::pass::pattern::wrap_type({pattern::any_input(), pattern::any_input()}); + auto pattern_abs = ov::pass::pattern::wrap_type({pattern_mul->output(0)}); + auto pattern_sig_opt = ov::pass::pattern::optional({pattern_abs->output(0)}); + auto pattern_relu = ov::pass::pattern::wrap_type({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(element::i32, shape); + auto model_param2 = std::make_shared(element::i32, shape); + auto model_add = std::make_shared(model_param1->output(0), model_param2->output(0)); + auto model_param3 = std::make_shared(element::i32, shape); + auto model_mul = std::make_shared(model_add->output(0), model_param3->output(0), false, false); + auto model_abs = std::make_shared(model_mul->output(0)); + auto model_relu = std::make_shared(model_abs->output(0)); + auto model_result = std::make_shared(model_relu->output(0)); + + // Create a sample pattern an optional top node + auto pattern_sig_opt = ov::pass::pattern::optional(pattern::any_input()); + auto pattern_mul = ov::pass::pattern::wrap_type({pattern_sig_opt, pattern::any_input()}); + auto pattern_abs = ov::pass::pattern::wrap_type({pattern_mul->output(0)}); + auto pattern_relu = ov::pass::pattern::wrap_type({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(element::i32, shape); + auto model_param2 = std::make_shared(element::i32, shape); + auto model_add = std::make_shared(model_param1->output(0), model_param2->output(0)); + auto model_param3 = std::make_shared(element::i32, shape); + auto model_mul = std::make_shared(model_add->output(0), model_param3->output(0), false, false); + auto model_abs = std::make_shared(model_mul->output(0)); + auto model_relu = std::make_shared(model_abs->output(0)); + auto model_result = std::make_shared(model_relu->output(0)); + + // Create a sample pattern an optional top node + auto pattern_mul = ov::pass::pattern::wrap_type({pattern::any_input(), pattern::any_input()}); + auto pattern_abs = ov::pass::pattern::wrap_type({pattern_mul->output(0)}); + auto pattern_relu = ov::pass::pattern::wrap_type({pattern_abs->output(0)}); + auto pattern_sig_opt = ov::pass::pattern::optional(pattern_relu); + + // Create a matcher and try to match the nodes + TestMatcher tm; + + // Should perfectly match + ASSERT_TRUE(tm.match(pattern_relu, model_relu)); +} \ No newline at end of file From 8eaa65e0268a6a2f5a0db6395efc2c5cbb8527ea Mon Sep 17 00:00:00 2001 From: Jorge Ragde Date: Tue, 11 Jun 2024 07:16:48 -0700 Subject: [PATCH 07/12] [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 --- tests/layer_tests/tensorflow_tests/test_tf_HSVToRGB.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/layer_tests/tensorflow_tests/test_tf_HSVToRGB.py b/tests/layer_tests/tensorflow_tests/test_tf_HSVToRGB.py index 0960801f957..9f3ab9845fb 100644 --- a/tests/layer_tests/tensorflow_tests/test_tf_HSVToRGB.py +++ b/tests/layer_tests/tensorflow_tests/test_tf_HSVToRGB.py @@ -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 = {} From f5c3fc333062ee0a0795716c65fdc987e4010dab Mon Sep 17 00:00:00 2001 From: Vladimir Paramuzov Date: Tue, 11 Jun 2024 19:54:33 +0400 Subject: [PATCH 08/12] [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 --- .../include/intel_gpu/graph/network.hpp | 1 + .../intel_gpu/runtime/debug_configuration.hpp | 1 + src/plugins/intel_gpu/src/graph/network.cpp | 21 ++++++++ .../print_model_statistics.cpp | 54 +++++++++++++++++++ .../print_model_statistics.hpp | 21 ++++++++ .../src/plugin/transformations_pipeline.cpp | 4 ++ .../src/runtime/debug_configuration.cpp | 3 ++ .../common_test_utils/src/ov_tensor_utils.cpp | 9 ++-- 8 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 src/plugins/intel_gpu/src/plugin/transformations/print_model_statistics.cpp create mode 100644 src/plugins/intel_gpu/src/plugin/transformations/print_model_statistics.hpp diff --git a/src/plugins/intel_gpu/include/intel_gpu/graph/network.hpp b/src/plugins/intel_gpu/include/intel_gpu/graph/network.hpp index f7f2099ac56..c70c44964d6 100644 --- a/src/plugins/intel_gpu/include/intel_gpu/graph/network.hpp +++ b/src/plugins/intel_gpu/include/intel_gpu/graph/network.hpp @@ -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 host_exec_times; #ifdef GPU_DEBUG_CONFIG int64_t iteration = 0; #endif diff --git a/src/plugins/intel_gpu/include/intel_gpu/runtime/debug_configuration.hpp b/src/plugins/intel_gpu/include/intel_gpu/runtime/debug_configuration.hpp index 3ec28d1e32a..d86bdab7578 100644 --- a/src/plugins/intel_gpu/include/intel_gpu/runtime/debug_configuration.hpp +++ b/src/plugins/intel_gpu/include/intel_gpu/runtime/debug_configuration.hpp @@ -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 diff --git a/src/plugins/intel_gpu/src/graph/network.cpp b/src/plugins/intel_gpu/src/graph/network.cpp index 604848b230c..83cba0887de 100644 --- a/src/plugins/intel_gpu/src/graph/network.cpp +++ b/src/plugins/intel_gpu/src/graph/network.cpp @@ -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(host_exec_times[0]); + double avg = static_cast(std::accumulate(host_exec_times.begin() + 1, host_exec_times.end(), (size_t)0, std::plus())); + 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 network::execute(const std::vector& 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(end - start).count()); auto output_ids = get_output_ids(); std::map result; diff --git a/src/plugins/intel_gpu/src/plugin/transformations/print_model_statistics.cpp b/src/plugins/intel_gpu/src/plugin/transformations/print_model_statistics.cpp new file mode 100644 index 00000000000..ece3b7487fc --- /dev/null +++ b/src/plugins/intel_gpu/src/plugin/transformations/print_model_statistics.cpp @@ -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 + +namespace ov { +namespace intel_gpu { +namespace { +size_t collect_stats(const std::shared_ptr& m, std::map& ops_stat) { + const std::vector> 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(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& m) { + std::map 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 diff --git a/src/plugins/intel_gpu/src/plugin/transformations/print_model_statistics.hpp b/src/plugins/intel_gpu/src/plugin/transformations/print_model_statistics.hpp new file mode 100644 index 00000000000..079028f0154 --- /dev/null +++ b/src/plugins/intel_gpu/src/plugin/transformations/print_model_statistics.hpp @@ -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& m) override; +}; + +} // namespace intel_gpu +} // namespace ov diff --git a/src/plugins/intel_gpu/src/plugin/transformations_pipeline.cpp b/src/plugins/intel_gpu/src/plugin/transformations_pipeline.cpp index 3c87af97b0c..424c2341fbe 100644 --- a/src/plugins/intel_gpu/src/plugin/transformations_pipeline.cpp +++ b/src/plugins/intel_gpu/src/plugin/transformations_pipeline.cpp @@ -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" @@ -831,6 +832,9 @@ void TransformationsPipeline::apply(std::shared_ptr 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(true); + GPU_DEBUG_IF(cldnn::debug_configuration::get_instance()->verbose >= 1) { + manager.register_pass(); + } manager.run_passes(func); } diff --git a/src/plugins/intel_gpu/src/runtime/debug_configuration.cpp b/src/plugins/intel_gpu/src/runtime/debug_configuration.cpp index fc9c1601470..3708a339b35 100644 --- a/src/plugins/intel_gpu/src/runtime/debug_configuration.cpp +++ b/src/plugins/intel_gpu/src/runtime/debug_configuration.cpp @@ -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); diff --git a/src/tests/test_utils/common_test_utils/src/ov_tensor_utils.cpp b/src/tests/test_utils/common_test_utils/src/ov_tensor_utils.cpp index c22b6d76ccb..0ec3dfa0365 100644 --- a/src/tests/test_utils/common_test_utils/src/ov_tensor_utils.cpp +++ b/src/tests/test_utils/common_test_utils/src/ov_tensor_utils.cpp @@ -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(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: "; From 4e6ee7f15e1991aefb2ae1a1b6a545456c8844b2 Mon Sep 17 00:00:00 2001 From: Aleksandr Voron Date: Tue, 11 Jun 2024 18:21:23 +0200 Subject: [PATCH 09/12] [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 --- .../src/nodes/executors/acl/acl_reduce.cpp | 32 +++++-- .../src/nodes/executors/acl/acl_utils.hpp | 29 +++++- src/plugins/intel_cpu/src/nodes/reduce.cpp | 3 +- .../arm/pass/convert_reduce_no_keep_dims.cpp | 38 ++++++++ .../arm/pass/convert_reduce_no_keep_dims.hpp | 71 +++++++++++++++ .../transformation_pipeline.cpp | 2 + .../instances/common/reduce.cpp | 5 +- .../arm/convert_reduce_no_keep_dims.cpp | 91 +++++++++++++++++++ 8 files changed, 253 insertions(+), 18 deletions(-) create mode 100644 src/plugins/intel_cpu/src/transformations/cpu_opset/arm/pass/convert_reduce_no_keep_dims.cpp create mode 100644 src/plugins/intel_cpu/src/transformations/cpu_opset/arm/pass/convert_reduce_no_keep_dims.hpp create mode 100644 src/plugins/intel_cpu/tests/unit/transformations/arm/convert_reduce_no_keep_dims.cpp diff --git a/src/plugins/intel_cpu/src/nodes/executors/acl/acl_reduce.cpp b/src/plugins/intel_cpu/src/nodes/executors/acl/acl_reduce.cpp index 339ea8aff59..e99747121cb 100644 --- a/src/plugins/intel_cpu/src/nodes/executors/acl/acl_reduce.cpp +++ b/src/plugins/intel_cpu/src/nodes/executors/acl/acl_reduce.cpp @@ -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(void)> exec_func; + std::vector 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 { + exec_func = [this, castedAxes]() -> std::unique_ptr { auto acl_op = std::make_unique(); - 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& src, const std::vect } } // namespace intel_cpu -} // namespace ov \ No newline at end of file +} // namespace ov diff --git a/src/plugins/intel_cpu/src/nodes/executors/acl/acl_utils.hpp b/src/plugins/intel_cpu/src/nodes/executors/acl/acl_utils.hpp index 32a2494d15f..b3077d4c16e 100644 --- a/src/plugins/intel_cpu/src/nodes/executors/acl/acl_utils.hpp +++ b/src/plugins/intel_cpu/src/nodes/executors/acl/acl_utils.hpp @@ -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 nhwcToNchw = {1, 2, 0, 3}; + // WHCN (reverted NCHW) (0, 1, 2, 3) into CWHN (reverted NHWC) (2, 0, 1, 3) + static std::vector 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) { diff --git a/src/plugins/intel_cpu/src/nodes/reduce.cpp b/src/plugins/intel_cpu/src/nodes/reduce.cpp index e3be52f7ae7..e09062a3f58 100644 --- a/src/plugins/intel_cpu/src/nodes/reduce.cpp +++ b/src/plugins/intel_cpu/src/nodes/reduce.cpp @@ -1992,8 +1992,7 @@ void Reduce::initSupportedPrimitiveDescriptors() { if (axis < 0) axis += static_cast(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) diff --git a/src/plugins/intel_cpu/src/transformations/cpu_opset/arm/pass/convert_reduce_no_keep_dims.cpp b/src/plugins/intel_cpu/src/transformations/cpu_opset/arm/pass/convert_reduce_no_keep_dims.cpp new file mode 100644 index 00000000000..def7250dd5b --- /dev/null +++ b/src/plugins/intel_cpu/src/transformations/cpu_opset/arm/pass/convert_reduce_no_keep_dims.cpp @@ -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 +ov::matcher_pass_callback ov::intel_cpu::ConvertReduceNoKeepDimsBase::convert_reduce() { + return [&](ov::pass::pattern::Matcher& m) { + auto reduce = std::dynamic_pointer_cast(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 squeeze = std::make_shared(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 +ov::intel_cpu::ConvertReduction::ConvertReduction() { + auto m = std::make_shared( + ov::pass::pattern::wrap_type({ov::pass::pattern::any_input(), + ov::pass::pattern::wrap_type()}), "ConvertReduction"); + register_matcher(m, convert_reduce()); +} + +template class ov::intel_cpu::ConvertReduction; +template class ov::intel_cpu::ConvertReduction; diff --git a/src/plugins/intel_cpu/src/transformations/cpu_opset/arm/pass/convert_reduce_no_keep_dims.hpp b/src/plugins/intel_cpu/src/transformations/cpu_opset/arm/pass/convert_reduce_no_keep_dims.hpp new file mode 100644 index 00000000000..2f3c7d19726 --- /dev/null +++ b/src/plugins/intel_cpu/src/transformations/cpu_opset/arm/pass/convert_reduce_no_keep_dims.hpp @@ -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 + ov::matcher_pass_callback convert_reduce(); +}; + +template +class ConvertReduction: public ConvertReduceNoKeepDimsBase { +public: + OPENVINO_RTTI("ConvertReduction", "0"); + ConvertReduction(); +}; + + +class ConvertReduceNoKeepDims: public ov::pass::GraphRewrite { +public: + OPENVINO_RTTI("ConvertReduceNoKeepDims", "0"); + ConvertReduceNoKeepDims() { + add_matcher>(); + add_matcher>(); + } +}; + +} // namespace intel_cpu +} // namespace ov diff --git a/src/plugins/intel_cpu/src/transformations/transformation_pipeline.cpp b/src/plugins/intel_cpu/src/transformations/transformation_pipeline.cpp index 0da2f8fe0cb..f84b0ba70b0 100644 --- a/src/plugins/intel_cpu/src/transformations/transformation_pipeline.cpp +++ b/src/plugins/intel_cpu/src/transformations/transformation_pipeline.cpp @@ -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& 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); diff --git a/src/plugins/intel_cpu/tests/functional/custom/single_layer_tests/instances/common/reduce.cpp b/src/plugins/intel_cpu/tests/functional/custom/single_layer_tests/instances/common/reduce.cpp index cd2f14949f3..c57a4cc27c9 100644 --- a/src/plugins/intel_cpu/tests/functional/custom/single_layer_tests/instances/common/reduce.cpp +++ b/src/plugins/intel_cpu/tests/functional/custom/single_layer_tests/instances/common/reduce.cpp @@ -46,10 +46,7 @@ std::vector> inputShapes_SingleBatch = { std::vector 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 \ No newline at end of file +} // namespace ov diff --git a/src/plugins/intel_cpu/tests/unit/transformations/arm/convert_reduce_no_keep_dims.cpp b/src/plugins/intel_cpu/tests/unit/transformations/arm/convert_reduce_no_keep_dims.cpp new file mode 100644 index 00000000000..2881fd68a82 --- /dev/null +++ b/src/plugins/intel_cpu/tests/unit/transformations/arm/convert_reduce_no_keep_dims.cpp @@ -0,0 +1,91 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include +#include +#include "common_test_utils/ov_test_utils.hpp" + +using namespace ov::intel_cpu; + +template +class ConvertReduceNoKeepDimsTest : public testing::Test {}; + +template +static std::shared_ptr createInitGraph(std::shared_ptr param) { + auto axes = ov::opset1::Constant::create(ov::element::i64, ov::Shape{2}, {0, 1}); + auto reduce = std::make_shared(param, axes, false); + return std::make_shared(ov::NodeVector{ reduce }, ov::ParameterVector{ param }); +} + +template +static std::shared_ptr createRefGraph(std::shared_ptr param) { + auto axes = ov::opset1::Constant::create(ov::element::i64, ov::Shape{2}, {0, 1}); + auto reduce = std::make_shared(param, axes, true); + auto squeeze = std::make_shared(reduce, axes); + return std::make_shared(ov::NodeVector{ squeeze }, ov::ParameterVector{ param }); +} + +template +static bool registerAndRunReducePass(std::shared_ptr model) { + ov::pass::Manager manager; + if (std::is_base_of::value) { + manager.register_pass>(); + } else if (std::is_base_of::value) { + manager.register_pass>(); + } 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::value ? + ov::element::boolean : ov::element::f32; + auto param = std::make_shared(dataType, static_param_shape); + auto model = createInitGraph(param); + auto model_ref = createRefGraph(param); + + if (!registerAndRunReducePass(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::value ? + ov::element::boolean : ov::element::f32; + auto param = std::make_shared(dataType, dynamic_param_shape); + auto model = createInitGraph(param); + auto model_ref = createRefGraph(param); + + if (!registerAndRunReducePass(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; +INSTANTIATE_TYPED_TEST_SUITE_P(ConvertReduce, ConvertReduceNoKeepDimsTest, reduceTypes); From 0908908227bff09674485d20bd1e7de0ffa46af2 Mon Sep 17 00:00:00 2001 From: tadamczx <156996781+tadamczx@users.noreply.github.com> Date: Tue, 11 Jun 2024 18:24:51 +0200 Subject: [PATCH 10/12] [DOCS] OpenVINO Python API docs build fix (#24952) ### Details: - *item1* - *...* ### Tickets: - *ticket-id* --- docs/CMakeLists.txt | 5 +- .../templates/custom-class-template.rst | 32 +++++++++ .../templates/custom-module-template.rst | 68 +++++++++++++++++++ .../install_appropriate_openvino_version.py | 66 ++++++++++++++++++ 4 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 docs/openvino_sphinx_theme/openvino_sphinx_theme/templates/custom-class-template.rst create mode 100644 docs/openvino_sphinx_theme/openvino_sphinx_theme/templates/custom-module-template.rst create mode 100644 docs/scripts/install_appropriate_openvino_version.py diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 8b57988ca14..797c95ef7d9 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -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() diff --git a/docs/openvino_sphinx_theme/openvino_sphinx_theme/templates/custom-class-template.rst b/docs/openvino_sphinx_theme/openvino_sphinx_theme/templates/custom-class-template.rst new file mode 100644 index 00000000000..710766c8aae --- /dev/null +++ b/docs/openvino_sphinx_theme/openvino_sphinx_theme/templates/custom-class-template.rst @@ -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 %} diff --git a/docs/openvino_sphinx_theme/openvino_sphinx_theme/templates/custom-module-template.rst b/docs/openvino_sphinx_theme/openvino_sphinx_theme/templates/custom-module-template.rst new file mode 100644 index 00000000000..fa60a39f44d --- /dev/null +++ b/docs/openvino_sphinx_theme/openvino_sphinx_theme/templates/custom-module-template.rst @@ -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 %} diff --git a/docs/scripts/install_appropriate_openvino_version.py b/docs/scripts/install_appropriate_openvino_version.py new file mode 100644 index 00000000000..d9cbb6a9ddb --- /dev/null +++ b/docs/scripts/install_appropriate_openvino_version.py @@ -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() \ No newline at end of file From 1d226d4edc2f20738683a9cfdf3ac16ba72eceff Mon Sep 17 00:00:00 2001 From: Alexandra Sidorova Date: Tue, 11 Jun 2024 19:45:40 +0300 Subject: [PATCH 11/12] [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 --- src/common/snippets/src/op/load.cpp | 3 +- .../snippets/src/pass/collapse_subgraph.cpp | 3 +- .../pass/explicit_transpose_matmul_inputs.cpp | 8 ++-- .../src/pass/fuse_transpose_brgemm.cpp | 4 +- .../src/pass/transpose_decomposition.cpp | 14 ++++--- .../snippets/transpose.cpp | 39 +++++++++++++++++-- .../snippets/transpose_softmax.cpp | 24 ++++++++++-- .../shared/include/snippets/transpose.hpp | 21 +++++----- .../include/snippets/transpose_softmax.hpp | 12 +++--- .../plugin/shared/src/snippets/transpose.cpp | 28 ++++++------- .../shared/src/snippets/transpose_softmax.cpp | 19 +++++---- .../include/subgraph_softmax.hpp | 4 +- .../src/subgraph_softmax.cpp | 6 +-- 13 files changed, 119 insertions(+), 66 deletions(-) diff --git a/src/common/snippets/src/op/load.cpp b/src/common/snippets/src/op/load.cpp index d48641c17cc..08352e025c9 100644 --- a/src/common/snippets/src/op/load.cpp +++ b/src/common/snippets/src/op/load.cpp @@ -44,11 +44,10 @@ std::shared_ptr Load::clone_with_new_inputs(const OutputVector& new_args) LoadReshape::LoadReshape(const Output& x, const size_t count, const size_t offset, std::vector 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 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(); diff --git a/src/common/snippets/src/pass/collapse_subgraph.cpp b/src/common/snippets/src/pass/collapse_subgraph.cpp index 0295e1eb6ef..b63b9612f81 100644 --- a/src/common/snippets/src/pass/collapse_subgraph.cpp +++ b/src/common/snippets/src/pass/collapse_subgraph.cpp @@ -59,8 +59,7 @@ auto is_supported_op(const std::shared_ptr &n) -> bool { }; auto is_supported_transpose = [](const std::shared_ptr& n) -> bool { const auto& transpose = as_type_ptr(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(parent) || ov::is_type(child); diff --git a/src/common/snippets/src/pass/explicit_transpose_matmul_inputs.cpp b/src/common/snippets/src/pass/explicit_transpose_matmul_inputs.cpp index f384abcb08c..bf8de657061 100644 --- a/src/common/snippets/src/pass/explicit_transpose_matmul_inputs.cpp +++ b/src/common/snippets/src/pass/explicit_transpose_matmul_inputs.cpp @@ -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 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::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::pass::pattern::any_input(), ov::pass::pattern::any_input()); register_matcher(std::make_shared(m_matmul0, matcher_name), [=](ov::pass::pattern::Matcher &m) { diff --git a/src/common/snippets/src/pass/fuse_transpose_brgemm.cpp b/src/common/snippets/src/pass/fuse_transpose_brgemm.cpp index 0edefb513f7..223f6d91940 100644 --- a/src/common/snippets/src/pass/fuse_transpose_brgemm.cpp +++ b/src/common/snippets/src/pass/fuse_transpose_brgemm.cpp @@ -61,7 +61,7 @@ FuseTransposeBrgemm::FuseTransposeBrgemm() { const auto& transpose_out = m.get_match_value(); const auto& const_order = ov::as_type_ptr(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()); 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(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()); } } diff --git a/src/common/snippets/src/pass/transpose_decomposition.cpp b/src/common/snippets/src/pass/transpose_decomposition.cpp index e921fa03f00..9c280e4806e 100644 --- a/src/common/snippets/src/pass/transpose_decomposition.cpp +++ b/src/common/snippets/src/pass/transpose_decomposition.cpp @@ -49,7 +49,7 @@ TransposeDecomposition::TransposeDecomposition() { const auto transpose = ov::as_type_ptr(pattern_to_output.at(match_transpose).get_node_shared_ptr()); const auto order = ov::as_type_ptr(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(); @@ -65,10 +65,14 @@ TransposeDecomposition::TransposeDecomposition() { auto load = std::make_shared(data_input, subtensor[0], 0, layout); auto store = std::make_shared(load, subtensor[0]); - PortDescriptorUtils::set_port_descriptor_ptr(load->input(0), std::make_shared(load->get_input_shape(0), subtensor, layout)); - PortDescriptorUtils::set_port_descriptor_ptr(load->output(0), std::make_shared(load->get_output_shape(0), subtensor)); - PortDescriptorUtils::set_port_descriptor_ptr(store->input(0), std::make_shared(store->get_input_shape(0), subtensor)); - PortDescriptorUtils::set_port_descriptor_ptr(store->output(0), std::make_shared(store->get_output_shape(0), subtensor)); + PortDescriptorUtils::set_port_descriptor_ptr(load->input(0), + std::make_shared(utils::pshape_to_vdims(load->get_input_partial_shape(0)), subtensor, layout)); + PortDescriptorUtils::set_port_descriptor_ptr(load->output(0), + std::make_shared(utils::pshape_to_vdims(load->get_output_partial_shape(0)), subtensor)); + PortDescriptorUtils::set_port_descriptor_ptr(store->input(0), + std::make_shared(utils::pshape_to_vdims(store->get_input_partial_shape(0)), subtensor)); + PortDescriptorUtils::set_port_descriptor_ptr(store->output(0), + std::make_shared(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)); diff --git a/src/plugins/intel_cpu/tests/functional/shared_tests_instances/snippets/transpose.cpp b/src/plugins/intel_cpu/tests/functional/shared_tests_instances/snippets/transpose.cpp index 4212102e569..905c27c4644 100644 --- a/src/plugins/intel_cpu/tests/functional/shared_tests_instances/snippets/transpose.cpp +++ b/src/plugins/intel_cpu/tests/functional/shared_tests_instances/snippets/transpose.cpp @@ -11,8 +11,26 @@ namespace snippets { namespace { -std::vector input_shapes_4D{{2, 3, 5, 13}, {2, 3, 2, 4}, {1, 7, 1, 4}}; -std::vector input_shapes_3D{{3, 5, 13}, {3, 2, 4}, {7, 1, 4}}; + +const std::vector 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 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> orders_4D{{0, 2, 3, 1}}; std::vector> 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> 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{{2, 3, 5, 31}}), + ::testing::ValuesIn(inputShapesPair), ::testing::Values(std::vector {0, 2, 3, 1}), ::testing::Values(1), // Transpose ::testing::Values(1), // Tokenized Transpose diff --git a/src/plugins/intel_cpu/tests/functional/shared_tests_instances/snippets/transpose_softmax.cpp b/src/plugins/intel_cpu/tests/functional/shared_tests_instances/snippets/transpose_softmax.cpp index fa03a5e4e6c..3cdb7c193fe 100644 --- a/src/plugins/intel_cpu/tests/functional/shared_tests_instances/snippets/transpose_softmax.cpp +++ b/src/plugins/intel_cpu/tests/functional/shared_tests_instances/snippets/transpose_softmax.cpp @@ -12,13 +12,29 @@ namespace snippets { namespace { -const std::vector inputShape = { - ov::Shape{1, 128, 3, 16}, +const std::vector> 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> 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{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{0, 2, 3, 1}), ::testing::Values(-1), ::testing::Values(1), diff --git a/src/tests/functional/plugin/shared/include/snippets/transpose.hpp b/src/tests/functional/plugin/shared/include/snippets/transpose.hpp index 37a374d4229..beec2c3750c 100644 --- a/src/tests/functional/plugin/shared/include/snippets/transpose.hpp +++ b/src/tests/functional/plugin/shared/include/snippets/transpose.hpp @@ -11,20 +11,19 @@ namespace test { namespace snippets { typedef std::tuple< - ov::PartialShape, // Input 0 Shape - std::vector, // Transpose order - size_t, // Expected num nodes - size_t, // Expected num subgraphs - std::string // Target Device + InputShape, // Input 0 Shape + std::vector, // 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, // Transpose order - size_t, // Expected num nodes - size_t, // Expected num subgraphs - std::string // Target Device + std::pair, // Input Shapes + std::vector, // Transpose order + size_t, // Expected num nodes + size_t, // Expected num subgraphs + std::string // Target Device > TransposeMulParams; class Transpose : public testing::WithParamInterface, diff --git a/src/tests/functional/plugin/shared/include/snippets/transpose_softmax.hpp b/src/tests/functional/plugin/shared/include/snippets/transpose_softmax.hpp index 952b7528a00..368b5826b34 100644 --- a/src/tests/functional/plugin/shared/include/snippets/transpose_softmax.hpp +++ b/src/tests/functional/plugin/shared/include/snippets/transpose_softmax.hpp @@ -11,12 +11,12 @@ namespace test { namespace snippets { typedef std::tuple< - std::vector, // Input shapes - std::vector, // Transpose Order - int64_t, // Softmax Axis - size_t, // Expected num nodes - size_t, // Expected num subgraphs - std::string // Target Device + std::vector, // Input shapes + std::vector, // Transpose Order + int64_t, // Softmax Axis + size_t, // Expected num nodes + size_t, // Expected num subgraphs + std::string // Target Device > TransposeSoftmaxParams; diff --git a/src/tests/functional/plugin/shared/src/snippets/transpose.cpp b/src/tests/functional/plugin/shared/src/snippets/transpose.cpp index f5c839b6499..5548deec919 100644 --- a/src/tests/functional/plugin/shared/src/snippets/transpose.cpp +++ b/src/tests/functional/plugin/shared/src/snippets/transpose.cpp @@ -12,14 +12,14 @@ namespace test { namespace snippets { std::string Transpose::getTestCaseName(testing::TestParamInfo obj) { - ov::PartialShape inputShape; + InputShape inputShapes; std::vector 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 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 obj) { - std::vector inputShapes(2); + std::pair inputShapes; std::vector 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 inputShapes(2); + std::pair inputShapes; std::vector 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"}); diff --git a/src/tests/functional/plugin/shared/src/snippets/transpose_softmax.cpp b/src/tests/functional/plugin/shared/src/snippets/transpose_softmax.cpp index 322eaa6a14b..739ab83cfd6 100644 --- a/src/tests/functional/plugin/shared/src/snippets/transpose_softmax.cpp +++ b/src/tests/functional/plugin/shared/src/snippets/transpose_softmax.cpp @@ -12,7 +12,7 @@ namespace test { namespace snippets { std::string TransposeSoftmax::getTestCaseName(testing::TestParamInfo obj) { - std::vector inputShapes; + std::vector inputShapes; std::vector order; int axis; std::string targetDevice; @@ -20,8 +20,13 @@ std::string TransposeSoftmax::getTestCaseName(testing::TestParamInfo inputShapes; + std::vector inputShapes; std::vector 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 inputShapes; + std::vector inputShapes; std::vector 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(); diff --git a/src/tests/ov_helpers/ov_snippets_models/include/subgraph_softmax.hpp b/src/tests/ov_helpers/ov_snippets_models/include/subgraph_softmax.hpp index 92897e7f791..eae6b308c55 100644 --- a/src/tests/ov_helpers/ov_snippets_models/include/subgraph_softmax.hpp +++ b/src/tests/ov_helpers/ov_snippets_models/include/subgraph_softmax.hpp @@ -56,7 +56,9 @@ protected: class TransposeSoftmaxEltwiseFunction : public TransposeSoftmaxFunction { public: explicit TransposeSoftmaxEltwiseFunction(const std::vector& inputShapes, const std::vector& 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 initOriginal() const override; }; diff --git a/src/tests/ov_helpers/ov_snippets_models/src/subgraph_softmax.cpp b/src/tests/ov_helpers/ov_snippets_models/src/subgraph_softmax.cpp index 1a7d147b75d..16571e1a0cd 100644 --- a/src/tests/ov_helpers/ov_snippets_models/src/subgraph_softmax.cpp +++ b/src/tests/ov_helpers/ov_snippets_models/src/subgraph_softmax.cpp @@ -78,11 +78,11 @@ std::shared_ptr TransposeSoftmaxEltwiseFunction::initOriginal() const const auto transpose0Param = std::make_shared(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(transpose0Param, transpose0Const); - const auto mulConst = ov::test::utils::make_constant(ov::element::f32, transpose2->get_shape()); - const auto mul = std::make_shared(transpose2, mulConst); + const auto mul1Param = std::make_shared(precision, input_shapes[1]); + const auto mul = std::make_shared(transpose2, mul1Param); const auto softMax = std::make_shared(mul, m_axis); const auto hswish = std::make_shared(softMax); - return std::make_shared(ov::NodeVector{hswish}, ov::ParameterVector{transpose0Param}, + return std::make_shared(ov::NodeVector{hswish}, ov::ParameterVector{transpose0Param, mul1Param}, "softmax_transpose"); } From 43de73cbd6f28d62f36f799030d8119a1f73ccbf Mon Sep 17 00:00:00 2001 From: Taylor Yeonbok Lee Date: Tue, 11 Jun 2024 17:27:13 -0700 Subject: [PATCH 12/12] 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* --- .../fully_connected/fully_connected_kernel_bf_tiled.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugins/intel_gpu/src/kernel_selector/kernels/fully_connected/fully_connected_kernel_bf_tiled.cpp b/src/plugins/intel_gpu/src/kernel_selector/kernels/fully_connected/fully_connected_kernel_bf_tiled.cpp index 250e38e694b..4e43398e391 100644 --- a/src/plugins/intel_gpu/src/kernel_selector/kernels/fully_connected/fully_connected_kernel_bf_tiled.cpp +++ b/src/plugins/intel_gpu/src/kernel_selector/kernels/fully_connected/fully_connected_kernel_bf_tiled.cpp @@ -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));